checkpoint-cli 0.4.1 → 0.5.1

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/index.js CHANGED
@@ -271,6 +271,19 @@ function launchNgrok(port) {
271
271
  } });
272
272
  });
273
273
  }
274
+ async function getUserWorkspaceId(sb, userId) {
275
+ const { data: membership, error } = await sb
276
+ .from('workspace_members')
277
+ .select('workspace_id')
278
+ .eq('user_id', userId)
279
+ .order('created_at', { ascending: true })
280
+ .limit(1)
281
+ .maybeSingle();
282
+ if (error) {
283
+ throw error;
284
+ }
285
+ return membership?.workspace_id ?? null;
286
+ }
274
287
  async function registerTunnel(sb, opts) {
275
288
  if (!isValidUrl(opts.tunnelUrl)) {
276
289
  console.log(chalk_1.default.yellow(' ⚠ Invalid tunnel URL'));
@@ -283,24 +296,32 @@ async function registerTunnel(sb, opts) {
283
296
  }
284
297
  const APP_URL = (0, config_js_1.getAppUrl)();
285
298
  const userId = userData.user.id;
299
+ const workspaceId = await getUserWorkspaceId(sb, userId);
300
+ if (!workspaceId) {
301
+ console.log(chalk_1.default.yellow(' ⚠ No workspace membership found. Complete setup in the dashboard first.'));
302
+ return null;
303
+ }
286
304
  // Try to find an existing tunnel with the same name for this user
287
305
  const { data: existing } = await sb
288
306
  .from('tunnels')
289
307
  .select('id, share_id')
290
308
  .eq('user_id', userId)
309
+ .eq('workspace_id', workspaceId)
291
310
  .eq('name', opts.name)
292
311
  .order('created_at', { ascending: false })
293
312
  .limit(1)
294
313
  .single();
295
314
  if (existing) {
296
315
  // Reconnect: reuse the same share_id, update the tunnel URL
316
+ const now = new Date().toISOString();
297
317
  const { error } = await sb
298
318
  .from('tunnels')
299
319
  .update({
300
320
  tunnel_url: opts.tunnelUrl,
301
321
  local_port: opts.port,
302
322
  status: 'active',
303
- last_seen_at: new Date().toISOString(),
323
+ activated_at: now,
324
+ last_seen_at: now,
304
325
  })
305
326
  .eq('id', existing.id);
306
327
  if (error) {
@@ -310,8 +331,32 @@ async function registerTunnel(sb, opts) {
310
331
  console.log(chalk_1.default.green(' ✓ Reconnected to existing tunnel') + chalk_1.default.gray(` (${opts.name})`));
311
332
  return { shareUrl: `${APP_URL}/share/${existing.share_id}`, tunnelId: existing.id };
312
333
  }
334
+ // Check plan limits before creating a new tunnel
335
+ const { data: subscription } = await sb
336
+ .from('subscriptions')
337
+ .select('status, billing_plans(name)')
338
+ .eq('workspace_id', workspaceId)
339
+ .maybeSingle();
340
+ const isActiveSub = subscription?.status === 'active' || subscription?.status === 'trialing';
341
+ const planName = subscription?.billing_plans?.name?.toLowerCase() ?? '';
342
+ const isPro = planName.includes('pro') || planName.includes('enterprise');
343
+ const FREE_TUNNEL_LIMIT = 1;
344
+ if (!isActiveSub || !isPro) {
345
+ const { count } = await sb
346
+ .from('tunnels')
347
+ .select('id', { count: 'exact', head: true })
348
+ .eq('workspace_id', workspaceId);
349
+ if ((count ?? 0) >= FREE_TUNNEL_LIMIT) {
350
+ console.log('');
351
+ console.log(chalk_1.default.red(` ✗ Tunnel limit reached (${FREE_TUNNEL_LIMIT} tunnels on the Free plan).`));
352
+ console.log(chalk_1.default.gray(' Upgrade to Pro for unlimited tunnels: ') + chalk_1.default.cyan(`${(0, config_js_1.getAppUrl)()}/dashboard/settings?tab=billing`));
353
+ console.log('');
354
+ return null;
355
+ }
356
+ }
313
357
  // No existing tunnel — create a new one
314
358
  const shareId = generateShareId();
359
+ const now = new Date().toISOString();
315
360
  const { data, error } = await sb.from('tunnels').insert({
316
361
  name: opts.name,
317
362
  local_port: opts.port,
@@ -319,7 +364,9 @@ async function registerTunnel(sb, opts) {
319
364
  share_id: shareId,
320
365
  status: 'active',
321
366
  user_id: userId,
322
- last_seen_at: new Date().toISOString(),
367
+ workspace_id: workspaceId,
368
+ activated_at: now,
369
+ last_seen_at: now,
323
370
  }).select('id').single();
324
371
  if (error || !data) {
325
372
  console.log(chalk_1.default.yellow(` ⚠ Could not register: ${error?.message ?? 'unknown'}`));
@@ -362,35 +409,60 @@ function openBrowser(url) {
362
409
  }
363
410
  function loginWithBrowser() {
364
411
  return new Promise((resolve, reject) => {
365
- const server = http_1.default.createServer((req, res) => {
412
+ const server = http_1.default.createServer(async (req, res) => {
366
413
  const url = new URL(req.url || '/', `http://localhost`);
367
414
  if (url.pathname === '/callback') {
368
- const access_token = url.searchParams.get('access_token');
369
- const refresh_token = url.searchParams.get('refresh_token');
370
- if (access_token && refresh_token) {
371
- // Success page
372
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
373
- res.end(`
374
- <html>
375
- <body style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #13120A; color: #f0ede6;">
376
- <div style="text-align: center; display: flex; flex-direction: column; align-items: center; gap: 16px;">
377
- <svg width="32" height="32" viewBox="0 0 256 256" fill="none">
378
- <circle cx="128" cy="128" r="112" fill="#22c55e"/>
379
- <polyline points="88,136 112,160 168,104" stroke="#13120A" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
380
- </svg>
381
- <h1 style="font-size: 20px; font-weight: 500; margin: 0; color: #f0ede6;">Logged in to Checkpoint</h1>
382
- <p style="color: #9e9889; margin: 0; font-size: 14px;">You can close this tab and return to your terminal.</p>
383
- <div style="margin-top: 8px; padding-top: 24px; border-top: 1px dashed rgba(73,67,58,0.3);">
384
- <p style="color: #9e9889; margin: 0px 0 24px; font-size: 13px;">Start sharing your localhost for feedback:</p>
385
- <code style="padding: 8px 12px; background: #1D1B15; border: 1px solid rgba(73,67,58,0.4); border-radius: 6px; font-family: 'JetBrains Mono','Fira Code','SF Mono',monospace; font-size: 13px; color: #9e9889;">checkpoint start -p <span style="color: #ff5700;">&lt;port&gt;</span></code>
386
- <p style="text-align: center; margin-top: 24px; font-size: 13px; color: #9e9889;">or <a href="${(0, config_js_1.getAppUrl)()}" style="color: #ff5700; text-decoration: none;">Go to Dashboard</a></p>
415
+ const code = url.searchParams.get('code');
416
+ if (code) {
417
+ try {
418
+ const APP_URL = (0, config_js_1.getAppUrl)();
419
+ const exchangeRes = await fetch(`${APP_URL}/api/cli/auth-code?code=${encodeURIComponent(code)}`);
420
+ if (!exchangeRes.ok)
421
+ throw new Error('Code exchange failed');
422
+ const { access_token, refresh_token } = await exchangeRes.json();
423
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
424
+ res.end(`
425
+ <html>
426
+ <body style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #13120A; color: #f0ede6;">
427
+ <div style="text-align: center; display: flex; flex-direction: column; align-items: center; gap: 16px;">
428
+ <svg width="32" height="32" viewBox="0 0 256 256" fill="none">
429
+ <circle cx="128" cy="128" r="112" fill="#22c55e"/>
430
+ <polyline points="88,136 112,160 168,104" stroke="#13120A" stroke-width="16" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
431
+ </svg>
432
+ <h1 style="font-size: 20px; font-weight: 500; margin: 0; color: #f0ede6;">Logged in to Checkpoint</h1>
433
+ <p style="color: #9e9889; margin: 0; font-size: 14px;">You can close this tab and return to your terminal.</p>
434
+ <div style="margin-top: 8px; padding-top: 24px; border-top: 1px dashed rgba(73,67,58,0.3);">
435
+ <p style="color: #9e9889; margin: 0px 0 24px; font-size: 13px;">Start sharing your localhost for feedback:</p>
436
+ <code style="padding: 8px 12px; background: #1D1B15; border: 1px solid rgba(73,67,58,0.4); border-radius: 6px; font-family: 'JetBrains Mono','Fira Code','SF Mono',monospace; font-size: 13px; color: #9e9889;">checkpoint start -p <span style="color: #ff5700;">&lt;port&gt;</span></code>
437
+ <p style="text-align: center; margin-top: 24px; font-size: 13px; color: #9e9889;">or <a href="${APP_URL}" style="color: #ff5700; text-decoration: none;">Go to Dashboard</a></p>
438
+ </div>
387
439
  </div>
388
- </div>
389
- </body>
390
- </html>
391
- `);
392
- server.close();
393
- resolve({ access_token, refresh_token });
440
+ </body>
441
+ </html>
442
+ `);
443
+ server.close();
444
+ resolve({ access_token, refresh_token });
445
+ }
446
+ catch {
447
+ res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
448
+ res.end(`
449
+ <html>
450
+ <body style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #13120A; color: #f0ede6;">
451
+ <div style="text-align: center; display: flex; flex-direction: column; align-items: center; gap: 16px;">
452
+ <svg width="48" height="48" viewBox="0 0 256 256" fill="none">
453
+ <circle cx="128" cy="128" r="112" fill="#ef4444"/>
454
+ <line x1="96" y1="96" x2="160" y2="160" stroke="#13120A" stroke-width="16" stroke-linecap="round"/>
455
+ <line x1="160" y1="96" x2="96" y2="160" stroke="#13120A" stroke-width="16" stroke-linecap="round"/>
456
+ </svg>
457
+ <h1 style="font-size: 20px; font-weight: 500; margin: 0; color: #f0ede6;">Login failed</h1>
458
+ <p style="color: #9e9889; margin: 0; font-size: 14px;">Code exchange failed. Please try again.</p>
459
+ </div>
460
+ </body>
461
+ </html>
462
+ `);
463
+ server.close();
464
+ reject(new Error('Code exchange failed'));
465
+ }
394
466
  }
395
467
  else {
396
468
  res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
@@ -404,13 +476,13 @@ function loginWithBrowser() {
404
476
  <line x1="160" y1="96" x2="96" y2="160" stroke="#13120A" stroke-width="16" stroke-linecap="round"/>
405
477
  </svg>
406
478
  <h1 style="font-size: 20px; font-weight: 500; margin: 0; color: #f0ede6;">Login failed</h1>
407
- <p style="color: #9e9889; margin: 0; font-size: 14px;">Missing tokens. Please try again.</p>
479
+ <p style="color: #9e9889; margin: 0; font-size: 14px;">Missing auth code. Please try again.</p>
408
480
  </div>
409
481
  </body>
410
482
  </html>
411
483
  `);
412
484
  server.close();
413
- reject(new Error('Missing tokens in callback'));
485
+ reject(new Error('Missing auth code in callback'));
414
486
  }
415
487
  }
416
488
  else {
@@ -431,15 +503,18 @@ function loginWithBrowser() {
431
503
  console.log('');
432
504
  const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
433
505
  rl.question(chalk_1.default.gray(' Press ENTER to open the browser...'), () => {
434
- rl.close();
435
506
  openBrowser(loginUrl);
507
+ // Keep stdin open so the HTTP server stays alive to receive the callback.
508
+ // Pause instead of closing so the event loop doesn't drain.
509
+ rl.close();
510
+ process.stdin.resume();
436
511
  });
437
512
  });
438
- // Timeout after 5 minutes
513
+ // Timeout after 10 minutes
439
514
  setTimeout(() => {
440
515
  server.close();
441
- reject(new Error('Login timed out (5 minutes). Please try again.'));
442
- }, 5 * 60 * 1000);
516
+ reject(new Error('Login timed out (10 minutes). Please try again.'));
517
+ }, 10 * 60 * 1000);
443
518
  });
444
519
  }
445
520
  /* ── CLI ── */
@@ -447,7 +522,7 @@ const program = new commander_1.Command();
447
522
  program
448
523
  .name('checkpoint')
449
524
  .description('Share your localhost with reviewers — get visual feedback directly on the page.\n\nQuick start:\n 1. checkpoint login Sign in to your Checkpoint account\n 2. checkpoint start -p 3000 Start tunneling and get a share link\n\nReuse a tunnel name to keep the same share URL and preserve all comments.')
450
- .version('0.4.1');
525
+ .version('0.5.1');
451
526
  // ── checkpoint login ──
452
527
  program
453
528
  .command('login')
@@ -636,8 +711,36 @@ program
636
711
  console.log(chalk_1.default.red(' Session expired. Run `checkpoint login` again.'));
637
712
  process.exit(1);
638
713
  }
714
+ const workspaceId = await getUserWorkspaceId(sb, userData.user.id);
715
+ if (!workspaceId) {
716
+ console.log(chalk_1.default.red(' Failed: no workspace membership found. Complete setup in the dashboard first.'));
717
+ process.exit(1);
718
+ }
719
+ // Check plan limits
720
+ const { data: subscription } = await sb
721
+ .from('subscriptions')
722
+ .select('status, billing_plans(name)')
723
+ .eq('workspace_id', workspaceId)
724
+ .maybeSingle();
725
+ const isActiveSub = subscription?.status === 'active' || subscription?.status === 'trialing';
726
+ const planName = subscription?.billing_plans?.name?.toLowerCase() ?? '';
727
+ const isPro = planName.includes('pro') || planName.includes('enterprise');
728
+ const FREE_TUNNEL_LIMIT = 1;
729
+ if (!isActiveSub || !isPro) {
730
+ const { count } = await sb
731
+ .from('tunnels')
732
+ .select('id', { count: 'exact', head: true })
733
+ .eq('workspace_id', workspaceId);
734
+ if ((count ?? 0) >= FREE_TUNNEL_LIMIT) {
735
+ console.log(chalk_1.default.red(` ✗ Tunnel limit reached (${FREE_TUNNEL_LIMIT} tunnels on the Free plan).`));
736
+ console.log(chalk_1.default.gray(' Upgrade to Pro for unlimited tunnels: ') + chalk_1.default.cyan(`${(0, config_js_1.getAppUrl)()}/dashboard/settings?tab=billing`));
737
+ console.log('');
738
+ process.exit(1);
739
+ }
740
+ }
639
741
  const APP_URL = (0, config_js_1.getAppUrl)();
640
742
  const shareId = generateShareId();
743
+ const now = new Date().toISOString();
641
744
  const { data, error } = await sb.from('tunnels').insert({
642
745
  name: opts.name,
643
746
  local_port: parseInt(opts.port, 10),
@@ -645,7 +748,9 @@ program
645
748
  share_id: shareId,
646
749
  status: 'active',
647
750
  user_id: userData.user.id,
648
- last_seen_at: new Date().toISOString(),
751
+ workspace_id: workspaceId,
752
+ activated_at: now,
753
+ last_seen_at: now,
649
754
  }).select('id').single();
650
755
  if (error || !data) {
651
756
  console.log(chalk_1.default.red(` Failed: ${error?.message ?? 'unknown error'}`));
@@ -14,7 +14,6 @@ function buildMinimalTrackingScript() {
14
14
  var lastMouseX=0;
15
15
  var lastMouseY=0;
16
16
  var inspectBox=null;
17
- var inspectLabel=null;
18
17
  var pickShield=null;
19
18
  var lastPath='';
20
19
  var mutationTick=false;
@@ -147,6 +146,214 @@ function buildMinimalTrackingScript() {
147
146
  return path.slice(-12);
148
147
  }
149
148
 
149
+ function withFrameworkSuffix(path,framework){
150
+ var safePath=Array.isArray(path)?path.slice(0):[];
151
+ if(framework) safePath.push('@fw:'+framework);
152
+ return safePath.slice(-16);
153
+ }
154
+
155
+ function getVueComponentFromElement(el){
156
+ if(!el||el.nodeType!==1) return null;
157
+ var current=el;
158
+ var depth=0;
159
+ while(current&&depth<8){
160
+ try{
161
+ if(current.__vueParentComponent) return current.__vueParentComponent;
162
+ if(current.__vue__) return current.__vue__;
163
+ if(current.__vnode&&current.__vnode.component) return current.__vnode.component;
164
+ }catch(e){}
165
+ current=current.parentElement;
166
+ depth++;
167
+ }
168
+ return null;
169
+ }
170
+
171
+ function getVueComponentName(instance){
172
+ try{
173
+ if(!instance) return '';
174
+ if(instance.type){
175
+ var t=instance.type;
176
+ if(typeof t.name==='string'&&t.name) return t.name;
177
+ if(typeof t.__name==='string'&&t.__name) return t.__name;
178
+ if(typeof t.displayName==='string'&&t.displayName) return t.displayName;
179
+ }
180
+ if(instance.$options&&typeof instance.$options.name==='string'&&instance.$options.name){
181
+ return instance.$options.name;
182
+ }
183
+ }catch(e){}
184
+ return '';
185
+ }
186
+
187
+ function getVueSourceFile(instance){
188
+ try{
189
+ if(!instance) return '';
190
+ if(instance.type&&typeof instance.type.__file==='string'&&instance.type.__file) return instance.type.__file;
191
+ if(instance.$options&&typeof instance.$options.__file==='string'&&instance.$options.__file) return instance.$options.__file;
192
+ }catch(e){}
193
+ return '';
194
+ }
195
+
196
+ function getVueOwnerPath(instance){
197
+ var path=[];
198
+ var current=instance;
199
+ var steps=0;
200
+ while(current&&steps<24){
201
+ var name=getVueComponentName(current);
202
+ var keyVal='';
203
+ try{
204
+ if(current.vnode&&current.vnode.key!=null) keyVal=String(current.vnode.key);
205
+ else if(current.$vnode&&current.$vnode.key!=null) keyVal=String(current.$vnode.key);
206
+ }catch(e){}
207
+ if(name) path.push(keyVal?name+'#'+keyVal:name);
208
+ current=current.parent||current.$parent||null;
209
+ steps++;
210
+ }
211
+ path.reverse();
212
+ return path.slice(-12);
213
+ }
214
+
215
+ function getVueInstanceKey(instance){
216
+ try{
217
+ if(!instance) return '';
218
+ if(instance.vnode&&instance.vnode.key!=null) return String(instance.vnode.key);
219
+ if(instance.$vnode&&instance.$vnode.key!=null) return String(instance.$vnode.key);
220
+ }catch(e){}
221
+ return '';
222
+ }
223
+
224
+ function getSvelteMetaFromElement(el){
225
+ if(!el||el.nodeType!==1) return null;
226
+ var current=el;
227
+ var depth=0;
228
+ while(current&&depth<8){
229
+ try{
230
+ if(current.__svelte_meta) return current.__svelte_meta;
231
+ }catch(e){}
232
+ current=current.parentElement;
233
+ depth++;
234
+ }
235
+ return null;
236
+ }
237
+
238
+ function basename(path){
239
+ if(!path) return '';
240
+ try{
241
+ var clean=String(path).split(/[\\/]/).pop()||'';
242
+ return clean;
243
+ }catch(e){}
244
+ return '';
245
+ }
246
+
247
+ function withoutExt(name){
248
+ if(!name) return '';
249
+ return String(name).replace(/\.[^.]+$/,'');
250
+ }
251
+
252
+ function getAngularComponentFromElement(el){
253
+ var ngApi=null;
254
+ try{ ngApi=window.ng||null; }catch(e){ ngApi=null; }
255
+ if(!ngApi||typeof ngApi.getComponent!=='function') return null;
256
+ var current=el;
257
+ var depth=0;
258
+ while(current&&depth<8){
259
+ try{
260
+ var comp=ngApi.getComponent(current);
261
+ if(comp) return { component:comp, host:current };
262
+ }catch(e){}
263
+ current=current.parentElement;
264
+ depth++;
265
+ }
266
+ return null;
267
+ }
268
+
269
+ function getAngularOwnerPath(el){
270
+ var ngApi=null;
271
+ try{ ngApi=window.ng||null; }catch(e){ ngApi=null; }
272
+ if(!ngApi||typeof ngApi.getComponent!=='function') return [];
273
+ var path=[];
274
+ var current=el;
275
+ var depth=0;
276
+ while(current&&depth<12){
277
+ try{
278
+ var comp=ngApi.getComponent(current);
279
+ if(comp&&comp.constructor&&comp.constructor.name){
280
+ path.push(String(comp.constructor.name));
281
+ }
282
+ }catch(e){}
283
+ current=current.parentElement;
284
+ depth++;
285
+ }
286
+ path.reverse();
287
+ return path.slice(-12);
288
+ }
289
+
290
+ function getFrameworkContextFromElement(el){
291
+ if(!el||el.nodeType!==1) return null;
292
+
293
+ var fiber=getReactFiberNode(el);
294
+ if(fiber){
295
+ var reactPath=getReactOwnerPathFromFiber(fiber);
296
+ var reactSource=getSourceDebugInfo(fiber);
297
+ return {
298
+ framework:'react',
299
+ component_name:getReactFiberName(fiber)||'',
300
+ owner_path:withFrameworkSuffix(reactPath,'react'),
301
+ source_file:reactSource&&reactSource.fileName?String(reactSource.fileName):'',
302
+ source_line:reactSource&&typeof reactSource.lineNumber==='number'?reactSource.lineNumber:undefined,
303
+ source_column:reactSource&&typeof reactSource.columnNumber==='number'?reactSource.columnNumber:undefined,
304
+ instance_key:fiber&&fiber.key!=null?String(fiber.key):''
305
+ };
306
+ }
307
+
308
+ var vueInstance=getVueComponentFromElement(el);
309
+ if(vueInstance){
310
+ var vueFile=getVueSourceFile(vueInstance);
311
+ return {
312
+ framework:'vue',
313
+ component_name:getVueComponentName(vueInstance)||withoutExt(basename(vueFile))||'',
314
+ owner_path:withFrameworkSuffix(getVueOwnerPath(vueInstance),'vue'),
315
+ source_file:vueFile||'',
316
+ source_line:undefined,
317
+ source_column:undefined,
318
+ instance_key:getVueInstanceKey(vueInstance)||''
319
+ };
320
+ }
321
+
322
+ var svelteMeta=getSvelteMetaFromElement(el);
323
+ if(svelteMeta){
324
+ var sLoc=svelteMeta.loc||null;
325
+ var sFile=sLoc&&sLoc.file?String(sLoc.file):'';
326
+ var sName=withoutExt(basename(sFile));
327
+ return {
328
+ framework:'svelte',
329
+ component_name:sName||'SvelteComponent',
330
+ owner_path:withFrameworkSuffix(sName?[sName]:[],'svelte'),
331
+ source_file:sFile||'',
332
+ source_line:sLoc&&typeof sLoc.line==='number'?sLoc.line:undefined,
333
+ source_column:sLoc&&typeof sLoc.column==='number'?sLoc.column:undefined,
334
+ instance_key:''
335
+ };
336
+ }
337
+
338
+ var ngMatch=getAngularComponentFromElement(el);
339
+ if(ngMatch&&ngMatch.component){
340
+ var ngName=(ngMatch.component.constructor&&ngMatch.component.constructor.name)
341
+ ? String(ngMatch.component.constructor.name)
342
+ : 'AngularComponent';
343
+ return {
344
+ framework:'angular',
345
+ component_name:ngName,
346
+ owner_path:withFrameworkSuffix(getAngularOwnerPath(el),'angular'),
347
+ source_file:'',
348
+ source_line:undefined,
349
+ source_column:undefined,
350
+ instance_key:''
351
+ };
352
+ }
353
+
354
+ return null;
355
+ }
356
+
150
357
  function getExplicitAnchorId(el){
151
358
  if(!el||typeof el.closest!=='function') return '';
152
359
  var holder=el.closest('[data-checkpoint-anchor]');
@@ -156,22 +363,26 @@ function buildMinimalTrackingScript() {
156
363
 
157
364
  function getSourceAnchorFromElement(el){
158
365
  if(!el||el.nodeType!==1) return null;
159
- var fiber=getReactFiberNode(el);
160
- var ownerPath=getReactOwnerPathFromFiber(fiber);
161
- var source=getSourceDebugInfo(fiber);
162
- var component=getReactFiberName(fiber);
163
366
  var explicitId=getExplicitAnchorId(el);
164
- var reactKey=fiber&&fiber.key!=null?String(fiber.key):'';
165
- var hasData=!!(explicitId||component||ownerPath.length>0||source||reactKey);
367
+ var context=getFrameworkContextFromElement(el);
368
+ var hasData=!!(
369
+ explicitId||
370
+ (context&&(
371
+ context.component_name||
372
+ (Array.isArray(context.owner_path)&&context.owner_path.length>0)||
373
+ context.source_file||
374
+ context.instance_key
375
+ ))
376
+ );
166
377
  if(!hasData) return null;
167
378
  return {
168
379
  explicit_id:explicitId||undefined,
169
- component_name:component||undefined,
170
- owner_path:ownerPath,
171
- source_file:source&&source.fileName?String(source.fileName):undefined,
172
- source_line:source&&typeof source.lineNumber==='number'?source.lineNumber:undefined,
173
- source_column:source&&typeof source.columnNumber==='number'?source.columnNumber:undefined,
174
- react_key:reactKey||undefined,
380
+ component_name:context&&context.component_name?context.component_name:undefined,
381
+ owner_path:context&&Array.isArray(context.owner_path)?context.owner_path:[],
382
+ source_file:context&&context.source_file?context.source_file:undefined,
383
+ source_line:context&&typeof context.source_line==='number'?context.source_line:undefined,
384
+ source_column:context&&typeof context.source_column==='number'?context.source_column:undefined,
385
+ react_key:context&&context.instance_key?context.instance_key:undefined,
175
386
  host_tag:el&&el.tagName?String(el.tagName).toLowerCase():undefined
176
387
  };
177
388
  }
@@ -249,17 +460,16 @@ function buildMinimalTrackingScript() {
249
460
  if(explicit===sourceAnchor.explicit_id) score+=2400;
250
461
  else if(explicit) score-=200;
251
462
  }
252
- var fiber=getReactFiberNode(el);
253
- var component=getReactFiberName(fiber);
254
- var source=getSourceDebugInfo(fiber);
255
- var ownerPath=getReactOwnerPathFromFiber(fiber);
463
+ var context=getFrameworkContextFromElement(el);
464
+ var component=context&&context.component_name?context.component_name:'';
465
+ var ownerPath=context&&Array.isArray(context.owner_path)?context.owner_path:[];
256
466
  if(sourceAnchor.component_name&&component===sourceAnchor.component_name) score+=240;
257
- if(sourceAnchor.react_key&&fiber&&fiber.key!=null&&String(fiber.key)===sourceAnchor.react_key) score+=360;
467
+ if(sourceAnchor.react_key&&context&&context.instance_key&&String(context.instance_key)===sourceAnchor.react_key) score+=360;
258
468
  score+=ownerSuffixScore(ownerPath,sourceAnchor.owner_path||[]);
259
- if(sourceAnchor.source_file&&source&&String(source.fileName||'')===String(sourceAnchor.source_file)){
469
+ if(sourceAnchor.source_file&&context&&context.source_file&&String(context.source_file||'')===String(sourceAnchor.source_file)){
260
470
  score+=420;
261
- if(typeof sourceAnchor.source_line==='number'&&source.lineNumber===sourceAnchor.source_line) score+=420;
262
- if(typeof sourceAnchor.source_column==='number'&&source.columnNumber===sourceAnchor.source_column) score+=80;
471
+ if(typeof sourceAnchor.source_line==='number'&&context.source_line===sourceAnchor.source_line) score+=420;
472
+ if(typeof sourceAnchor.source_column==='number'&&context.source_column===sourceAnchor.source_column) score+=80;
263
473
  }
264
474
  if(sourceAnchor.host_tag&&el.tagName&&String(el.tagName).toLowerCase()===sourceAnchor.host_tag) score+=40;
265
475
  return score;
@@ -451,19 +661,6 @@ function buildMinimalTrackingScript() {
451
661
  return { coordFallback:null, strategy:'none', matchedSelector:null, status:'none' };
452
662
  }
453
663
 
454
- function getInspectLabelText(target){
455
- if(!target||!target.tagName) return '';
456
- var tag=String(target.tagName).toLowerCase();
457
- var id=target.id?('#'+target.id):'';
458
- var cls='';
459
- try{
460
- var classes=(target.className&&typeof target.className==='string')
461
- ? target.className.trim().split(/\s+/).filter(Boolean).slice(0,3)
462
- : [];
463
- if(classes.length>0) cls='.'+classes.join('.');
464
- }catch(e){}
465
- return shortText(tag+id+cls);
466
- }
467
664
 
468
665
  function ensureInspectUi(){
469
666
  if(!document.body) return;
@@ -483,31 +680,10 @@ inspectBox.style.border='2px solid #F26522';
483
680
  inspectBox.style.display='none';
484
681
  document.body.appendChild(inspectBox);
485
682
  }
486
- if(!inspectLabel){
487
- inspectLabel=document.createElement('div');
488
- inspectLabel.style.position='fixed';
489
- inspectLabel.style.left='0';
490
- inspectLabel.style.top='0';
491
- inspectLabel.style.padding='6px 8px';
492
- inspectLabel.style.maxWidth='420px';
493
- inspectLabel.style.borderRadius='8px';
494
- inspectLabel.style.border='1px solid rgba(148,163,184,0.45)';
495
- inspectLabel.style.background='rgba(15,23,42,0.96)';
496
- inspectLabel.style.color='#e2e8f0';
497
- inspectLabel.style.font='12px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace';
498
- inspectLabel.style.whiteSpace='nowrap';
499
- inspectLabel.style.wordBreak='break-word';
500
- inspectLabel.style.pointerEvents='none';
501
- inspectLabel.style.zIndex='2147483647';
502
- inspectLabel.style.display='none';
503
- inspectLabel.style.boxShadow='0 6px 20px rgba(0,0,0,0.35)';
504
- document.body.appendChild(inspectLabel);
505
- }
506
683
  }
507
684
 
508
685
  function hideInspectUi(){
509
- if(inspectBox) inspectBox.style.display='none';
510
- if(inspectLabel) inspectLabel.style.display='none';
686
+ if(inspectBox) inspectBox.style.display='none';
511
687
  }
512
688
 
513
689
  function ensurePickShield(){
@@ -534,7 +710,7 @@ inspectBox.style.border='2px solid #F26522';
534
710
 
535
711
  function renderInspectAt(x,y){
536
712
  ensureInspectUi();
537
- if(!inspectBox||!inspectLabel){
713
+ if(!inspectBox){
538
714
  return;
539
715
  }
540
716
  var target=getElementAtPoint(x,y);
@@ -552,18 +728,6 @@ inspectBox.style.border='2px solid #F26522';
552
728
  inspectBox.style.top=Math.max(0,rect.top)+'px';
553
729
  inspectBox.style.width=Math.max(1,rect.width)+'px';
554
730
  inspectBox.style.height=Math.max(1,rect.height)+'px';
555
- inspectLabel.textContent=getInspectLabelText(target);
556
- inspectLabel.style.display='block';
557
-
558
- var vw=window.innerWidth||document.documentElement.clientWidth||0;
559
- var vh=window.innerHeight||document.documentElement.clientHeight||0;
560
- var lr=inspectLabel.getBoundingClientRect();
561
- var left=x+12;
562
- var top=y+12;
563
- if(left+lr.width>vw-8) left=Math.max(8,x-lr.width-12);
564
- if(top+lr.height>vh-8) top=Math.max(8,y-lr.height-12);
565
- inspectLabel.style.left=left+'px';
566
- inspectLabel.style.top=top+'px';
567
731
  }
568
732
 
569
733
  function postPickResult(target,clientX,clientY){
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "checkpoint-cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Share your localhost with reviewers — get visual feedback directly on the page",
5
5
  "keywords": [
6
6
  "checkpoint",