@selvakumaresra/specship 0.1.1 → 0.1.2

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.
@@ -16,9 +16,9 @@ import { existsSync, promises as fs } from 'node:fs';
16
16
  import { fileURLToPath, pathToFileURL } from 'node:url';
17
17
  import Fastify from 'fastify';
18
18
  import cors from '@fastify/cors';
19
- import fastifyStatic from '@fastify/static';
20
19
  import { startWatcher } from './ingest/index.js';
21
20
  import { ProjectRegistry } from './project-registry.js';
21
+ import { makeStaticHandler } from './static-handler.js';
22
22
  import { registerGraphRoutes } from './routes/graph.js';
23
23
  import { registerSpecRoutes } from './routes/spec.js';
24
24
  import { registerWorkflowRoutes } from './routes/workflow.js';
@@ -155,18 +155,17 @@ export async function createServer(options) {
155
155
  }
156
156
  }
157
157
  if (indexBuffer) {
158
- await app.register(fastifyStatic, {
159
- root: options.webDir,
160
- prefix: '/',
161
- // index.html is served via the SPA fallback so it always reflects
162
- // the latest bytes (even when a future hot-swap drops a new build).
163
- index: false,
164
- wildcard: false,
165
- });
166
158
  const cachedIndex = indexBuffer;
159
+ const serveStatic = makeStaticHandler(options.webDir);
160
+ // A single 404 handler does both jobs:
161
+ // 1. Try the requested URL as a real file under webDir → serve it
162
+ // with the right content-type. Covers the SPA's chunk-*.js,
163
+ // styles.css, favicons, fonts, etc.
164
+ // 2. Fall through to index.html for any other GET so Angular's
165
+ // client-side router can take over (`/memory`, `/graph`, …).
166
+ // Non-GET methods and `/api/*` paths still 404 cleanly so the UI
167
+ // can surface them.
167
168
  app.setNotFoundHandler((request, reply) => {
168
- // Only fall back for browser GETs that aren't API requests. API
169
- // 404s should stay 404s so the UI can surface them properly.
170
169
  if (request.method !== 'GET') {
171
170
  reply.code(404).send({ error: 'not found' });
172
171
  return;
@@ -175,6 +174,11 @@ export async function createServer(options) {
175
174
  reply.code(404).send({ error: 'not found' });
176
175
  return;
177
176
  }
177
+ const hit = serveStatic(request.url);
178
+ if (hit) {
179
+ reply.code(200).type(hit.contentType).send(hit.body);
180
+ return;
181
+ }
178
182
  reply.code(200).type('text/html').send(cachedIndex);
179
183
  });
180
184
  if (verbose)
@@ -0,0 +1,87 @@
1
+ /**
2
+ * In-house static file serving for the desktop SPA. Replaces the
3
+ * `@fastify/static` plugin so we don't pull in `glob` (deprecated maintainer-side
4
+ * across every 10.x and 11.x release, which polluted every user's install
5
+ * with a deprecation warning).
6
+ *
7
+ * Usage from server.ts:
8
+ *
9
+ * const serveStatic = makeStaticHandler(options.webDir);
10
+ * app.setNotFoundHandler((req, reply) => {
11
+ * if (req.method !== 'GET' || req.url.startsWith('/api/')) {
12
+ * reply.code(404).send({ error: 'not found' });
13
+ * return;
14
+ * }
15
+ * const hit = serveStatic(req.url);
16
+ * if (hit) { reply.type(hit.contentType).send(hit.body); return; }
17
+ * reply.code(200).type('text/html').send(cachedIndex);
18
+ * });
19
+ *
20
+ * Path traversal is blocked by resolving the requested path inside webDir and
21
+ * rejecting anything that resolves outside.
22
+ */
23
+ import { readFileSync, statSync } from 'node:fs';
24
+ import path from 'node:path';
25
+ const CT = {
26
+ '.html': 'text/html; charset=utf-8',
27
+ '.htm': 'text/html; charset=utf-8',
28
+ '.js': 'application/javascript; charset=utf-8',
29
+ '.mjs': 'application/javascript; charset=utf-8',
30
+ '.css': 'text/css; charset=utf-8',
31
+ '.json': 'application/json; charset=utf-8',
32
+ '.map': 'application/json; charset=utf-8',
33
+ '.svg': 'image/svg+xml',
34
+ '.png': 'image/png',
35
+ '.jpg': 'image/jpeg',
36
+ '.jpeg': 'image/jpeg',
37
+ '.gif': 'image/gif',
38
+ '.ico': 'image/x-icon',
39
+ '.webp': 'image/webp',
40
+ '.woff': 'font/woff',
41
+ '.woff2': 'font/woff2',
42
+ '.ttf': 'font/ttf',
43
+ '.otf': 'font/otf',
44
+ '.txt': 'text/plain; charset=utf-8',
45
+ '.wasm': 'application/wasm',
46
+ };
47
+ export function makeStaticHandler(rootDir) {
48
+ const absRoot = path.resolve(rootDir);
49
+ return function serveStatic(urlPath) {
50
+ // Strip query/hash — Fastify gives us the raw URL.
51
+ const qIdx = urlPath.indexOf('?');
52
+ const hIdx = urlPath.indexOf('#');
53
+ let clean = urlPath;
54
+ if (qIdx >= 0)
55
+ clean = clean.slice(0, qIdx);
56
+ if (hIdx >= 0)
57
+ clean = clean.slice(0, hIdx);
58
+ // Decode percent-encoding (but reject anything that throws).
59
+ let decoded;
60
+ try {
61
+ decoded = decodeURIComponent(clean);
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ // Strip leading slash so path.resolve doesn't escape the root.
67
+ const rel = decoded.replace(/^\/+/, '');
68
+ // Resolve under root and verify containment to block ../../ traversal.
69
+ const candidate = path.resolve(absRoot, rel);
70
+ if (candidate !== absRoot && !candidate.startsWith(absRoot + path.sep)) {
71
+ return null;
72
+ }
73
+ let stat;
74
+ try {
75
+ stat = statSync(candidate);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ if (!stat.isFile())
81
+ return null;
82
+ const body = readFileSync(candidate);
83
+ const ext = path.extname(candidate).toLowerCase();
84
+ const contentType = CT[ext] ?? 'application/octet-stream';
85
+ return { body, contentType };
86
+ };
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selvakumaresra/specship",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Supercharge Claude Code with semantic code intelligence. 94% fewer tool calls • 77% faster exploration • 100% local.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -40,7 +40,6 @@
40
40
  "dependencies": {
41
41
  "@clack/prompts": "^1.3.0",
42
42
  "@fastify/cors": "^9.0.1",
43
- "@fastify/static": "^7.0.4",
44
43
  "commander": "^14.0.2",
45
44
  "fast-string-width": "^3.0.2",
46
45
  "fast-wrap-ansi": "^0.2.0",
@@ -53,9 +52,6 @@
53
52
  "web-tree-sitter": "^0.25.3",
54
53
  "yaml": "^2.9.0"
55
54
  },
56
- "overrides": {
57
- "glob": "^13.0.6"
58
- },
59
55
  "devDependencies": {
60
56
  "@types/better-sqlite3": "^7.6.0",
61
57
  "@types/node": "^20.19.30",