@monoes/monodesign 1.2.18 → 1.2.22

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.
Files changed (104) hide show
  1. package/README.md +3 -3
  2. package/cli/engine/design-system-checks.mjs +381 -0
  3. package/cli/engine/design-system-parse.mjs +433 -0
  4. package/cli/engine/design-system.mjs +20 -736
  5. package/cli/engine/detect-antipatterns-browser.js +12 -1
  6. package/cli/engine/engines/regex/detect-text-matchers.mjs +371 -0
  7. package/cli/engine/engines/regex/detect-text-run.mjs +186 -0
  8. package/cli/engine/engines/regex/detect-text.mjs +11 -523
  9. package/cli/engine/engines/static-html/css-cascade-color.mjs +219 -0
  10. package/cli/engine/engines/static-html/css-cascade-static-data.mjs +138 -0
  11. package/cli/engine/engines/static-html/css-cascade-static-dom.mjs +132 -0
  12. package/cli/engine/engines/static-html/css-cascade-static-parse.mjs +393 -0
  13. package/cli/engine/engines/static-html/css-cascade.mjs +24 -837
  14. package/cli/engine/registry/antipatterns-provider.mjs +60 -0
  15. package/cli/engine/registry/antipatterns-quality.mjs +218 -0
  16. package/cli/engine/registry/antipatterns-slop.mjs +226 -0
  17. package/cli/engine/registry/antipatterns.mjs +7 -494
  18. package/cli/engine/rules/checks-background.mjs +155 -0
  19. package/cli/engine/rules/checks-css-values.mjs +137 -0
  20. package/cli/engine/rules/checks-dom-effects.mjs +243 -0
  21. package/cli/engine/rules/checks-dom.mjs +148 -0
  22. package/cli/engine/rules/checks-element.mjs +286 -0
  23. package/cli/engine/rules/checks-html.mjs +204 -0
  24. package/cli/engine/rules/checks-overflow.mjs +263 -0
  25. package/cli/engine/rules/checks-page.mjs +465 -0
  26. package/cli/engine/rules/checks-pure.mjs +453 -0
  27. package/cli/engine/rules/checks-quality.mjs +447 -0
  28. package/cli/engine/rules/checks-stylesheet.mjs +324 -0
  29. package/cli/engine/rules/checks.mjs +98 -2939
  30. package/cli/lib/monodesign-config-consent.mjs +93 -0
  31. package/cli/lib/monodesign-config-core.mjs +37 -0
  32. package/cli/lib/monodesign-config-detection.mjs +133 -0
  33. package/cli/lib/monodesign-config-filter.mjs +198 -0
  34. package/cli/lib/monodesign-config-ignore-value.mjs +212 -0
  35. package/cli/lib/monodesign-config.mjs +21 -623
  36. package/package.json +2 -2
  37. package/skill/scripts/context-fields.mjs +65 -0
  38. package/skill/scripts/context-resolve.mjs +425 -0
  39. package/skill/scripts/context-update.mjs +151 -0
  40. package/skill/scripts/context-utils.mjs +68 -0
  41. package/skill/scripts/context-workspace.mjs +245 -0
  42. package/skill/scripts/context.mjs +17 -891
  43. package/skill/scripts/hook-admin-config.mjs +152 -0
  44. package/skill/scripts/hook-admin-ignore.mjs +131 -0
  45. package/skill/scripts/hook-admin-manifest.mjs +292 -0
  46. package/skill/scripts/hook-admin.mjs +8 -541
  47. package/skill/scripts/hook-before-edit-shell.mjs +184 -0
  48. package/skill/scripts/hook-before-edit.mjs +9 -165
  49. package/skill/scripts/hook-lib-cache.mjs +145 -0
  50. package/skill/scripts/hook-lib-config.mjs +292 -0
  51. package/skill/scripts/hook-lib-detector.mjs +72 -0
  52. package/skill/scripts/hook-lib-events.mjs +182 -0
  53. package/skill/scripts/hook-lib-findings.mjs +201 -0
  54. package/skill/scripts/hook-lib-ignore.mjs +205 -0
  55. package/skill/scripts/hook-lib-render.mjs +273 -0
  56. package/skill/scripts/hook-lib-run.mjs +281 -0
  57. package/skill/scripts/hook-lib-scan-targets.mjs +128 -0
  58. package/skill/scripts/hook-lib.mjs +71 -1747
  59. package/skill/scripts/lib/design-parser-colors.mjs +182 -0
  60. package/skill/scripts/lib/design-parser-extract.mjs +333 -0
  61. package/skill/scripts/lib/design-parser-sections.mjs +214 -0
  62. package/skill/scripts/lib/design-parser-yaml.mjs +135 -0
  63. package/skill/scripts/lib/design-parser.mjs +10 -783
  64. package/skill/scripts/live/manual-apply-chunk.mjs +151 -0
  65. package/skill/scripts/live/manual-apply-compact.mjs +122 -0
  66. package/skill/scripts/live/manual-apply-evidence.mjs +115 -0
  67. package/skill/scripts/live/manual-apply-log.mjs +179 -0
  68. package/skill/scripts/live/manual-apply-transaction.mjs +122 -0
  69. package/skill/scripts/live/manual-apply.mjs +65 -640
  70. package/skill/scripts/live/svelte-component-accept.mjs +248 -0
  71. package/skill/scripts/live/svelte-component-css.mjs +230 -0
  72. package/skill/scripts/live/svelte-component-markup.mjs +104 -0
  73. package/skill/scripts/live/svelte-component-scaffold.mjs +118 -0
  74. package/skill/scripts/live/svelte-component-session.mjs +158 -0
  75. package/skill/scripts/live/svelte-component.mjs +32 -792
  76. package/skill/scripts/live-accept-mutate.mjs +152 -0
  77. package/skill/scripts/live-accept-parse.mjs +370 -0
  78. package/skill/scripts/live-accept-scrub.mjs +59 -0
  79. package/skill/scripts/live-accept-search.mjs +58 -0
  80. package/skill/scripts/live-accept.mjs +15 -631
  81. package/skill/scripts/live-commit-manual-edits-paths.mjs +27 -0
  82. package/skill/scripts/live-commit-manual-edits-repair.mjs +166 -0
  83. package/skill/scripts/live-commit-manual-edits-rollback.mjs +231 -0
  84. package/skill/scripts/live-commit-manual-edits-utils.mjs +167 -0
  85. package/skill/scripts/live-commit-manual-edits-verify.mjs +356 -0
  86. package/skill/scripts/live-commit-manual-edits.mjs +24 -881
  87. package/skill/scripts/live-copy-edit-agent.mjs +16 -551
  88. package/skill/scripts/live-copy-edit-compact.mjs +137 -0
  89. package/skill/scripts/live-copy-edit-post-apply.mjs +175 -0
  90. package/skill/scripts/live-copy-edit-runners.mjs +297 -0
  91. package/skill/scripts/live-inject-csp.mjs +132 -0
  92. package/skill/scripts/live-inject-files.mjs +229 -0
  93. package/skill/scripts/live-inject-tag.mjs +114 -0
  94. package/skill/scripts/live-inject.mjs +13 -440
  95. package/skill/scripts/live-server-poll.mjs +232 -0
  96. package/skill/scripts/live-server-routes.mjs +449 -0
  97. package/skill/scripts/live-server-state.mjs +361 -0
  98. package/skill/scripts/live-server.mjs +10 -946
  99. package/skill/scripts/live-wrap-css.mjs +132 -0
  100. package/skill/scripts/live-wrap-manual-edit.mjs +153 -0
  101. package/skill/scripts/live-wrap-search.mjs +247 -0
  102. package/skill/scripts/live-wrap.mjs +21 -484
  103. package/skill/scripts/palette-seeds.mjs +401 -0
  104. package/skill/scripts/palette.mjs +1 -395
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Accepting a chosen Svelte component variant back into route source for
3
+ * live/svelte-component.mjs: restoring prop expressions, merging the
4
+ * original element's top-level attributes, and splicing the markup (and any
5
+ * baked CSS) into the source file.
6
+ *
7
+ * Split out of live/svelte-component.mjs. See that file for context.
8
+ */
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import {
12
+ parseSvelteComponentFile,
13
+ substitutePropsWithExprs,
14
+ } from './svelte-component-markup.mjs';
15
+ import {
16
+ appendCssToSvelteStyle,
17
+ bakeParamValuesInCss,
18
+ sanitizeAcceptedSvelteCss,
19
+ } from './svelte-component-css.mjs';
20
+ import {
21
+ removeSvelteComponentSession,
22
+ resolveSourceFile,
23
+ } from './svelte-component-session.mjs';
24
+
25
+ export function inlineSvelteComponentAccept(manifest, variantNum, paramValues = null, cwd = process.cwd()) {
26
+ const sourceFile = resolveSourceFile(manifest.sourceFile, cwd);
27
+ const variantPath = path.join(cwd, manifest.componentDir, `v${variantNum}.svelte`);
28
+ const resultBase = {
29
+ file: manifest.sourceFile,
30
+ sourceFile: manifest.sourceFile,
31
+ previewMode: 'svelte-component',
32
+ componentDir: manifest.componentDir,
33
+ carbonize: false,
34
+ };
35
+ if (!fs.existsSync(variantPath)) {
36
+ return { handled: false, error: `Variant ${variantNum} not found`, ...resultBase };
37
+ }
38
+
39
+ const { markup, cssLines } = parseSvelteComponentFile(fs.readFileSync(variantPath, 'utf-8'));
40
+ if (manifest.mode === 'insert') {
41
+ return inlineSvelteComponentInsertAccept({
42
+ manifest,
43
+ markup,
44
+ cssLines,
45
+ variantNum,
46
+ paramValues,
47
+ sourceFile,
48
+ resultBase,
49
+ cwd,
50
+ });
51
+ }
52
+
53
+ const rootTag = matchOpeningTag(markup)?.tag || 'div';
54
+ const contract = manifest.propContract || [];
55
+ const mergedMarkup = mergeOriginalTopLevelAttrs(markup, manifest.originalMarkup || '');
56
+ const restoredMarkup = substitutePropsWithExprs(mergedMarkup, contract)
57
+ .split('\n')
58
+ .map((line) => line.trimEnd());
59
+
60
+ const sourceContent = fs.readFileSync(sourceFile, 'utf-8');
61
+ const sourceLines = sourceContent.split('\n');
62
+ const start = Number(manifest.sourceStartLine) - 1;
63
+ const end = Number(manifest.sourceEndLine) - 1;
64
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end >= sourceLines.length) {
65
+ return { handled: false, error: `Invalid source line range for ${manifest.sourceFile}`, ...resultBase };
66
+ }
67
+
68
+ const indent = sourceLines[start].match(/^(\s*)/)?.[1] || '';
69
+ const indentedMarkup = restoredMarkup.map((line) => {
70
+ if (line.trim() === '') return '';
71
+ return indent + line.trimStart();
72
+ });
73
+
74
+ let newLines = [
75
+ ...sourceLines.slice(0, start),
76
+ ...indentedMarkup,
77
+ ...sourceLines.slice(end + 1),
78
+ ];
79
+
80
+ const sanitizedCss = sanitizeAcceptedSvelteCss(cssLines, variantNum, paramValues, rootTag);
81
+ const bakedCss = bakeParamValuesInCss(sanitizedCss, paramValues);
82
+ if (bakedCss.length > 0) {
83
+ newLines = appendCssToSvelteStyle(newLines, bakedCss);
84
+ }
85
+
86
+ try {
87
+ fs.writeFileSync(sourceFile, newLines.join('\n'), 'utf-8');
88
+ } catch (err) {
89
+ return { handled: false, error: `Failed to write Svelte source: ${err.message}`, ...resultBase };
90
+ }
91
+ removeSvelteComponentSession(manifest.id, cwd);
92
+
93
+ return {
94
+ handled: true,
95
+ ...resultBase,
96
+ };
97
+ }
98
+
99
+ function inlineSvelteComponentInsertAccept({
100
+ manifest,
101
+ markup,
102
+ cssLines,
103
+ variantNum,
104
+ paramValues,
105
+ sourceFile,
106
+ resultBase,
107
+ cwd,
108
+ }) {
109
+ if (!svelteMarkupHasVisibleContent(markup)) {
110
+ return { handled: false, error: 'Accepted Svelte insert variant is empty', ...resultBase };
111
+ }
112
+ if (/\bdata-monodesign-[\w-]*\s*=/.test(markup)) {
113
+ return { handled: false, error: 'Accepted Svelte insert variant contains preview-only data-monodesign attributes', ...resultBase };
114
+ }
115
+
116
+ const rootTag = matchOpeningTag(markup)?.tag || 'div';
117
+ const restoredMarkup = String(markup || '')
118
+ .split('\n')
119
+ .map((line) => line.trimEnd());
120
+ const sourceContent = fs.readFileSync(sourceFile, 'utf-8');
121
+ const sourceLines = sourceContent.split('\n');
122
+ const insertIndex = Number(manifest.insertLine) - 1;
123
+ if (!Number.isInteger(insertIndex) || insertIndex < 0 || insertIndex > sourceLines.length) {
124
+ return { handled: false, error: `Invalid insert line for ${manifest.sourceFile}`, ...resultBase };
125
+ }
126
+
127
+ const nearbyLine = sourceLines[insertIndex] ?? sourceLines[insertIndex - 1] ?? '';
128
+ const indent = nearbyLine.match(/^(\s*)/)?.[1] || '';
129
+ const indentedMarkup = restoredMarkup.map((line) => {
130
+ if (line.trim() === '') return '';
131
+ return indent + line.trimStart();
132
+ });
133
+
134
+ let newLines = [
135
+ ...sourceLines.slice(0, insertIndex),
136
+ ...indentedMarkup,
137
+ ...sourceLines.slice(insertIndex),
138
+ ];
139
+
140
+ const sanitizedCss = sanitizeAcceptedSvelteCss(cssLines, variantNum, paramValues, rootTag);
141
+ const bakedCss = bakeParamValuesInCss(sanitizedCss, paramValues);
142
+ if (bakedCss.length > 0) {
143
+ newLines = appendCssToSvelteStyle(newLines, bakedCss);
144
+ }
145
+
146
+ try {
147
+ fs.writeFileSync(sourceFile, newLines.join('\n'), 'utf-8');
148
+ } catch (err) {
149
+ return { handled: false, error: `Failed to write Svelte source: ${err.message}`, ...resultBase };
150
+ }
151
+ removeSvelteComponentSession(manifest.id, cwd);
152
+
153
+ return {
154
+ handled: true,
155
+ ...resultBase,
156
+ };
157
+ }
158
+
159
+ function svelteMarkupHasVisibleContent(markup) {
160
+ const text = String(markup || '')
161
+ .replace(/<script[\s\S]*?<\/script>/gi, '')
162
+ .replace(/<style[\s\S]*?<\/style>/gi, '')
163
+ .replace(/<!--[\s\S]*?-->/g, '')
164
+ .replace(/<[^>]+>/g, ' ')
165
+ .replace(/\s+/g, ' ')
166
+ .trim();
167
+ if (text.length > 0) return true;
168
+ return /<(img|svg|canvas|video|audio|picture|input|button|select|textarea)\b/i.test(markup || '');
169
+ }
170
+
171
+ function mergeOriginalTopLevelAttrs(markup, originalMarkup) {
172
+ const variantOpen = matchOpeningTag(markup);
173
+ const originalOpen = matchOpeningTag(originalMarkup);
174
+ if (!variantOpen || !originalOpen) return markup;
175
+ if (variantOpen.tag.toLowerCase() !== originalOpen.tag.toLowerCase()) return markup;
176
+
177
+ const variantAttrs = parseAttrSegments(variantOpen.attrs);
178
+ const originalAttrs = parseAttrSegments(originalOpen.attrs);
179
+ const additions = [];
180
+ let attrs = variantOpen.attrs;
181
+
182
+ const originalClass = originalAttrs.get('class');
183
+ const variantClass = variantAttrs.get('class');
184
+ if (originalClass && variantClass) {
185
+ const merged = mergeStaticClassAttr(originalClass, variantClass);
186
+ if (merged) {
187
+ attrs = attrs.slice(0, variantClass.start) + merged + attrs.slice(variantClass.end);
188
+ variantAttrs.set('class', { ...variantClass, raw: merged });
189
+ }
190
+ } else if (originalClass && !variantClass) {
191
+ additions.push(originalClass.raw);
192
+ }
193
+
194
+ for (const [name, attr] of originalAttrs) {
195
+ if (name === 'class') continue;
196
+ if (!variantAttrs.has(name)) additions.push(attr.raw);
197
+ }
198
+
199
+ if (additions.length === 0 && attrs === variantOpen.attrs) return markup;
200
+ const nextOpen = variantOpen.prefix
201
+ + variantOpen.tag
202
+ + attrs
203
+ + additions.map((attr) => ` ${attr.trim()}`).join('')
204
+ + variantOpen.close;
205
+ return markup.slice(0, variantOpen.index) + nextOpen + markup.slice(variantOpen.index + variantOpen.raw.length);
206
+ }
207
+
208
+ function matchOpeningTag(markup) {
209
+ const match = String(markup || '').match(/^(\s*<)([A-Za-z][\w:-]*)([^>]*?)(\/?>)/);
210
+ if (!match) return null;
211
+ return {
212
+ raw: match[0],
213
+ prefix: match[1],
214
+ tag: match[2],
215
+ attrs: match[3] || '',
216
+ close: match[4],
217
+ index: match.index || 0,
218
+ };
219
+ }
220
+
221
+ function parseAttrSegments(attrs) {
222
+ const out = new Map();
223
+ const re = /([A-Za-z_:][\w:.-]*)(?:\s*=\s*(?:"[^"]*"|'[^']*'|\{[^}]*\}|[^\s"'>=]+))?/g;
224
+ let match;
225
+ while ((match = re.exec(attrs))) {
226
+ const raw = match[0];
227
+ const name = match[1];
228
+ out.set(name, {
229
+ name,
230
+ raw,
231
+ start: match.index,
232
+ end: match.index + raw.length,
233
+ });
234
+ }
235
+ return out;
236
+ }
237
+
238
+ function mergeStaticClassAttr(originalClass, variantClass) {
239
+ const originalValue = originalClass.raw.match(/class\s*=\s*(["'])(.*?)\1/);
240
+ const variantValue = variantClass.raw.match(/class\s*=\s*(["'])(.*?)\1/);
241
+ if (!originalValue || !variantValue) return null;
242
+ const quote = variantValue[1];
243
+ const classes = [
244
+ ...variantValue[2].split(/\s+/),
245
+ ...originalValue[2].split(/\s+/),
246
+ ].filter(Boolean);
247
+ return `class=${quote}${[...new Set(classes)].join(' ')}${quote}`;
248
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * CSS sanitization for accepted Svelte component variants in
3
+ * live/svelte-component.mjs: stripping preview-only selectors, rewriting
4
+ * `:scope`/`@scope` and param-conditional selectors down to the plain CSS
5
+ * that lands in the route source's <style> block.
6
+ *
7
+ * Split out of live/svelte-component.mjs. See that file for context.
8
+ */
9
+
10
+ export function appendCssToSvelteStyle(lines, cssLines) {
11
+ const closeIdx = findLastStyleCloseLine(lines);
12
+ const prepared = ['', ...cssLines.map((line) => (line.trim() === '' ? '' : ` ${line.trimStart()}`))];
13
+ if (closeIdx === -1) {
14
+ return [...lines, '', '<style>', ...prepared.slice(1), '</style>'];
15
+ }
16
+ return [
17
+ ...lines.slice(0, closeIdx),
18
+ ...prepared,
19
+ ...lines.slice(closeIdx),
20
+ ];
21
+ }
22
+
23
+ function findLastStyleCloseLine(lines) {
24
+ for (let i = lines.length - 1; i >= 0; i--) {
25
+ if (/<\/style\s*>/.test(lines[i])) return i;
26
+ }
27
+ return -1;
28
+ }
29
+
30
+ export function bakeParamValuesInCss(cssLines, paramValues) {
31
+ if (!paramValues || Object.keys(paramValues).length === 0) return cssLines;
32
+ return cssLines.map((line) => {
33
+ let out = line;
34
+ for (const [key, value] of Object.entries(paramValues)) {
35
+ const varName = `--p-${key}`;
36
+ out = out.replace(new RegExp(`var\\(${escapeRegExp(varName)}(?:,\\s*[^)]+)?\\)`, 'g'), String(value));
37
+ }
38
+ return out;
39
+ });
40
+ }
41
+
42
+ export function sanitizeAcceptedSvelteCss(cssLines, variantNum, paramValues = null, rootTag = 'div') {
43
+ const css = String((cssLines || []).join('\n'));
44
+ if (!/data-monodesign-variant|monodesign-variant-ready/.test(css)) return cssLines;
45
+
46
+ const rules = parseCssRules(css);
47
+ const output = [];
48
+ for (const rule of rules) {
49
+ appendSanitizedCssRule(output, rule, variantNum, paramValues, rootTag);
50
+ }
51
+ return output.join('\n')
52
+ .split('\n')
53
+ .map((line) => line.trimEnd())
54
+ .filter((line) => line.trim() !== '');
55
+ }
56
+
57
+ function appendSanitizedCssRule(output, rule, variantNum, paramValues, rootTag) {
58
+ const prelude = rule.prelude.trim();
59
+ const body = rule.body.trim();
60
+ if (!prelude || !body || /--monodesign-variant-ready\s*:/.test(body)) return;
61
+
62
+ if (/^@scope\b/i.test(prelude)) {
63
+ if (/data-monodesign-variant/.test(prelude) && !selectorHasVariant(prelude, variantNum)) return;
64
+ const inner = parseCssRules(body);
65
+ for (const innerRule of inner) {
66
+ const rewrittenPrelude = rewriteAcceptedSvelteSelector(innerRule.prelude, variantNum, paramValues, rootTag, true);
67
+ if (!rewrittenPrelude || /--monodesign-variant-ready\s*:/.test(innerRule.body)) continue;
68
+ output.push(formatCssRule(rewrittenPrelude, innerRule.body.trim()));
69
+ }
70
+ return;
71
+ }
72
+
73
+ const rewrittenPrelude = rewriteAcceptedSvelteSelector(prelude, variantNum, paramValues, rootTag, false);
74
+ if (!rewrittenPrelude) return;
75
+ output.push(formatCssRule(rewrittenPrelude, body));
76
+ }
77
+
78
+ function parseCssRules(css) {
79
+ const rules = [];
80
+ const text = String(css || '');
81
+ let i = 0;
82
+ while (i < text.length) {
83
+ while (i < text.length && /\s/.test(text[i])) i++;
84
+ const preludeStart = i;
85
+ while (i < text.length && text[i] !== '{') i++;
86
+ if (i >= text.length) break;
87
+ const prelude = text.slice(preludeStart, i).trim();
88
+ i++;
89
+ const bodyStart = i;
90
+ let depth = 1;
91
+ let quote = null;
92
+ let comment = false;
93
+ while (i < text.length && depth > 0) {
94
+ const ch = text[i];
95
+ const next = text[i + 1];
96
+ if (comment) {
97
+ if (ch === '*' && next === '/') {
98
+ comment = false;
99
+ i += 2;
100
+ continue;
101
+ }
102
+ i++;
103
+ continue;
104
+ }
105
+ if (quote) {
106
+ if (ch === '\\') {
107
+ i += 2;
108
+ continue;
109
+ }
110
+ if (ch === quote) quote = null;
111
+ i++;
112
+ continue;
113
+ }
114
+ if (ch === '/' && next === '*') {
115
+ comment = true;
116
+ i += 2;
117
+ continue;
118
+ }
119
+ if (ch === '"' || ch === "'") {
120
+ quote = ch;
121
+ i++;
122
+ continue;
123
+ }
124
+ if (ch === '{') depth++;
125
+ else if (ch === '}') depth--;
126
+ i++;
127
+ }
128
+ const body = text.slice(bodyStart, Math.max(bodyStart, i - 1));
129
+ if (prelude) rules.push({ prelude, body });
130
+ }
131
+ return rules;
132
+ }
133
+
134
+ function rewriteAcceptedSvelteSelector(prelude, variantNum, paramValues, rootTag, fromScope) {
135
+ const selectors = splitSelectorList(prelude);
136
+ const rewritten = [];
137
+ for (const selector of selectors) {
138
+ const next = rewriteAcceptedSvelteSelectorPart(selector, variantNum, paramValues, rootTag, fromScope);
139
+ if (next) rewritten.push(next);
140
+ }
141
+ return rewritten.join(', ');
142
+ }
143
+
144
+ function rewriteAcceptedSvelteSelectorPart(selector, variantNum, paramValues, rootTag, fromScope) {
145
+ let out = selector.trim();
146
+ const hasVariant = /data-monodesign-variant/.test(out);
147
+ if (hasVariant && !selectorHasVariant(out, variantNum)) return '';
148
+ if (hasVariant) {
149
+ out = out.replace(variantSelectorRegex(variantNum), '');
150
+ out = out.replace(/\[data-monodesign-variant=(["']).*?\1\]/g, '');
151
+ }
152
+
153
+ const paramResult = rewriteParamSelectors(out, paramValues);
154
+ if (!paramResult.keep) return '';
155
+ out = paramResult.selector;
156
+
157
+ out = out
158
+ .replace(/:scope(?:\[[^\]]+\])?\s*>\s*/g, '')
159
+ .replace(/:scope(?:\[[^\]]+\])?/g, rootTag || '')
160
+ .replace(/\s+/g, ' ')
161
+ .trim();
162
+
163
+ out = out.replace(/^[>+~]\s*/, '').trim();
164
+ if (!out && (hasVariant || fromScope)) return rootTag || ':global(*)';
165
+ return out;
166
+ }
167
+
168
+ function rewriteParamSelectors(selector, paramValues) {
169
+ let keep = true;
170
+ const next = selector.replace(/\[data-p-([A-Za-z0-9_-]+)(?:=(["'])(.*?)\2)?\]/g, (_match, key, _quote, expected) => {
171
+ if (!paramValues || !Object.hasOwn(paramValues, key)) return '';
172
+ const actual = paramValues[key];
173
+ if (expected != null && String(actual) !== String(expected)) {
174
+ keep = false;
175
+ return '';
176
+ }
177
+ if (expected == null && (actual === false || actual == null || actual === 'false' || actual === 'off' || actual === '0')) {
178
+ keep = false;
179
+ return '';
180
+ }
181
+ return '';
182
+ });
183
+ return { keep, selector: next };
184
+ }
185
+
186
+ function splitSelectorList(prelude) {
187
+ const selectors = [];
188
+ let start = 0;
189
+ let bracket = 0;
190
+ let paren = 0;
191
+ let quote = null;
192
+ for (let i = 0; i < prelude.length; i++) {
193
+ const ch = prelude[i];
194
+ if (quote) {
195
+ if (ch === '\\') i++;
196
+ else if (ch === quote) quote = null;
197
+ continue;
198
+ }
199
+ if (ch === '"' || ch === "'") {
200
+ quote = ch;
201
+ continue;
202
+ }
203
+ if (ch === '[') bracket++;
204
+ else if (ch === ']') bracket = Math.max(0, bracket - 1);
205
+ else if (ch === '(') paren++;
206
+ else if (ch === ')') paren = Math.max(0, paren - 1);
207
+ else if (ch === ',' && bracket === 0 && paren === 0) {
208
+ selectors.push(prelude.slice(start, i));
209
+ start = i + 1;
210
+ }
211
+ }
212
+ selectors.push(prelude.slice(start));
213
+ return selectors;
214
+ }
215
+
216
+ function selectorHasVariant(selector, variantNum) {
217
+ return variantSelectorRegex(variantNum).test(selector);
218
+ }
219
+
220
+ function variantSelectorRegex(variantNum) {
221
+ return new RegExp(`\\[data-monodesign-variant=(["'])${escapeRegExp(String(variantNum))}\\1\\]`, 'g');
222
+ }
223
+
224
+ function formatCssRule(selector, body) {
225
+ return `${selector} { ${body.trim()} }`;
226
+ }
227
+
228
+ function escapeRegExp(value) {
229
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
230
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Markup <-> prop-contract translation for live/svelte-component.mjs: mapping
3
+ * mustache expressions in wrapped markup to named Svelte props (and back),
4
+ * and parsing a variant .svelte file into its markup/CSS parts.
5
+ *
6
+ * Split out of live/svelte-component.mjs. See that file for context.
7
+ */
8
+
9
+ const MUSTACHE_RE = /\{([^{}]+)\}/g;
10
+
11
+ export function extractMustacheExpressions(text) {
12
+ const expressions = [];
13
+ const seen = new Set();
14
+ const lines = String(text || '').split('\n');
15
+ for (const line of lines) {
16
+ const trimmed = line.trim();
17
+ if (trimmed.startsWith('<!--')) continue;
18
+ let match;
19
+ MUSTACHE_RE.lastIndex = 0;
20
+ while ((match = MUSTACHE_RE.exec(line)) !== null) {
21
+ const expr = match[1].trim();
22
+ if (!expr || seen.has(expr)) continue;
23
+ seen.add(expr);
24
+ expressions.push(expr);
25
+ }
26
+ }
27
+ return expressions;
28
+ }
29
+
30
+ export function buildPropContract(expressions) {
31
+ return expressions.map((expr, index) => {
32
+ const derived = derivePropName(expr, index);
33
+ return {
34
+ prop: derived,
35
+ expr,
36
+ placeholder: `{${expr}}`,
37
+ };
38
+ });
39
+ }
40
+
41
+ function derivePropName(expr, index) {
42
+ const tail = expr.match(/(?:\.|\[)(\w+)\s*\]?$/);
43
+ if (tail?.[1] && /^[A-Za-z_$][\w$]*$/.test(tail[1])) {
44
+ return tail[1];
45
+ }
46
+ return `prop${index}`;
47
+ }
48
+
49
+ export function substituteExprsWithProps(markup, contract) {
50
+ let out = String(markup || '');
51
+ for (const entry of contract) {
52
+ out = out.split(entry.placeholder).join(`{${entry.prop}}`);
53
+ }
54
+ return out;
55
+ }
56
+
57
+ export function substitutePropsWithExprs(markup, contract) {
58
+ let out = String(markup || '');
59
+ for (const entry of contract) {
60
+ out = out.split(`{${entry.prop}}`).join(`{${entry.expr}}`);
61
+ }
62
+ return out;
63
+ }
64
+
65
+ export function parseSvelteComponentFile(content) {
66
+ const text = String(content || '');
67
+ const scriptMatch = text.match(/^([\s\S]*?)<script\b[^>]*>[\s\S]*?<\/script>/i);
68
+ const withoutScript = scriptMatch ? text.slice(scriptMatch[0].length) : text;
69
+ const styleMatch = withoutScript.match(/<style\b[^>]*>[\s\S]*?<\/style\s*>/i);
70
+ const styleBlock = styleMatch ? styleMatch[0] : '';
71
+ const markup = styleMatch
72
+ ? withoutScript.slice(0, styleMatch.index).trim()
73
+ : withoutScript.trim();
74
+ const cssLines = styleBlock
75
+ ? styleBlock
76
+ .replace(/^<style\b[^>]*>/i, '')
77
+ .replace(/<\/style\s*>$/i, '')
78
+ .split('\n')
79
+ .map((line) => line.trimEnd())
80
+ : [];
81
+ while (cssLines.length > 0 && cssLines[0].trim() === '') cssLines.shift();
82
+ while (cssLines.length > 0 && cssLines[cssLines.length - 1].trim() === '') cssLines.pop();
83
+ return { markup, cssLines, styleBlock };
84
+ }
85
+
86
+ function buildPropsScript(contract) {
87
+ if (contract.length === 0) {
88
+ return '<script>\n /** @type {Record<string, never>} */\n let {} = $props();\n</script>\n';
89
+ }
90
+ const names = contract.map((c) => c.prop).join(', ');
91
+ const typeFields = contract.map((c) => ` ${c.prop}: string;`).join('\n');
92
+ return `<script>\n /** @type {{\n${typeFields}\n }} */\n let { ${names} } = $props();\n</script>\n`;
93
+ }
94
+
95
+ export function buildVariantStub(variantNum, originalWithProps, contract) {
96
+ const propsComment = contract.length > 0
97
+ ? `\n<!-- Props: ${contract.map((c) => `${c.prop} <- {${c.expr}}`).join(', ')} -->\n`
98
+ : '';
99
+ return `${buildPropsScript(contract)}${propsComment}${originalWithProps.trim()}\n\n<style>\n /* Variant ${variantNum}: add scoped CSS here */\n</style>\n`;
100
+ }
101
+
102
+ export function buildInsertVariantStub(variantNum) {
103
+ return `${buildPropsScript([])}<div class="monodesign-insert-preview">Insert variant ${variantNum}</div>\n\n<style>\n .monodesign-insert-preview { display: block; }\n</style>\n`;
104
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Scaffolding a new Svelte component-injection session for
3
+ * live/svelte-component.mjs: writing the manifest and initial variant stub
4
+ * files under the session's component directory.
5
+ *
6
+ * Split out of live/svelte-component.mjs. See that file for context.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import {
11
+ componentSessionDir,
12
+ ensureRuntimeHelper,
13
+ SVELTE_RUNTIME_FILE,
14
+ } from './svelte-component-session.mjs';
15
+ import {
16
+ buildInsertVariantStub,
17
+ buildPropContract,
18
+ buildVariantStub,
19
+ extractMustacheExpressions,
20
+ substituteExprsWithProps,
21
+ } from './svelte-component-markup.mjs';
22
+
23
+ export function scaffoldSvelteComponentSession({
24
+ id,
25
+ count,
26
+ sourceFile,
27
+ sourceStartLine,
28
+ sourceEndLine,
29
+ originalLines,
30
+ cwd = process.cwd(),
31
+ }) {
32
+ ensureRuntimeHelper(cwd);
33
+ const dir = componentSessionDir(id, cwd);
34
+ fs.mkdirSync(dir, { recursive: true });
35
+
36
+ const originalMarkup = originalLines.join('\n');
37
+ const contract = buildPropContract(extractMustacheExpressions(originalMarkup));
38
+ const originalWithProps = substituteExprsWithProps(originalMarkup, contract);
39
+
40
+ const manifest = {
41
+ id,
42
+ previewMode: 'svelte-component',
43
+ sourceFile: sourceFile.split(path.sep).join('/'),
44
+ sourceStartLine,
45
+ sourceEndLine,
46
+ count,
47
+ propContract: contract,
48
+ originalMarkup,
49
+ componentDir: path.relative(cwd, dir).split(path.sep).join('/'),
50
+ runtimeModule: `/${SVELTE_RUNTIME_FILE}`,
51
+ };
52
+
53
+ fs.writeFileSync(path.join(dir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
54
+
55
+ for (let n = 1; n <= count; n++) {
56
+ const variantFile = path.join(dir, `v${n}.svelte`);
57
+ if (!fs.existsSync(variantFile)) {
58
+ fs.writeFileSync(variantFile, buildVariantStub(n, originalWithProps, contract), 'utf-8');
59
+ }
60
+ }
61
+
62
+ return {
63
+ manifest,
64
+ manifestFile: path.relative(cwd, path.join(dir, 'manifest.json')).split(path.sep).join('/'),
65
+ componentDir: manifest.componentDir,
66
+ propContract: contract,
67
+ };
68
+ }
69
+
70
+ export function scaffoldSvelteComponentInsertSession({
71
+ id,
72
+ count,
73
+ sourceFile,
74
+ insertLine,
75
+ position,
76
+ anchorStartLine,
77
+ anchorEndLine,
78
+ anchorLines,
79
+ cwd = process.cwd(),
80
+ }) {
81
+ ensureRuntimeHelper(cwd);
82
+ const dir = componentSessionDir(id, cwd);
83
+ fs.mkdirSync(dir, { recursive: true });
84
+
85
+ const anchorMarkup = (anchorLines || []).join('\n');
86
+ const manifest = {
87
+ id,
88
+ mode: 'insert',
89
+ previewMode: 'svelte-component',
90
+ sourceFile: sourceFile.split(path.sep).join('/'),
91
+ insertLine,
92
+ position,
93
+ anchorStartLine,
94
+ anchorEndLine,
95
+ originalMarkup: anchorMarkup,
96
+ anchorMarkup,
97
+ count,
98
+ propContract: [],
99
+ componentDir: path.relative(cwd, dir).split(path.sep).join('/'),
100
+ runtimeModule: `/${SVELTE_RUNTIME_FILE}`,
101
+ };
102
+
103
+ fs.writeFileSync(path.join(dir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
104
+
105
+ for (let n = 1; n <= count; n++) {
106
+ const variantFile = path.join(dir, `v${n}.svelte`);
107
+ if (!fs.existsSync(variantFile)) {
108
+ fs.writeFileSync(variantFile, buildInsertVariantStub(n), 'utf-8');
109
+ }
110
+ }
111
+
112
+ return {
113
+ manifest,
114
+ manifestFile: path.relative(cwd, path.join(dir, 'manifest.json')).split(path.sep).join('/'),
115
+ componentDir: manifest.componentDir,
116
+ propContract: [],
117
+ };
118
+ }