@vnphu/nestjs-api-explorer 0.2.0 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"api-explorer.html.d.ts","sourceRoot":"","sources":["../src/api-explorer.html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAEnE,wBAAgB,eAAe,CAAC,OAAO,EAAE,0BAA0B,GAAG,MAAM,CAmhD3E"}
1
+ {"version":3,"file":"api-explorer.html.d.ts","sourceRoot":"","sources":["../src/api-explorer.html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAEnE,wBAAgB,eAAe,CAAC,OAAO,EAAE,0BAA0B,GAAG,MAAM,CAqnD3E"}
@@ -254,7 +254,7 @@ function getExplorerHtml(options) {
254
254
 
255
255
  /* ── Summary Panel ── */
256
256
  #summary-panel {
257
- width: 240px; flex-shrink: 0; border-left: 1px solid var(--border);
257
+ width: 400px; flex-shrink: 0; border-left: 1px solid var(--border);
258
258
  background: var(--bg-white); display: flex; flex-direction: column; overflow: hidden;
259
259
  }
260
260
  #summary-panel.hidden { display: none; }
@@ -263,6 +263,13 @@ function getExplorerHtml(options) {
263
263
  display: flex; align-items: center; justify-content: space-between;
264
264
  }
265
265
  .summary-header-title { font-size: 11px; font-weight: 700; color: var(--text); text-transform: uppercase; letter-spacing: 0.5px; }
266
+ .summary-copy-btn {
267
+ display: flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 500;
268
+ color: var(--text-muted); background: none; border: 1px solid var(--border);
269
+ border-radius: var(--radius-sm); padding: 3px 8px; cursor: pointer; transition: all 0.15s;
270
+ }
271
+ .summary-copy-btn:hover { background: var(--bg-input); color: var(--accent); border-color: var(--accent); }
272
+ .summary-copy-btn.copied { color: #16a34a; border-color: #86efac; background: #f0fdf4; }
266
273
  #summary-body { flex: 1; overflow-y: auto; padding: 10px 14px 16px; display: flex; flex-direction: column; gap: 14px; }
267
274
  .summary-section { display: flex; flex-direction: column; gap: 6px; }
268
275
  .summary-section-label {
@@ -607,6 +614,9 @@ function getExplorerHtml(options) {
607
614
  <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
608
615
  </svg>
609
616
  </button>
617
+ <a href="https://github.com/vnphu/nestjs-api-explorer" target="_blank" rel="noopener" class="icon-btn" title="View on GitHub" style="display:inline-flex;align-items:center;justify-content:center;text-decoration:none">
618
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
619
+ </a>
610
620
  <button class="icon-btn" id="reload-btn" title="Reload routes">
611
621
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
612
622
  <polyline points="23 4 23 10 17 10"/>
@@ -829,6 +839,10 @@ function getExplorerHtml(options) {
829
839
  <div id="summary-panel" class="hidden">
830
840
  <div class="summary-header">
831
841
  <span class="summary-header-title">Request Summary</span>
842
+ <button id="summary-copy-btn" class="summary-copy-btn hidden" title="Copy summary">
843
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
844
+ <span id="summary-copy-label">Copy</span>
845
+ </button>
832
846
  </div>
833
847
  <div id="summary-body">
834
848
  <div class="summary-empty" id="summary-empty">
@@ -928,11 +942,9 @@ function groupRoutes(routes) {
928
942
 
929
943
  function renderRouteItem(r) {
930
944
  const active = S.selected?.method === r.method && S.selected?.path === r.path ? ' active' : '';
931
- const desc = r.description ? \`<span class="route-desc">\${esc(r.description.split('\\n')[0])}</span>\` : '';
932
945
  return \`<div class="route-item\${active}" data-method="\${r.method}" data-path="\${esc(r.path)}">
933
946
  <span class="method-badge method-\${r.method}">\${r.method}</span>
934
947
  <span class="route-path" title="\${esc(r.path)}">\${esc(r.path)}</span>
935
- \${desc}
936
948
  </div>\`;
937
949
  }
938
950
 
@@ -1011,8 +1023,28 @@ function selectRoute(route) {
1011
1023
  document.querySelectorAll('#req-panel-inner .tab-panel').forEach(p => p.classList.remove('active'));
1012
1024
  document.getElementById('tab-params').classList.add('active');
1013
1025
 
1014
- // Auto-switch to Body tab for methods that send a body
1026
+ // Auto-fill body editor from body schema if available
1015
1027
  const bodyMethods = ['POST', 'PUT', 'PATCH'];
1028
+ if (bodyMethods.includes(route.method) && route.body?.length) {
1029
+ const template = {};
1030
+ route.body.forEach(f => {
1031
+ if (f.type === 'number') template[f.name] = 0;
1032
+ else if (f.type === 'boolean') template[f.name] = false;
1033
+ else if (f.type === 'array') template[f.name] = [];
1034
+ else if (f.type === 'object') template[f.name] = {};
1035
+ else template[f.name] = '';
1036
+ });
1037
+ S.body.content = JSON.stringify(template, null, 2);
1038
+ S.body.type = 'json';
1039
+ el.bodyEditor.value = S.body.content;
1040
+ // Sync body type radio
1041
+ document.querySelectorAll('[name="body-type"]').forEach(r => {
1042
+ r.checked = r.value === 'json';
1043
+ });
1044
+ el.formatBtn.style.display = '';
1045
+ }
1046
+
1047
+ // Auto-switch to Body tab for methods that send a body
1016
1048
  if (bodyMethods.includes(route.method) && !route.params.length) {
1017
1049
  el.reqTabBar.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
1018
1050
  el.reqTabBar.querySelector('[data-tab="body"]').classList.add('active');
@@ -1198,16 +1230,21 @@ function renderSummary() {
1198
1230
  const empty = $('summary-empty');
1199
1231
  const content = $('summary-content');
1200
1232
 
1233
+ const copyBtn = $('summary-copy-btn');
1234
+
1201
1235
  if (!S.selected) {
1202
1236
  panel.classList.remove('hidden');
1203
1237
  empty.style.display = 'flex';
1204
- content.style.display = 'none';
1238
+ content.classList.add('hidden');
1239
+ copyBtn.classList.add('hidden');
1205
1240
  return;
1206
1241
  }
1207
1242
 
1208
1243
  panel.classList.remove('hidden');
1209
1244
  empty.style.display = 'none';
1245
+ content.classList.remove('hidden');
1210
1246
  content.style.display = 'flex';
1247
+ copyBtn.classList.remove('hidden');
1211
1248
 
1212
1249
  const method = S.selected.method;
1213
1250
  const fullUrl = buildUrl();
@@ -1320,6 +1357,65 @@ function renderSummary() {
1320
1357
  \`;
1321
1358
  }
1322
1359
 
1360
+ // ── Copy Summary ───────────────────────────────────────────────────
1361
+ function copySummary() {
1362
+ if (!S.selected) return;
1363
+ const r = S.selected;
1364
+ const lines = [];
1365
+
1366
+ lines.push(\`\${r.method} \${buildUrl()}\`);
1367
+ if (r.description) lines.push(\`\`, ...r.description.split('\\n').map(l => \`# \${l}\`));
1368
+
1369
+ if (r.params?.length) {
1370
+ lines.push(\`\`, \`[Path Params]\`);
1371
+ r.params.forEach(p => lines.push(\` \${p}: \${S.pathParams[p] || ''}\`));
1372
+ }
1373
+
1374
+ const activeQuery = S.queryParams.filter(p => p.enabled && p.key);
1375
+ if (activeQuery.length) {
1376
+ lines.push(\`\`, \`[Query Params]\`);
1377
+ activeQuery.forEach(p => lines.push(\` \${p.key}: \${p.value}\`));
1378
+ }
1379
+
1380
+ if (r.query?.length) {
1381
+ lines.push(\`\`, \`[Query Schema]\`);
1382
+ r.query.forEach(f => lines.push(\` \${f.name}: \${f.type}\${f.required ? ' (required)' : ''}\${f.rules?.length ? ' | ' + f.rules.join(', ') : ''}\${f.description ? ' — ' + f.description : ''}\`));
1383
+ }
1384
+
1385
+ const activeHeaders = S.reqHeaders.filter(h => h.enabled && h.key);
1386
+ if (activeHeaders.length) {
1387
+ lines.push(\`\`, \`[Headers]\`);
1388
+ activeHeaders.forEach(h => lines.push(\` \${h.key}: \${h.value}\`));
1389
+ }
1390
+
1391
+ if (r.headers?.length) {
1392
+ lines.push(\`\`, \`[Required Headers]\`);
1393
+ r.headers.forEach(f => lines.push(\` \${f.name}\${f.required ? ' (required)' : ''}\${f.description ? ' — ' + f.description : ''}\`));
1394
+ }
1395
+
1396
+ if (r.body?.length) {
1397
+ lines.push(\`\`, \`[Body Schema]\`);
1398
+ r.body.forEach(f => lines.push(\` \${f.name}: \${f.type}\${f.required ? ' (required)' : ' (optional)'}\${f.rules?.length ? ' | ' + f.rules.join(', ') : ''}\${f.description ? ' — ' + f.description : ''}\`));
1399
+ }
1400
+
1401
+ if (['POST','PUT','PATCH'].includes(r.method) && S.body.type !== 'none' && S.body.content) {
1402
+ lines.push(\`\`, \`[Body Content (\${S.body.type})]\`, S.body.content);
1403
+ }
1404
+
1405
+ if (r.response?.length) {
1406
+ lines.push(\`\`, \`[Response Schema]\`);
1407
+ r.response.forEach(f => lines.push(\` \${f.name}: \${f.type}\${f.description ? ' — ' + f.description : ''}\`));
1408
+ }
1409
+
1410
+ navigator.clipboard.writeText(lines.join('\\n')).then(() => {
1411
+ const btn = $('summary-copy-btn');
1412
+ const label = $('summary-copy-label');
1413
+ btn.classList.add('copied');
1414
+ label.textContent = 'Copied!';
1415
+ setTimeout(() => { btn.classList.remove('copied'); label.textContent = 'Copy'; }, 1800);
1416
+ });
1417
+ }
1418
+
1323
1419
  // Render a list of DocField items (body/query/headers/response) in the summary panel
1324
1420
  function renderDocFields(label, fields) {
1325
1421
  if (!fields || fields.length === 0) return '';
@@ -1487,6 +1583,8 @@ function bindEvents() {
1487
1583
  el.baseUrl.addEventListener('input', () => { renderUrlBar(); renderSummary(); });
1488
1584
  el.sendBtn.addEventListener('click', sendRequest);
1489
1585
 
1586
+ $('summary-copy-btn').addEventListener('click', copySummary);
1587
+
1490
1588
  el.addQueryBtn.addEventListener('click', () => {
1491
1589
  S.queryParams.push({ id: uid(), key: '', value: '', enabled: true });
1492
1590
  renderQueryParams();
@@ -1 +1 @@
1
- {"version":3,"file":"api-explorer.html.js","sourceRoot":"","sources":["../src/api-explorer.html.ts"],"names":[],"mappings":";;AAEA,0CAmhDC;AAnhDD,SAAgB,eAAe,CAAC,OAAmC;IACjE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,OAAO,UAAU,CAAC;;;;;WAKT,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAm0BC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAssBf,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"api-explorer.html.js","sourceRoot":"","sources":["../src/api-explorer.html.ts"],"names":[],"mappings":";;AAEA,0CAqnDC;AArnDD,SAAgB,eAAe,CAAC,OAAmC;IACjE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,OAAO,UAAU,CAAC;;;;;WAKT,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAi1BC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA0xBf,CAAC;AACT,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vnphu/nestjs-api-explorer",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "An in-app API explorer for NestJS — like Postman, but built right into your app.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,6 +25,14 @@
25
25
  ],
26
26
  "author": "vnphu",
27
27
  "license": "MIT",
28
+ "homepage": "https://github.com/vnphu/nestjs-api-explorer#readme",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/vnphu/nestjs-api-explorer.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/vnphu/nestjs-api-explorer/issues"
35
+ },
28
36
  "peerDependencies": {
29
37
  "@nestjs/common": ">=9.0.0",
30
38
  "@nestjs/core": ">=9.0.0",