yap2app 1.1.6 → 1.1.8
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 +495 -474
- package/bin/cli.js +495 -474
- package/bridge.js +45 -18
- package/mcp.js +1 -30
- package/package.json +2 -1
- package/scanner.js +7 -0
package/bridge.js
CHANGED
|
@@ -17,6 +17,21 @@ const PORT = Number(process.env.YAP2APP_PORT) || 10420;
|
|
|
17
17
|
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
|
+
const CLI_VERSION = '1.1.8';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validates and resolves relative file paths strictly inside PROJECT_ROOT.
|
|
24
|
+
* Prevents directory traversal attacks (e.g. ../../etc/passwd or absolute path escapes).
|
|
25
|
+
*/
|
|
26
|
+
function safeResolvePath(baseDir, relativePath) {
|
|
27
|
+
const cleanRel = (relativePath || '').replace(/^[\\\/]+/, '');
|
|
28
|
+
const resolved = path.resolve(baseDir, cleanRel);
|
|
29
|
+
const normalizedBase = path.resolve(baseDir);
|
|
30
|
+
if (!resolved.startsWith(normalizedBase + path.sep) && resolved !== normalizedBase) {
|
|
31
|
+
throw new Error(`Security Exception: Path traversal attempt blocked for path: "${relativePath}"`);
|
|
32
|
+
}
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
20
35
|
|
|
21
36
|
// Helper to send outbound HTTPS requests to the Yap2App Cloud Relay
|
|
22
37
|
function sendCloudRelayRequest(method, endpoint, payload) {
|
|
@@ -32,7 +47,7 @@ function sendCloudRelayRequest(method, endpoint, payload) {
|
|
|
32
47
|
headers: {
|
|
33
48
|
'Content-Type': 'application/json',
|
|
34
49
|
'Content-Length': Buffer.byteLength(body),
|
|
35
|
-
'User-Agent':
|
|
50
|
+
'User-Agent': `yap2app-cli/${CLI_VERSION}`
|
|
36
51
|
},
|
|
37
52
|
timeout: 6000
|
|
38
53
|
}, (res) => {
|
|
@@ -149,7 +164,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
149
164
|
res.end(JSON.stringify({
|
|
150
165
|
status: 'healthy',
|
|
151
166
|
service: 'yap2app-bridge',
|
|
152
|
-
version:
|
|
167
|
+
version: CLI_VERSION,
|
|
153
168
|
port: PORT,
|
|
154
169
|
pair_code: PAIR_CODE,
|
|
155
170
|
project_root: PROJECT_ROOT,
|
|
@@ -163,7 +178,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
163
178
|
if (req.method === 'GET' && (pathname === '/api/context' || pathname === '/context')) {
|
|
164
179
|
try {
|
|
165
180
|
const promptQuery = searchParams.get('q') || '';
|
|
166
|
-
console.log(` [bridge] Scanning codebase in ${path.basename(PROJECT_ROOT)}${promptQuery ? ` (query: "${promptQuery}")` : ''}`);
|
|
167
181
|
const context = await scanCodebase(PROJECT_ROOT, promptQuery);
|
|
168
182
|
res.writeHead(200, headers);
|
|
169
183
|
res.end(JSON.stringify(context));
|
|
@@ -230,7 +244,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
230
244
|
const written = [];
|
|
231
245
|
|
|
232
246
|
for (const file of files) {
|
|
233
|
-
const fullPath =
|
|
247
|
+
const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
|
|
234
248
|
|
|
235
249
|
// Ensure parent directory exists
|
|
236
250
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
@@ -266,11 +280,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
266
280
|
// Gracefully handle EADDRINUSE without crashing
|
|
267
281
|
server.on('error', (err) => {
|
|
268
282
|
if (err && err.code === 'EADDRINUSE') {
|
|
269
|
-
console.log(`\n
|
|
270
|
-
console.log(`
|
|
271
|
-
console.log(`
|
|
272
|
-
console.log(`
|
|
273
|
-
console.log(`
|
|
283
|
+
console.log(`\n---------------------------------------------------------------`);
|
|
284
|
+
console.log(`Yap2App Bridge`);
|
|
285
|
+
console.log(`Port ${PORT} is active. Cloud Relay connected.`);
|
|
286
|
+
console.log(`Active Workspace: ${PROJECT_ROOT}`);
|
|
287
|
+
console.log(`Pairing Code: ${PAIR_CODE}`);
|
|
288
|
+
console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
|
|
289
|
+
console.log(`---------------------------------------------------------------\n`);
|
|
290
|
+
console.log(`Bridge is running.`);
|
|
291
|
+
console.log(`Open Web Studio in your browser to sync components to this workspace.`);
|
|
292
|
+
console.log(`Press Ctrl/Cmd+C to stop.\n`);
|
|
274
293
|
return;
|
|
275
294
|
}
|
|
276
295
|
console.error(' [bridge] Error:', err.message || err);
|
|
@@ -278,24 +297,32 @@ server.on('error', (err) => {
|
|
|
278
297
|
|
|
279
298
|
export async function startBridge(port = PORT) {
|
|
280
299
|
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(`
|
|
300
|
+
console.log(`\n---------------------------------------------------------------`);
|
|
301
|
+
console.log(`Yap2App Bridge v${CLI_VERSION}`);
|
|
302
|
+
console.log(`Local Endpoint: http://127.0.0.1:${port}`);
|
|
303
|
+
console.log(`Active Workspace: ${PROJECT_ROOT}`);
|
|
304
|
+
console.log(`Pairing Code: ${PAIR_CODE}`);
|
|
305
|
+
console.log(`Web Studio: ${CLOUD_RELAY_URL}`);
|
|
306
|
+
console.log(`---------------------------------------------------------------\n`);
|
|
307
|
+
console.log(`Bridge is running.`);
|
|
308
|
+
console.log(`Open Web Studio in your browser to sync components to this workspace.`);
|
|
309
|
+
console.log(`Press Ctrl/Cmd+C to stop.\n`);
|
|
287
310
|
});
|
|
288
311
|
|
|
289
312
|
// Initial scan and Cloud Relay registration
|
|
290
313
|
try {
|
|
291
314
|
const initialContext = await scanCodebase(PROJECT_ROOT);
|
|
315
|
+
if (initialContext && initialContext.total_files_count === 0) {
|
|
316
|
+
console.log(` ℹ️ Fresh workspace detected (0 code files).`);
|
|
317
|
+
console.log(` Yap2App will automatically scaffold directories and write files when synced from Web Studio.\n`);
|
|
318
|
+
}
|
|
292
319
|
await sendCloudRelayRequest('POST', '/api/v1/bridge/heartbeat', {
|
|
293
320
|
pair_code: PAIR_CODE,
|
|
294
321
|
project_name: path.basename(PROJECT_ROOT),
|
|
295
322
|
project_root: PROJECT_ROOT,
|
|
296
323
|
framework: initialContext.framework,
|
|
297
324
|
context: initialContext,
|
|
298
|
-
version:
|
|
325
|
+
version: CLI_VERSION
|
|
299
326
|
});
|
|
300
327
|
} catch (e) {}
|
|
301
328
|
|
|
@@ -309,7 +336,7 @@ export async function startBridge(port = PORT) {
|
|
|
309
336
|
project_root: PROJECT_ROOT,
|
|
310
337
|
framework: context.framework,
|
|
311
338
|
context: context,
|
|
312
|
-
version:
|
|
339
|
+
version: CLI_VERSION
|
|
313
340
|
});
|
|
314
341
|
|
|
315
342
|
// If pending write tasks, fetch and execute them on local disk
|
|
@@ -319,7 +346,7 @@ export async function startBridge(port = PORT) {
|
|
|
319
346
|
for (const task of tasks) {
|
|
320
347
|
const written = [];
|
|
321
348
|
for (const file of (task.files || [])) {
|
|
322
|
-
const fullPath =
|
|
349
|
+
const fullPath = safeResolvePath(PROJECT_ROOT, file.path);
|
|
323
350
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
324
351
|
await fs.writeFile(fullPath, file.content, 'utf8');
|
|
325
352
|
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 =>
|