yap2app 1.1.5 â 1.1.7
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-esm.js +25 -13
- package/bin/cli.bundle.js +512 -498
- package/bin/cli.js +512 -498
- package/bridge.js +50 -28
- package/mcp.js +2 -31
- package/package.json +2 -1
- package/scanner.js +7 -0
package/bridge.js
CHANGED
|
@@ -18,6 +18,20 @@ const PROJECT_ROOT = process.cwd();
|
|
|
18
18
|
const CLOUD_RELAY_URL = process.env.YAP2APP_CLOUD_URL || 'https://yap2app-dot-cybage-hackathon.uc.r.appspot.com';
|
|
19
19
|
const PAIR_CODE = (process.env.YAP2APP_PAIR_CODE || ('CYB-' + Math.floor(100 + Math.random() * 900))).toUpperCase();
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Validates and resolves relative file paths strictly inside PROJECT_ROOT.
|
|
23
|
+
* Prevents directory traversal attacks (e.g. ../../etc/passwd or absolute path escapes).
|
|
24
|
+
*/
|
|
25
|
+
function safeResolvePath(baseDir, relativePath) {
|
|
26
|
+
const cleanRel = (relativePath || '').replace(/^[\\\/]+/, '');
|
|
27
|
+
const resolved = path.resolve(baseDir, cleanRel);
|
|
28
|
+
const normalizedBase = path.resolve(baseDir);
|
|
29
|
+
if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) {
|
|
30
|
+
throw new Error(`Security Exception: Path traversal attempt blocked for path: "${relativePath}"`);
|
|
31
|
+
}
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
// Helper to send outbound HTTPS requests to the Yap2App Cloud Relay
|
|
22
36
|
function sendCloudRelayRequest(method, endpoint, payload) {
|
|
23
37
|
return new Promise((resolve) => {
|
|
@@ -148,8 +162,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
148
162
|
res.writeHead(200, headers);
|
|
149
163
|
res.end(JSON.stringify({
|
|
150
164
|
status: 'healthy',
|
|
151
|
-
service: '
|
|
152
|
-
version: '1.1.
|
|
165
|
+
service: 'yap2app-bridge',
|
|
166
|
+
version: '1.1.6',
|
|
153
167
|
port: PORT,
|
|
154
168
|
pair_code: PAIR_CODE,
|
|
155
169
|
project_root: PROJECT_ROOT,
|
|
@@ -163,12 +177,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
163
177
|
if (req.method === 'GET' && (pathname === '/api/context' || pathname === '/context')) {
|
|
164
178
|
try {
|
|
165
179
|
const promptQuery = searchParams.get('q') || '';
|
|
166
|
-
console.log(`[Yap2App Bridge] đ Scanning shallow codebase context in: ${PROJECT_ROOT}${promptQuery ? ` (ranking for query: "${promptQuery}")` : ''}`);
|
|
167
180
|
const context = await scanCodebase(PROJECT_ROOT, promptQuery);
|
|
168
181
|
res.writeHead(200, headers);
|
|
169
182
|
res.end(JSON.stringify(context));
|
|
170
183
|
} catch (e) {
|
|
171
|
-
console.error('[
|
|
184
|
+
console.error(' [bridge] Context scan error:', e.message);
|
|
172
185
|
res.writeHead(500, headers);
|
|
173
186
|
res.end(JSON.stringify({ error: e.message }));
|
|
174
187
|
}
|
|
@@ -211,7 +224,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
211
224
|
diffs: diffResults
|
|
212
225
|
}));
|
|
213
226
|
} catch (e) {
|
|
214
|
-
console.error('[
|
|
227
|
+
console.error(' [bridge] Diff error:', e.message);
|
|
215
228
|
res.writeHead(500, headers);
|
|
216
229
|
res.end(JSON.stringify({ error: e.message }));
|
|
217
230
|
}
|
|
@@ -230,14 +243,14 @@ const server = http.createServer(async (req, res) => {
|
|
|
230
243
|
const written = [];
|
|
231
244
|
|
|
232
245
|
for (const file of files) {
|
|
233
|
-
const fullPath =
|
|
246
|
+
const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
|
|
234
247
|
|
|
235
248
|
// Ensure parent directory exists
|
|
236
249
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
237
250
|
|
|
238
251
|
// Write file with utf8 encoding
|
|
239
252
|
await fs.writeFile(fullPath, file.content, 'utf8');
|
|
240
|
-
console.log(`[
|
|
253
|
+
console.log(` [bridge] Wrote ${file.path}`);
|
|
241
254
|
written.push(file.path);
|
|
242
255
|
}
|
|
243
256
|
|
|
@@ -250,7 +263,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
250
263
|
message: `Successfully wrote ${written.length} files to ${path.basename(PROJECT_ROOT)}`
|
|
251
264
|
}));
|
|
252
265
|
} catch (e) {
|
|
253
|
-
console.error('[
|
|
266
|
+
console.error(' [bridge] Write error:', e.message);
|
|
254
267
|
res.writeHead(500, headers);
|
|
255
268
|
res.end(JSON.stringify({ error: e.message }));
|
|
256
269
|
}
|
|
@@ -266,40 +279,49 @@ const server = http.createServer(async (req, res) => {
|
|
|
266
279
|
// Gracefully handle EADDRINUSE without crashing
|
|
267
280
|
server.on('error', (err) => {
|
|
268
281
|
if (err && err.code === 'EADDRINUSE') {
|
|
269
|
-
console.log(`\n
|
|
270
|
-
console.log(
|
|
271
|
-
console.log(
|
|
272
|
-
console.log(
|
|
273
|
-
console.log(
|
|
274
|
-
console.log(
|
|
282
|
+
console.log(`\n---------------------------------------------------------------`);
|
|
283
|
+
console.log(`Yap2App Bridge`);
|
|
284
|
+
console.log(`Port ${PORT} is active. Cloud Relay connected.`);
|
|
285
|
+
console.log(`Active Workspace: ${PROJECT_ROOT}`);
|
|
286
|
+
console.log(`Pairing Code: ${PAIR_CODE}`);
|
|
287
|
+
console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
|
|
288
|
+
console.log(`---------------------------------------------------------------\n`);
|
|
289
|
+
console.log(`Bridge is running.`);
|
|
290
|
+
console.log(`Open Web Studio in your browser to sync components to this workspace.`);
|
|
291
|
+
console.log(`Press Ctrl/Cmd+C to stop.\n`);
|
|
275
292
|
return;
|
|
276
293
|
}
|
|
277
|
-
console.error('[
|
|
294
|
+
console.error(' [bridge] Error:', err.message || err);
|
|
278
295
|
});
|
|
279
296
|
|
|
280
297
|
export async function startBridge(port = PORT) {
|
|
281
298
|
server.listen(port, '0.0.0.0', () => {
|
|
282
|
-
console.log(`\n
|
|
283
|
-
console.log(
|
|
284
|
-
console.log(
|
|
285
|
-
console.log(
|
|
286
|
-
console.log(
|
|
287
|
-
console.log(
|
|
288
|
-
console.log(
|
|
289
|
-
console.log(
|
|
290
|
-
console.log(`
|
|
299
|
+
console.log(`\n---------------------------------------------------------------`);
|
|
300
|
+
console.log(`Yap2App Bridge v1.1.6`);
|
|
301
|
+
console.log(`Local Endpoint: http://127.0.0.1:${port}`);
|
|
302
|
+
console.log(`Active Workspace: ${PROJECT_ROOT}`);
|
|
303
|
+
console.log(`Pairing Code: ${PAIR_CODE}`);
|
|
304
|
+
console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
|
|
305
|
+
console.log(`---------------------------------------------------------------\n`);
|
|
306
|
+
console.log(`Bridge is running.`);
|
|
307
|
+
console.log(`Open Web Studio in your browser to sync components to this workspace.`);
|
|
308
|
+
console.log(`Press Ctrl/Cmd+C to stop.\n`);
|
|
291
309
|
});
|
|
292
310
|
|
|
293
311
|
// Initial scan and Cloud Relay registration
|
|
294
312
|
try {
|
|
295
313
|
const initialContext = await scanCodebase(PROJECT_ROOT);
|
|
314
|
+
if (initialContext && initialContext.total_files_count === 0) {
|
|
315
|
+
console.log(` âšī¸ Fresh workspace detected (0 code files).`);
|
|
316
|
+
console.log(` Yap2App will automatically scaffold directories and write files when synced from Web Studio.\n`);
|
|
317
|
+
}
|
|
296
318
|
await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
|
|
297
319
|
pair_code: PAIR_CODE,
|
|
298
320
|
project_name: path.basename(PROJECT_ROOT),
|
|
299
321
|
project_root: PROJECT_ROOT,
|
|
300
322
|
framework: initialContext.framework,
|
|
301
323
|
context: initialContext,
|
|
302
|
-
version: '1.1.
|
|
324
|
+
version: '1.1.6'
|
|
303
325
|
});
|
|
304
326
|
} catch (e) {}
|
|
305
327
|
|
|
@@ -313,7 +335,7 @@ export async function startBridge(port = PORT) {
|
|
|
313
335
|
project_root: PROJECT_ROOT,
|
|
314
336
|
framework: context.framework,
|
|
315
337
|
context: context,
|
|
316
|
-
version: '1.1.
|
|
338
|
+
version: '1.1.6'
|
|
317
339
|
});
|
|
318
340
|
|
|
319
341
|
// If pending write tasks, fetch and execute them on local disk
|
|
@@ -323,10 +345,10 @@ export async function startBridge(port = PORT) {
|
|
|
323
345
|
for (const task of tasks) {
|
|
324
346
|
const written = [];
|
|
325
347
|
for (const file of (task.files || [])) {
|
|
326
|
-
const fullPath =
|
|
348
|
+
const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
|
|
327
349
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
328
350
|
await fs.writeFile(fullPath, file.content, 'utf8');
|
|
329
|
-
console.log(`[
|
|
351
|
+
console.log(` [bridge] Wrote ${file.path}`);
|
|
330
352
|
written.push(file.path);
|
|
331
353
|
}
|
|
332
354
|
await sendCloudRelayRequest('POST', '/api/v1/bridge/complete-write', {
|
package/mcp.js
CHANGED
|
@@ -41,19 +41,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
41
41
|
required: ["componentName"]
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
{
|
|
45
|
-
name: "yap2app_mount_snippet",
|
|
46
|
-
description: "Generates the parent import statement and JSX mounting code snippet for integrating a new component into an existing page or parent component.",
|
|
47
|
-
inputSchema: {
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
componentName: { type: "string", description: "The component name" },
|
|
51
|
-
filePath: { type: "string", description: "The component's relative file path" },
|
|
52
|
-
parentFile: { type: "string", description: "Optional parent file (e.g. 'src/App.tsx' or 'src/app/page.tsx')" }
|
|
53
|
-
},
|
|
54
|
-
required: ["componentName", "filePath"]
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
44
|
{
|
|
58
45
|
name: "yap2app_write_component",
|
|
59
46
|
description: "Safely writes a React/Tailwind component to the local filesystem, ensuring directories exist.",
|
|
@@ -106,7 +93,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
106
93
|
const suggestion = {
|
|
107
94
|
component_name: componentName,
|
|
108
95
|
target_file_path: relativePath,
|
|
109
|
-
import_statement: `import { ${componentName} } from "${importPath}";`,
|
|
110
96
|
usage_example: `<${componentName} />`
|
|
111
97
|
};
|
|
112
98
|
|
|
@@ -115,22 +101,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
115
101
|
};
|
|
116
102
|
}
|
|
117
103
|
|
|
118
|
-
// 3.
|
|
119
|
-
if (name === "yap2app_mount_snippet") {
|
|
120
|
-
const { componentName, filePath, parentFile } = args;
|
|
121
|
-
const cleanImport = filePath.replace(/\.[^/.]+$/, '').replace(/^src\//, '@/');
|
|
122
|
-
const snippet = {
|
|
123
|
-
target_parent_file: parentFile || "src/App.tsx (or src/app/page.tsx)",
|
|
124
|
-
import_line: `import { ${componentName} } from "${cleanImport}";`,
|
|
125
|
-
jsx_insertion: `{/* Render ${componentName} */}\n<${componentName} />`
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
content: [{ type: "text", text: JSON.stringify(snippet, null, 2) }]
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 4. yap2app_write_component
|
|
104
|
+
// 3. yap2app_write_component
|
|
134
105
|
if (name === "yap2app_write_component") {
|
|
135
106
|
const { filePath, code } = args;
|
|
136
107
|
const fullPath = path.join(TARGET_DIR, filePath);
|
|
@@ -155,7 +126,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
155
126
|
export async function runMcpServer() {
|
|
156
127
|
const transport = new StdioServerTransport();
|
|
157
128
|
await server.connect(transport);
|
|
158
|
-
console.error("
|
|
129
|
+
console.error("Yap2App MCP Server running on stdio");
|
|
159
130
|
}
|
|
160
131
|
|
|
161
132
|
const isDirectRun = Boolean(typeof process !== 'undefined' && process.argv && process.argv[1] && (process.argv[1].endsWith('mcp.js') || process.argv[1].endsWith('mcp')));
|
package/package.json
CHANGED
package/scanner.js
CHANGED
|
@@ -106,6 +106,13 @@ function parsePackageJson(content) {
|
|
|
106
106
|
* Returns null if no verified signature matches with high confidence.
|
|
107
107
|
*/
|
|
108
108
|
function detectVerifiedFramework(manifests, allFiles, extensionHistogram) {
|
|
109
|
+
if (!allFiles || allFiles.length === 0) {
|
|
110
|
+
return {
|
|
111
|
+
framework: 'plain_html_css_js',
|
|
112
|
+
displayName: 'Plain HTML / CSS / JS (Fresh Workspace)'
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
// 1. Google Brightspot CMS / Glue-Bundle / Handlebars Frontend
|
|
110
117
|
const hasHbsFiles = (extensionHistogram['.hbs'] || 0) > 0 || (extensionHistogram['.handlebars'] || 0) > 0;
|
|
111
118
|
const hasBrightspotConfigs = allFiles.some(f =>
|