yap2app 1.1.6 → 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 +15 -1
- package/bin/cli.bundle.js +489 -470
- package/bin/cli.js +489 -470
- package/bridge.js +40 -14
- package/mcp.js +1 -30
- 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) => {
|
|
@@ -163,7 +177,6 @@ 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(` [bridge] Scanning codebase in ${path.basename(PROJECT_ROOT)}${promptQuery ? ` (query: "${promptQuery}")` : ''}`);
|
|
167
180
|
const context = await scanCodebase(PROJECT_ROOT, promptQuery);
|
|
168
181
|
res.writeHead(200, headers);
|
|
169
182
|
res.end(JSON.stringify(context));
|
|
@@ -230,7 +243,7 @@ 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 });
|
|
@@ -266,11 +279,16 @@ 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(`
|
|
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`);
|
|
274
292
|
return;
|
|
275
293
|
}
|
|
276
294
|
console.error(' [bridge] Error:', err.message || err);
|
|
@@ -278,17 +296,25 @@ server.on('error', (err) => {
|
|
|
278
296
|
|
|
279
297
|
export async function startBridge(port = PORT) {
|
|
280
298
|
server.listen(port, '0.0.0.0', () => {
|
|
281
|
-
console.log(`\n
|
|
282
|
-
console.log(`
|
|
283
|
-
console.log(`
|
|
284
|
-
console.log(`
|
|
285
|
-
console.log(`
|
|
286
|
-
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`);
|
|
287
309
|
});
|
|
288
310
|
|
|
289
311
|
// Initial scan and Cloud Relay registration
|
|
290
312
|
try {
|
|
291
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
|
+
}
|
|
292
318
|
await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
|
|
293
319
|
pair_code: PAIR_CODE,
|
|
294
320
|
project_name: path.basename(PROJECT_ROOT),
|
|
@@ -319,7 +345,7 @@ export async function startBridge(port = PORT) {
|
|
|
319
345
|
for (const task of tasks) {
|
|
320
346
|
const written = [];
|
|
321
347
|
for (const file of (task.files || [])) {
|
|
322
|
-
const fullPath =
|
|
348
|
+
const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
|
|
323
349
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
324
350
|
await fs.writeFile(fullPath, file.content, 'utf8');
|
|
325
351
|
console.log(` [bridge] Wrote ${file.path}`);
|
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);
|
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 =>
|