@displaydev/cli 0.6.0 → 0.7.0

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/README.md CHANGED
@@ -99,6 +99,38 @@ dsp mcp
99
99
 
100
100
  Visibility and share flags **default to "keep current"** on update: omitting `--public`/`--company` leaves visibility unchanged, and omitting `--share`/`--clear-shares` leaves the guest list unchanged. `--public`/`--company` are mutually exclusive; `--share`/`--clear-shares` are mutually exclusive.
101
101
 
102
+ ## Testing
103
+
104
+ The CLI suite uses three distinct fetch-faking strategies, each pinned to a
105
+ layer of the stack. New contributors pick the one that matches the layer
106
+ they're testing — adding a fourth is almost always wrong.
107
+
108
+ - **Unit-level fetch fake (`stubFetchJson(...)`)** — for exercising
109
+ `ApiClient` and anything that composes it purely in-process. Low ceremony,
110
+ full control over responses, no cross-process boundary. Use the
111
+ `stubFetchJson(body, init?)` helper from
112
+ [`cli/src/test-helpers.ts`](src/test-helpers.ts) — it stubs `global.fetch`
113
+ with a fresh JSON `Response` per call and returns the `vi.fn()` so tests
114
+ can still inspect `fetchMock.mock.calls[...]`. Fall back to raw
115
+ `vi.stubGlobal('fetch', vi.fn().mockRejectedValue(...))` only for network-
116
+ failure cases. Representative example:
117
+ [`cli/src/api-client.spec.ts`](src/api-client.spec.ts) and the integration
118
+ wire-up assertion in
119
+ [`cli/src/mcp-mode-selection.spec.ts`](src/mcp-mode-selection.spec.ts).
120
+ - **Hand-rolled `createMockApiClient()`** — for MCP tool specs that verify
121
+ argument-routing from an MCP tool handler to the right `ApiClient` method.
122
+ No HTTP in the loop at all; the fake is a plain object of `vi.fn()`s.
123
+ Representative example: [`cli/src/mcp-server.spec.ts`](src/mcp-server.spec.ts)
124
+ and [`cli/src/mcp-publish-file-read.spec.ts`](src/mcp-publish-file-read.spec.ts).
125
+ - **Real localhost HTTP fixture (`createHttpFixture` + `spawnCli`)** — for
126
+ `program.action` E2E specs that need to exercise commander argv parsing,
127
+ the stdout/stderr split, exit codes, and the full HTTP round-trip. Drives
128
+ the CLI as a real child process. Representative examples:
129
+ [`cli/src/cli-mode-selection.spec.ts`](src/cli-mode-selection.spec.ts),
130
+ [`cli/src/cli-subcommands.spec.ts`](src/cli-subcommands.spec.ts),
131
+ [`cli/src/login-otp.spec.ts`](src/login-otp.spec.ts),
132
+ [`cli/src/login-otp-send.spec.ts`](src/login-otp-send.spec.ts).
133
+
102
134
  ## MCP
103
135
 
104
136
  The CLI doubles as an MCP server over stdio. Add to your MCP client config:
@@ -1,7 +1,13 @@
1
1
  /**
2
2
  * HTTP client for the display.dev API.
3
3
  * Used by the stdio MCP server to proxy tool calls to the REST API.
4
- */ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
4
+ */ function _assert_this_initialized(self) {
5
+ if (self === void 0) {
6
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
7
+ }
8
+ return self;
9
+ }
10
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
5
11
  try {
6
12
  var info = gen[key](arg);
7
13
  var value = info.value;
@@ -30,11 +36,32 @@ function _async_to_generator(fn) {
30
36
  });
31
37
  };
32
38
  }
39
+ function _call_super(_this, derived, args) {
40
+ derived = _get_prototype_of(derived);
41
+ return _possible_constructor_return(_this, _is_native_reflect_construct() ? Reflect.construct(derived, args || [], _get_prototype_of(_this).constructor) : derived.apply(_this, args));
42
+ }
33
43
  function _class_call_check(instance, Constructor) {
34
44
  if (!(instance instanceof Constructor)) {
35
45
  throw new TypeError("Cannot call a class as a function");
36
46
  }
37
47
  }
