heyiam 0.2.18 → 0.2.20
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/dist/db.js +6 -6
- package/dist/export.js +33 -9
- package/dist/mount.js +23205 -0
- package/dist/render/templates/project.liquid +1 -1
- package/dist/routes/preview.js +5 -1
- package/dist/routes/publish.js +0 -2
- package/dist/routes/settings.js +1 -1
- package/dist/screenshot.js +8 -3
- package/dist/search.js +3 -2
- package/dist/server.js +11 -7
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div class="heyiam-project" data-render-version="2">
|
|
1
|
+
<div class="heyiam-project" data-render-version="2"{% if sessionBaseUrl %} data-session-base-url="{{ sessionBaseUrl }}"{% endif %}>
|
|
2
2
|
|
|
3
3
|
{%- comment -%} Title {%- endcomment -%}
|
|
4
4
|
<h1 class="project-title">{{ project.title }}</h1>
|
package/dist/routes/preview.js
CHANGED
|
@@ -180,7 +180,11 @@ export function createPreviewRouter(ctx) {
|
|
|
180
180
|
});
|
|
181
181
|
// Serve @heyiam/ui mount script for preview pages
|
|
182
182
|
router.get('/heyiam-mount.js', (_req, res) => {
|
|
183
|
-
|
|
183
|
+
// In built dist: dist/mount.js (copied during build)
|
|
184
|
+
// In dev: ../../../packages/ui/dist/mount.js (monorepo layout)
|
|
185
|
+
const builtPath = path.resolve(__dirname, '..', 'mount.js');
|
|
186
|
+
const devPath = path.resolve(__dirname, '..', '..', '..', 'packages', 'ui', 'dist', 'mount.js');
|
|
187
|
+
const mountPath = existsSync(builtPath) ? builtPath : devPath;
|
|
184
188
|
try {
|
|
185
189
|
const js = readFileSync(mountPath, 'utf-8');
|
|
186
190
|
res.type('application/javascript').send(js);
|
package/dist/routes/publish.js
CHANGED
|
@@ -134,7 +134,6 @@ export function createPublishRouter(ctx) {
|
|
|
134
134
|
}
|
|
135
135
|
const projectData = await projectRes.json();
|
|
136
136
|
send({ type: 'project', status: 'created', projectId: projectData.project_id, slug: projectData.slug });
|
|
137
|
-
let screenshotUploaded = false;
|
|
138
137
|
// Step 1b: Upload screenshot (non-fatal)
|
|
139
138
|
if (screenshotBase64 || projectUrl) {
|
|
140
139
|
try {
|
|
@@ -177,7 +176,6 @@ export function createPublishRouter(ctx) {
|
|
|
177
176
|
},
|
|
178
177
|
body: JSON.stringify({ key }),
|
|
179
178
|
});
|
|
180
|
-
screenshotUploaded = true;
|
|
181
179
|
send({ type: 'screenshot', status: 'uploaded' });
|
|
182
180
|
}
|
|
183
181
|
else {
|
package/dist/routes/settings.js
CHANGED
package/dist/screenshot.js
CHANGED
|
@@ -52,14 +52,19 @@ function isUrlSafe(raw) {
|
|
|
52
52
|
return false;
|
|
53
53
|
// Reject localhost and private IPs
|
|
54
54
|
const host = parsed.hostname.toLowerCase();
|
|
55
|
-
|
|
55
|
+
// Strip IPv6 brackets for comparison
|
|
56
|
+
const bare = host.startsWith('[') ? host.slice(1, -1) : host;
|
|
57
|
+
if (bare === 'localhost' || bare === '127.0.0.1' || bare === '::1' || bare === '0.0.0.0') {
|
|
56
58
|
// Allow our own preview server
|
|
57
59
|
const port = parsed.port || (parsed.protocol === 'https:' ? '443' : '80');
|
|
58
60
|
if (port !== '17845')
|
|
59
61
|
return false;
|
|
60
62
|
}
|
|
61
|
-
// Reject private IP ranges
|
|
62
|
-
if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.)/.test(
|
|
63
|
+
// Reject private IP ranges (IPv4)
|
|
64
|
+
if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.)/.test(bare))
|
|
65
|
+
return false;
|
|
66
|
+
// Reject IPv6 private ranges (link-local, ULA, loopback, IPv4-mapped)
|
|
67
|
+
if (/^(fe80:|fc|fd|::ffff:(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.))/i.test(bare))
|
|
63
68
|
return false;
|
|
64
69
|
if (host.endsWith('.local') || host.endsWith('.internal'))
|
|
65
70
|
return false;
|
package/dist/search.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Search logic combining FTS5 content search with metadata filters
|
|
2
2
|
import { searchFts } from './db.js';
|
|
3
|
+
import { escapeLikeWildcards } from './format-utils.js';
|
|
3
4
|
// ── Helpers ──────────────────────────────────────────────────
|
|
4
5
|
/**
|
|
5
6
|
* Decode projectDir to a human-readable project name.
|
|
@@ -69,7 +70,7 @@ function searchWithFilters(db, filters) {
|
|
|
69
70
|
const params = [];
|
|
70
71
|
if (filters?.project) {
|
|
71
72
|
conditions.push('s.project_dir LIKE ?');
|
|
72
|
-
params.push(`%${filters.project}%`);
|
|
73
|
+
params.push(`%${escapeLikeWildcards(filters.project)}%`);
|
|
73
74
|
}
|
|
74
75
|
if (filters?.source) {
|
|
75
76
|
conditions.push('s.source = ?');
|
|
@@ -103,7 +104,7 @@ function searchWithFilters(db, filters) {
|
|
|
103
104
|
ORDER BY s.start_time DESC
|
|
104
105
|
LIMIT ?
|
|
105
106
|
`;
|
|
106
|
-
params.unshift(`%${filters.file}%`);
|
|
107
|
+
params.unshift(`%${escapeLikeWildcards(filters.file)}%`);
|
|
107
108
|
params.push(MAX_RESULTS);
|
|
108
109
|
}
|
|
109
110
|
else {
|
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from '
|
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
|
-
import { getDatabase } from './db.js';
|
|
8
|
+
import { getDatabase, closeDatabase } from './db.js';
|
|
9
9
|
import { syncWithTracking, startFileWatcher, startCursorPolling, markSyncPending } from './sync.js';
|
|
10
10
|
import { createRouteContext, createProjectsRouter, createEnhanceRouter, createPublishRouter, createSearchRouter, createSessionsRouter, createArchiveRouter, createAuthRouter, createSettingsRouter, createExportRouter, createPreviewRouter, createDashboardRouter, } from './routes/index.js';
|
|
11
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -77,13 +77,16 @@ export function createApp(sessionsBasePath, dbPath) {
|
|
|
77
77
|
}
|
|
78
78
|
next();
|
|
79
79
|
});
|
|
80
|
-
|
|
80
|
+
const corsOrigins = ['http://localhost:17845', 'http://127.0.0.1:17845'];
|
|
81
|
+
if (process.env.NODE_ENV !== 'production')
|
|
82
|
+
corsOrigins.push('http://localhost:5173');
|
|
83
|
+
app.use(cors({ origin: corsOrigins }));
|
|
81
84
|
app.use((_req, res, next) => {
|
|
82
85
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
83
86
|
res.setHeader('X-Frame-Options', 'DENY');
|
|
84
87
|
next();
|
|
85
88
|
});
|
|
86
|
-
app.use(express.json({ limit: '
|
|
89
|
+
app.use(express.json({ limit: '10mb' }));
|
|
87
90
|
// ── Mount domain routers ───────────────────────────────────
|
|
88
91
|
app.use(createProjectsRouter(ctx));
|
|
89
92
|
app.use(createEnhanceRouter(ctx));
|
|
@@ -108,11 +111,11 @@ export function createApp(sessionsBasePath, dbPath) {
|
|
|
108
111
|
const staticDir = existsSync(prodDir) ? prodDir : devDir;
|
|
109
112
|
app.use(express.static(staticDir));
|
|
110
113
|
// SPA fallback -- serve index.html for non-API routes
|
|
111
|
-
|
|
114
|
+
// Express 5 requires { root } option for sendFile (absolute paths fail silently)
|
|
112
115
|
app.get('/{*splat}', (_req, res) => {
|
|
113
|
-
res.sendFile(
|
|
116
|
+
res.sendFile('index.html', { root: staticDir }, (err) => {
|
|
114
117
|
if (err && !res.headersSent) {
|
|
115
|
-
console.error(`[spa] sendFile failed for ${
|
|
118
|
+
console.error(`[spa] sendFile failed for ${staticDir}/index.html:`, err.message);
|
|
116
119
|
res.status(404).send('Page not found');
|
|
117
120
|
}
|
|
118
121
|
});
|
|
@@ -146,11 +149,12 @@ export function startServer(port = 17845, options) {
|
|
|
146
149
|
server.on('close', () => {
|
|
147
150
|
stopFileWatcher();
|
|
148
151
|
stopCursorPolling();
|
|
152
|
+
closeDatabase();
|
|
149
153
|
removeServerPidFile();
|
|
150
154
|
});
|
|
151
155
|
}
|
|
152
156
|
else {
|
|
153
|
-
server.on('close', () => { removeServerPidFile(); });
|
|
157
|
+
server.on('close', () => { closeDatabase(); removeServerPidFile(); });
|
|
154
158
|
}
|
|
155
159
|
resolve(server);
|
|
156
160
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "heyiam",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.20",
|
|
4
4
|
"description": "Turn AI coding sessions into portfolio case studies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"clean": "rm -rf dist",
|
|
26
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/index.js && cp -r src/render/templates dist/render/templates && cd app && npx vite build && rm -rf ../dist/public && cp -r dist ../dist/public",
|
|
26
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/index.js && cp -r src/render/templates dist/render/templates && (test -f ../packages/ui/dist/mount.js && cp ../packages/ui/dist/mount.js dist/mount.js || echo 'Warning: packages/ui/dist/mount.js not found — skipping') && cd app && npx vite build && rm -rf ../dist/public && cp -r dist ../dist/public",
|
|
27
27
|
"verify-build": "node -e \"const fs=require('fs'); const assert=require('assert'); const pkg=require('./package.json'); assert(fs.existsSync('dist/index.js'),'missing dist/index.js'); assert(fs.existsSync('dist/public/index.html'),'missing dist/public/index.html'); assert(fs.existsSync('dist/render/templates'),'missing templates'); const js=fs.readdirSync('dist/public/assets').filter(f=>f.endsWith('.js')); assert(js.length===1,'expected exactly 1 JS bundle, found '+js.length); const html=fs.readFileSync('dist/public/index.html','utf8'); assert(html.includes(js[0]),'index.html does not reference the JS bundle'); const tests=fs.readdirSync('dist',{recursive:true}).filter(f=>String(f).includes('.test.')); assert(tests.length===0,'test files found in dist: '+tests.join(', ')); console.log('Build verified: v'+pkg.version+' — dist/index.js + frontend + templates, 0 test files')\"",
|
|
28
28
|
"prepublishOnly": "npm run test:backend && npm run build && npm run verify-build",
|
|
29
29
|
"prepack": "npm run verify-build",
|