@decocms/blocks-cli 7.37.4 → 7.37.6
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/blocks-cli",
|
|
3
|
-
"version": "7.37.
|
|
3
|
+
"version": "7.37.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"lint:unused": "knip"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@decocms/blocks": "7.37.
|
|
34
|
+
"@decocms/blocks": "7.37.6",
|
|
35
35
|
"ts-morph": "^27.0.0",
|
|
36
36
|
"tsx": "^4.22.5"
|
|
37
37
|
},
|
|
@@ -191,6 +191,68 @@ describe("typeToJsonSchema with intersection types", () => {
|
|
|
191
191
|
}, 30_000);
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
+
describe("typeToJsonSchema Section-typed props", () => {
|
|
195
|
+
// The framework's `Section` is opaque (`export type Section = any`), so a
|
|
196
|
+
// Section-typed prop is a "pick any section" reference emitted as a
|
|
197
|
+
// __SECTION_REF__ picker. This must keep working.
|
|
198
|
+
it("emits a __SECTION_REF__ picker for the framework's opaque Section[]", () => {
|
|
199
|
+
const project = new Project({
|
|
200
|
+
useInMemoryFileSystem: true,
|
|
201
|
+
compilerOptions: { skipLibCheck: true },
|
|
202
|
+
});
|
|
203
|
+
const sf = project.createSourceFile(
|
|
204
|
+
"props.ts",
|
|
205
|
+
`
|
|
206
|
+
type Section = any;
|
|
207
|
+
export interface Props {
|
|
208
|
+
children?: Section[] | null;
|
|
209
|
+
}
|
|
210
|
+
`,
|
|
211
|
+
);
|
|
212
|
+
const schema = typeToJsonSchema(sf.getInterfaceOrThrow("Props").getType());
|
|
213
|
+
|
|
214
|
+
expect(schema.properties.children).toMatchObject({
|
|
215
|
+
type: "array",
|
|
216
|
+
items: { $ref: "#/definitions/__SECTION_REF__" },
|
|
217
|
+
});
|
|
218
|
+
}, 30_000);
|
|
219
|
+
|
|
220
|
+
// Regression (casaevideo footer): a component that declares its own local
|
|
221
|
+
// `type Section = { label; items }` used to collide with the magic name and
|
|
222
|
+
// collapse into a non-editable __SECTION_REF__ picker — the footer columns
|
|
223
|
+
// showed only a drag handle, no expand arrow, no form. A concretely-shaped
|
|
224
|
+
// local type is user data and must render as an inline editable object.
|
|
225
|
+
it("keeps a user-defined local `type Section` as an inline editable object", () => {
|
|
226
|
+
const project = new Project({
|
|
227
|
+
useInMemoryFileSystem: true,
|
|
228
|
+
compilerOptions: { skipLibCheck: true },
|
|
229
|
+
});
|
|
230
|
+
const sf = project.createSourceFile(
|
|
231
|
+
"props.ts",
|
|
232
|
+
`
|
|
233
|
+
interface Item { label: string; href: string; }
|
|
234
|
+
type Section = { label: string; items: Item[] };
|
|
235
|
+
export interface Props {
|
|
236
|
+
sections?: Section[];
|
|
237
|
+
}
|
|
238
|
+
`,
|
|
239
|
+
);
|
|
240
|
+
const schema = typeToJsonSchema(sf.getInterfaceOrThrow("Props").getType());
|
|
241
|
+
|
|
242
|
+
const sections = schema.properties.sections;
|
|
243
|
+
// Editable array of objects — NOT a section-picker reference.
|
|
244
|
+
expect(sections.type).toBe("array");
|
|
245
|
+
expect(JSON.stringify(sections)).not.toContain("__SECTION_REF__");
|
|
246
|
+
|
|
247
|
+
const item = sections.items;
|
|
248
|
+
expect(item.type).toBe("object");
|
|
249
|
+
expect(item.properties.label).toMatchObject({ type: "string" });
|
|
250
|
+
expect(item.properties.items.type).toBe("array");
|
|
251
|
+
expect(item.properties.items.items.type).toBe("object");
|
|
252
|
+
expect(item.properties.items.items.properties.href).toMatchObject({ type: "string" });
|
|
253
|
+
}, 30_000);
|
|
254
|
+
});
|
|
255
|
+
|
|
194
256
|
describe("typeToJsonSchema loader block-ref matching", () => {
|
|
195
257
|
// Regression: a loader that returns an array whose element type has no
|
|
196
258
|
// resolvable name (`VNode[]`, `string[]`, …) used to be bucketed under the
|
|
@@ -306,6 +306,33 @@ const SECTION_REF_DEF_KEY = "__SECTION_REF__";
|
|
|
306
306
|
// Well-known definition key for Resolvable (saved blocks picker)
|
|
307
307
|
const RESOLVABLE_KEY = "Resolvable";
|
|
308
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Whether a prop annotated `Section` / `Section[]` is the framework's opaque
|
|
311
|
+
* Section type (a "pick any section" reference) rather than a user-defined type
|
|
312
|
+
* that merely happens to be named `Section`.
|
|
313
|
+
*
|
|
314
|
+
* The framework's `Section` is opaque — `export type Section = any` in the
|
|
315
|
+
* scaffolded `~/types/deco.ts` — so it resolves to `any` (or `unknown`). A
|
|
316
|
+
* component that declares its own local `type Section = { label; items }`
|
|
317
|
+
* (e.g. a footer column list) resolves to a concrete object with properties;
|
|
318
|
+
* that is user data and must render as an inline editable object, NOT a picker.
|
|
319
|
+
*
|
|
320
|
+
* Discriminates by the resolved shape after stripping `| null | undefined` and
|
|
321
|
+
* the array wrapper: only the opaque type is a section reference.
|
|
322
|
+
*/
|
|
323
|
+
function isOpaqueSectionType(propType: Type): boolean {
|
|
324
|
+
let type = propType;
|
|
325
|
+
if (type.isUnion()) {
|
|
326
|
+
const nonNull = type.getUnionTypes().filter((u) => !u.isNull() && !u.isUndefined());
|
|
327
|
+
if (nonNull.length === 1) type = nonNull[0];
|
|
328
|
+
}
|
|
329
|
+
if (type.isArray()) {
|
|
330
|
+
const el = type.getArrayElementType();
|
|
331
|
+
if (el) type = el;
|
|
332
|
+
}
|
|
333
|
+
return type.isAny() || type.isUnknown();
|
|
334
|
+
}
|
|
335
|
+
|
|
309
336
|
// Only truly React-internal props that are never user-defined.
|
|
310
337
|
// Do NOT include "children", "type", "props", or "key" — those are commonly
|
|
311
338
|
// used as legitimate property names in data interfaces (e.g. SelectedFacet
|
|
@@ -560,9 +587,16 @@ export function typeToJsonSchema(type: Type, visited = new Set<string>(), ctx?:
|
|
|
560
587
|
if (tn) typeHint = tn.getText();
|
|
561
588
|
}
|
|
562
589
|
|
|
563
|
-
// Section type → section picker reference (resolved by composeMeta)
|
|
590
|
+
// Section type → section picker reference (resolved by composeMeta).
|
|
591
|
+
// Guard on the resolved shape: the name `Section` is not reserved, so a
|
|
592
|
+
// component may declare its own local `type Section = { label; items }`
|
|
593
|
+
// (e.g. a footer column list). Only the framework's opaque Section type
|
|
594
|
+
// becomes a picker; a concretely-shaped local type falls through to the
|
|
595
|
+
// normal inline-object handling below (`isOpaqueSectionType`).
|
|
564
596
|
const baseHint = typeHint.replace(/\s*\|\s*(null|undefined)/g, "").trim();
|
|
565
|
-
|
|
597
|
+
const isSectionName =
|
|
598
|
+
baseHint === "Section" || baseHint === "Section[]" || baseHint === "Section[] | null";
|
|
599
|
+
if (isSectionName && isOpaqueSectionType(propType)) {
|
|
566
600
|
const isArray = baseHint.includes("[]");
|
|
567
601
|
const sectionSchema: any = isArray
|
|
568
602
|
? {
|