@myapihq/cli 2.27.1 → 2.27.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.
package/dist/errors.js CHANGED
@@ -125,6 +125,28 @@ export function friendlyError(err) {
125
125
  out += '\n Current numbers: myapi account sending';
126
126
  return withOrgContext(out, err);
127
127
  }
128
+ // A failure on the platform's side, with no message of its own. The handler
129
+ // that raises it calls envelope.Error(w, r, 500, "internal_error") — a bare
130
+ // string, so there is no `message` to fall through to and the caller was left
131
+ // looking at the word `internal_error` alone: no cause, no next step, and
132
+ // nothing to quote at anybody.
133
+ //
134
+ // The one genuinely useful thing about a 500 is its request id, which the
135
+ // platform returns on every response and the SDK used to discard.
136
+ //
137
+ // Deliberately claims nothing about what did or did not happen. A 500 can
138
+ // land before or after the write, and inventing "nothing was changed" here
139
+ // would be the kind of reassuring guess this codebase keeps having to undo.
140
+ if (err.status >= 500 || err.code === 'internal_error') {
141
+ const lines = ['The platform failed on this request — this is our fault, not a bad command.'];
142
+ if (err.detail && err.detail !== err.code)
143
+ lines.push(` ${err.detail}`);
144
+ lines.push(err.requestId
145
+ ? ` Quote this when reporting it: ${err.requestId}`
146
+ : ' No request id came back, so quote the exact command and the time instead.');
147
+ lines.push(' Whether the change went through is not knowable from here — check before retrying.');
148
+ return withOrgContext(lines.join('\n'), err);
149
+ }
128
150
  const base = ERROR_MESSAGES[err.code] || err.code;
129
151
  // Generic fallback: append the backend's `message` when it adds info beyond
130
152
  // the friendly mapping. Future per-service detail fields can be added here.
@@ -68,3 +68,42 @@ describe('paused sending points at the CLI, not an HTTP route', () => {
68
68
  expect(msg).toContain('myapi account resume-sending');
69
69
  });
70
70
  });
71
+ describe('a 500 must be reportable', () => {
72
+ // `envelope.Error(w, r, 500, "internal_error")` sends a bare string: no
73
+ // message to fall through to. The caller used to see the word
74
+ // `internal_error` alone — no cause, no next step, nothing to quote. Hit it
75
+ // for real on 2026-08-23 running `funnel push` with an empty --funnel.
76
+ //
77
+ // The one useful thing about a 500 is the request id, which the platform
78
+ // returns on every response and the SDK discarded.
79
+ function fail(code, status, requestId, detail) {
80
+ const e = new MyApiError(code, status, detail);
81
+ if (requestId)
82
+ e.requestId = requestId;
83
+ return e;
84
+ }
85
+ it('quotes the request id so it can be reported and traced', () => {
86
+ const out = friendlyError(fail('internal_error', 500, 'req_1adc0b171e187b45'));
87
+ expect(out).toContain('req_1adc0b171e187b45');
88
+ expect(out).toMatch(/our fault/i);
89
+ });
90
+ it('says what to quote when no request id came back', () => {
91
+ const out = friendlyError(fail('internal_error', 500));
92
+ expect(out).toMatch(/exact command/i);
93
+ });
94
+ it('never claims the write did not happen', () => {
95
+ // A 500 can land either side of the write. Saying "nothing was changed"
96
+ // would be a reassuring guess, and this codebase keeps having to undo those.
97
+ const out = friendlyError(fail('internal_error', 500, 'req_x'));
98
+ expect(out).not.toMatch(/nothing was (changed|written|charged)/i);
99
+ expect(out).toMatch(/not knowable|check before retrying/i);
100
+ });
101
+ it('applies to any 5xx, not only the internal_error code', () => {
102
+ expect(friendlyError(fail('BAD_GATEWAY', 502, 'req_y'))).toContain('req_y');
103
+ });
104
+ it('leaves 4xx alone — those have their own guidance', () => {
105
+ const out = friendlyError(fail('INVALID_SLUG', 422, 'req_z', 'slug must look like /about'));
106
+ expect(out).toContain('slug must look like /about');
107
+ expect(out).not.toMatch(/our fault/i);
108
+ });
109
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/cli",
3
3
  "license": "Apache-2.0",
4
- "version": "2.27.1",
4
+ "version": "2.27.2",
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.27.1"
49
+ "@myapihq/sdk": "^2.27.2"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^25.6.0",