sanjang 0.3.6 → 0.3.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/dashboard/app.js CHANGED
@@ -1736,23 +1736,40 @@ function renderWorkspace(data) {
1736
1736
  const previewEl = document.getElementById('ws-preview');
1737
1737
  const previewToolbar = document.getElementById('ws-preview-toolbar');
1738
1738
  if (previewUrl) {
1739
- const port = new URL(previewUrl).port || '80';
1740
- const proxyUrl = `/preview/${port}/`;
1741
- previewEl.innerHTML = `
1742
- <iframe src="${escHtml(proxyUrl)}" class="ws-preview-iframe"></iframe>
1743
- <div class="ws-preview-fallback" style="display:none">
1744
- <a href="${escHtml(previewUrl)}" target="_blank" class="btn btn-primary">
1745
- 탭에서 열기 → ${escHtml(previewUrl)}
1746
- </a>
1747
- </div>`;
1748
- const iframe = previewEl.querySelector('iframe');
1749
- iframe.addEventListener('error', () => {
1750
- iframe.style.display = 'none';
1751
- previewEl.querySelector('.ws-preview-fallback').style.display = 'flex';
1752
- });
1753
- if (previewToolbar) previewToolbar.style.display = '';
1754
- updateUrlBar('/');
1755
- if (currentViewport !== 'desktop') setViewport(currentViewport);
1739
+ const hasExtension = !!window.__SANJANG_EXTENSION__;
1740
+ if (hasExtension) {
1741
+ previewEl.innerHTML = `
1742
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:16px;">
1743
+ <div style="font-size:48px">⛰</div>
1744
+ <div style="color:var(--text-muted);font-size:14px;text-align:center;">
1745
+ 확장이 설치되어 있어유!<br>
1746
+ dev 서버를 직접 볼 수 있어유.
1747
+ </div>
1748
+ <a href="${escHtml(previewUrl)}" target="_blank" class="btn btn-primary" style="text-decoration:none">
1749
+ 탭에서 열기 → ${escHtml(previewUrl)}
1750
+ </a>
1751
+ </div>`;
1752
+ if (previewToolbar) previewToolbar.style.display = 'none';
1753
+ } else {
1754
+ // No extension — use iframe+proxy fallback
1755
+ const port = new URL(previewUrl).port || '80';
1756
+ const proxyUrl = `/preview/${port}/`;
1757
+ previewEl.innerHTML = `
1758
+ <iframe src="${escHtml(proxyUrl)}" class="ws-preview-iframe"></iframe>
1759
+ <div class="ws-preview-fallback" style="display:none">
1760
+ <a href="${escHtml(previewUrl)}" target="_blank" class="btn btn-primary">
1761
+ 새 탭에서 열기 → ${escHtml(previewUrl)}
1762
+ </a>
1763
+ </div>`;
1764
+ const iframe = previewEl.querySelector('iframe');
1765
+ iframe.addEventListener('error', () => {
1766
+ iframe.style.display = 'none';
1767
+ previewEl.querySelector('.ws-preview-fallback').style.display = 'flex';
1768
+ });
1769
+ if (previewToolbar) previewToolbar.style.display = '';
1770
+ updateUrlBar('/');
1771
+ if (currentViewport !== 'desktop') setViewport(currentViewport);
1772
+ }
1756
1773
  } else {
1757
1774
  if (previewToolbar) previewToolbar.style.display = 'none';
1758
1775
  previewEl.innerHTML = `
@@ -540,6 +540,38 @@ export async function createApp(projectRoot, options = {}) {
540
540
  // -------------------------------------------------------------------------
541
541
  // REST API
542
542
  // -------------------------------------------------------------------------
543
+ // CORS for extension — localhost cross-port requests
544
+ app.use("/api/", (req, res, next) => {
545
+ const origin = req.headers.origin || "";
546
+ if (origin.startsWith("http://localhost:") || origin.startsWith("http://127.0.0.1:") ||
547
+ origin.startsWith("https://localhost:") || origin.startsWith("https://127.0.0.1:")) {
548
+ res.setHeader("Access-Control-Allow-Origin", origin);
549
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
550
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
551
+ }
552
+ if (req.method === "OPTIONS")
553
+ return res.sendStatus(204);
554
+ next();
555
+ });
556
+ // Extension API — 확장이 현재 포트가 산장 캠프인지 확인
557
+ app.get("/api/camps/by-port/:port", (req, res) => {
558
+ const targetPort = parseInt(req.params.port, 10);
559
+ if (!Number.isFinite(targetPort))
560
+ return res.status(400).json({ error: "invalid port" });
561
+ const camps = getAll();
562
+ const camp = camps.find((c) => c.fePort === targetPort && c.status === "running");
563
+ if (!camp)
564
+ return res.status(404).json({ error: "not found" });
565
+ res.json({
566
+ name: camp.name,
567
+ branch: camp.branch,
568
+ status: camp.status,
569
+ fePort: camp.fePort,
570
+ });
571
+ });
572
+ app.get("/api/extension/version", (_req, res) => {
573
+ res.json({ serverVersion: "0.3.7", minExtVersion: "0.1.0" });
574
+ });
543
575
  // Project info — used by dashboard header
544
576
  const projectName = projectRoot.split("/").pop() ?? "project";
545
577
  app.get("/api/project", (_req, res) => res.json({ name: projectName }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanjang",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "AI dev environment for vibe coders — camp isolation, self-healing, smart PR",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,10 +9,12 @@
9
9
  "scripts": {
10
10
  "start": "node bin/sanjang.js",
11
11
  "build": "tsc -p tsconfig.build.json",
12
- "prepublishOnly": "npm run build && echo '#!/usr/bin/env node' | cat - dist/bin/sanjang.js > dist/bin/_tmp && mv dist/bin/_tmp dist/bin/sanjang.js",
12
+ "prepublishOnly": "npm run build && npm run ext:build && echo '#!/usr/bin/env node' | cat - dist/bin/sanjang.js > dist/bin/_tmp && mv dist/bin/_tmp dist/bin/sanjang.js",
13
13
  "test": "node --experimental-transform-types --test test/**/*.test.ts bin/__tests__/*.test.ts",
14
14
  "typecheck": "tsc --noEmit",
15
- "lint": "npx @biomejs/biome check lib/ test/ bin/"
15
+ "lint": "npx @biomejs/biome check lib/ test/ bin/",
16
+ "ext:dev": "cd extension && npx wxt",
17
+ "ext:build": "cd extension && npx wxt build"
16
18
  },
17
19
  "dependencies": {
18
20
  "express": "^4.19.2",
@@ -36,7 +38,8 @@
36
38
  "files": [
37
39
  "dist/",
38
40
  "dashboard/",
39
- "templates/"
41
+ "templates/",
42
+ "extension/.output/chrome-mv3/"
40
43
  ],
41
44
  "devDependencies": {
42
45
  "@biomejs/biome": "^2.4.10",