48
+ function _construct(Parent, args, Class) {
49
+ if (_is_native_reflect_construct()) {
50
+ _construct = Reflect.construct;
51
+ } else {
52
+ _construct = function construct(Parent, args, Class) {
53
+ var a = [
54
+ null
55
+ ];
56
+ a.push.apply(a, args);
57
+ var Constructor = Function.bind.apply(Parent, a);
58
+ var instance = new Constructor();
59
+ if (Class) _set_prototype_of(instance, Class.prototype);
60
+ return instance;
61
+ };
62
+ }
63
+ return _construct.apply(null, arguments);
64
+ }
38
65
  function _defineProperties(target, props) {
39
66
  for(var i = 0; i < props.length; i++){
40
67
  var descriptor = props[i];
@@ -62,6 +89,25 @@ function _define_property(obj, key, value) {
62
89
  }
63
90
  return obj;
64
91
  }
92
+ function _get_prototype_of(o) {
93
+ _get_prototype_of = Object.setPrototypeOf ? Object.getPrototypeOf : function getPrototypeOf(o) {
94
+ return o.__proto__ || Object.getPrototypeOf(o);
95
+ };
96
+ return _get_prototype_of(o);
97
+ }
98
+ function _inherits(subClass, superClass) {
99
+ if (typeof superClass !== "function" && superClass !== null) {
100
+ throw new TypeError("Super expression must either be null or a function");
101
+ }
102
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
103
+ constructor: {
104
+ value: subClass,
105
+ writable: true,
106
+ configurable: true
107
+ }
108
+ });
109
+ if (superClass) _set_prototype_of(subClass, superClass);
110
+ }
65
111
  function _instanceof(left, right) {
66
112
  "@swc/helpers - instanceof";
67
113
  if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
@@ -70,6 +116,9 @@ function _instanceof(left, right) {
70
116
  return left instanceof right;
71
117
  }
72
118
  }
119
+ function _is_native_function(fn) {
120
+ return Function.toString.call(fn).indexOf("[native code]") !== -1;
121
+ }
73
122
  function _object_spread(target) {
74
123
  for(var i = 1; i < arguments.length; i++){
75
124
  var source = arguments[i] != null ? arguments[i] : {};
@@ -85,6 +134,57 @@ function _object_spread(target) {
85
134
  }
86
135
  return target;
87
136
  }
137
+ function _possible_constructor_return(self, call) {
138
+ if (call && (_type_of(call) === "object" || typeof call === "function")) {
139
+ return call;
140
+ }
141
+ return _assert_this_initialized(self);
142
+ }
143
+ function _set_prototype_of(o, p) {
144
+ _set_prototype_of = Object.setPrototypeOf || function setPrototypeOf(o, p) {
145
+ o.__proto__ = p;
146
+ return o;
147
+ };
148
+ return _set_prototype_of(o, p);
149
+ }
150
+ function _type_of(obj) {
151
+ "@swc/helpers - typeof";
152
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
153
+ }
154
+ function _wrap_native_super(Class) {
155
+ var _cache = typeof Map === "function" ? new Map() : undefined;
156
+ _wrap_native_super = function wrapNativeSuper(Class) {
157
+ if (Class === null || !_is_native_function(Class)) return Class;
158
+ if (typeof Class !== "function") {
159
+ throw new TypeError("Super expression must either be null or a function");
160
+ }
161
+ if (typeof _cache !== "undefined") {
162
+ if (_cache.has(Class)) return _cache.get(Class);
163
+ _cache.set(Class, Wrapper);
164
+ }
165
+ function Wrapper() {
166
+ return _construct(Class, arguments, _get_prototype_of(this).constructor);
167
+ }
168
+ Wrapper.prototype = Object.create(Class.prototype, {
169
+ constructor: {
170
+ value: Wrapper,
171
+ enumerable: false,
172
+ writable: true,
173
+ configurable: true
174
+ }
175
+ });
176
+ return _set_prototype_of(Wrapper, Class);
177
+ };
178
+ return _wrap_native_super(Class);
179
+ }
180
+ function _is_native_reflect_construct() {
181
+ try {
182
+ var result = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
183
+ } catch (_) {}
184
+ return (_is_native_reflect_construct = function() {
185
+ return !!result;
186
+ })();
187
+ }
88
188
  function _ts_generator(thisArg, body) {
89
189
  var f, y, t, _ = {
90
190
  label: 0,
@@ -184,6 +284,20 @@ function _ts_generator(thisArg, body) {
184
284
  };
185
285
  }
186
286
  }
287
+ export var ApiError = /*#__PURE__*/ function(Error1) {
288
+ "use strict";
289
+ _inherits(ApiError, Error1);
290
+ function ApiError(message, status) {
291
+ _class_call_check(this, ApiError);
292
+ var _this;
293
+ _this = _call_super(this, ApiError, [
294
+ message
295
+ ]), _define_property(_this, "status", void 0), _this.status = status;
296
+ _this.name = 'ApiError';
297
+ return _this;
298
+ }
299
+ return ApiError;
300
+ }(_wrap_native_super(Error));
187
301
  export var ApiClient = /*#__PURE__*/ function() {
188
302
  "use strict";
189
303
  function ApiClient(config) {
@@ -210,6 +324,36 @@ export var ApiClient = /*#__PURE__*/ function() {
210
324
  }).call(this);
211
325
  }
212
326
  },
