@vedangiitb/qwintly-core 1.4.3 → 1.4.5

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 (40) hide show
  1. package/dist/ai/tools/helpers/pageConfigJson.helpers.d.ts +3 -0
  2. package/dist/ai/tools/helpers/pageConfigJson.helpers.d.ts.map +1 -1
  3. package/dist/ai/tools/helpers/pageConfigJson.helpers.js +61 -0
  4. package/dist/ai/tools/helpers/pageConfigJson.helpers.js.map +1 -1
  5. package/dist/ai/tools/implementations/deleteElement.impl.js +2 -2
  6. package/dist/ai/tools/implementations/deleteElement.impl.js.map +1 -1
  7. package/dist/ai/tools/implementations/insertElement.impl.js +2 -2
  8. package/dist/ai/tools/implementations/insertElement.impl.js.map +1 -1
  9. package/dist/ai/tools/implementations/updateClassName.impl.js +2 -2
  10. package/dist/ai/tools/implementations/updateClassName.impl.js.map +1 -1
  11. package/dist/ai/tools/implementations/updateProps.impl.js +2 -2
  12. package/dist/ai/tools/implementations/updateProps.impl.js.map +1 -1
  13. package/dist/ai/tools/schemas/createNewRoute.schema.js +1 -1
  14. package/dist/ai/tools/schemas/createNewRoute.schema.js.map +1 -1
  15. package/dist/ai/tools/schemas/deleteElement.schema.js +1 -1
  16. package/dist/ai/tools/schemas/deleteElement.schema.js.map +1 -1
  17. package/dist/ai/tools/schemas/insertElement.schema.js +1 -1
  18. package/dist/ai/tools/schemas/insertElement.schema.js.map +1 -1
  19. package/dist/ai/tools/schemas/updateClassName.schema.js +1 -1
  20. package/dist/ai/tools/schemas/updateClassName.schema.js.map +1 -1
  21. package/dist/ai/tools/schemas/updateProps.schema.js +1 -1
  22. package/dist/ai/tools/schemas/updateProps.schema.js.map +1 -1
  23. package/dist/ai/tools/validators/builderElement.zod.js +2 -2
  24. package/dist/ai/tools/validators/builderElement.zod.js.map +1 -1
  25. package/dist/image/unsplash.service.js +1 -1
  26. package/dist/image/unsplash.service.js.map +1 -1
  27. package/dist/indexer/projectInfoIndex.d.ts.map +1 -1
  28. package/dist/indexer/projectInfoIndex.js +70 -203
  29. package/dist/indexer/projectInfoIndex.js.map +1 -1
  30. package/dist/tests/insertUpdate.impl.test.js +93 -0
  31. package/dist/tests/insertUpdate.impl.test.js.map +1 -1
  32. package/dist/tests/projectInfoIndex.test.d.ts +2 -0
  33. package/dist/tests/projectInfoIndex.test.d.ts.map +1 -0
  34. package/dist/tests/projectInfoIndex.test.js +75 -0
  35. package/dist/tests/projectInfoIndex.test.js.map +1 -0
  36. package/dist/tests/unsplash.service.test.d.ts +2 -0
  37. package/dist/tests/unsplash.service.test.d.ts.map +1 -0
  38. package/dist/tests/unsplash.service.test.js +94 -0
  39. package/dist/tests/unsplash.service.test.js.map +1 -0
  40. package/package.json +1 -1
