@swell/cli 0.1.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 ADDED
@@ -0,0 +1,160 @@
1
+ # @swell/cli
2
+
3
+ We're in beta: commands and arguments can still change.
4
+
5
+ # Commands
6
+
7
+ ## General options
8
+
9
+ Commands tend to support the following helper options:
10
+
11
+ - --quiet (-q): Boolean, quiets the progress indicator and output of intermediary commands
12
+ - --verbose (-v): Boolean, outputs all commands
13
+ - --dry-run: Boolean, dry runs requests that delete data, but doesn't run them
14
+ - --yes (-y): Boolean, answers all confirmation questions with **yes**.
15
+ - --secret-key: String, Enables ephemeral secret-key based authentication for commands
16
+
17
+ ## Store namespaces
18
+
19
+ All commands accept a `--store` or `-s` option, which allows switching the context of the CLI commands to the selected store. When not in use, all commands refer to the set default store.
20
+
21
+ ## Login
22
+
23
+ The `login` command authenticates and stores a user's session locally. Saved sessions can/will be used by other commands, unless a secret key is specified.
24
+
25
+ ```sh
26
+ swell login [--username (-u) String] [--password (-p) String] [--set-default Boolean]
27
+ ```
28
+
29
+ ### Examples
30
+
31
+ ```sh
32
+ swell login
33
+ swell login --store test
34
+ swell login --store test-default --set-default
35
+ # local login
36
+ ADMIN_API_BASE_URL='http://${STORE_ID}.swell.test:4001/admin/api' swell login
37
+ ```
38
+
39
+ ## Storefront
40
+
41
+ ### Settings
42
+
43
+ Push or pull storefront settings. Scopes include `settings`, `editor` and `menus`.
44
+
45
+ ```sh
46
+ # pull => retrieves settings from the server and tries to merge and/or write to a specified JSON file or stdout
47
+ swell settings pull SCOPE [--file (-f) String] [--output (-o) Boolean] [--strategy String]
48
+
49
+ # push => send settings from a JSON file or stdin to the server
50
+ swell settings push SCOPE [--file (-f) String] [--input (-i) Boolean] [--strategy String]
51
+ ```
52
+
53
+ ### Examples
54
+
55
+ ```sh
56
+ swell storefront settings pull settings -f settings/store.json
57
+ swell storefront settings pull settings -f settings/store.json --strategy ours # keeps our version of the file in case of conflicts
58
+
59
+ swell storefront settings push editor -f settings/editor.json
60
+ swell storefront settings push editor -f settings/editor.json --strategy "set" # overwrites the settings object instead of merging
61
+
62
+ cat settings/menus.json | swell storefront settings push menus -i --strategy "defaults" # merges remote settings on top of the settings being pushed, as a default mechanism
63
+ ```
64
+
65
+ ### SSH Keys
66
+
67
+ Manage a user's SSH keys to a storefront. Needs session authentication (`swell login`).
68
+
69
+ ```sh
70
+ swell storefront ssh-keys list
71
+ swell storefront ssh-keys add [--name String]
72
+ swell storefront ssh-keys delete KEY_ID [--dry-run Boolean]
73
+ ```
74
+
75
+ ### Examples
76
+
77
+ ```sh
78
+ swell storefront ssh-keys list
79
+ swell storefront ssh-keys add --name deploy-key
80
+ swell storefront ssh-keys delete abcdef1234567890
81
+ ```
82
+
83
+ ## Model
84
+
85
+ Push or pull model definitions, including namespaced models (e.g. content models), abstract content models and content fields.
86
+
87
+ ```sh
88
+ # pull => retrieves a model from the server and tries to merge and/or write to a specified JSON file or stdout
89
+ # --content flag toggles between base models and abstract content models or content fields
90
+ swell model pull MODEL_ID [--file (-f) String] [--output (-o) Boolean] [--strategy String] [--content Boolean]
91
+
92
+ # push => send a model definition from a JSON file or stdin to the server
93
+ swell model push [--file (-f) String] [--input (-i) Boolean] [--content Boolean]
94
+ ```
95
+
96
+ It is important that the model definition includes some fields depending on the type/source of the model.
97
+
98
+ #### Basic model
99
+
100
+ ```json
101
+ {
102
+ "name": "Test", // slug will be assumed from this field
103
+ "slug": "test_model", // or you can define one explicitly
104
+ "fields": { ... }
105
+ }
106
+ ```
107
+
108
+ #### Namespaced model
109
+
110
+ ```json
111
+ {
112
+ "name": "Page", // slug will be assumed from this field
113
+ "namespace": "content",
114
+ "fields": { ... }
115
+ }
116
+ ```
117
+
118
+ #### Theme content (abstract) model/fields (use with --content flag)
119
+
120
+ ```json
121
+ {
122
+ "name": "Test", // slug will be assumed from this field
123
+ "fields": { ... },
124
+ "source_type": "theme",
125
+ "source_id": "origin"
126
+ }
127
+ ```
128
+
129
+ ### Examples
130
+
131
+ ```sh
132
+ swell model pull products -f models/products.json
133
+ swell model pull products -f models/products.json --strategy ours # keeps our version of the file in case of conflicts
134
+ swell model pull theme.origin.page --content --f models/page.json # pull a content model
135
+
136
+ swell model push -f models/orders.json
137
+ swell model push --content --f models/page.json # push a content model
138
+ ```
139
+
140
+ ## Settings
141
+
142
+ Push or pull store settings (e.g. payments, shipping, etc...)
143
+
144
+ ```sh
145
+ # pull => retrieves settings from the server and tries to merge and/or write to a specified JSON file or stdout
146
+ swell settings pull SCOPE [--file (-f) String] [--output (-o) Boolean] [--strategy String]
147
+
148
+ # push => send settings from a JSON file or stdin to the server
149
+ swell settings push SCOPE [--file (-f) String] [--input (-i) Boolean] [--strategy String]
150
+ ```
151
+
152
+ ### Examples
153
+
154
+ ```sh
155
+ swell settings pull payments -f settings/payments.json
156
+ swell settings pull payments -f settings/payments.json --strategy ours # keeps our version of the file in case of conflicts
157
+
158
+ swell settings push payments -f settings/payments.json
159
+ swell settings push payments -f settings/payments.json --strategy "set" # overwrites the settings object instead of merging
160
+ ```
@@ -0,0 +1,458 @@
1
+ #!/usr/bin/env node
2
+
3
+ import 'fs';
4
+ import 'path';
5
+ import { F as FormData, a as File } from './swell-b7920988.js';
6
+ import 'assert';
7
+ import 'child_process';
8
+ import 'async_hooks';
9
+ import 'util';
10
+ import 'process';
11
+ import 'os';
12
+ import 'tty';
13
+ import 'stream';
14
+ import 'string_decoder';
15
+ import 'buffer';
16
+ import 'events';
17
+ import 'url';
18
+ import 'http';
19
+ import 'https';
20
+ import 'zlib';
21
+ import 'stream/web';
22
+ import 'net';
23
+ import 'worker_threads';
24
+ import 'readline';
25
+ import 'constants';
26
+ import 'crypto';
27
+
28
+ let s = 0;
29
+ const S = {
30
+ START_BOUNDARY: s++,
31
+ HEADER_FIELD_START: s++,
32
+ HEADER_FIELD: s++,
33
+ HEADER_VALUE_START: s++,
34
+ HEADER_VALUE: s++,
35
+ HEADER_VALUE_ALMOST_DONE: s++,
36
+ HEADERS_ALMOST_DONE: s++,
37
+ PART_DATA_START: s++,
38
+ PART_DATA: s++,
39
+ END: s++
40
+ };
41
+
42
+ let f = 1;
43
+ const F = {
44
+ PART_BOUNDARY: f,
45
+ LAST_BOUNDARY: f *= 2
46
+ };
47
+
48
+ const LF = 10;
49
+ const CR = 13;
50
+ const SPACE = 32;
51
+ const HYPHEN = 45;
52
+ const COLON = 58;
53
+ const A = 97;
54
+ const Z = 122;
55
+
56
+ const lower = c => c | 0x20;
57
+
58
+ const noop = () => {};
59
+
60
+ class MultipartParser {
61
+ /**
62
+ * @param {string} boundary
63
+ */
64
+ constructor(boundary) {
65
+ this.index = 0;
66
+ this.flags = 0;
67
+
68
+ this.onHeaderEnd = noop;
69
+ this.onHeaderField = noop;
70
+ this.onHeadersEnd = noop;
71
+ this.onHeaderValue = noop;
72
+ this.onPartBegin = noop;
73
+ this.onPartData = noop;
74
+ this.onPartEnd = noop;
75
+
76
+ this.boundaryChars = {};
77
+
78
+ boundary = '\r\n--' + boundary;
79
+ const ui8a = new Uint8Array(boundary.length);
80
+ for (let i = 0; i < boundary.length; i++) {
81
+ ui8a[i] = boundary.charCodeAt(i);
82
+ this.boundaryChars[ui8a[i]] = true;
83
+ }
84
+
85
+ this.boundary = ui8a;
86
+ this.lookbehind = new Uint8Array(this.boundary.length + 8);
87
+ this.state = S.START_BOUNDARY;
88
+ }
89
+
90
+ /**
91
+ * @param {Uint8Array} data
92
+ */
93
+ write(data) {
94
+ let i = 0;
95
+ const length_ = data.length;
96
+ let previousIndex = this.index;
97
+ let {lookbehind, boundary, boundaryChars, index, state, flags} = this;
98
+ const boundaryLength = this.boundary.length;
99
+ const boundaryEnd = boundaryLength - 1;
100
+ const bufferLength = data.length;
101
+ let c;
102
+ let cl;
103
+
104
+ const mark = name => {
105
+ this[name + 'Mark'] = i;
106
+ };
107
+
108
+ const clear = name => {
109
+ delete this[name + 'Mark'];
110
+ };
111
+
112
+ const callback = (callbackSymbol, start, end, ui8a) => {
113
+ if (start === undefined || start !== end) {
114
+ this[callbackSymbol](ui8a && ui8a.subarray(start, end));
115
+ }
116
+ };
117
+
118
+ const dataCallback = (name, clear) => {
119
+ const markSymbol = name + 'Mark';
120
+ if (!(markSymbol in this)) {
121
+ return;
122
+ }
123
+
124
+ if (clear) {
125
+ callback(name, this[markSymbol], i, data);
126
+ delete this[markSymbol];
127
+ } else {
128
+ callback(name, this[markSymbol], data.length, data);
129
+ this[markSymbol] = 0;
130
+ }
131
+ };
132
+
133
+ for (i = 0; i < length_; i++) {
134
+ c = data[i];
135
+
136
+ switch (state) {
137
+ case S.START_BOUNDARY:
138
+ if (index === boundary.length - 2) {
139
+ if (c === HYPHEN) {
140
+ flags |= F.LAST_BOUNDARY;
141
+ } else if (c !== CR) {
142
+ return;
143
+ }
144
+
145
+ index++;
146
+ break;
147
+ } else if (index - 1 === boundary.length - 2) {
148
+ if (flags & F.LAST_BOUNDARY && c === HYPHEN) {
149
+ state = S.END;
150
+ flags = 0;
151
+ } else if (!(flags & F.LAST_BOUNDARY) && c === LF) {
152
+ index = 0;
153
+ callback('onPartBegin');
154
+ state = S.HEADER_FIELD_START;
155
+ } else {
156
+ return;
157
+ }
158
+
159
+ break;
160
+ }
161
+
162
+ if (c !== boundary[index + 2]) {
163
+ index = -2;
164
+ }
165
+
166
+ if (c === boundary[index + 2]) {
167
+ index++;
168
+ }
169
+
170
+ break;
171
+ case S.HEADER_FIELD_START:
172
+ state = S.HEADER_FIELD;
173
+ mark('onHeaderField');
174
+ index = 0;
175
+ // falls through
176
+ case S.HEADER_FIELD:
177
+ if (c === CR) {
178
+ clear('onHeaderField');
179
+ state = S.HEADERS_ALMOST_DONE;
180
+ break;
181
+ }
182
+
183
+ index++;
184
+ if (c === HYPHEN) {
185
+ break;
186
+ }
187
+
188
+ if (c === COLON) {
189
+ if (index === 1) {
190
+ // empty header field
191
+ return;
192
+ }
193
+
194
+ dataCallback('onHeaderField', true);
195
+ state = S.HEADER_VALUE_START;
196
+ break;
197
+ }
198
+
199
+ cl = lower(c);
200
+ if (cl < A || cl > Z) {
201
+ return;
202
+ }
203
+
204
+ break;
205
+ case S.HEADER_VALUE_START:
206
+ if (c === SPACE) {
207
+ break;
208
+ }
209
+
210
+ mark('onHeaderValue');
211
+ state = S.HEADER_VALUE;
212
+ // falls through
213
+ case S.HEADER_VALUE:
214
+ if (c === CR) {
215
+ dataCallback('onHeaderValue', true);
216
+ callback('onHeaderEnd');
217
+ state = S.HEADER_VALUE_ALMOST_DONE;
218
+ }
219
+
220
+ break;
221
+ case S.HEADER_VALUE_ALMOST_DONE:
222
+ if (c !== LF) {
223
+ return;
224
+ }
225
+
226
+ state = S.HEADER_FIELD_START;
227
+ break;
228
+ case S.HEADERS_ALMOST_DONE:
229
+ if (c !== LF) {
230
+ return;
231
+ }
232
+
233
+ callback('onHeadersEnd');
234
+ state = S.PART_DATA_START;
235
+ break;
236
+ case S.PART_DATA_START:
237
+ state = S.PART_DATA;
238
+ mark('onPartData');
239
+ // falls through
240
+ case S.PART_DATA:
241
+ previousIndex = index;
242
+
243
+ if (index === 0) {
244
+ // boyer-moore derrived algorithm to safely skip non-boundary data
245
+ i += boundaryEnd;
246
+ while (i < bufferLength && !(data[i] in boundaryChars)) {
247
+ i += boundaryLength;
248
+ }
249
+
250
+ i -= boundaryEnd;
251
+ c = data[i];
252
+ }
253
+
254
+ if (index < boundary.length) {
255
+ if (boundary[index] === c) {
256
+ if (index === 0) {
257
+ dataCallback('onPartData', true);
258
+ }
259
+
260
+ index++;
261
+ } else {
262
+ index = 0;
263
+ }
264
+ } else if (index === boundary.length) {
265
+ index++;
266
+ if (c === CR) {
267
+ // CR = part boundary
268
+ flags |= F.PART_BOUNDARY;
269
+ } else if (c === HYPHEN) {
270
+ // HYPHEN = end boundary
271
+ flags |= F.LAST_BOUNDARY;
272
+ } else {
273
+ index = 0;
274
+ }
275
+ } else if (index - 1 === boundary.length) {
276
+ if (flags & F.PART_BOUNDARY) {
277
+ index = 0;
278
+ if (c === LF) {
279
+ // unset the PART_BOUNDARY flag
280
+ flags &= ~F.PART_BOUNDARY;
281
+ callback('onPartEnd');
282
+ callback('onPartBegin');
283
+ state = S.HEADER_FIELD_START;
284
+ break;
285
+ }
286
+ } else if (flags & F.LAST_BOUNDARY) {
287
+ if (c === HYPHEN) {
288
+ callback('onPartEnd');
289
+ state = S.END;
290
+ flags = 0;
291
+ } else {
292
+ index = 0;
293
+ }
294
+ } else {
295
+ index = 0;
296
+ }
297
+ }
298
+
299
+ if (index > 0) {
300
+ // when matching a possible boundary, keep a lookbehind reference
301
+ // in case it turns out to be a false lead
302
+ lookbehind[index - 1] = c;
303
+ } else if (previousIndex > 0) {
304
+ // if our boundary turned out to be rubbish, the captured lookbehind
305
+ // belongs to partData
306
+ const _lookbehind = new Uint8Array(lookbehind.buffer, lookbehind.byteOffset, lookbehind.byteLength);
307
+ callback('onPartData', 0, previousIndex, _lookbehind);
308
+ previousIndex = 0;
309
+ mark('onPartData');
310
+
311
+ // reconsider the current character even so it interrupted the sequence
312
+ // it could be the beginning of a new sequence
313
+ i--;
314
+ }
315
+
316
+ break;
317
+ case S.END:
318
+ break;
319
+ default:
320
+ throw new Error(`Unexpected state entered: ${state}`);
321
+ }
322
+ }
323
+
324
+ dataCallback('onHeaderField');
325
+ dataCallback('onHeaderValue');
326
+ dataCallback('onPartData');
327
+
328
+ // Update properties for the next call
329
+ this.index = index;
330
+ this.state = state;
331
+ this.flags = flags;
332
+ }
333
+
334
+ end() {
335
+ if ((this.state === S.HEADER_FIELD_START && this.index === 0) ||
336
+ (this.state === S.PART_DATA && this.index === this.boundary.length)) {
337
+ this.onPartEnd();
338
+ } else if (this.state !== S.END) {
339
+ throw new Error('MultipartParser.end(): stream ended unexpectedly');
340
+ }
341
+ }
342
+ }
343
+
344
+ function _fileName(headerValue) {
345
+ // matches either a quoted-string or a token (RFC 2616 section 19.5.1)
346
+ const m = headerValue.match(/\bfilename=("(.*?)"|([^()<>@,;:\\"/[\]?={}\s\t]+))($|;\s)/i);
347
+ if (!m) {
348
+ return;
349
+ }
350
+
351
+ const match = m[2] || m[3] || '';
352
+ let filename = match.slice(match.lastIndexOf('\\') + 1);
353
+ filename = filename.replace(/%22/g, '"');
354
+ filename = filename.replace(/&#(\d{4});/g, (m, code) => {
355
+ return String.fromCharCode(code);
356
+ });
357
+ return filename;
358
+ }
359
+
360
+ async function toFormData(Body, ct) {
361
+ if (!/multipart/i.test(ct)) {
362
+ throw new TypeError('Failed to fetch');
363
+ }
364
+
365
+ const m = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
366
+
367
+ if (!m) {
368
+ throw new TypeError('no or bad content-type header, no multipart boundary');
369
+ }
370
+
371
+ const parser = new MultipartParser(m[1] || m[2]);
372
+
373
+ let headerField;
374
+ let headerValue;
375
+ let entryValue;
376
+ let entryName;
377
+ let contentType;
378
+ let filename;
379
+ const entryChunks = [];
380
+ const formData = new FormData();
381
+
382
+ const onPartData = ui8a => {
383
+ entryValue += decoder.decode(ui8a, {stream: true});
384
+ };
385
+
386
+ const appendToFile = ui8a => {
387
+ entryChunks.push(ui8a);
388
+ };
389
+
390
+ const appendFileToFormData = () => {
391
+ const file = new File(entryChunks, filename, {type: contentType});
392
+ formData.append(entryName, file);
393
+ };
394
+
395
+ const appendEntryToFormData = () => {
396
+ formData.append(entryName, entryValue);
397
+ };
398
+
399
+ const decoder = new TextDecoder('utf-8');
400
+ decoder.decode();
401
+
402
+ parser.onPartBegin = function () {
403
+ parser.onPartData = onPartData;
404
+ parser.onPartEnd = appendEntryToFormData;
405
+
406
+ headerField = '';
407
+ headerValue = '';
408
+ entryValue = '';
409
+ entryName = '';
410
+ contentType = '';
411
+ filename = null;
412
+ entryChunks.length = 0;
413
+ };
414
+
415
+ parser.onHeaderField = function (ui8a) {
416
+ headerField += decoder.decode(ui8a, {stream: true});
417
+ };
418
+
419
+ parser.onHeaderValue = function (ui8a) {
420
+ headerValue += decoder.decode(ui8a, {stream: true});
421
+ };
422
+
423
+ parser.onHeaderEnd = function () {
424
+ headerValue += decoder.decode();
425
+ headerField = headerField.toLowerCase();
426
+
427
+ if (headerField === 'content-disposition') {
428
+ // matches either a quoted-string or a token (RFC 2616 section 19.5.1)
429
+ const m = headerValue.match(/\bname=("([^"]*)"|([^()<>@,;:\\"/[\]?={}\s\t]+))/i);
430
+
431
+ if (m) {
432
+ entryName = m[2] || m[3] || '';
433
+ }
434
+
435
+ filename = _fileName(headerValue);
436
+
437
+ if (filename) {
438
+ parser.onPartData = appendToFile;
439
+ parser.onPartEnd = appendFileToFormData;
440
+ }
441
+ } else if (headerField === 'content-type') {
442
+ contentType = headerValue;
443
+ }
444
+
445
+ headerValue = '';
446
+ headerField = '';
447
+ };
448
+
449
+ for await (const chunk of Body) {
450
+ parser.write(chunk);
451
+ }
452
+
453
+ parser.end();
454
+
455
+ return formData;
456
+ }
457
+
458
+ export { toFormData };