@colixsystems/widget-sdk 0.187.0 → 0.188.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 +26 -1
- package/dist/contract.cjs +10 -3
- package/dist/contract.js +10 -3
- package/dist/linter.cjs +85 -1
- package/dist/linter.js +85 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,32 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
75
75
|
|
|
76
76
|
## Status
|
|
77
77
|
|
|
78
|
-
`v0.
|
|
78
|
+
`v0.188.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
79
|
+
|
|
80
|
+
### What's new in 0.188.0 (contract 1.149.0)
|
|
81
|
+
|
|
82
|
+
**New linter rules for Leaflet maps: `leaflet-css-missing` and `leaflet-default-marker-icon`.** A widget's web entry is bundled with its own copy of Leaflet, and nothing else loads Leaflet's stylesheet. A map that skipped `import "leaflet/dist/leaflet.css"` drew a small patch of tiles inside an otherwise empty box, with its panes out of place and its markers invisible. Even with the stylesheet, Leaflet looks up its default marker image relative to the page, which 404s inside a widget, so a `<Marker>` with no `icon` showed no pin.
|
|
83
|
+
|
|
84
|
+
- **`leaflet-css-missing` (severity `error`).** A file that imports `leaflet` or `react-leaflet` code (an image-only import does not count) must also import `"leaflet/dist/leaflet.css"`. The bundler turns that import into an injected `<style>`. Import it in every file that uses Leaflet; the bundler includes it once.
|
|
85
|
+
- **`leaflet-default-marker-icon` (severity `warning`).** A react-leaflet `<Marker>` with no `icon` prop. Import the marker PNGs (the bundler inlines them as data URLs), build the icon once at module scope and pass it in:
|
|
86
|
+
|
|
87
|
+
```jsx
|
|
88
|
+
import L from "leaflet";
|
|
89
|
+
import "leaflet/dist/leaflet.css";
|
|
90
|
+
import markerIcon from "leaflet/dist/images/marker-icon.png";
|
|
91
|
+
import markerIcon2x from "leaflet/dist/images/marker-icon-2x.png";
|
|
92
|
+
import markerShadow from "leaflet/dist/images/marker-shadow.png";
|
|
93
|
+
|
|
94
|
+
const pin = L.icon({
|
|
95
|
+
iconUrl: markerIcon, iconRetinaUrl: markerIcon2x, shadowUrl: markerShadow,
|
|
96
|
+
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41],
|
|
97
|
+
});
|
|
98
|
+
// <Marker position={[lat, lng]} icon={pin} />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
It is a warning because a widget that configures `L.Icon.Default` itself is fine, and is not flagged. `<CircleMarker>` needs no icon.
|
|
102
|
+
|
|
103
|
+
The `leaflet` and `react-leaflet` entries in `CONTRACT.vettedImports` now say the same. Comments are not scanned, so documenting the pattern is safe.
|
|
79
104
|
|
|
80
105
|
### What's new in 0.187.0 (contract 1.148.0)
|
|
81
106
|
|
package/dist/contract.cjs
CHANGED
|
@@ -3296,13 +3296,16 @@ const VETTED_IMPORTS = [
|
|
|
3296
3296
|
platforms: ["web"],
|
|
3297
3297
|
category: "geo",
|
|
3298
3298
|
description:
|
|
3299
|
-
"Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps."
|
|
3299
|
+
"Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps. " +
|
|
3300
|
+
'Every file that imports it must also `import "leaflet/dist/leaflet.css"` (the linter enforces it), and a marker needs an ' +
|
|
3301
|
+
"explicit `L.icon` built from the imported `leaflet/dist/images/*.png`: Leaflet's default icon cannot resolve its image inside a bundle.",
|
|
3300
3302
|
},
|
|
3301
3303
|
{
|
|
3302
3304
|
specifier: "react-leaflet",
|
|
3303
3305
|
platforms: ["web"],
|
|
3304
3306
|
category: "geo",
|
|
3305
|
-
description:
|
|
3307
|
+
description:
|
|
3308
|
+
'React bindings for leaflet. Web-only. Import "leaflet/dist/leaflet.css" alongside it and pass `icon` to every <Marker>.',
|
|
3306
3309
|
},
|
|
3307
3310
|
{
|
|
3308
3311
|
specifier: "expo-audio",
|
|
@@ -4864,7 +4867,11 @@ const CONTRACT = deepFreeze({
|
|
|
4864
4867
|
// New `useUserPhoto()` changes it through the new OPTIONAL context
|
|
4865
4868
|
// slice `account` ({ setPhoto, removePhoto }); a host without it reads
|
|
4866
4869
|
// `canEdit: false`.
|
|
4867
|
-
|
|
4870
|
+
// 1.149.0: narrowing (sc-10466) — new linter rule `leaflet-css-missing`
|
|
4871
|
+
// (error): a file importing leaflet / react-leaflet must import
|
|
4872
|
+
// "leaflet/dist/leaflet.css". `leaflet-default-marker-icon` (warning)
|
|
4873
|
+
// flags a react-leaflet <Marker> with no `icon`.
|
|
4874
|
+
version: "1.149.0",
|
|
4868
4875
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4869
4876
|
hooks: HOOKS,
|
|
4870
4877
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -3292,13 +3292,16 @@ const VETTED_IMPORTS = [
|
|
|
3292
3292
|
platforms: ["web"],
|
|
3293
3293
|
category: "geo",
|
|
3294
3294
|
description:
|
|
3295
|
-
"Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps."
|
|
3295
|
+
"Web-only mapping library. Use alongside react-leaflet in widget.web.jsx as the web counterpart to react-native-maps. " +
|
|
3296
|
+
'Every file that imports it must also `import "leaflet/dist/leaflet.css"` (the linter enforces it), and a marker needs an ' +
|
|
3297
|
+
"explicit `L.icon` built from the imported `leaflet/dist/images/*.png`: Leaflet's default icon cannot resolve its image inside a bundle.",
|
|
3296
3298
|
},
|
|
3297
3299
|
{
|
|
3298
3300
|
specifier: "react-leaflet",
|
|
3299
3301
|
platforms: ["web"],
|
|
3300
3302
|
category: "geo",
|
|
3301
|
-
description:
|
|
3303
|
+
description:
|
|
3304
|
+
'React bindings for leaflet. Web-only. Import "leaflet/dist/leaflet.css" alongside it and pass `icon` to every <Marker>.',
|
|
3302
3305
|
},
|
|
3303
3306
|
{
|
|
3304
3307
|
specifier: "expo-audio",
|
|
@@ -4860,7 +4863,11 @@ const CONTRACT = deepFreeze({
|
|
|
4860
4863
|
// New `useUserPhoto()` changes it through the new OPTIONAL context
|
|
4861
4864
|
// slice `account` ({ setPhoto, removePhoto }); a host without it reads
|
|
4862
4865
|
// `canEdit: false`.
|
|
4863
|
-
|
|
4866
|
+
// 1.149.0: narrowing (sc-10466) — new linter rule `leaflet-css-missing`
|
|
4867
|
+
// (error): a file importing leaflet / react-leaflet must import
|
|
4868
|
+
// "leaflet/dist/leaflet.css". `leaflet-default-marker-icon` (warning)
|
|
4869
|
+
// flags a react-leaflet <Marker> with no `icon`.
|
|
4870
|
+
version: "1.149.0",
|
|
4864
4871
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
4865
4872
|
hooks: HOOKS,
|
|
4866
4873
|
primitives: PRIMITIVES,
|
package/dist/linter.cjs
CHANGED
|
@@ -252,7 +252,9 @@ function _packageRoot(spec) {
|
|
|
252
252
|
const parts = spec.split("/");
|
|
253
253
|
// A traversal / empty segment is a non-canonical specifier — never resolve it
|
|
254
254
|
// to a vetted root, or "leaflet/../evil" would pass the gate as "leaflet".
|
|
255
|
-
if (parts.some((p) => p === "" || p === "." || p === ".."))
|
|
255
|
+
if (parts.some((p) => p === "" || p === "." || p === ".." || p.includes("\\"))) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
256
258
|
return spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
257
259
|
}
|
|
258
260
|
|
|
@@ -336,6 +338,87 @@ function _importRules(source, manifest) {
|
|
|
336
338
|
return findings;
|
|
337
339
|
}
|
|
338
340
|
|
|
341
|
+
// sc-10466 — Leaflet is bundled per widget; no host supplies its stylesheet.
|
|
342
|
+
// Without `leaflet/dist/leaflet.css` the tiles, panes and markers are
|
|
343
|
+
// unpositioned: a patch of map in an empty box, and no visible pin. And once
|
|
344
|
+
// the CSS is injected inline its default marker image resolves against the
|
|
345
|
+
// page URL and 404s, so a `<Marker>` needs an explicit `icon`.
|
|
346
|
+
const _LEAFLET_ROOTS = new Set(["leaflet", "react-leaflet"]);
|
|
347
|
+
const _LEAFLET_CSS = "leaflet/dist/leaflet.css";
|
|
348
|
+
const _MARKER_TAG_RE = /<Marker\b/g;
|
|
349
|
+
|
|
350
|
+
const _ASSET_SPECIFIER_RE = /\.(css|png|jpe?g|gif)$/i;
|
|
351
|
+
const _ICON_DEFAULT_CONFIGURED_RE =
|
|
352
|
+
/\bIcon\s*\.\s*Default\s*(\.\s*mergeOptions\s*\(|\.\s*imagePath\s*=)/;
|
|
353
|
+
|
|
354
|
+
function _lineAt(code, index) {
|
|
355
|
+
return code.slice(0, index).split(/\r?\n/).length;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function _leafletRules(source) {
|
|
359
|
+
const code = _stripNonCode(source, { keepStrings: true });
|
|
360
|
+
const sourceLines = source.split(/\r?\n/);
|
|
361
|
+
let firstLeafletLine = 0;
|
|
362
|
+
let importsCss = false;
|
|
363
|
+
let importsReactLeaflet = false;
|
|
364
|
+
// Whole-source scan: a Prettier-wrapped import spans several lines.
|
|
365
|
+
IMPORT_SPECIFIER_RE.lastIndex = 0;
|
|
366
|
+
let m;
|
|
367
|
+
while ((m = IMPORT_SPECIFIER_RE.exec(code))) {
|
|
368
|
+
const spec = m[1];
|
|
369
|
+
if (spec === _LEAFLET_CSS) importsCss = true;
|
|
370
|
+
const root = _packageRoot(spec);
|
|
371
|
+
if (!_LEAFLET_ROOTS.has(root) || _ASSET_SPECIFIER_RE.test(spec)) continue;
|
|
372
|
+
if (root === "react-leaflet") importsReactLeaflet = true;
|
|
373
|
+
if (!firstLeafletLine) firstLeafletLine = _lineAt(code, m.index + m[0].length);
|
|
374
|
+
}
|
|
375
|
+
IMPORT_SPECIFIER_RE.lastIndex = 0;
|
|
376
|
+
if (!firstLeafletLine) return [];
|
|
377
|
+
const findings = [];
|
|
378
|
+
if (!importsCss) {
|
|
379
|
+
findings.push({
|
|
380
|
+
rule: "leaflet-css-missing",
|
|
381
|
+
severity: "error",
|
|
382
|
+
label:
|
|
383
|
+
`this file uses Leaflet but never imports its stylesheet — without ` +
|
|
384
|
+
`it the map draws a partial patch of tiles inside an empty box and ` +
|
|
385
|
+
`markers are invisible. Add \`import "${_LEAFLET_CSS}";\` to this file.`,
|
|
386
|
+
line: firstLeafletLine,
|
|
387
|
+
snippet: (sourceLines[firstLeafletLine - 1] || "").trim().slice(0, 200),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (
|
|
391
|
+
!importsReactLeaflet ||
|
|
392
|
+
_ICON_DEFAULT_CONFIGURED_RE.test(_stripNonCode(source))
|
|
393
|
+
) {
|
|
394
|
+
return findings;
|
|
395
|
+
}
|
|
396
|
+
_MARKER_TAG_RE.lastIndex = 0;
|
|
397
|
+
let tag;
|
|
398
|
+
while ((tag = _MARKER_TAG_RE.exec(code))) {
|
|
399
|
+
const end = _jsxOpenTagEnd(code, tag.index + tag[0].length);
|
|
400
|
+
if (/(^|\s)icon\s*=/.test(code.slice(tag.index, end))) continue;
|
|
401
|
+
const line = _lineAt(code, tag.index);
|
|
402
|
+
findings.push({
|
|
403
|
+
rule: "leaflet-default-marker-icon",
|
|
404
|
+
severity: "warning",
|
|
405
|
+
label:
|
|
406
|
+
`<Marker> uses Leaflet's default icon, whose image path cannot ` +
|
|
407
|
+
`resolve inside a widget bundle, so the pin does not show. Import the ` +
|
|
408
|
+
`images (\`import markerIcon from "leaflet/dist/images/marker-icon.png"\`, ` +
|
|
409
|
+
`plus marker-icon-2x.png and marker-shadow.png), build ` +
|
|
410
|
+
`\`L.icon({ iconUrl, iconRetinaUrl, shadowUrl, iconSize: [25, 41], ` +
|
|
411
|
+
`iconAnchor: [12, 41] })\` once at module scope, and pass it as ` +
|
|
412
|
+
`\`icon={…}\`. Warning only — a widget that sets \`L.Icon.Default\` ` +
|
|
413
|
+
`itself (\`mergeOptions\` / \`imagePath\`) is not flagged.`,
|
|
414
|
+
line,
|
|
415
|
+
snippet: (sourceLines[line - 1] || "").trim().slice(0, 200),
|
|
416
|
+
});
|
|
417
|
+
_MARKER_TAG_RE.lastIndex = end;
|
|
418
|
+
}
|
|
419
|
+
return findings;
|
|
420
|
+
}
|
|
421
|
+
|
|
339
422
|
// Explicit opt-out. A widget legitimately calling a THIRD-PARTY API with a
|
|
340
423
|
// relative path against an `axios` baseURL cannot avoid the substring match,
|
|
341
424
|
// and post-sc-3589 an unavoidable finding burns AI repair turns on correct
|
|
@@ -1853,6 +1936,7 @@ function lintSource(source, options) {
|
|
|
1853
1936
|
severity: f.severity || "error",
|
|
1854
1937
|
})),
|
|
1855
1938
|
);
|
|
1939
|
+
findings.push(..._leafletRules(source));
|
|
1856
1940
|
findings.push(..._hostApiUrlRules(source));
|
|
1857
1941
|
// sc-6970 — soft warning: HTML markup spliced into a content string.
|
|
1858
1942
|
findings.push(..._htmlInContentRules(source));
|
package/dist/linter.js
CHANGED
|
@@ -135,7 +135,9 @@ function _packageRoot(spec) {
|
|
|
135
135
|
const parts = spec.split("/");
|
|
136
136
|
// A traversal / empty segment is a non-canonical specifier — never resolve it
|
|
137
137
|
// to a vetted root, or "leaflet/../evil" would pass the gate as "leaflet".
|
|
138
|
-
if (parts.some((p) => p === "" || p === "." || p === ".."))
|
|
138
|
+
if (parts.some((p) => p === "" || p === "." || p === ".." || p.includes("\\"))) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
139
141
|
return spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
140
142
|
}
|
|
141
143
|
|
|
@@ -229,6 +231,87 @@ function _importRules(source, manifest) {
|
|
|
229
231
|
return findings;
|
|
230
232
|
}
|
|
231
233
|
|
|
234
|
+
// sc-10466 — Leaflet is bundled per widget; no host supplies its stylesheet.
|
|
235
|
+
// Without `leaflet/dist/leaflet.css` the tiles, panes and markers are
|
|
236
|
+
// unpositioned: a patch of map in an empty box, and no visible pin. And once
|
|
237
|
+
// the CSS is injected inline its default marker image resolves against the
|
|
238
|
+
// page URL and 404s, so a `<Marker>` needs an explicit `icon`.
|
|
239
|
+
const _LEAFLET_ROOTS = new Set(["leaflet", "react-leaflet"]);
|
|
240
|
+
const _LEAFLET_CSS = "leaflet/dist/leaflet.css";
|
|
241
|
+
const _MARKER_TAG_RE = /<Marker\b/g;
|
|
242
|
+
|
|
243
|
+
const _ASSET_SPECIFIER_RE = /\.(css|png|jpe?g|gif)$/i;
|
|
244
|
+
const _ICON_DEFAULT_CONFIGURED_RE =
|
|
245
|
+
/\bIcon\s*\.\s*Default\s*(\.\s*mergeOptions\s*\(|\.\s*imagePath\s*=)/;
|
|
246
|
+
|
|
247
|
+
function _lineAt(code, index) {
|
|
248
|
+
return code.slice(0, index).split(/\r?\n/).length;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function _leafletRules(source) {
|
|
252
|
+
const code = _stripNonCode(source, { keepStrings: true });
|
|
253
|
+
const sourceLines = source.split(/\r?\n/);
|
|
254
|
+
let firstLeafletLine = 0;
|
|
255
|
+
let importsCss = false;
|
|
256
|
+
let importsReactLeaflet = false;
|
|
257
|
+
// Whole-source scan: a Prettier-wrapped import spans several lines.
|
|
258
|
+
IMPORT_SPECIFIER_RE.lastIndex = 0;
|
|
259
|
+
let m;
|
|
260
|
+
while ((m = IMPORT_SPECIFIER_RE.exec(code))) {
|
|
261
|
+
const spec = m[1];
|
|
262
|
+
if (spec === _LEAFLET_CSS) importsCss = true;
|
|
263
|
+
const root = _packageRoot(spec);
|
|
264
|
+
if (!_LEAFLET_ROOTS.has(root) || _ASSET_SPECIFIER_RE.test(spec)) continue;
|
|
265
|
+
if (root === "react-leaflet") importsReactLeaflet = true;
|
|
266
|
+
if (!firstLeafletLine) firstLeafletLine = _lineAt(code, m.index + m[0].length);
|
|
267
|
+
}
|
|
268
|
+
IMPORT_SPECIFIER_RE.lastIndex = 0;
|
|
269
|
+
if (!firstLeafletLine) return [];
|
|
270
|
+
const findings = [];
|
|
271
|
+
if (!importsCss) {
|
|
272
|
+
findings.push({
|
|
273
|
+
rule: "leaflet-css-missing",
|
|
274
|
+
severity: "error",
|
|
275
|
+
label:
|
|
276
|
+
`this file uses Leaflet but never imports its stylesheet — without ` +
|
|
277
|
+
`it the map draws a partial patch of tiles inside an empty box and ` +
|
|
278
|
+
`markers are invisible. Add \`import "${_LEAFLET_CSS}";\` to this file.`,
|
|
279
|
+
line: firstLeafletLine,
|
|
280
|
+
snippet: (sourceLines[firstLeafletLine - 1] || "").trim().slice(0, 200),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (
|
|
284
|
+
!importsReactLeaflet ||
|
|
285
|
+
_ICON_DEFAULT_CONFIGURED_RE.test(_stripNonCode(source))
|
|
286
|
+
) {
|
|
287
|
+
return findings;
|
|
288
|
+
}
|
|
289
|
+
_MARKER_TAG_RE.lastIndex = 0;
|
|
290
|
+
let tag;
|
|
291
|
+
while ((tag = _MARKER_TAG_RE.exec(code))) {
|
|
292
|
+
const end = _jsxOpenTagEnd(code, tag.index + tag[0].length);
|
|
293
|
+
if (/(^|\s)icon\s*=/.test(code.slice(tag.index, end))) continue;
|
|
294
|
+
const line = _lineAt(code, tag.index);
|
|
295
|
+
findings.push({
|
|
296
|
+
rule: "leaflet-default-marker-icon",
|
|
297
|
+
severity: "warning",
|
|
298
|
+
label:
|
|
299
|
+
`<Marker> uses Leaflet's default icon, whose image path cannot ` +
|
|
300
|
+
`resolve inside a widget bundle, so the pin does not show. Import the ` +
|
|
301
|
+
`images (\`import markerIcon from "leaflet/dist/images/marker-icon.png"\`, ` +
|
|
302
|
+
`plus marker-icon-2x.png and marker-shadow.png), build ` +
|
|
303
|
+
`\`L.icon({ iconUrl, iconRetinaUrl, shadowUrl, iconSize: [25, 41], ` +
|
|
304
|
+
`iconAnchor: [12, 41] })\` once at module scope, and pass it as ` +
|
|
305
|
+
`\`icon={…}\`. Warning only — a widget that sets \`L.Icon.Default\` ` +
|
|
306
|
+
`itself (\`mergeOptions\` / \`imagePath\`) is not flagged.`,
|
|
307
|
+
line,
|
|
308
|
+
snippet: (sourceLines[line - 1] || "").trim().slice(0, 200),
|
|
309
|
+
});
|
|
310
|
+
_MARKER_TAG_RE.lastIndex = end;
|
|
311
|
+
}
|
|
312
|
+
return findings;
|
|
313
|
+
}
|
|
314
|
+
|
|
232
315
|
// REQ-WSDK-PLATFORM §3.5: soft warning when source contains host-API URL
|
|
233
316
|
// substrings. `severity: "warning"` so a HUMAN-authored submission still
|
|
234
317
|
// publishes and the marketplace review queue flags it for a human pass.
|
|
@@ -1925,6 +2008,7 @@ export function lintSource(source, options) {
|
|
|
1925
2008
|
severity: f.severity || "error",
|
|
1926
2009
|
})),
|
|
1927
2010
|
);
|
|
2011
|
+
findings.push(..._leafletRules(source));
|
|
1928
2012
|
// REQ-WSDK-PLATFORM §3.5: soft host-API URL warning (does not block).
|
|
1929
2013
|
findings.push(..._hostApiUrlRules(source));
|
|
1930
2014
|
// sc-6970 — soft warning: HTML markup spliced into a content string.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.188.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|