327
+ {
328
+ key: "publishPublic",
329
+ value: /**
330
+ * Anonymous (no API key) publish. Returns a claimable preview URL plus
331
+ * the one-shot claim URL the user must click to own the artifact.
332
+ */ function publishPublic(params) {
333
+ return _async_to_generator(function() {
334
+ var ext, blob, form;
335
+ return _ts_generator(this, function(_state) {
336
+ ext = params.format === 'md' ? '.md' : '.html';
337
+ blob = new Blob([
338
+ params.content
339
+ ], {
340
+ type: 'application/octet-stream'
341
+ });
342
+ form = new FormData();
343
+ form.append('file', blob, "content".concat(ext));
344
+ if (params.name) {
345
+ form.append('name', params.name);
346
+ }
347
+ return [
348
+ 2,
349
+ this.doFetch('POST', '/v1/public/artifacts', form, {
350
+ 'X-Client-Type': this.clientType
351
+ })
352
+ ];
353
+ });
354
+ }).call(this);
355
+ }
356
+ },
213
357
  {
214
358
  key: "publishVersion",
215
359
  value: function publishVersion(shortId, params) {
@@ -223,6 +367,22 @@ export var ApiClient = /*#__PURE__*/ function() {
223
367
  }).call(this);
224
368
  }
225
369
  },