@@ -122,7 +122,7 @@ export const resolveUnsplashImagesDeep = async (el) => {
122
122
  const anyEl = el;
123
123
  if (anyEl?.type === "image") {
124
124
  const alt = String(anyEl?.props?.alt ?? "").trim();
125
- resolveUnsplashImageForElement(el, alt);
125
+ await resolveUnsplashImageForElement(el, alt);
126
126
  }
127
127
  const kids = anyEl?.children;
128
128
  if (Array.isArray(kids)) {
@@ -1 +1 @@
1
- {"version":3,"file":"unsplash.service.js","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"crypto\";\r\nimport { BuilderElement } from \"../types/elements.js\";\r\n\r\nexport interface ResolvedImage {\r\n id: string;\r\n alt: string;\r\n imageUrl: string;\r\n thumbUrl: string;\r\n photographer: string;\r\n photographerUrl: string;\r\n unsplashUrl: string;\r\n width: number;\r\n height: number;\r\n}\r\n\r\ntype UnsplashConfig = {\r\n url: string;\r\n accessKey: string;\r\n};\r\n\r\nlet unsplashConfig: UnsplashConfig | null = null;\r\n\r\nconst cache = new Map<string, ResolvedImage>();\r\n\r\nasync function fetchWithTimeout(url: string, options: RequestInit = {}): Promise<Response> {\r\n const controller = new AbortController();\r\n const id = setTimeout(() => controller.abort(), 5000);\r\n try {\r\n return await fetch(url, {\r\n ...options,\r\n signal: controller.signal,\r\n });\r\n } finally {\r\n clearTimeout(id);\r\n }\r\n}\r\n\r\nexport function initUnsplash(\r\n config: Partial<UnsplashConfig> | null | undefined,\r\n) {\r\n const url = String(config?.url ?? \"\").trim();\r\n const accessKey = String(config?.accessKey ?? \"\").trim();\r\n\r\n if (!url || !accessKey) {\r\n unsplashConfig = null;\r\n return;\r\n }\r\n\r\n unsplashConfig = {\r\n url: url.replace(/\\/+$/, \"\"),\r\n accessKey,\r\n };\r\n}\r\n\r\nfunction hashIntent(imgQuery: string) {\r\n return crypto.createHash(\"sha256\").update(imgQuery).digest(\"hex\");\r\n}\r\n\r\nfunction scoreImage(photo: any) {\r\n let score = 0;\r\n\r\n score += (photo.likes || 0) * 0.3;\r\n\r\n if (photo.width > photo.height) {\r\n score += 50;\r\n }\r\n\r\n score += (photo.user.total_photos || 0) * 0.05;\r\n\r\n return score;\r\n}\r\n\r\nexport async function searchUnsplashImage(\r\n imgQuery: string,\r\n): Promise<ResolvedImage | null> {\r\n console.log(\"searchUnsplashImage\", imgQuery);\r\n const cfg = unsplashConfig;\r\n if (!cfg) throw new Error(\"Unsplash not configured\");\r\n\r\n const cacheKey = hashIntent(imgQuery);\r\n\r\n const cached = cache.get(cacheKey);\r\n\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const params = new URLSearchParams({\r\n imgQuery,\r\n per_page: \"10\",\r\n });\r\n\r\n const response = await fetchWithTimeout(\r\n `${cfg.url}/search/photos?${params.toString()}`,\r\n {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (!response.ok) {\r\n throw new Error(`Unsplash API failed: ${response.status}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n const photos = data.results;\r\n\r\n if (!photos.length) {\r\n return null;\r\n }\r\n\r\n const ranked = [...photos].sort((a, b) => {\r\n return scoreImage(b) - scoreImage(a);\r\n });\r\n\r\n const top3 = ranked.slice(0, 3);\r\n\r\n const selected = top3[Math.floor(Math.random() * top3.length)];\r\n\r\n // IMPORTANT:\r\n // Required by Unsplash API terms\r\n // Tracks image download usage\r\n if (selected.links.download_location) {\r\n await fetchWithTimeout(selected.links.download_location, {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n });\r\n }\r\n\r\n const resolved: ResolvedImage = {\r\n id: selected.id,\r\n alt: imgQuery,\r\n\r\n imageUrl: selected.urls.regular,\r\n thumbUrl: selected.urls.small,\r\n\r\n photographer: selected.user.name,\r\n photographerUrl: selected.user.links.html,\r\n\r\n unsplashUrl: selected.links.html,\r\n\r\n width: selected.width,\r\n height: selected.height,\r\n };\r\n\r\n cache.set(cacheKey, resolved);\r\n\r\n return resolved;\r\n}\r\n\r\nexport const resolveUnsplashImageForElement = async (\r\n el: BuilderElement,\r\n alt: string,\r\n): Promise<void> => {\r\n if (!unsplashConfig) return;\r\n const query = String(alt ?? \"\").trim();\r\n if (!query) return;\r\n if (el.props?.src) {\r\n console.log(\"src already exists, skipping\");\r\n return;\r\n }\r\n try {\r\n const resolved = await searchUnsplashImage(query);\r\n if (!resolved?.imageUrl) return;\r\n\r\n const anyEl = el as any;\r\n if (!anyEl.props || typeof anyEl.props !== \"object\") anyEl.props = {};\r\n anyEl.props.src = resolved.imageUrl;\r\n anyEl.props.alt = query;\r\n } catch {\r\n // Ignore Unsplash lookup errors and still allow prop updates.\r\n }\r\n};\r\n\r\nexport const resolveUnsplashImagesDeep = async (\r\n el: BuilderElement,\r\n): Promise<void> => {\r\n const anyEl = el as any;\r\n\r\n if (anyEl?.type === \"image\") {\r\n const alt = String(anyEl?.props?.alt ?? \"\").trim();\r\n resolveUnsplashImageForElement(el, alt);\r\n }\r\n\r\n const kids = anyEl?.children;\r\n if (Array.isArray(kids)) {\r\n for (const child of kids) {\r\n await resolveUnsplashImagesDeep(child);\r\n }\r\n }\r\n};\r\n"]}
1
+ {"version":3,"file":"unsplash.service.js","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"crypto\";\r\nimport { BuilderElement } from \"../types/elements.js\";\r\n\r\nexport interface ResolvedImage {\r\n id: string;\r\n alt: string;\r\n imageUrl: string;\r\n thumbUrl: string;\r\n photographer: string;\r\n photographerUrl: string;\r\n unsplashUrl: string;\r\n width: number;\r\n height: number;\r\n}\r\n\r\ntype UnsplashConfig = {\r\n url: string;\r\n accessKey: string;\r\n};\r\n\r\nlet unsplashConfig: UnsplashConfig | null = null;\r\n\r\nconst cache = new Map<string, ResolvedImage>();\r\n\r\nasync function fetchWithTimeout(url: string, options: RequestInit = {}): Promise<Response> {\r\n const controller = new AbortController();\r\n const id = setTimeout(() => controller.abort(), 5000);\r\n try {\r\n return await fetch(url, {\r\n ...options,\r\n signal: controller.signal,\r\n });\r\n } finally {\r\n clearTimeout(id);\r\n }\r\n}\r\n\r\nexport function initUnsplash(\r\n config: Partial<UnsplashConfig> | null | undefined,\r\n) {\r\n const url = String(config?.url ?? \"\").trim();\r\n const accessKey = String(config?.accessKey ?? \"\").trim();\r\n\r\n if (!url || !accessKey) {\r\n unsplashConfig = null;\r\n return;\r\n }\r\n\r\n unsplashConfig = {\r\n url: url.replace(/\\/+$/, \"\"),\r\n accessKey,\r\n };\r\n}\r\n\r\nfunction hashIntent(imgQuery: string) {\r\n return crypto.createHash(\"sha256\").update(imgQuery).digest(\"hex\");\r\n}\r\n\r\nfunction scoreImage(photo: any) {\r\n let score = 0;\r\n\r\n score += (photo.likes || 0) * 0.3;\r\n\r\n if (photo.width > photo.height) {\r\n score += 50;\r\n }\r\n\r\n score += (photo.user.total_photos || 0) * 0.05;\r\n\r\n return score;\r\n}\r\n\r\nexport async function searchUnsplashImage(\r\n imgQuery: string,\r\n): Promise<ResolvedImage | null> {\r\n console.log(\"searchUnsplashImage\", imgQuery);\r\n const cfg = unsplashConfig;\r\n if (!cfg) throw new Error(\"Unsplash not configured\");\r\n\r\n const cacheKey = hashIntent(imgQuery);\r\n\r\n const cached = cache.get(cacheKey);\r\n\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const params = new URLSearchParams({\r\n imgQuery,\r\n per_page: \"10\",\r\n });\r\n\r\n const response = await fetchWithTimeout(\r\n `${cfg.url}/search/photos?${params.toString()}`,\r\n {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (!response.ok) {\r\n throw new Error(`Unsplash API failed: ${response.status}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n const photos = data.results;\r\n\r\n if (!photos.length) {\r\n return null;\r\n }\r\n\r\n const ranked = [...photos].sort((a, b) => {\r\n return scoreImage(b) - scoreImage(a);\r\n });\r\n\r\n const top3 = ranked.slice(0, 3);\r\n\r\n const selected = top3[Math.floor(Math.random() * top3.length)];\r\n\r\n // IMPORTANT:\r\n // Required by Unsplash API terms\r\n // Tracks image download usage\r\n if (selected.links.download_location) {\r\n await fetchWithTimeout(selected.links.download_location, {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n });\r\n }\r\n\r\n const resolved: ResolvedImage = {\r\n id: selected.id,\r\n alt: imgQuery,\r\n\r\n imageUrl: selected.urls.regular,\r\n thumbUrl: selected.urls.small,\r\n\r\n photographer: selected.user.name,\r\n photographerUrl: selected.user.links.html,\r\n\r\n unsplashUrl: selected.links.html,\r\n\r\n width: selected.width,\r\n height: selected.height,\r\n };\r\n\r\n cache.set(cacheKey, resolved);\r\n\r\n return resolved;\r\n}\r\n\r\nexport const resolveUnsplashImageForElement = async (\r\n el: BuilderElement,\r\n alt: string,\r\n): Promise<void> => {\r\n if (!unsplashConfig) return;\r\n const query = String(alt ?? \"\").trim();\r\n if (!query) return;\r\n if (el.props?.src) {\r\n console.log(\"src already exists, skipping\");\r\n return;\r\n }\r\n try {\r\n const resolved = await searchUnsplashImage(query);\r\n if (!resolved?.imageUrl) return;\r\n\r\n const anyEl = el as any;\r\n if (!anyEl.props || typeof anyEl.props !== \"object\") anyEl.props = {};\r\n anyEl.props.src = resolved.imageUrl;\r\n anyEl.props.alt = query;\r\n } catch {\r\n // Ignore Unsplash lookup errors and still allow prop updates.\r\n }\r\n};\r\n\r\nexport const resolveUnsplashImagesDeep = async (\r\n el: BuilderElement,\r\n): Promise<void> => {\r\n const anyEl = el as any;\r\n\r\n if (anyEl?.type === \"image\") {\r\n const alt = String(anyEl?.props?.alt ?? \"\").trim();\r\n await resolveUnsplashImageForElement(el, alt);\r\n }\r\n\r\n const kids = anyEl?.children;\r\n if (Array.isArray(kids)) {\r\n for (const child of kids) {\r\n await resolveUnsplashImagesDeep(child);\r\n }\r\n }\r\n};\r\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"projectInfoIndex.d.ts","sourceRoot":"","sources":["../../src/indexer/projectInfoIndex.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAuQ5D,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAkDtB"}
1
+ {"version":3,"file":"projectInfoIndex.d.ts","sourceRoot":"","sources":["../../src/indexer/projectInfoIndex.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAyF5D,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAkDtB"}
@@ -1,19 +1,5 @@
1
- import path from "node:path";
1
+ import { getAvailableRoutes, getPageConfigJsonPath, } from "../ai/tools/helpers/pageConfigJson.helpers.js";
2
2
  import { readFile, safeReadDir } from "../utils/workspace.js";
3
- const isRouteGroup = (segment) => segment.startsWith("(") && segment.endsWith(")");
4
- const isParallelRoute = (segment) => segment.startsWith("@");
5
- const toPosixPath = (p) => p.replace(/\\/g, "/");
6
- const extractQuotedString = (match) => {
7
- if (!match)
8
- return "";
9
- return String(match[1] ?? match[2] ?? match[3] ?? "").trim();
10
- };
11
- const extractPropString = (objText, prop) => {
12
- const re = prop === "id"
13
- ? /\bid\s*:\s*(?:"([^"]+)"|'([^']+)'|`([^`]+)`)/
14
- : /\btype\s*:\s*(?:"([^"]+)"|'([^']+)'|`([^`]+)`)/;
15
- return extractQuotedString(objText.match(re));
16
- };
17
3
  const sectionNameFromId = (id) => {
18
4
  if (!id)
19
5
  return "";
@@ -23,216 +9,97 @@ const sectionNameFromId = (id) => {
23
9
  return id.slice(0, -"-container".length);
24
10
  return id;
25
11
  };
26
- const extractDirectContainerIdsFromArrayText = (arrayText) => {
27
- const seen = new Set();
28
- const results = [];
29
- let bracketDepth = 0;
30
- const objectStartStack = [];
31
- let inString = null;
32
- for (let i = 0; i < arrayText.length; i++) {
33
- const ch = arrayText[i];
34
- if (inString) {
35
- if (inString !== "`" && ch === "\\") {
36
- i += 1;
37
- continue;
38
- }
39
- if (ch === inString)
40
- inString = null;
41
- continue;
42
- }
43
- if (ch === '"' || ch === "'" || ch === "`") {
44
- inString = ch;
45
- continue;
46
- }
47
- if (ch === "[") {
48
- bracketDepth += 1;
49
- continue;
50
- }
51
- if (ch === "]") {
52
- bracketDepth -= 1;
53
- continue;
54
- }
55
- if (ch === "{") {
56
- if (bracketDepth >= 1)
57
- objectStartStack.push(i);
58
- continue;
59
- }
60
- if (ch === "}") {
61
- const start = objectStartStack.pop();
62
- if (start === undefined)
63
- continue;
64
- // only consider objects that are direct children of the current array
65
- if (bracketDepth !== 1 || objectStartStack.length !== 0)
66
- continue;
67
- const objText = arrayText.slice(start, i + 1);
68
- const id = extractPropString(objText, "id");
69
- const type = extractPropString(objText, "type");
70
- if (type === "div" && id && id !== "root" && !seen.has(id)) {
71
- seen.add(id);
72
- results.push(id);
73
- }
74
- continue;
75
- }
76
- }
77
- return results;
12
+ const computePageNameFromRoute = (pageRoute) => {
13
+ if (pageRoute === "/")
14
+ return "root";
15
+ return pageRoute.slice(1).split("/").join("-");
78
16
  };
79
- const extractRootChildSectionNames = (rootObjText) => {
80
- const childrenKey = rootObjText.indexOf("children");
81
- if (childrenKey < 0)
17
+ const extractSectionNamesFromParsedConfig = (config) => {
18
+ if (!config || !Array.isArray(config.elements))
82
19
  return [];
83
- const arrayStart = rootObjText.indexOf("[", childrenKey);
84
- if (arrayStart < 0)
85
- return [];
86
- // isolate the children array text (including brackets)
87
- let bracketDepth = 0;
88
- let inString = null;
89
- for (let i = arrayStart; i < rootObjText.length; i++) {
90
- const ch = rootObjText[i];
91
- if (inString) {
92
- if (inString !== "`" && ch === "\\") {
93
- i += 1;
94
- continue;
20
+ const elements = config.elements;
21
+ // Let's find the root element (where type === "div" && id === "root") recursively
22
+ let rootElement = null;
23
+ const findRoot = (els) => {
24
+ for (const el of els) {
25
+ if (el && typeof el === "object") {
26
+ if (el.type === "div" && el.id === "root") {
27
+ rootElement = el;
28
+ return;
29
+ }
30
+ if (Array.isArray(el.children)) {
31
+ findRoot(el.children);
32
+ if (rootElement)
33
+ return;
34
+ }
95
35
  }
96
- if (ch === inString)
97
- inString = null;
98
- continue;
99
- }
100
- if (ch === '"' || ch === "'" || ch === "`") {
101
- inString = ch;
102
- continue;
103
36
  }
104
- if (ch === "[")
105
- bracketDepth += 1;
106
- if (ch === "]") {
107
- bracketDepth -= 1;
108
- if (bracketDepth === 0) {
109
- const arrayText = rootObjText.slice(arrayStart, i + 1);
110
- const ids = extractDirectContainerIdsFromArrayText(arrayText);
111
- return ids.map(sectionNameFromId).filter(Boolean);
37
+ };
38
+ findRoot(elements);
39
+ if (rootElement && Array.isArray(rootElement.children)) {
40
+ const rootSections = [];
41
+ for (const child of rootElement.children) {
42
+ if (child &&
43
+ typeof child === "object" &&
44
+ child.type === "div" &&
45
+ child.id &&
46
+ child.id !== "root") {
47
+ const name = sectionNameFromId(child.id);
48
+ if (name) {
49
+ rootSections.push(name);
50
+ }
112
51
  }
113
52
  }
53
+ if (rootSections.length > 0) {
54
+ return rootSections;
55
+ }
114
56
  }
115
- return [];
116
- };
117
- const extractSectionNamesFromConfig = (content) => {
118
- if (!content)
119
- return [];
120
- const elementsKey = content.indexOf("elements");
121
- if (elementsKey < 0)
122
- return [];
123
- const arrayStart = content.indexOf("[", elementsKey);
124
- if (arrayStart < 0)
125
- return [];
57
+ // Fallback: collect all divs with id ending in "-section" or "-container"
126
58
  const seen = new Set();
127
59
  const results = [];
128
- let bracketDepth = 0;
129
- const objectStartStack = [];
130
- let inString = null;
131
- for (let i = arrayStart; i < content.length; i++) {
132
- const ch = content[i];
133
- if (inString) {
134
- if (inString !== "`" && ch === "\\") {
135
- i += 1;
136
- continue;
137
- }
138
- if (ch === inString) {
139
- inString = null;
140
- }
141
- continue;
142
- }
143
- if (ch === '"' || ch === "'" || ch === "`") {
144
- inString = ch;
145
- continue;
146
- }
147
- if (ch === "[") {
148
- bracketDepth += 1;
149
- continue;
150
- }
151
- if (ch === "]") {
152
- bracketDepth -= 1;
153
- if (bracketDepth <= 0)
154
- break;
155
- continue;
156
- }
157
- if (ch === "{") {
158
- if (bracketDepth >= 1) {
159
- objectStartStack.push(i);
160
- }
161
- continue;
162
- }
163
- if (ch === "}") {
164
- const objectStart = objectStartStack.pop();
165
- if (bracketDepth >= 1 && objectStart !== undefined) {
166
- const objText = content.slice(objectStart, i + 1);
167
- const id = extractPropString(objText, "id");
168
- const type = extractPropString(objText, "type");
169
- if (type === "div" && id === "root") {
170
- const rootSections = extractRootChildSectionNames(objText);
171
- if (rootSections.length > 0) {
172
- return rootSections;
173
- }
174
- }
175
- if (type === "div" &&
176
- id &&
177
- id !== "root" &&
178
- (id.endsWith("-section") || id.endsWith("-container"))) {
179
- const sectionName = sectionNameFromId(id);
60
+ const collectFallback = (els) => {
61
+ for (const el of els) {
62
+ if (el && typeof el === "object") {
63
+ if (el.type === "div" &&
64
+ el.id &&
65
+ el.id !== "root" &&
66
+ (el.id.endsWith("-section") || el.id.endsWith("-container"))) {
67
+ const sectionName = sectionNameFromId(el.id);
180
68
  if (sectionName && !seen.has(sectionName)) {
181
69
  seen.add(sectionName);
182
70
  results.push(sectionName);
183
71
  }
184
72
  }
73
+ if (Array.isArray(el.children)) {
74
+ collectFallback(el.children);
75
+ }
185
76
  }
186
- continue;
187
77
  }
188
- }
189
- return results;
190
- };
191
- const computePageRouteFromSegments = (segments) => {
192
- const filtered = segments.filter((s) => s && !isRouteGroup(s) && !isParallelRoute(s));
193
- if (filtered.length === 0)
194
- return "/";
195
- return `/${filtered.join("/")}`;
196
- };
197
- const computePageNameFromRoute = (pageRoute) => {
198
- if (pageRoute === "/")
199
- return "root";
200
- return pageRoute.slice(1).split("/").join("-");
201
- };
202
- const findPageConfigFiles = async (dir) => {
203
- const entries = await safeReadDir(dir);
204
- const results = [];
205
- for (const entry of entries) {
206
- if (!entry.isDirectory() && !entry.isFile())
207
- continue;
208
- const fullPath = path.join(dir, entry.name);
209
- if (entry.isDirectory()) {
210
- results.push(...(await findPageConfigFiles(fullPath)));
211
- continue;
212
- }
213
- if (entry.name === "page.config.ts") {
214
- results.push(fullPath);
215
- }
216
- }
78
+ };
79
+ collectFallback(elements);
217
80
  return results;
218
81
  };
219
82
  export async function computeProjectInfo(rootDir) {
220
- const effectiveRoot = rootDir;
221
- const appDir = path.join(effectiveRoot, "app");
222
- const pageConfigFiles = await findPageConfigFiles(appDir);
83
+ const routes = await getAvailableRoutes({
84
+ workspaceRoot: rootDir,
85
+ fs: { safeReadDir },
86
+ });
223
87
  const uiPages = [];
224
- for (const filePath of pageConfigFiles) {
225
- const relFromApp = toPosixPath(path.relative(appDir, filePath));
226
- const relDir = path.posix.dirname(relFromApp);
227
- const segments = relDir === "." ? [] : relDir.split("/").filter(Boolean);
228
- if (segments.some(isParallelRoute)) {
229
- continue;
230
- }
231
- const pageRoute = computePageRouteFromSegments(segments);
88
+ for (const pageRoute of routes) {
232
89
  const pageName = computePageNameFromRoute(pageRoute);
233
90
  const description = `${pageName} page for this project`;
234
- const content = await readFile(filePath);
235
- const sectionNames = extractSectionNamesFromConfig(content);
91
+ let sectionNames = [];
92
+ try {
93
+ const configPath = getPageConfigJsonPath(rootDir, pageRoute);
94
+ const content = await readFile(configPath);
95
+ if (content) {
96
+ const config = JSON.parse(content);
97
+ sectionNames = extractSectionNamesFromParsedConfig(config);
98
+ }
99
+ }
100
+ catch (err) {
101
+ // Ignore reading/parsing errors, treat as no sections
102
+ }
236
103
  const page = {
237
104
  pageRoute,
238
105
  pageName,
@@ -1 +1 @@
1
- {"version":3,"file":"projectInfoIndex.js","sourceRoot":"","sources":["../../src/indexer/projectInfoIndex.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { ContextRepository } from \"../repository/context.repository.js\";\nimport { ProjectInfo } from \"../types/projectInfo.types.js\";\nimport { readFile, safeReadDir } from \"../utils/workspace.js\";\n\ntype UiPage = ProjectInfo[\"uiPages\"][number];\n\nconst isRouteGroup = (segment: string) =>\n segment.startsWith(\"(\") && segment.endsWith(\")\");\n\nconst isParallelRoute = (segment: string) => segment.startsWith(\"@\");\n\nconst toPosixPath = (p: string) => p.replace(/\\\\/g, \"/\");\n\nconst extractQuotedString = (match: RegExpMatchArray | null): string => {\n if (!match) return \"\";\n return String(match[1] ?? match[2] ?? match[3] ?? \"\").trim();\n};\n\nconst extractPropString = (objText: string, prop: \"id\" | \"type\"): string => {\n const re =\n prop === \"id\"\n ? /\\bid\\s*:\\s*(?:\"([^\"]+)\"|'([^']+)'|`([^`]+)`)/\n : /\\btype\\s*:\\s*(?:\"([^\"]+)\"|'([^']+)'|`([^`]+)`)/;\n\n return extractQuotedString(objText.match(re));\n};\n\nconst sectionNameFromId = (id: string): string => {\n if (!id) return \"\";\n if (id.endsWith(\"-section\")) return id.slice(0, -\"-section\".length);\n if (id.endsWith(\"-container\")) return id.slice(0, -\"-container\".length);\n return id;\n};\n\nconst extractDirectContainerIdsFromArrayText = (\n arrayText: string,\n): string[] => {\n const seen = new Set<string>();\n const results: string[] = [];\n\n let bracketDepth = 0;\n const objectStartStack: number[] = [];\n let inString: '\"' | \"'\" | \"`\" | null = null;\n\n for (let i = 0; i < arrayText.length; i++) {\n const ch = arrayText[i]!;\n\n if (inString) {\n if (inString !== \"`\" && ch === \"\\\\\") {\n i += 1;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n continue;\n }\n\n if (ch === \"[\") {\n bracketDepth += 1;\n continue;\n }\n if (ch === \"]\") {\n bracketDepth -= 1;\n continue;\n }\n\n if (ch === \"{\") {\n if (bracketDepth >= 1) objectStartStack.push(i);\n continue;\n }\n\n if (ch === \"}\") {\n const start = objectStartStack.pop();\n if (start === undefined) continue;\n\n // only consider objects that are direct children of the current array\n if (bracketDepth !== 1 || objectStartStack.length !== 0) continue;\n\n const objText = arrayText.slice(start, i + 1);\n const id = extractPropString(objText, \"id\");\n const type = extractPropString(objText, \"type\");\n\n if (type === \"div\" && id && id !== \"root\" && !seen.has(id)) {\n seen.add(id);\n results.push(id);\n }\n\n continue;\n }\n }\n\n return results;\n};\n\nconst extractRootChildSectionNames = (rootObjText: string): string[] => {\n const childrenKey = rootObjText.indexOf(\"children\");\n if (childrenKey < 0) return [];\n\n const arrayStart = rootObjText.indexOf(\"[\", childrenKey);\n if (arrayStart < 0) return [];\n\n // isolate the children array text (including brackets)\n let bracketDepth = 0;\n let inString: '\"' | \"'\" | \"`\" | null = null;\n\n for (let i = arrayStart; i < rootObjText.length; i++) {\n const ch = rootObjText[i]!;\n\n if (inString) {\n if (inString !== \"`\" && ch === \"\\\\\") {\n i += 1;\n continue;\n }\n if (ch === inString) inString = null;\n continue;\n }\n\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n continue;\n }\n\n if (ch === \"[\") bracketDepth += 1;\n if (ch === \"]\") {\n bracketDepth -= 1;\n if (bracketDepth === 0) {\n const arrayText = rootObjText.slice(arrayStart, i + 1);\n const ids = extractDirectContainerIdsFromArrayText(arrayText);\n return ids.map(sectionNameFromId).filter(Boolean);\n }\n }\n }\n\n return [];\n};\n\nconst extractSectionNamesFromConfig = (content: string): string[] => {\n if (!content) return [];\n\n const elementsKey = content.indexOf(\"elements\");\n if (elementsKey < 0) return [];\n\n const arrayStart = content.indexOf(\"[\", elementsKey);\n if (arrayStart < 0) return [];\n\n const seen = new Set<string>();\n const results: string[] = [];\n\n let bracketDepth = 0;\n const objectStartStack: number[] = [];\n let inString: '\"' | \"'\" | \"`\" | null = null;\n\n for (let i = arrayStart; i < content.length; i++) {\n const ch = content[i]!;\n\n if (inString) {\n if (inString !== \"`\" && ch === \"\\\\\") {\n i += 1;\n continue;\n }\n\n if (ch === inString) {\n inString = null;\n }\n\n continue;\n }\n\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n continue;\n }\n\n if (ch === \"[\") {\n bracketDepth += 1;\n continue;\n }\n\n if (ch === \"]\") {\n bracketDepth -= 1;\n if (bracketDepth <= 0) break;\n continue;\n }\n\n if (ch === \"{\") {\n if (bracketDepth >= 1) {\n objectStartStack.push(i);\n }\n continue;\n }\n\n if (ch === \"}\") {\n const objectStart = objectStartStack.pop();\n if (bracketDepth >= 1 && objectStart !== undefined) {\n const objText = content.slice(objectStart, i + 1);\n const id = extractPropString(objText, \"id\");\n const type = extractPropString(objText, \"type\");\n\n if (type === \"div\" && id === \"root\") {\n const rootSections = extractRootChildSectionNames(objText);\n if (rootSections.length > 0) {\n return rootSections;\n }\n }\n\n if (\n type === \"div\" &&\n id &&\n id !== \"root\" &&\n (id.endsWith(\"-section\") || id.endsWith(\"-container\"))\n ) {\n const sectionName = sectionNameFromId(id);\n if (sectionName && !seen.has(sectionName)) {\n seen.add(sectionName);\n results.push(sectionName);\n }\n }\n }\n continue;\n }\n }\n\n return results;\n};\n\nconst computePageRouteFromSegments = (segments: string[]): string => {\n const filtered = segments.filter(\n (s) => s && !isRouteGroup(s) && !isParallelRoute(s),\n );\n\n if (filtered.length === 0) return \"/\";\n return `/${filtered.join(\"/\")}`;\n};\n\nconst computePageNameFromRoute = (pageRoute: string): string => {\n if (pageRoute === \"/\") return \"root\";\n return pageRoute.slice(1).split(\"/\").join(\"-\");\n};\n\nconst findPageConfigFiles = async (dir: string): Promise<string[]> => {\n const entries = await safeReadDir(dir);\n const results: string[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory() && !entry.isFile()) continue;\n\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...(await findPageConfigFiles(fullPath)));\n continue;\n }\n\n if (entry.name === \"page.config.ts\") {\n results.push(fullPath);\n }\n }\n\n return results;\n};\n\nexport async function computeProjectInfo(\n rootDir: string,\n): Promise<ProjectInfo> {\n const effectiveRoot = rootDir;\n\n const appDir = path.join(effectiveRoot, \"app\");\n const pageConfigFiles = await findPageConfigFiles(appDir);\n\n const uiPages: UiPage[] = [];\n\n for (const filePath of pageConfigFiles) {\n const relFromApp = toPosixPath(path.relative(appDir, filePath));\n const relDir = path.posix.dirname(relFromApp);\n const segments = relDir === \".\" ? [] : relDir.split(\"/\").filter(Boolean);\n\n if (segments.some(isParallelRoute)) {\n continue;\n }\n\n const pageRoute = computePageRouteFromSegments(segments);\n const pageName = computePageNameFromRoute(pageRoute);\n const description = `${pageName} page for this project`;\n\n const content = await readFile(filePath);\n const sectionNames = extractSectionNamesFromConfig(content);\n\n const page: UiPage = {\n pageRoute,\n pageName,\n description,\n };\n\n if (sectionNames.length > 0) {\n page.sections = sectionNames.map((sectionName) => ({\n sectionName,\n description: `${sectionName} section for this page`,\n }));\n }\n\n uiPages.push(page);\n }\n\n uiPages.sort((a, b) =>\n a.pageRoute.localeCompare(b.pageRoute, undefined, {\n sensitivity: \"base\",\n }),\n );\n\n return {\n uiPages,\n lastUpdatedPlanVersion: 1,\n };\n}"]}
1
+ {"version":3,"file":"projectInfoIndex.js","sourceRoot":"","sources":["../../src/indexer/projectInfoIndex.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getAvailableRoutes,\n getPageConfigJsonPath,\n} from \"../ai/tools/helpers/pageConfigJson.helpers.js\";\nimport { ProjectInfo } from \"../types/projectInfo.types.js\";\nimport { readFile, safeReadDir } from \"../utils/workspace.js\";\n\ntype UiPage = ProjectInfo[\"uiPages\"][number];\n\nconst sectionNameFromId = (id: string): string => {\n if (!id) return \"\";\n if (id.endsWith(\"-section\")) return id.slice(0, -\"-section\".length);\n if (id.endsWith(\"-container\")) return id.slice(0, -\"-container\".length);\n return id;\n};\n\nconst computePageNameFromRoute = (pageRoute: string): string => {\n if (pageRoute === \"/\") return \"root\";\n return pageRoute.slice(1).split(\"/\").join(\"-\");\n};\n\nconst extractSectionNamesFromParsedConfig = (config: any): string[] => {\n if (!config || !Array.isArray(config.elements)) return [];\n\n const elements = config.elements;\n\n // Let's find the root element (where type === \"div\" && id === \"root\") recursively\n let rootElement: any = null;\n const findRoot = (els: any[]) => {\n for (const el of els) {\n if (el && typeof el === \"object\") {\n if (el.type === \"div\" && el.id === \"root\") {\n rootElement = el;\n return;\n }\n if (Array.isArray(el.children)) {\n findRoot(el.children);\n if (rootElement) return;\n }\n }\n }\n };\n findRoot(elements);\n\n if (rootElement && Array.isArray(rootElement.children)) {\n const rootSections: string[] = [];\n for (const child of rootElement.children) {\n if (\n child &&\n typeof child === \"object\" &&\n child.type === \"div\" &&\n child.id &&\n child.id !== \"root\"\n ) {\n const name = sectionNameFromId(child.id);\n if (name) {\n rootSections.push(name);\n }\n }\n }\n if (rootSections.length > 0) {\n return rootSections;\n }\n }\n\n // Fallback: collect all divs with id ending in \"-section\" or \"-container\"\n const seen = new Set<string>();\n const results: string[] = [];\n const collectFallback = (els: any[]) => {\n for (const el of els) {\n if (el && typeof el === \"object\") {\n if (\n el.type === \"div\" &&\n el.id &&\n el.id !== \"root\" &&\n (el.id.endsWith(\"-section\") || el.id.endsWith(\"-container\"))\n ) {\n const sectionName = sectionNameFromId(el.id);\n if (sectionName && !seen.has(sectionName)) {\n seen.add(sectionName);\n results.push(sectionName);\n }\n }\n if (Array.isArray(el.children)) {\n collectFallback(el.children);\n }\n }\n }\n };\n collectFallback(elements);\n return results;\n};\n\nexport async function computeProjectInfo(\n rootDir: string,\n): Promise<ProjectInfo> {\n const routes = await getAvailableRoutes({\n workspaceRoot: rootDir,\n fs: { safeReadDir },\n });\n\n const uiPages: UiPage[] = [];\n\n for (const pageRoute of routes) {\n const pageName = computePageNameFromRoute(pageRoute);\n const description = `${pageName} page for this project`;\n\n let sectionNames: string[] = [];\n try {\n const configPath = getPageConfigJsonPath(rootDir, pageRoute);\n const content = await readFile(configPath);\n if (content) {\n const config = JSON.parse(content);\n sectionNames = extractSectionNamesFromParsedConfig(config);\n }\n } catch (err) {\n // Ignore reading/parsing errors, treat as no sections\n }\n\n const page: UiPage = {\n pageRoute,\n pageName,\n description,\n };\n\n if (sectionNames.length > 0) {\n page.sections = sectionNames.map((sectionName) => ({\n sectionName,\n description: `${sectionName} section for this page`,\n }));\n }\n\n uiPages.push(page);\n }\n\n uiPages.sort((a, b) =>\n a.pageRoute.localeCompare(b.pageRoute, undefined, {\n sensitivity: \"base\",\n }),\n );\n\n return {\n uiPages,\n lastUpdatedPlanVersion: 1,\n };\n}"]}
@@ -6,6 +6,8 @@ import fs from "node:fs/promises";
6
6
  import { createInsertElementImpl } from "../ai/tools/implementations/insertElement.impl.js";
7
7
  import { createUpdatePropsImpl } from "../ai/tools/implementations/updateProps.impl.js";
8
8
  import { createUpdateClassNameImpl } from "../ai/tools/implementations/updateClassName.impl.js";
9
+ import { createDeleteElementImpl } from "../ai/tools/implementations/deleteElement.impl.js";
10
+ import { matchRoute } from "../ai/tools/helpers/pageConfigJson.helpers.js";
9
11
  const makeRealFs = (overrides) => {
10
12
  return {
11
13
  readFile: async (absolutePath) => fs.readFile(absolutePath, "utf-8"),
@@ -77,4 +79,95 @@ test("insert/update tools: inject ids, insert under parent, update props and cla
77
79
  await fs.rm(workspaceRoot, { recursive: true, force: true });
78
80
  }
79
81
  });
82
+ test("matchRoute helper correctly matches physical paths", () => {
83
+ // exact match
84
+ assert.equal(matchRoute("/", "/"), true);
85
+ assert.equal(matchRoute("/dashboard", "/dashboard"), true);
86
+ assert.equal(matchRoute("/dashboard", "/dashboard/"), true);
87
+ assert.equal(matchRoute("/dashboard/", "/dashboard"), true);
88
+ // dynamic segment
89
+ assert.equal(matchRoute("/product/[id]", "/product/123"), true);
90
+ assert.equal(matchRoute("/product/[id]", "/product/hi"), true);
91
+ assert.equal(matchRoute("/product/[id]", "/product/"), false);
92
+ assert.equal(matchRoute("/product/[id]", "/product/123/reviews"), false);
93
+ assert.equal(matchRoute("/product/[id]/reviews", "/product/abc/reviews"), true);
94
+ // catch-all segment
95
+ assert.equal(matchRoute("/blog/[...slug]", "/blog/react"), true);
96
+ assert.equal(matchRoute("/blog/[...slug]", "/blog/react/hooks/state"), true);
97
+ assert.equal(matchRoute("/blog/[...slug]", "/blog"), false); // required catch-all requires at least one segment
98
+ // optional catch-all segment
99
+ assert.equal(matchRoute("/blog/[[...slug]]", "/blog"), true);
100
+ assert.equal(matchRoute("/blog/[[...slug]]", "/blog/react"), true);
101
+ assert.equal(matchRoute("/blog/[[...slug]]", "/blog/react/hooks"), true);
102
+ });
103
+ test("dynamic/nested routes resolution in tools", async () => {
104
+ const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qwintly-core-dynamic-"));
105
+ try {
106
+ // 1. Create a dynamic route /product/[id]
107
+ const routeDir = path.join(workspaceRoot, "app", "product", "[id]");
108
+ await fs.mkdir(routeDir, { recursive: true });
109
+ const filePath = path.join(routeDir, "pageConfig.json");
110
+ await fs.writeFile(filePath, JSON.stringify({
111
+ elements: [
112
+ {
113
+ id: "root",
114
+ type: "div",
115
+ children: [{ id: "prod_title", type: "text", props: { text: "Product Name" } }],
116
+ },
117
+ ],
118
+ }, null, 2) + "\n", "utf-8");
119
+ // 2. Create a catch-all route /blog/[...slug]
120
+ const blogDir = path.join(workspaceRoot, "app", "blog", "[...slug]");
121
+ await fs.mkdir(blogDir, { recursive: true });
122
+ const blogFilePath = path.join(blogDir, "pageConfig.json");
123
+ await fs.writeFile(blogFilePath, JSON.stringify({
124
+ elements: [
125
+ {
126
+ id: "blog_root",
127
+ type: "div",
128
+ children: [{ id: "blog_content", type: "text", props: { text: "Blog Post" } }],
129
+ },
130
+ ],
131
+ }, null, 2) + "\n", "utf-8");
132
+ const deps = { workspaceRoot, fs: makeRealFs() };
133
+ const insert = createInsertElementImpl(deps);
134
+ const updateProps = createUpdatePropsImpl(deps);
135
+ const updateClassName = createUpdateClassNameImpl(deps);
136
+ const deleteElement = createDeleteElementImpl(deps);
137
+ // Test insert_element on dynamic route '/product/123'
138
+ const insRes = await insert("/product/123", "root", {
139
+ type: "button",
140
+ props: { text: "Buy Now" },
141
+ });
142
+ assert.equal(insRes.success, true);
143
+ // Verify it updated '/app/product/[id]/pageConfig.json'
144
+ const afterProduct = JSON.parse(await fs.readFile(filePath, "utf-8"));
145
+ assert.equal(afterProduct.elements[0].children.length, 2);
146
+ const newBtn = afterProduct.elements[0].children[1];
147
+ assert.equal(newBtn.type, "button");
148
+ assert.equal(newBtn.props.text, "Buy Now");
149
+ // Test update_props on dynamic route '/product/hi-there'
150
+ const upRes = await updateProps({
151
+ route: "/product/hi-there",
152
+ element_id: "prod_title",
153
+ text: "Updated Product Title",
154
+ });
155
+ assert.equal(upRes.success, true);
156
+ // Test update_classname on catch-all route '/blog/react/hooks/state'
157
+ const classRes = await updateClassName("/blog/react/hooks/state", "blog_content", "text-blue-500");
158
+ assert.equal(classRes.success, true);
159
+ // Verify blog config updated
160
+ const afterBlog = JSON.parse(await fs.readFile(blogFilePath, "utf-8"));
161
+ const updatedTitle = afterBlog.elements[0].children[0];
162
+ assert.equal(updatedTitle.className, "text-blue-500");
163
+ // Test delete_element on catch-all route
164
+ const delRes = await deleteElement("/blog/react/something-else", "blog_content");
165
+ assert.equal(delRes.success, true);
166
+ const afterDelBlog = JSON.parse(await fs.readFile(blogFilePath, "utf-8"));
167
+ assert.equal(afterDelBlog.elements[0].children.length, 0);
168
+ }
169
+ finally {
170
+ await fs.rm(workspaceRoot, { recursive: true, force: true });
171
+ }
172
+ });
80
173
  //# sourceMappingURL=insertUpdate.impl.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"insertUpdate.impl.test.js","sourceRoot":"","sources":["../../src/tests/insertUpdate.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport { createInsertElementImpl } from \"../ai/tools/implementations/insertElement.impl.js\";\nimport { createUpdatePropsImpl } from \"../ai/tools/implementations/updateProps.impl.js\";\nimport { createUpdateClassNameImpl } from \"../ai/tools/implementations/updateClassName.impl.js\";\n\ntype Deps = Parameters<typeof createInsertElementImpl>[0];\ntype CoreFs = Deps[\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"insert/update tools: inject ids, insert under parent, update props and classname\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const routeDir = path.join(workspaceRoot, \"app\", \"a\");\n await fs.mkdir(routeDir, { recursive: true });\n const filePath = path.join(routeDir, \"pageConfig.json\");\n await fs.writeFile(\n filePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"root\",\n type: \"div\",\n children: [{ id: \"existing\", type: \"text\", props: { text: \"x\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n const deps = { workspaceRoot, fs: makeRealFs() } as any;\n const insert = createInsertElementImpl(deps);\n const updateProps = createUpdatePropsImpl(deps);\n const updateClassName = createUpdateClassNameImpl(deps);\n\n const inserted = await insert(\"/a\", \"root\", {\n type: \"text\",\n props: { text: \"hello\" },\n } as any);\n assert.equal(\n (inserted as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted)}`,\n );\n const insertedId = (inserted as any).inserted_id as string;\n assert.ok(typeof insertedId === \"string\" && insertedId.startsWith(\"el_\"));\n\n const inserted2 = await insert({\n route: \"/a\",\n parent_id: \"root\",\n before_id: \"existing\",\n element: { type: \"text\", props: { text: \"first\" } },\n } as any);\n assert.equal(\n (inserted2 as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted2)}`,\n );\n const inserted2Id = (inserted2 as any).inserted_id as string;\n assert.ok(typeof inserted2Id === \"string\" && inserted2Id.startsWith(\"el_\"));\n\n const up1 = await updateProps({\n route: \"/a\",\n element_id: insertedId,\n text: \"updated\",\n href: \"/x\",\n } as any);\n assert.equal((up1 as any).success, true);\n\n const up2 = await updateClassName(\"/a\", insertedId, \"text-red-500\");\n assert.equal((up2 as any).success, true);\n\n const after = JSON.parse(await fs.readFile(filePath, \"utf-8\"));\n const children = after.elements[0].children as any[];\n assert.equal(children[0].id, inserted2Id);\n assert.equal(children[1].id, \"existing\");\n\n const found = children.find((e) => e.id === insertedId);\n assert.ok(found);\n assert.equal(found.props.text, \"updated\");\n assert.equal(found.props.href, \"/x\");\n assert.equal(found.className, \"text-red-500\");\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
1
+ {"version":3,"file":"insertUpdate.impl.test.js","sourceRoot":"","sources":["../../src/tests/insertUpdate.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport { createInsertElementImpl } from \"../ai/tools/implementations/insertElement.impl.js\";\nimport { createUpdatePropsImpl } from \"../ai/tools/implementations/updateProps.impl.js\";\nimport { createUpdateClassNameImpl } from \"../ai/tools/implementations/updateClassName.impl.js\";\nimport { createDeleteElementImpl } from \"../ai/tools/implementations/deleteElement.impl.js\";\nimport { matchRoute } from \"../ai/tools/helpers/pageConfigJson.helpers.js\";\n\ntype Deps = Parameters<typeof createInsertElementImpl>[0];\ntype CoreFs = Deps[\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"insert/update tools: inject ids, insert under parent, update props and classname\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const routeDir = path.join(workspaceRoot, \"app\", \"a\");\n await fs.mkdir(routeDir, { recursive: true });\n const filePath = path.join(routeDir, \"pageConfig.json\");\n await fs.writeFile(\n filePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"root\",\n type: \"div\",\n children: [{ id: \"existing\", type: \"text\", props: { text: \"x\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n const deps = { workspaceRoot, fs: makeRealFs() } as any;\n const insert = createInsertElementImpl(deps);\n const updateProps = createUpdatePropsImpl(deps);\n const updateClassName = createUpdateClassNameImpl(deps);\n\n const inserted = await insert(\"/a\", \"root\", {\n type: \"text\",\n props: { text: \"hello\" },\n } as any);\n assert.equal(\n (inserted as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted)}`,\n );\n const insertedId = (inserted as any).inserted_id as string;\n assert.ok(typeof insertedId === \"string\" && insertedId.startsWith(\"el_\"));\n\n const inserted2 = await insert({\n route: \"/a\",\n parent_id: \"root\",\n before_id: \"existing\",\n element: { type: \"text\", props: { text: \"first\" } },\n } as any);\n assert.equal(\n (inserted2 as any).success,\n true,\n `unexpected response: ${JSON.stringify(inserted2)}`,\n );\n const inserted2Id = (inserted2 as any).inserted_id as string;\n assert.ok(typeof inserted2Id === \"string\" && inserted2Id.startsWith(\"el_\"));\n\n const up1 = await updateProps({\n route: \"/a\",\n element_id: insertedId,\n text: \"updated\",\n href: \"/x\",\n } as any);\n assert.equal((up1 as any).success, true);\n\n const up2 = await updateClassName(\"/a\", insertedId, \"text-red-500\");\n assert.equal((up2 as any).success, true);\n\n const after = JSON.parse(await fs.readFile(filePath, \"utf-8\"));\n const children = after.elements[0].children as any[];\n assert.equal(children[0].id, inserted2Id);\n assert.equal(children[1].id, \"existing\");\n\n const found = children.find((e) => e.id === insertedId);\n assert.ok(found);\n assert.equal(found.props.text, \"updated\");\n assert.equal(found.props.href, \"/x\");\n assert.equal(found.className, \"text-red-500\");\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"matchRoute helper correctly matches physical paths\", () => {\n // exact match\n assert.equal(matchRoute(\"/\", \"/\"), true);\n assert.equal(matchRoute(\"/dashboard\", \"/dashboard\"), true);\n assert.equal(matchRoute(\"/dashboard\", \"/dashboard/\"), true);\n assert.equal(matchRoute(\"/dashboard/\", \"/dashboard\"), true);\n \n // dynamic segment\n assert.equal(matchRoute(\"/product/[id]\", \"/product/123\"), true);\n assert.equal(matchRoute(\"/product/[id]\", \"/product/hi\"), true);\n assert.equal(matchRoute(\"/product/[id]\", \"/product/\"), false);\n assert.equal(matchRoute(\"/product/[id]\", \"/product/123/reviews\"), false);\n assert.equal(matchRoute(\"/product/[id]/reviews\", \"/product/abc/reviews\"), true);\n\n // catch-all segment\n assert.equal(matchRoute(\"/blog/[...slug]\", \"/blog/react\"), true);\n assert.equal(matchRoute(\"/blog/[...slug]\", \"/blog/react/hooks/state\"), true);\n assert.equal(matchRoute(\"/blog/[...slug]\", \"/blog\"), false); // required catch-all requires at least one segment\n\n // optional catch-all segment\n assert.equal(matchRoute(\"/blog/[[...slug]]\", \"/blog\"), true);\n assert.equal(matchRoute(\"/blog/[[...slug]]\", \"/blog/react\"), true);\n assert.equal(matchRoute(\"/blog/[[...slug]]\", \"/blog/react/hooks\"), true);\n});\n\ntest(\"dynamic/nested routes resolution in tools\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-dynamic-\"));\n try {\n // 1. Create a dynamic route /product/[id]\n const routeDir = path.join(workspaceRoot, \"app\", \"product\", \"[id]\");\n await fs.mkdir(routeDir, { recursive: true });\n const filePath = path.join(routeDir, \"pageConfig.json\");\n await fs.writeFile(\n filePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"root\",\n type: \"div\",\n children: [{ id: \"prod_title\", type: \"text\", props: { text: \"Product Name\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n // 2. Create a catch-all route /blog/[...slug]\n const blogDir = path.join(workspaceRoot, \"app\", \"blog\", \"[...slug]\");\n await fs.mkdir(blogDir, { recursive: true });\n const blogFilePath = path.join(blogDir, \"pageConfig.json\");\n await fs.writeFile(\n blogFilePath,\n JSON.stringify(\n {\n elements: [\n {\n id: \"blog_root\",\n type: \"div\",\n children: [{ id: \"blog_content\", type: \"text\", props: { text: \"Blog Post\" } }],\n },\n ],\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf-8\",\n );\n\n const deps = { workspaceRoot, fs: makeRealFs() } as any;\n const insert = createInsertElementImpl(deps);\n const updateProps = createUpdatePropsImpl(deps);\n const updateClassName = createUpdateClassNameImpl(deps);\n const deleteElement = createDeleteElementImpl(deps);\n\n // Test insert_element on dynamic route '/product/123'\n const insRes = await insert(\"/product/123\", \"root\", {\n type: \"button\",\n props: { text: \"Buy Now\" },\n } as any);\n assert.equal(insRes.success, true);\n\n // Verify it updated '/app/product/[id]/pageConfig.json'\n const afterProduct = JSON.parse(await fs.readFile(filePath, \"utf-8\"));\n assert.equal(afterProduct.elements[0].children.length, 2);\n const newBtn = afterProduct.elements[0].children[1];\n assert.equal(newBtn.type, \"button\");\n assert.equal(newBtn.props.text, \"Buy Now\");\n\n // Test update_props on dynamic route '/product/hi-there'\n const upRes = await updateProps({\n route: \"/product/hi-there\",\n element_id: \"prod_title\",\n text: \"Updated Product Title\",\n } as any);\n assert.equal(upRes.success, true);\n\n // Test update_classname on catch-all route '/blog/react/hooks/state'\n const classRes = await updateClassName(\"/blog/react/hooks/state\", \"blog_content\", \"text-blue-500\");\n assert.equal(classRes.success, true);\n\n // Verify blog config updated\n const afterBlog = JSON.parse(await fs.readFile(blogFilePath, \"utf-8\"));\n const updatedTitle = afterBlog.elements[0].children[0];\n assert.equal(updatedTitle.className, \"text-blue-500\");\n\n // Test delete_element on catch-all route\n const delRes = await deleteElement(\"/blog/react/something-else\", \"blog_content\");\n assert.equal(delRes.success, true);\n\n const afterDelBlog = JSON.parse(await fs.readFile(blogFilePath, \"utf-8\"));\n assert.equal(afterDelBlog.elements[0].children.length, 0);\n\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=projectInfoIndex.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectInfoIndex.test.d.ts","sourceRoot":"","sources":["../../src/tests/projectInfoIndex.test.ts"],"names":[],"mappings":""}