meno-core 1.0.40 → 1.0.41
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/bin/cli.ts +33 -0
- package/build-astro.ts +3 -27
- package/dist/bin/cli.js +30 -2
- package/dist/bin/cli.js.map +2 -2
- package/dist/build-static.js +7 -7
- package/dist/chunks/{chunk-3NOZVNM4.js → chunk-EQOSDQS2.js} +4 -4
- package/dist/chunks/{chunk-V4SVSX3X.js → chunk-IBR2F4IL.js} +4 -5
- package/dist/chunks/{chunk-V4SVSX3X.js.map → chunk-IBR2F4IL.js.map} +2 -2
- package/dist/chunks/{chunk-OJ5SROQN.js → chunk-IGVQF5GY.js} +8 -6
- package/dist/chunks/chunk-IGVQF5GY.js.map +7 -0
- package/dist/chunks/{chunk-Z7SAOCDG.js → chunk-LBWIHPN7.js} +5 -2
- package/dist/chunks/{chunk-Z7SAOCDG.js.map → chunk-LBWIHPN7.js.map} +2 -2
- package/dist/chunks/{chunk-A6KWUEA6.js → chunk-MKB2J6AD.js} +9 -1
- package/dist/chunks/chunk-MKB2J6AD.js.map +7 -0
- package/dist/chunks/{chunk-LOJLO2EY.js → chunk-S2HXJTAF.js} +1 -1
- package/dist/chunks/chunk-S2HXJTAF.js.map +7 -0
- package/dist/chunks/{chunk-GKICS7CF.js → chunk-SK3TLNUP.js} +120 -107
- package/dist/chunks/chunk-SK3TLNUP.js.map +7 -0
- package/dist/chunks/{chunk-MOCRENNU.js → chunk-SNUROC7E.js} +6 -6
- package/dist/chunks/{configService-TXBNUBBL.js → configService-MICL4S2L.js} +2 -2
- package/dist/chunks/{constants-L75FR445.js → constants-ZEU4TZCA.js} +2 -2
- package/dist/entries/server-router.js +7 -7
- package/dist/lib/client/index.js +9 -4
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +76 -2966
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +3 -3
- package/dist/lib/test-utils/index.js +1 -1
- package/lib/client/core/ComponentBuilder.ts +1 -1
- package/lib/client/routing/Router.tsx +6 -0
- package/lib/client/templateEngine.test.ts +178 -0
- package/lib/client/templateEngine.ts +1 -2
- package/lib/server/astro/cmsPageEmitter.ts +3 -0
- package/lib/server/astro/componentEmitter.ts +60 -12
- package/lib/server/astro/nodeToAstro.test.ts +1101 -0
- package/lib/server/astro/nodeToAstro.ts +43 -4
- package/lib/server/astro/pageEmitter.ts +4 -0
- package/lib/server/index.ts +18 -4
- package/lib/server/services/configService.ts +12 -0
- package/lib/server/ssr/htmlGenerator.ts +0 -5
- package/lib/server/ssr/imageMetadata.ts +3 -3
- package/lib/server/ssr/ssrRenderer.ts +48 -19
- package/lib/shared/constants.ts +2 -0
- package/lib/shared/types/components.ts +8 -4
- package/lib/shared/validation/propValidator.ts +2 -1
- package/lib/shared/validation/schemas.ts +3 -1
- package/package.json +1 -1
- package/templates/index-router.html +0 -5
- package/dist/chunks/chunk-A6KWUEA6.js.map +0 -7
- package/dist/chunks/chunk-GKICS7CF.js.map +0 -7
- package/dist/chunks/chunk-LOJLO2EY.js.map +0 -7
- package/dist/chunks/chunk-OJ5SROQN.js.map +0 -7
- /package/dist/chunks/{chunk-3NOZVNM4.js.map → chunk-EQOSDQS2.js.map} +0 -0
- /package/dist/chunks/{chunk-MOCRENNU.js.map → chunk-SNUROC7E.js.map} +0 -0
- /package/dist/chunks/{configService-TXBNUBBL.js.map → configService-MICL4S2L.js.map} +0 -0
- /package/dist/chunks/{constants-L75FR445.js.map → constants-ZEU4TZCA.js.map} +0 -0
package/dist/lib/shared/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
isValidIdentifier,
|
|
24
24
|
resolvePaletteColor,
|
|
25
25
|
resolveSafePath
|
|
26
|
-
} from "../../chunks/chunk-
|
|
26
|
+
} from "../../chunks/chunk-S2HXJTAF.js";
|
|
27
27
|
import {
|
|
28
28
|
CSS_PROPERTIES,
|
|
29
29
|
CSS_PROPERTIES_DEFINITION,
|
|
@@ -201,7 +201,7 @@ import {
|
|
|
201
201
|
validatePath,
|
|
202
202
|
validatePropDefinition,
|
|
203
203
|
validateStructuredComponentDefinition
|
|
204
|
-
} from "../../chunks/chunk-
|
|
204
|
+
} from "../../chunks/chunk-IGVQF5GY.js";
|
|
205
205
|
import {
|
|
206
206
|
DEFAULT_BREAKPOINTS,
|
|
207
207
|
DEFAULT_I18N_CONFIG,
|
|
@@ -255,7 +255,7 @@ import {
|
|
|
255
255
|
TREE_SCROLL_DELAY_MS,
|
|
256
256
|
WEBSOCKET_STATES,
|
|
257
257
|
init_constants
|
|
258
|
-
} from "../../chunks/chunk-
|
|
258
|
+
} from "../../chunks/chunk-LBWIHPN7.js";
|
|
259
259
|
import "../../chunks/chunk-KSBZ2L7C.js";
|
|
260
260
|
|
|
261
261
|
// lib/shared/index.ts
|
|
@@ -637,7 +637,7 @@ export class ComponentBuilder {
|
|
|
637
637
|
if (utilityClasses.length > 0) {
|
|
638
638
|
const existingClassName = (props.className || '') as string;
|
|
639
639
|
const classArray = existingClassName ? existingClassName.split(/\s+/) : [];
|
|
640
|
-
return { ...props, className: [...classArray, ...utilityClasses].filter(Boolean).join(' ') };
|
|
640
|
+
return { ...props, className: [...classArray, ...utilityClasses].filter(Boolean).join(' '), style: processedStyle };
|
|
641
641
|
}
|
|
642
642
|
}
|
|
643
643
|
|
|
@@ -255,6 +255,12 @@ export function Router(props: RouterProps = {}): ReactElement {
|
|
|
255
255
|
if (typeof window === 'undefined') return;
|
|
256
256
|
|
|
257
257
|
const handleMessage = (event: MessageEvent) => {
|
|
258
|
+
if (event.data?.type === IFRAME_MESSAGE_TYPES.CSS_VARIABLE_UPDATE) {
|
|
259
|
+
const { name, value } = event.data as { name: string; value: string };
|
|
260
|
+
document.documentElement.style.setProperty(name, value);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
258
264
|
if (event.data?.type === IFRAME_MESSAGE_TYPES.INTERACTIVE_CSS_UPDATE) {
|
|
259
265
|
const css = event.data.css as string;
|
|
260
266
|
const styleId = 'interactive-styles';
|
|
@@ -807,3 +807,181 @@ describe("Template Engine - normalizeStyle", () => {
|
|
|
807
807
|
expect(result).toBeNull();
|
|
808
808
|
});
|
|
809
809
|
});
|
|
810
|
+
|
|
811
|
+
// ==========================================================================
|
|
812
|
+
// Targeted tests for `as any` code paths in processStructure
|
|
813
|
+
// These exercise specific type-cast paths to ensure safety before refactoring
|
|
814
|
+
// ==========================================================================
|
|
815
|
+
|
|
816
|
+
describe("Template Engine - processStructure as-any paths", () => {
|
|
817
|
+
const baseContext: TemplateContext = {
|
|
818
|
+
props: { text: "Hello", size: "lg", variant: "primary" },
|
|
819
|
+
componentDef: {} as StructuredComponentDefinition,
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
describe("Boolean preservation (line 429)", () => {
|
|
823
|
+
test("should preserve false boolean values in structure", () => {
|
|
824
|
+
// processStructure should return false as-is, not convert to null
|
|
825
|
+
const result = processStructure(false as any, baseContext);
|
|
826
|
+
expect(result).toBe(false);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("should preserve true boolean values in structure", () => {
|
|
830
|
+
const result = processStructure(true as any, baseContext);
|
|
831
|
+
expect(result).toBe(true);
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
describe("Object template evaluation returning objects (line 454)", () => {
|
|
836
|
+
test("should return object result from full template expression", () => {
|
|
837
|
+
const context: TemplateContext = {
|
|
838
|
+
props: { link: { href: "/about", target: "_blank" } },
|
|
839
|
+
componentDef: {} as StructuredComponentDefinition,
|
|
840
|
+
};
|
|
841
|
+
// Full template {{link}} should return the object as-is
|
|
842
|
+
const result = processStructure("{{link}}", context);
|
|
843
|
+
expect(result).toEqual({ href: "/about", target: "_blank" });
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
describe("Slot default content via 'default' property (lines 483-486, 516-517)", () => {
|
|
848
|
+
test("should render slot default array when no instance children", () => {
|
|
849
|
+
// Slot markers use type: "slot" (NODE_TYPE.SLOT)
|
|
850
|
+
const structure = [
|
|
851
|
+
{ type: "node", tag: "div", children: [
|
|
852
|
+
{ type: "slot", default: [{ type: "node", tag: "span", children: "fallback" }] }
|
|
853
|
+
]}
|
|
854
|
+
] as unknown as ComponentNode[];
|
|
855
|
+
const result = processStructure(structure, baseContext);
|
|
856
|
+
expect(Array.isArray(result)).toBe(true);
|
|
857
|
+
const root = (result as any[])[0];
|
|
858
|
+
expect(root.tag).toBe("div");
|
|
859
|
+
// The slot default should have been rendered
|
|
860
|
+
expect(root.children.length).toBe(1);
|
|
861
|
+
expect(root.children[0].tag).toBe("span");
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("should render slot default string when no instance children", () => {
|
|
865
|
+
const structure = [
|
|
866
|
+
{ type: "node", tag: "div", children: [
|
|
867
|
+
{ type: "slot", default: "Default text" }
|
|
868
|
+
]}
|
|
869
|
+
] as unknown as ComponentNode[];
|
|
870
|
+
const result = processStructure(structure, baseContext);
|
|
871
|
+
const root = (result as any[])[0];
|
|
872
|
+
expect(root.children[0]).toBe("Default text");
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
test("should use instance children over slot default", () => {
|
|
876
|
+
const structure = [
|
|
877
|
+
{ type: "node", tag: "div", children: [
|
|
878
|
+
{ type: "slot", default: [{ type: "node", tag: "span", children: "fallback" }] }
|
|
879
|
+
]}
|
|
880
|
+
] as unknown as ComponentNode[];
|
|
881
|
+
const instanceChildren = [
|
|
882
|
+
{ type: "node", tag: "b", children: "override" }
|
|
883
|
+
] as unknown as ComponentNode[];
|
|
884
|
+
const result = processStructure(structure, baseContext, undefined, instanceChildren);
|
|
885
|
+
const root = (result as any[])[0];
|
|
886
|
+
expect(root.children[0].tag).toBe("b");
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
test("should handle slot marker as standalone object with default", () => {
|
|
890
|
+
const slotMarker = {
|
|
891
|
+
type: "slot",
|
|
892
|
+
default: [{ type: "node", tag: "div", children: "content" }],
|
|
893
|
+
};
|
|
894
|
+
const result = processStructure(slotMarker as any, baseContext);
|
|
895
|
+
expect(result).toBeDefined();
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
describe("Plain object processing (lines 538, 543)", () => {
|
|
900
|
+
test("should process plain objects recursively and resolve templates", () => {
|
|
901
|
+
// An object without a valid node type gets treated as a plain props object
|
|
902
|
+
const structure = { label: "{{text}}", visible: true } as any;
|
|
903
|
+
const result = processStructure(structure, baseContext);
|
|
904
|
+
expect(result).toBeDefined();
|
|
905
|
+
expect((result as any).label).toBe("Hello");
|
|
906
|
+
expect((result as any).visible).toBe(true);
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
test("should strip null/undefined values from plain objects", () => {
|
|
910
|
+
const context: TemplateContext = {
|
|
911
|
+
props: { defined: "value" },
|
|
912
|
+
componentDef: {} as StructuredComponentDefinition,
|
|
913
|
+
};
|
|
914
|
+
const structure = { a: "{{defined}}", b: "{{missing}}" } as any;
|
|
915
|
+
const result = processStructure(structure, context);
|
|
916
|
+
expect((result as any).a).toBe("value");
|
|
917
|
+
// Missing props resolve to empty string, not null
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
describe("Embed node creation (line 562)", () => {
|
|
922
|
+
test("should process embed node with html content", () => {
|
|
923
|
+
const node: ComponentNode = {
|
|
924
|
+
type: "embed" as any,
|
|
925
|
+
html: "<script>alert('test')</script>",
|
|
926
|
+
} as any;
|
|
927
|
+
const result = processStructure(node, baseContext);
|
|
928
|
+
expect(result).toBeDefined();
|
|
929
|
+
expect((result as any).type).toBe("embed");
|
|
930
|
+
expect((result as any).html).toBe("<script>alert('test')</script>");
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
describe("Link node creation (line 569)", () => {
|
|
935
|
+
test("should process link node with href", () => {
|
|
936
|
+
const node = {
|
|
937
|
+
type: "link",
|
|
938
|
+
href: "/about",
|
|
939
|
+
children: ["About us"],
|
|
940
|
+
} as any;
|
|
941
|
+
const result = processStructure(node, baseContext);
|
|
942
|
+
expect(result).toBeDefined();
|
|
943
|
+
expect((result as any).type).toBe("link");
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
test("should process link node with template href", () => {
|
|
947
|
+
const context: TemplateContext = {
|
|
948
|
+
props: { url: "/contact" },
|
|
949
|
+
componentDef: {} as StructuredComponentDefinition,
|
|
950
|
+
};
|
|
951
|
+
const node = {
|
|
952
|
+
type: "link",
|
|
953
|
+
href: "{{url}}",
|
|
954
|
+
children: ["Contact"],
|
|
955
|
+
} as any;
|
|
956
|
+
const result = processStructure(node, context);
|
|
957
|
+
expect(result).toBeDefined();
|
|
958
|
+
expect((result as any).type).toBe("link");
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
describe("Locale-list node creation (line 574)", () => {
|
|
963
|
+
test("should process locale-list node", () => {
|
|
964
|
+
const node = {
|
|
965
|
+
type: "locale-list",
|
|
966
|
+
style: { display: "flex" },
|
|
967
|
+
} as any;
|
|
968
|
+
const result = processStructure(node, baseContext);
|
|
969
|
+
expect(result).toBeDefined();
|
|
970
|
+
expect((result as any).type).toBe("locale-list");
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
describe("List node with legacy cms-list type (line 575)", () => {
|
|
975
|
+
test("should process list node", () => {
|
|
976
|
+
const node = {
|
|
977
|
+
type: "list",
|
|
978
|
+
source: "items",
|
|
979
|
+
sourceType: "prop",
|
|
980
|
+
children: [{ type: "html" as const, tag: "div", children: [] }],
|
|
981
|
+
} as any;
|
|
982
|
+
const result = processStructure(node, baseContext);
|
|
983
|
+
expect(result).toBeDefined();
|
|
984
|
+
expect((result as any).type).toBe("list");
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
});
|
|
@@ -814,8 +814,7 @@ export function processStructure(
|
|
|
814
814
|
if (isResponsiveStyle(resolvedStyle)) {
|
|
815
815
|
// Apply responsive styles directly to node.style or props.style
|
|
816
816
|
if (isComponentNode(processed)) {
|
|
817
|
-
processed.
|
|
818
|
-
processed.props.style = resolvedStyle as ResponsiveStyleObject;
|
|
817
|
+
(processed as any).style = resolvedStyle as ResponsiveStyleObject;
|
|
819
818
|
} else if (isHtmlNode(processed) || isEmbedNode(processed) || isLocaleListNode(processed) || isLinkNode(processed) || isListNode(processed)) {
|
|
820
819
|
processed.style = resolvedStyle as ResponsiveStyleObject;
|
|
821
820
|
}
|
|
@@ -54,6 +54,8 @@ export interface CMSPageEmitOptions {
|
|
|
54
54
|
isMultiLocale: boolean;
|
|
55
55
|
/** Slug mappings for translating internal link hrefs */
|
|
56
56
|
slugMappings?: SlugMap[];
|
|
57
|
+
/** Image format: 'webp' uses plain <img>, 'avif' uses <picture> */
|
|
58
|
+
imageFormat?: 'webp' | 'avif';
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// ---------------------------------------------------------------------------
|
|
@@ -295,6 +297,7 @@ export function emitCMSPage(options: CMSPageEmitOptions): string {
|
|
|
295
297
|
cmsWrapFn: wrapFn,
|
|
296
298
|
slugMappings,
|
|
297
299
|
i18nDefaultLocale: i18nConfig.defaultLocale,
|
|
300
|
+
imageFormat: options.imageFormat,
|
|
298
301
|
};
|
|
299
302
|
|
|
300
303
|
// Emit the template body
|
|
@@ -76,6 +76,47 @@ function formatDefault(def: PropDefinition): string | null {
|
|
|
76
76
|
return JSON.stringify(val);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Merge the `className` variable onto the root element's class attribute.
|
|
81
|
+
* Handles: class="existing", class:list={[...]}, class={expr}, or no class at all.
|
|
82
|
+
*/
|
|
83
|
+
function mergeClassNameOntoRoot(template: string): string {
|
|
84
|
+
// Find the first opening tag (skip whitespace/newlines)
|
|
85
|
+
const tagMatch = template.match(/^(\s*<)(\w[\w-]*)([\s\S]*?)(\/?>)/);
|
|
86
|
+
if (!tagMatch) return template;
|
|
87
|
+
|
|
88
|
+
const [fullMatch, prefix, tagName, attrs, close] = tagMatch;
|
|
89
|
+
|
|
90
|
+
// Check for existing class:list
|
|
91
|
+
const classListMatch = attrs.match(/\s+class:list=\{(\[[\s\S]*?\])\}/);
|
|
92
|
+
if (classListMatch) {
|
|
93
|
+
const existingList = classListMatch[1];
|
|
94
|
+
// Append className to the array
|
|
95
|
+
const newList = existingList.replace(/\]$/, `, className]`);
|
|
96
|
+
const newAttrs = attrs.replace(classListMatch[0], ` class:list={${newList}}`);
|
|
97
|
+
return prefix + tagName + newAttrs + close + template.slice(fullMatch.length);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check for existing class="..." (static)
|
|
101
|
+
const classStaticMatch = attrs.match(/\s+class="([^"]*)"/);
|
|
102
|
+
if (classStaticMatch) {
|
|
103
|
+
const existing = classStaticMatch[1];
|
|
104
|
+
const newAttrs = attrs.replace(classStaticMatch[0], ` class:list={["${existing}", className]}`);
|
|
105
|
+
return prefix + tagName + newAttrs + close + template.slice(fullMatch.length);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for existing class={expr} (dynamic)
|
|
109
|
+
const classDynMatch = attrs.match(/\s+class=\{([^}]+)\}/);
|
|
110
|
+
if (classDynMatch) {
|
|
111
|
+
const expr = classDynMatch[1];
|
|
112
|
+
const newAttrs = attrs.replace(classDynMatch[0], ` class:list={[${expr}, className]}`);
|
|
113
|
+
return prefix + tagName + newAttrs + close + template.slice(fullMatch.length);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// No existing class — add class={className}
|
|
117
|
+
return prefix + tagName + ` class={className}` + attrs + close + template.slice(fullMatch.length);
|
|
118
|
+
}
|
|
119
|
+
|
|
79
120
|
// ---------------------------------------------------------------------------
|
|
80
121
|
// Main emitter
|
|
81
122
|
// ---------------------------------------------------------------------------
|
|
@@ -115,9 +156,12 @@ export function emitAstroComponent(
|
|
|
115
156
|
};
|
|
116
157
|
|
|
117
158
|
// Emit the template body
|
|
118
|
-
|
|
159
|
+
let templateBody = nodeToAstro(structure, ctx);
|
|
119
160
|
|
|
120
|
-
//
|
|
161
|
+
// Merge instance className onto the root element (for acceptsStyles support)
|
|
162
|
+
templateBody = mergeClassNameOntoRoot(templateBody);
|
|
163
|
+
|
|
164
|
+
// Build frontmatter (includes class prop for instance style support)
|
|
121
165
|
const frontmatter = buildFrontmatter(name, propDefs, ctx.imports, ctx.dynamicTags, ctx.needsI18nResolver ? defaultLocale : undefined);
|
|
122
166
|
|
|
123
167
|
// Build style/script sections
|
|
@@ -150,7 +194,8 @@ function buildFrontmatter(
|
|
|
150
194
|
|
|
151
195
|
const propEntries = Object.entries(propDefs);
|
|
152
196
|
|
|
153
|
-
|
|
197
|
+
// Always generate Props interface and destructuring (at minimum for class prop)
|
|
198
|
+
{
|
|
154
199
|
// Interface
|
|
155
200
|
lines.push('interface Props {');
|
|
156
201
|
for (const [propName, propDef] of propEntries) {
|
|
@@ -159,6 +204,8 @@ function buildFrontmatter(
|
|
|
159
204
|
const optional = 'default' in propDef && propDef.default !== undefined;
|
|
160
205
|
lines.push(` ${propName}${optional ? '?' : ''}: ${tsType};`);
|
|
161
206
|
}
|
|
207
|
+
// Always include class prop for instance style support
|
|
208
|
+
lines.push(' class?: string;');
|
|
162
209
|
lines.push('}');
|
|
163
210
|
lines.push('');
|
|
164
211
|
|
|
@@ -176,16 +223,17 @@ function buildFrontmatter(
|
|
|
176
223
|
}
|
|
177
224
|
}
|
|
178
225
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
lines.push(
|
|
226
|
+
// Always include class prop (renamed to className to avoid reserved word)
|
|
227
|
+
destructParts.push('class: className = ""');
|
|
228
|
+
|
|
229
|
+
if (destructParts.length <= 3 && destructParts.join(', ').length < 80) {
|
|
230
|
+
lines.push(`const { ${destructParts.join(', ')} } = Astro.props;`);
|
|
231
|
+
} else {
|
|
232
|
+
lines.push('const {');
|
|
233
|
+
for (const part of destructParts) {
|
|
234
|
+
lines.push(` ${part},`);
|
|
188
235
|
}
|
|
236
|
+
lines.push('} = Astro.props;');
|
|
189
237
|
}
|
|
190
238
|
}
|
|
191
239
|
|