370
+ {
371
+ key: "share",
372
+ value: /**
373
+ * Delta-update an artifact's visibility or sharedWith list without
374
+ * re-publishing content (spec §5 share endpoint).
375
+ */ function share(shortId, params) {
376
+ return _async_to_generator(function() {
377
+ return _ts_generator(this, function(_state) {
378
+ return [
379
+ 2,
380
+ this.request('POST', "/v1/artifacts/".concat(shortId, "/share"), params)
381
+ ];
382
+ });
383
+ }).call(this);
384
+ }
385
+ },
226
386
  {
227
387
  key: "buildPublishForm",
228
388
  value: function buildPublishForm(params) {
@@ -248,7 +408,7 @@ export var ApiClient = /*#__PURE__*/ function() {
248
408
  try {
249
409
  for(var _iterator = params.share[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
250
410
  var email = _step.value;
251
- form.append('guestEmails', email);
411
+ form.append('sharedWith', email);
252
412
  }
253
413
  } catch (err) {
254
414
  _didIteratorError = true;
@@ -266,7 +426,7 @@ export var ApiClient = /*#__PURE__*/ function() {
266
426
  }
267
427
  }
268
428
  if (params.clearShares) {
269
- form.append('clearGuestEmails', 'true');
429
+ form.append('clearSharedWith', 'true');
270
430
  }
271
431
  if (params.showBranding) {
272
432
  form.append('showBranding', params.showBranding);
@@ -378,6 +538,70 @@ export var ApiClient = /*#__PURE__*/ function() {
378
538
  }).call(this);
379
539
  }
380
540
  },
541
+ {
542
+ key: "exportSource",
543
+ value: /**
544
+ * Fetch the raw source bytes of an artifact (the markdown for `.md`,
545
+ * the served HTML for `.html`). Returns the body plus the response's
546
+ * Content-Type so callers can disambiguate. Throws `ApiError` on
547
+ * non-2xx (the CLI maps 404 to a clean message + exit 4).
548
+ */ function exportSource(shortId, version) {
549
+ return _async_to_generator(function() {
550
+ var _res_headers_get, qs, url, res, _parsed_message, text, parsed, arrayBuffer;
551
+ return _ts_generator(this, function(_state) {
552
+ switch(_state.label){
553
+ case 0:
554
+ qs = version !== undefined ? "?v=".concat(version) : '';
555
+ url = "".concat(this.baseUrl, "/v1/artifacts/").concat(shortId, "/source").concat(qs);
556
+ return [
557
+ 4,
558
+ fetch(url, {
559
+ method: 'GET',
560
+ headers: {
561
+ 'Authorization': "Bearer ".concat(this.apiKey),
562
+ 'X-Client-Type': this.clientType
563
+ }
564
+ })
565
+ ];
566
+ case 1:
567
+ res = _state.sent();
568
+ if (!!res.ok) return [
569
+ 3,
570
+ 3
571
+ ];
572
+ return [
573
+ 4,
574
+ res.text()
575
+ ];
576
+ case 2:
577
+ text = _state.sent();
578
+ try {
579
+ parsed = JSON.parse(text);
580
+ } catch (unused) {
581
+ parsed = {
582
+ message: text
583
+ };
584
+ }
585
+ throw new ApiError((_parsed_message = parsed.message) !== null && _parsed_message !== void 0 ? _parsed_message : "API error ".concat(res.status), res.status);
586
+ case 3:
587
+ return [
588
+ 4,
589
+ res.arrayBuffer()
590
+ ];
591
+ case 4:
592
+ arrayBuffer = _state.sent();
593
+ return [
594
+ 2,
595
+ {
596
+ data: Buffer.from(arrayBuffer),
597
+ contentType: (_res_headers_get = res.headers.get('content-type')) !== null && _res_headers_get !== void 0 ? _res_headers_get : 'application/octet-stream'
598
+ }
599
+ ];
600
+ }
601
+ });
602
+ }).call(this);
603
+ }
604
+ },
381
605
  {
382
606
  key: "delete",
383
607
  value: function _delete(shortId) {
@@ -634,7 +858,7 @@ export var ApiClient = /*#__PURE__*/ function() {
634
858
  message: text
635
859
  };
636
860
  }
637
- throw new Error((_parsed_message = parsed.message) !== null && _parsed_message !== void 0 ? _parsed_message : "API error ".concat(res.status));
861
+ throw new ApiError((_parsed_message = parsed.message) !== null && _parsed_message !== void 0 ? _parsed_message : "API error ".concat(res.status), res.status);
638
862
  case 3:
639
863
  return [
640
864
  2,
package/dist/config.js ADDED
@@ -0,0 +1,220 @@
1
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
2
+ try {
3
+ var info = gen[key](arg);
4
+ var value = info.value;
5
+ } catch (error) {
6
+ reject(error);
7
+ return;
8
+ }
9
+ if (info.done) {
10
+ resolve(value);
11
+ } else {
12
+ Promise.resolve(value).then(_next, _throw);
13
+ }
14
+ }
15
+ function _async_to_generator(fn) {
16
+ return function() {
17
+ var self = this, args = arguments;
18
+ return new Promise(function(resolve, reject) {
19
+ var gen = fn.apply(self, args);
20
+ function _next(value) {
21
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
22
+ }
23
+ function _throw(err) {
24
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
25
+ }
26
+ _next(undefined);
27
+ });
28
+ };
29
+ }
30
+ function _ts_generator(thisArg, body) {
31
+ var f, y, t, _ = {
32
+ label: 0,
33
+ sent: function() {
34
+ if (t[0] & 1) throw t[1];
35
+ return t[1];
36
+ },
37
+ trys: [],
38
+ ops: []
39
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
40
+ return d(g, "next", {
41
+ value: verb(0)
42
+ }), d(g, "throw", {
43
+ value: verb(1)
44
+ }), d(g, "return", {
45
+ value: verb(2)
46
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
47
+ value: function() {
48
+ return this;
49
+ }
50
+ }), g;
51
+ function verb(n) {
52
+ return function(v) {
53
+ return step([
54
+ n,
55
+ v
56
+ ]);
57
+ };
58
+ }
59
+ function step(op) {
60
+ if (f) throw new TypeError("Generator is already executing.");
61
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
62
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
63
+ if (y = 0, t) op = [
64
+ op[0] & 2,
65
+ t.value
66
+ ];
67
+ switch(op[0]){
68
+ case 0:
69
+ case 1:
70
+ t = op;
71
+ break;
72
+ case 4:
73
+ _.label++;
74
+ return {
75
+ value: op[1],
76
+ done: false
77
+ };
78
+ case 5:
79
+ _.label++;
80
+ y = op[1];
81
+ op = [
82
+ 0
83
+ ];
84
+ continue;
85
+ case 7:
86
+ op = _.ops.pop();
87
+ _.trys.pop();
88
+ continue;
89
+ default:
90
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
91
+ _ = 0;
92
+ continue;
93
+ }
94
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
95
+ _.label = op[1];
96
+ break;
97
+ }
98
+ if (op[0] === 6 && _.label < t[1]) {
99
+ _.label = t[1];
100
+ t = op;
101
+ break;
102
+ }
103
+ if (t && _.label < t[2]) {
104
+ _.label = t[2];
105
+ _.ops.push(op);
106
+ break;
107
+ }
108
+ if (t[2]) _.ops.pop();
109
+ _.trys.pop();
110
+ continue;
111
+ }
112
+ op = body.call(thisArg, _);
113
+ } catch (e) {
114
+ op = [
115
+ 6,
116
+ e
117
+ ];
118
+ y = 0;
119
+ } finally{
120
+ f = t = 0;
121
+ }
122
+ if (op[0] & 5) throw op[1];
123
+ return {
124
+ value: op[0] ? op[1] : void 0,
125
+ done: true
126
+ };
127
+ }
128
+ }
129
+ import { readFile, mkdir, writeFile, rename, chmod } from 'node:fs/promises';
130
+ import { homedir } from 'node:os';
131
+ import { dirname, join } from 'node:path';
132
+ export function defaultConfigIo() {
133
+ return {
134
+ path: join(homedir(), '.displaydev', 'config.json')
135
+ };
136
+ }
137
+ export function loadConfig() {
138
+ return _async_to_generator(function() {
139
+ var io, raw, unused;
140
+ var _arguments = arguments;
141
+ return _ts_generator(this, function(_state) {
142
+ switch(_state.label){
143
+ case 0:
144
+ io = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : defaultConfigIo();
145
+ _state.label = 1;
146
+ case 1:
147
+ _state.trys.push([
148
+ 1,
149
+ 3,
150
+ ,
151
+ 4
152
+ ]);
153
+ return [
154
+ 4,
155
+ readFile(io.path, 'utf-8')
156
+ ];
157
+ case 2:
158
+ raw = _state.sent();
159
+ return [
160
+ 2,
161
+ JSON.parse(raw)
162
+ ];
163
+ case 3:
164
+ unused = _state.sent();
165
+ return [
166
+ 2,
167
+ null
168
+ ];
169
+ case 4:
170
+ return [
171
+ 2
172
+ ];
173
+ }
174
+ });
175
+ }).apply(this, arguments);
176
+ }
177
+ export function saveConfig(_0) {
178
+ return _async_to_generator(function(config) {
179
+ var io, tmpPath;
180
+ var _arguments = arguments;
181
+ return _ts_generator(this, function(_state) {
182
+ switch(_state.label){
183
+ case 0:
184
+ io = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : defaultConfigIo();
185
+ return [
186
+ 4,
187
+ mkdir(dirname(io.path), {
188
+ recursive: true
189
+ })
190
+ ];
191
+ case 1:
192
+ _state.sent();
193
+ tmpPath = "".concat(io.path, ".tmp");
194
+ return [
195
+ 4,
196
+ writeFile(tmpPath, "".concat(JSON.stringify(config, null, 2), "\n"), {
197
+ mode: 384
198
+ })
199
+ ];
200
+ case 2:
201
+ _state.sent();
202
+ return [
203
+ 4,
204
+ rename(tmpPath, io.path)
205
+ ];
206
+ case 3:
207
+ _state.sent();
208
+ return [
209
+ 4,
210
+ chmod(io.path, 384)
211
+ ];
212
+ case 4:
213
+ _state.sent();
214
+ return [
215
+ 2
216
+ ];
217
+ }
218
+ });
219
+ }).apply(this, arguments);
220
+ }