@myapihq/cli 2.16.0 → 2.16.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.
@@ -20,6 +20,11 @@ export const EXPOSES = [
20
20
  'DELETE /feedback/orgs/{org_id}/widgets/{id}',
21
21
  ];
22
22
  export const SCHEMA = {
23
+ // Declared because `list` reads flags.limit/offset, --help documents both,
24
+ // and a truncated page prints "raise --limit" — while the parser refused
25
+ // them as unknown. Reported by ImmoPilot on 2026-08-19.
26
+ limit: 'number',
27
+ offset: 'number',
23
28
  trace: 'boolean',
24
29
  routes: 'string',
25
30
  accent: 'string',
@@ -109,6 +114,12 @@ export async function list(flags) {
109
114
  info(' screenshot yes');
110
115
  else if (i.screenshot_asset_id)
111
116
  info(' screenshot yes (link could not be signed)');
117
+ // A missing picture used to be silent, and silence read as "nobody
118
+ // attached one" when it often meant "the renderer threw on this page".
119
+ // The backend records which it was; say so rather than printing nothing.
120
+ else if (i.screenshot_status === 'failed') {
121
+ info(` screenshot FAILED${i.screenshot_error ? ` — ${i.screenshot_error}` : ''}`);
122
+ }
112
123
  if (flags.trace && trace.length) {
113
124
  info('');
114
125
  for (const e of trace) {
@@ -219,6 +230,14 @@ export async function widget(sub, arg, flags) {
219
230
  // the URL from a key and a base by hand.
220
231
  info(` ${w.script_tag || `<script src="https://api.myapihq.com/feedback/in/${w.key}/widget.js" async></script>`}`);
221
232
  info(` routes: ${w.routes?.length ? w.routes.join(', ') : '(everywhere)'}`);
233
+ // The API returns these inside `theme`; printing only name/key/routes
234
+ // meant the only way to read back a widget's position or label was to
235
+ // fetch widget.js and read the JavaScript.
236
+ const th = w.theme ?? {};
237
+ if (th.accent || th.position || th.label) {
238
+ info(` theme: ${[th.label && `label "${th.label}"`, th.position, th.accent]
239
+ .filter(Boolean).join(' · ')}`);
240
+ }
222
241
  info(` origins: ${w.allowed_origins?.length ? w.allowed_origins.join(', ') : '(any — anyone can post with this key)'}`);
223
242
  info('');
224
243
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ // SDK-level unit tests for feedback.listWidgets — the unwrap that made
2
+ // `myapi feedback widget list` report "No widgets yet" to orgs that had
3
+ // widgets. Mocks global fetch — does NOT hit the network.
4
+ //
5
+ // Reported by ImmoPilot four times before it was traced. The HTTP API was
6
+ // answering correctly throughout: GET /feedback/orgs/{org}/widgets returns
7
+ // { widgets, total }, the same envelope shape as the item list. The SDK typed
8
+ // it Promise<Widget[]> and returned the object unwrapped, so the CLI's
9
+ // `!ws.length` was always true. Nothing tested the SDK against a real response
10
+ // shape, which is the only reason a four-time report was possible.
11
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
12
+ import { feedback } from '@myapihq/sdk';
13
+ const API_KEY = 'myapi_test_abc';
14
+ const ORG = '7f1aa7f1-f6eb-4a6d-87b7-1bbe6584f5d6';
15
+ let fetchMock;
16
+ function ok(data) {
17
+ return new Response(JSON.stringify({ success: true, data, meta: {} }), {
18
+ status: 200,
19
+ headers: { 'content-type': 'application/json' },
20
+ });
21
+ }
22
+ const WIDGET = {
23
+ id: '27de19f6-1a18-4074-a347-a14e737c3f47',
24
+ org_id: ORG,
25
+ name: 'app',
26
+ key: 'fw_0123456789abcdef',
27
+ routes: ['/app/**'],
28
+ theme: { accent: '#2563eb', position: 'bottom-left', label: 'Feedback' },
29
+ };
30
+ describe('feedback.listWidgets', () => {
31
+ beforeEach(() => {
32
+ fetchMock = vi.fn();
33
+ vi.stubGlobal('fetch', fetchMock);
34
+ });
35
+ afterEach(() => vi.unstubAllGlobals());
36
+ it('unwraps the { widgets, total } envelope the API actually returns', async () => {
37
+ fetchMock.mockResolvedValueOnce(ok({ widgets: [WIDGET, { ...WIDGET, name: 'marketing' }], total: 2 }));
38
+ const ws = await feedback.listWidgets(API_KEY, ORG);
39
+ expect(Array.isArray(ws)).toBe(true);
40
+ expect(ws).toHaveLength(2);
41
+ expect(ws[0].key).toBe(WIDGET.key);
42
+ });
43
+ it('carries theme through, so position and label can be read back', async () => {
44
+ // Without this the only way to see what --position and --label were set to
45
+ // was to fetch widget.js and read the JavaScript.
46
+ fetchMock.mockResolvedValueOnce(ok({ widgets: [WIDGET], total: 1 }));
47
+ const [w] = await feedback.listWidgets(API_KEY, ORG);
48
+ expect(w.theme?.position).toBe('bottom-left');
49
+ expect(w.theme?.label).toBe('Feedback');
50
+ });
51
+ it('returns [] rather than undefined when the org has none', async () => {
52
+ fetchMock.mockResolvedValueOnce(ok({ widgets: [], total: 0 }));
53
+ await expect(feedback.listWidgets(API_KEY, ORG)).resolves.toEqual([]);
54
+ });
55
+ it('tolerates a bare array, in case the endpoint is ever simplified', async () => {
56
+ fetchMock.mockResolvedValueOnce(ok([WIDGET]));
57
+ await expect(feedback.listWidgets(API_KEY, ORG)).resolves.toHaveLength(1);
58
+ });
59
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
3
  "license": "Apache-2.0",
4
- "version": "2.16.0",
4
+ "version": "2.16.1",
5
5
  "description": "MyAPI command-line interface",
6
6
  "repository": {
7
7
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  "lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
47
47
  },
48
48
  "dependencies": {
49
- "@myapihq/sdk": "^2.16.0"
49
+ "@myapihq/sdk": "^2.16.1"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^25.6.0",