sharetribe-cli 1.15.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/.eslintrc.json +29 -0
- package/.prettierrc +9 -0
- package/build.js +58 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +7 -0
- package/package.json +58 -0
- package/src/commands/assets/index.ts +338 -0
- package/src/commands/events/index.ts +289 -0
- package/src/commands/help.ts +19 -0
- package/src/commands/listing-approval.ts +121 -0
- package/src/commands/login.ts +43 -0
- package/src/commands/logout.ts +17 -0
- package/src/commands/notifications/index.ts +221 -0
- package/src/commands/process/aliases.ts +82 -0
- package/src/commands/process/combined.ts +62 -0
- package/src/commands/process/create.ts +35 -0
- package/src/commands/process/index.ts +309 -0
- package/src/commands/process/list.ts +75 -0
- package/src/commands/process/pull.ts +81 -0
- package/src/commands/process/push.ts +67 -0
- package/src/commands/search/index.ts +254 -0
- package/src/commands/stripe/index.ts +114 -0
- package/src/commands/version.ts +40 -0
- package/src/index.ts +131 -0
- package/src/types/index.ts +21 -0
- package/src/util/command-router.ts +41 -0
- package/src/util/help-formatter.ts +266 -0
- package/src/util/output.ts +83 -0
- package/test/help-comparison.test.ts +255 -0
- package/test/process-builder.test.ts +14 -0
- package/test/process-integration.test.ts +189 -0
- package/test/strict-comparison.test.ts +722 -0
- package/tsconfig.json +50 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict byte-by-byte comparison tests
|
|
3
|
+
*
|
|
4
|
+
* These tests verify EXACT output matching with zero tolerance for differences
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const MARKETPLACE = 'expertapplication-dev';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Executes a CLI command and returns output (stdout + stderr combined)
|
|
14
|
+
*/
|
|
15
|
+
function runCli(command: string, cli: 'flex' | 'sharetribe'): string {
|
|
16
|
+
const cliName = cli === 'flex' ? 'flex-cli' : 'sharetribe-cli';
|
|
17
|
+
try {
|
|
18
|
+
return execSync(`${cliName} ${command}`, {
|
|
19
|
+
encoding: 'utf-8',
|
|
20
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error instanceof Error && 'stdout' in error && 'stderr' in error) {
|
|
24
|
+
const stdout = (error as any).stdout || '';
|
|
25
|
+
const stderr = (error as any).stderr || '';
|
|
26
|
+
return stdout + stderr;
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalizes dynamic data for comparison
|
|
34
|
+
*/
|
|
35
|
+
function normalizeOutput(output: string, type: 'table' | 'json' | 'text'): string {
|
|
36
|
+
if (type === 'json') {
|
|
37
|
+
// Parse and re-stringify to normalize formatting
|
|
38
|
+
const lines = output.trim().split('\n');
|
|
39
|
+
return lines.map(line => {
|
|
40
|
+
try {
|
|
41
|
+
const obj = JSON.parse(line);
|
|
42
|
+
// Remove dynamic fields
|
|
43
|
+
delete obj.createdAt;
|
|
44
|
+
delete obj.sequenceId;
|
|
45
|
+
delete obj.id;
|
|
46
|
+
delete obj.marketplaceId;
|
|
47
|
+
return JSON.stringify(obj);
|
|
48
|
+
} catch {
|
|
49
|
+
return line;
|
|
50
|
+
}
|
|
51
|
+
}).join('\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (type === 'table') {
|
|
55
|
+
// For tables, we verify structure but accept dynamic data
|
|
56
|
+
return output;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return output;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('Strict Byte-by-Byte Comparison Tests', () => {
|
|
63
|
+
describe('version command', () => {
|
|
64
|
+
it('matches flex-cli version output exactly', () => {
|
|
65
|
+
const flexOutput = runCli('version', 'flex').trim();
|
|
66
|
+
const shareOutput = runCli('version', 'sharetribe').trim();
|
|
67
|
+
expect(shareOutput).toBe(flexOutput);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('error messages', () => {
|
|
72
|
+
it('events without marketplace - exact match', () => {
|
|
73
|
+
const flexOutput = runCli('events 2>&1', 'flex');
|
|
74
|
+
const shareOutput = runCli('events 2>&1', 'sharetribe');
|
|
75
|
+
|
|
76
|
+
// Both should output the same error message
|
|
77
|
+
expect(shareOutput).toContain('Could not parse arguments:');
|
|
78
|
+
expect(shareOutput).toContain('--marketplace is required');
|
|
79
|
+
|
|
80
|
+
// Check exact format
|
|
81
|
+
const flexLines = flexOutput.trim().split('\n');
|
|
82
|
+
const shareLines = shareOutput.trim().split('\n');
|
|
83
|
+
expect(shareLines).toEqual(flexLines);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('table output format', () => {
|
|
88
|
+
it('process list --process has exact column spacing', () => {
|
|
89
|
+
const flexOutput = runCli(`process list --marketplace ${MARKETPLACE} --process=default-purchase`, 'flex');
|
|
90
|
+
const shareOutput = runCli(`process list --marketplace ${MARKETPLACE} --process=default-purchase`, 'sharetribe');
|
|
91
|
+
|
|
92
|
+
// Split into lines
|
|
93
|
+
const flexLines = flexOutput.split('\n');
|
|
94
|
+
const shareLines = shareOutput.split('\n');
|
|
95
|
+
|
|
96
|
+
// Same number of lines
|
|
97
|
+
expect(shareLines.length).toBe(flexLines.length);
|
|
98
|
+
|
|
99
|
+
// Header line (index 1) should match exactly
|
|
100
|
+
if (flexLines.length > 1 && shareLines.length > 1) {
|
|
101
|
+
expect(shareLines[1]).toBe(flexLines[1]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Empty lines should match
|
|
105
|
+
expect(shareLines[0]).toBe(flexLines[0]); // Before table
|
|
106
|
+
expect(shareLines[shareLines.length - 1]).toBe(flexLines[flexLines.length - 1]); // After table
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('events table has consistent column structure', () => {
|
|
110
|
+
const output = runCli(`events --marketplace ${MARKETPLACE} --limit 3`, 'sharetribe');
|
|
111
|
+
const lines = output.split('\n');
|
|
112
|
+
|
|
113
|
+
// Should have empty line at start and end
|
|
114
|
+
expect(lines[0]).toBe('');
|
|
115
|
+
expect(lines[lines.length - 1]).toBe('');
|
|
116
|
+
|
|
117
|
+
// Header should be present
|
|
118
|
+
const header = lines[1];
|
|
119
|
+
expect(header).toContain('Seq ID');
|
|
120
|
+
expect(header).toContain('Resource ID');
|
|
121
|
+
expect(header).toContain('Event type');
|
|
122
|
+
expect(header).toContain('Created at local time');
|
|
123
|
+
expect(header).toContain('Source');
|
|
124
|
+
expect(header).toContain('Actor');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('JSON output format', () => {
|
|
129
|
+
it('events --json has valid JSON on each line', () => {
|
|
130
|
+
const output = runCli(`events --marketplace ${MARKETPLACE} --json --limit 3`, 'sharetribe');
|
|
131
|
+
const lines = output.trim().split('\n');
|
|
132
|
+
|
|
133
|
+
// Each line should be valid JSON
|
|
134
|
+
for (const line of lines) {
|
|
135
|
+
expect(() => JSON.parse(line)).not.toThrow();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('events --json structure matches flex-cli', () => {
|
|
140
|
+
const flexOutput = runCli(`events --marketplace ${MARKETPLACE} --json --limit 3`, 'flex');
|
|
141
|
+
const shareOutput = runCli(`events --marketplace ${MARKETPLACE} --json --limit 3`, 'sharetribe');
|
|
142
|
+
|
|
143
|
+
const flexLines = flexOutput.trim().split('\n');
|
|
144
|
+
const shareLines = shareOutput.trim().split('\n');
|
|
145
|
+
|
|
146
|
+
// Should have same number of events
|
|
147
|
+
expect(shareLines.length).toBeGreaterThan(0);
|
|
148
|
+
|
|
149
|
+
// Check that all objects have the same keys
|
|
150
|
+
if (flexLines.length > 0 && shareLines.length > 0) {
|
|
151
|
+
const flexObj = JSON.parse(flexLines[0]);
|
|
152
|
+
const shareObj = JSON.parse(shareLines[0]);
|
|
153
|
+
|
|
154
|
+
const flexKeys = Object.keys(flexObj).sort();
|
|
155
|
+
const shareKeys = Object.keys(shareObj).sort();
|
|
156
|
+
|
|
157
|
+
expect(shareKeys).toEqual(flexKeys);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('help output format', () => {
|
|
163
|
+
it('main help has VERSION section', () => {
|
|
164
|
+
const output = runCli('--help', 'sharetribe');
|
|
165
|
+
|
|
166
|
+
expect(output).toContain('VERSION');
|
|
167
|
+
expect(output).toContain('1.15.0');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('main help has USAGE section', () => {
|
|
171
|
+
const output = runCli('--help', 'sharetribe');
|
|
172
|
+
|
|
173
|
+
expect(output).toContain('USAGE');
|
|
174
|
+
expect(output).toContain('$ sharetribe-cli [COMMAND]');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('main help has COMMANDS section', () => {
|
|
178
|
+
const output = runCli('--help', 'sharetribe');
|
|
179
|
+
|
|
180
|
+
expect(output).toContain('COMMANDS');
|
|
181
|
+
expect(output).toContain('events');
|
|
182
|
+
expect(output).toContain('process');
|
|
183
|
+
expect(output).toContain('search');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('main help does NOT have OPTIONS section', () => {
|
|
187
|
+
const output = runCli('--help', 'sharetribe');
|
|
188
|
+
|
|
189
|
+
// Main help should not have OPTIONS section (flex-cli doesn't show it)
|
|
190
|
+
const lines = output.split('\n');
|
|
191
|
+
const commandsIndex = lines.findIndex(l => l === 'COMMANDS');
|
|
192
|
+
const subcommandIndex = lines.findIndex(l => l.startsWith('Subcommand help:'));
|
|
193
|
+
|
|
194
|
+
// Between COMMANDS and Subcommand help, there should be no OPTIONS
|
|
195
|
+
if (commandsIndex !== -1 && subcommandIndex !== -1) {
|
|
196
|
+
const betweenLines = lines.slice(commandsIndex, subcommandIndex);
|
|
197
|
+
const hasOptions = betweenLines.some(l => l === 'OPTIONS');
|
|
198
|
+
expect(hasOptions).toBe(false);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('subcommand help shows command structure', () => {
|
|
203
|
+
// Note: Commander.js "help process list" shows parent "process" help
|
|
204
|
+
// Direct command "--help" works: "process list --help"
|
|
205
|
+
const output = runCli('process list --help', 'sharetribe');
|
|
206
|
+
|
|
207
|
+
expect(output).toContain('OPTIONS');
|
|
208
|
+
expect(output).toContain('--process');
|
|
209
|
+
expect(output).toContain('--marketplace');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('command descriptions match flex-cli', () => {
|
|
214
|
+
it('events command description', () => {
|
|
215
|
+
const output = runCli('--help', 'sharetribe');
|
|
216
|
+
expect(output).toContain('Get a list of events.');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('events tail description', () => {
|
|
220
|
+
const output = runCli('--help', 'sharetribe');
|
|
221
|
+
expect(output).toContain('Tail events live as they happen');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('process description', () => {
|
|
225
|
+
const output = runCli('--help', 'sharetribe');
|
|
226
|
+
expect(output).toContain('describe a process file');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('process list description', () => {
|
|
230
|
+
const output = runCli('--help', 'sharetribe');
|
|
231
|
+
expect(output).toContain('list all transaction processes');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('notifications preview description', () => {
|
|
235
|
+
const output = runCli('--help', 'sharetribe');
|
|
236
|
+
expect(output).toContain('render a preview of an email template');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('notifications send description', () => {
|
|
240
|
+
const output = runCli('--help', 'sharetribe');
|
|
241
|
+
expect(output).toContain('send a preview of an email template to the logged in admin');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('column width consistency', () => {
|
|
246
|
+
it('all table columns use minimum 10 char total width', () => {
|
|
247
|
+
const output = runCli(`events --marketplace ${MARKETPLACE} --limit 1`, 'sharetribe');
|
|
248
|
+
const lines = output.split('\n').filter(l => l.trim().length > 0);
|
|
249
|
+
|
|
250
|
+
if (lines.length > 1) {
|
|
251
|
+
const header = lines[0];
|
|
252
|
+
|
|
253
|
+
// Check that columns are properly spaced
|
|
254
|
+
// flex-cli uses minimum 10 chars total per column (content + spacing)
|
|
255
|
+
const columns = header.split(/\s{2,}/);
|
|
256
|
+
|
|
257
|
+
expect(columns.length).toBeGreaterThan(0);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('events command', () => {
|
|
263
|
+
it('events --marketplace matches flex-cli exactly', () => {
|
|
264
|
+
const flexOutput = runCli(`events --marketplace ${MARKETPLACE} --limit 3`, 'flex');
|
|
265
|
+
const shareOutput = runCli(`events --marketplace ${MARKETPLACE} --limit 3`, 'sharetribe');
|
|
266
|
+
|
|
267
|
+
// Split into lines
|
|
268
|
+
const flexLines = flexOutput.split('\n');
|
|
269
|
+
const shareLines = shareOutput.split('\n');
|
|
270
|
+
|
|
271
|
+
// Same structure (same number of lines)
|
|
272
|
+
expect(shareLines.length).toBe(flexLines.length);
|
|
273
|
+
|
|
274
|
+
// Header should match exactly
|
|
275
|
+
expect(shareLines[1]).toBe(flexLines[1]);
|
|
276
|
+
|
|
277
|
+
// Empty lines match
|
|
278
|
+
expect(shareLines[0]).toBe(flexLines[0]);
|
|
279
|
+
expect(shareLines[shareLines.length - 1]).toBe(flexLines[flexLines.length - 1]);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('events --json matches flex-cli structure', () => {
|
|
283
|
+
const flexOutput = runCli(`events --marketplace ${MARKETPLACE} --json --limit 2`, 'flex');
|
|
284
|
+
const shareOutput = runCli(`events --marketplace ${MARKETPLACE} --json --limit 2`, 'sharetribe');
|
|
285
|
+
|
|
286
|
+
const flexLines = flexOutput.trim().split('\n');
|
|
287
|
+
const shareLines = shareOutput.trim().split('\n');
|
|
288
|
+
|
|
289
|
+
// Same number of events
|
|
290
|
+
expect(shareLines.length).toBe(flexLines.length);
|
|
291
|
+
|
|
292
|
+
// Parse and compare structure (not values, since timestamps differ)
|
|
293
|
+
for (let i = 0; i < Math.min(flexLines.length, shareLines.length); i++) {
|
|
294
|
+
const flexObj = JSON.parse(flexLines[i]);
|
|
295
|
+
const shareObj = JSON.parse(shareLines[i]);
|
|
296
|
+
|
|
297
|
+
expect(Object.keys(shareObj).sort()).toEqual(Object.keys(flexObj).sort());
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('events --limit 5 matches flex-cli', () => {
|
|
302
|
+
const flexOutput = runCli(`events --marketplace ${MARKETPLACE} --limit 5`, 'flex');
|
|
303
|
+
const shareOutput = runCli(`events --marketplace ${MARKETPLACE} --limit 5`, 'sharetribe');
|
|
304
|
+
|
|
305
|
+
const flexLines = flexOutput.split('\n').filter(l => l.trim() && !l.includes('Seq ID'));
|
|
306
|
+
const shareLines = shareOutput.split('\n').filter(l => l.trim() && !l.includes('Seq ID'));
|
|
307
|
+
|
|
308
|
+
// Should have exactly 5 data rows
|
|
309
|
+
expect(shareLines.length).toBe(5);
|
|
310
|
+
expect(flexLines.length).toBe(5);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('events --filter user/created matches flex-cli', () => {
|
|
314
|
+
const flexOutput = runCli(`events --marketplace ${MARKETPLACE} --filter user/created --limit 3`, 'flex');
|
|
315
|
+
const shareOutput = runCli(`events --marketplace ${MARKETPLACE} --filter user/created --limit 3`, 'sharetribe');
|
|
316
|
+
|
|
317
|
+
// Structure should match
|
|
318
|
+
const flexLines = flexOutput.split('\n');
|
|
319
|
+
const shareLines = shareOutput.split('\n');
|
|
320
|
+
|
|
321
|
+
expect(shareLines[0]).toBe(flexLines[0]); // Empty line
|
|
322
|
+
expect(shareLines[1]).toBe(flexLines[1]); // Header
|
|
323
|
+
|
|
324
|
+
// All data lines should contain user/created
|
|
325
|
+
const dataLines = shareOutput.split('\n').filter(l => l.trim() && !l.includes('Event type'));
|
|
326
|
+
for (const line of dataLines) {
|
|
327
|
+
expect(line).toContain('user/created');
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('events tail --help matches flex-cli', () => {
|
|
332
|
+
const flexOutput = runCli('events tail --help', 'flex');
|
|
333
|
+
const shareOutput = runCli('events tail --help', 'sharetribe');
|
|
334
|
+
|
|
335
|
+
// Should contain same key elements (exact match would differ due to CLI name)
|
|
336
|
+
expect(shareOutput).toContain('Tail events live');
|
|
337
|
+
expect(shareOutput).toContain('--marketplace');
|
|
338
|
+
expect(shareOutput).toContain('--filter');
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('process command', () => {
|
|
343
|
+
it('process list --marketplace matches flex-cli', () => {
|
|
344
|
+
const flexOutput = runCli(`process list --marketplace ${MARKETPLACE}`, 'flex');
|
|
345
|
+
const shareOutput = runCli(`process list --marketplace ${MARKETPLACE}`, 'sharetribe');
|
|
346
|
+
|
|
347
|
+
const flexLines = flexOutput.split('\n');
|
|
348
|
+
const shareLines = shareOutput.split('\n');
|
|
349
|
+
|
|
350
|
+
// Same structure
|
|
351
|
+
expect(shareLines.length).toBe(flexLines.length);
|
|
352
|
+
|
|
353
|
+
// Header matches exactly
|
|
354
|
+
expect(shareLines[1]).toBe(flexLines[1]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('process list --process=default-purchase matches flex-cli', () => {
|
|
358
|
+
const flexOutput = runCli(`process list --marketplace ${MARKETPLACE} --process=default-purchase`, 'flex');
|
|
359
|
+
const shareOutput = runCli(`process list --marketplace ${MARKETPLACE} --process=default-purchase`, 'sharetribe');
|
|
360
|
+
|
|
361
|
+
const flexLines = flexOutput.split('\n');
|
|
362
|
+
const shareLines = shareOutput.split('\n');
|
|
363
|
+
|
|
364
|
+
// Same number of lines
|
|
365
|
+
expect(shareLines.length).toBe(flexLines.length);
|
|
366
|
+
|
|
367
|
+
// Header matches
|
|
368
|
+
expect(shareLines[1]).toBe(flexLines[1]);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('search command', () => {
|
|
373
|
+
it('search --marketplace matches flex-cli exactly', () => {
|
|
374
|
+
const flexOutput = runCli(`search --marketplace ${MARKETPLACE}`, 'flex');
|
|
375
|
+
const shareOutput = runCli(`search --marketplace ${MARKETPLACE}`, 'sharetribe');
|
|
376
|
+
|
|
377
|
+
// Should match byte-for-byte
|
|
378
|
+
expect(shareOutput).toBe(flexOutput);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('search set --help matches flex-cli structure', () => {
|
|
382
|
+
const flexOutput = runCli('search set --help', 'flex');
|
|
383
|
+
const shareOutput = runCli('search set --help', 'sharetribe');
|
|
384
|
+
|
|
385
|
+
expect(shareOutput).toContain('set search schema');
|
|
386
|
+
expect(shareOutput).toContain('--key');
|
|
387
|
+
expect(shareOutput).toContain('--scope');
|
|
388
|
+
expect(shareOutput).toContain('--type');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('search unset --help matches flex-cli structure', () => {
|
|
392
|
+
const flexOutput = runCli('search unset --help', 'flex');
|
|
393
|
+
const shareOutput = runCli('search unset --help', 'sharetribe');
|
|
394
|
+
|
|
395
|
+
expect(shareOutput).toContain('unset search schema');
|
|
396
|
+
expect(shareOutput).toContain('--key');
|
|
397
|
+
expect(shareOutput).toContain('--scope');
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe('assets command', () => {
|
|
402
|
+
it('assets pull --help matches flex-cli structure', () => {
|
|
403
|
+
const flexOutput = runCli('assets pull --help', 'flex');
|
|
404
|
+
const shareOutput = runCli('assets pull --help', 'sharetribe');
|
|
405
|
+
|
|
406
|
+
expect(shareOutput).toContain('pull assets from remote');
|
|
407
|
+
expect(shareOutput).toContain('--marketplace');
|
|
408
|
+
expect(shareOutput).toContain('--path');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('assets push --help matches flex-cli structure', () => {
|
|
412
|
+
const flexOutput = runCli('assets push --help', 'flex');
|
|
413
|
+
const shareOutput = runCli('assets push --help', 'sharetribe');
|
|
414
|
+
|
|
415
|
+
expect(shareOutput).toContain('push assets to remote');
|
|
416
|
+
expect(shareOutput).toContain('--marketplace');
|
|
417
|
+
expect(shareOutput).toContain('--path');
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe('notifications command', () => {
|
|
422
|
+
it('notifications preview --help matches flex-cli structure', () => {
|
|
423
|
+
const flexOutput = runCli('notifications preview --help', 'flex');
|
|
424
|
+
const shareOutput = runCli('notifications preview --help', 'sharetribe');
|
|
425
|
+
|
|
426
|
+
expect(shareOutput).toContain('render a preview of an email template');
|
|
427
|
+
expect(shareOutput).toContain('--marketplace');
|
|
428
|
+
expect(shareOutput).toContain('--template');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('notifications send --help matches flex-cli structure', () => {
|
|
432
|
+
const flexOutput = runCli('notifications send --help', 'flex');
|
|
433
|
+
const shareOutput = runCli('notifications send --help', 'sharetribe');
|
|
434
|
+
|
|
435
|
+
expect(shareOutput).toContain('send a preview of an email template');
|
|
436
|
+
expect(shareOutput).toContain('--marketplace');
|
|
437
|
+
expect(shareOutput).toContain('--template');
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe('listing-approval command', () => {
|
|
442
|
+
it('listing-approval --help shows DEPRECATED', () => {
|
|
443
|
+
const shareOutput = runCli('listing-approval --help', 'sharetribe');
|
|
444
|
+
|
|
445
|
+
expect(shareOutput).toContain('DEPRECATED');
|
|
446
|
+
expect(shareOutput).toContain('Console');
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('listing-approval enable --help matches flex-cli structure', () => {
|
|
450
|
+
const flexOutput = runCli('listing-approval enable --help', 'flex');
|
|
451
|
+
const shareOutput = runCli('listing-approval enable --help', 'sharetribe');
|
|
452
|
+
|
|
453
|
+
expect(shareOutput).toContain('enable listing approvals');
|
|
454
|
+
expect(shareOutput).toContain('--marketplace');
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('listing-approval disable --help matches flex-cli structure', () => {
|
|
458
|
+
const flexOutput = runCli('listing-approval disable --help', 'flex');
|
|
459
|
+
const shareOutput = runCli('listing-approval disable --help', 'sharetribe');
|
|
460
|
+
|
|
461
|
+
expect(shareOutput).toContain('disable listing approvals');
|
|
462
|
+
expect(shareOutput).toContain('--marketplace');
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
describe('stripe command', () => {
|
|
467
|
+
it('stripe update-version --help matches flex-cli structure', () => {
|
|
468
|
+
const flexOutput = runCli('stripe update-version --help', 'flex');
|
|
469
|
+
const shareOutput = runCli('stripe update-version --help', 'sharetribe');
|
|
470
|
+
|
|
471
|
+
expect(shareOutput).toContain('update Stripe API version');
|
|
472
|
+
expect(shareOutput).toContain('--marketplace');
|
|
473
|
+
expect(shareOutput).toContain('--version');
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('login/logout commands', () => {
|
|
478
|
+
it('login --help matches flex-cli structure', () => {
|
|
479
|
+
const flexOutput = runCli('login --help', 'flex');
|
|
480
|
+
const shareOutput = runCli('login --help', 'sharetribe');
|
|
481
|
+
|
|
482
|
+
expect(shareOutput).toContain('log in with API key');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('logout --help matches flex-cli structure', () => {
|
|
486
|
+
const flexOutput = runCli('logout --help', 'flex');
|
|
487
|
+
const shareOutput = runCli('logout --help', 'sharetribe');
|
|
488
|
+
|
|
489
|
+
expect(shareOutput).toContain('logout');
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
describe('workflow tests', () => {
|
|
494
|
+
it('search set/unset workflow matches flex-cli', async () => {
|
|
495
|
+
// 1. List existing schemas to find one we can test with
|
|
496
|
+
const listFlexOutput = runCli(`search --marketplace ${MARKETPLACE}`, 'flex');
|
|
497
|
+
const listShareOutput = runCli(`search --marketplace ${MARKETPLACE}`, 'sharetribe');
|
|
498
|
+
|
|
499
|
+
// Headers should match exactly
|
|
500
|
+
const flexLines = listFlexOutput.split('\n');
|
|
501
|
+
const shareLines = listShareOutput.split('\n');
|
|
502
|
+
expect(shareLines[1]).toBe(flexLines[1]); // Header line
|
|
503
|
+
|
|
504
|
+
// Find an existing schema to test with
|
|
505
|
+
// Avoid schemas "defined in Console" which can't be edited with CLI
|
|
506
|
+
// Skip empty lines and header line
|
|
507
|
+
const schemaLines = flexLines.filter(line =>
|
|
508
|
+
line.trim().length > 0 &&
|
|
509
|
+
!line.includes('Schema for') &&
|
|
510
|
+
!line.includes('Console')
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
if (schemaLines.length === 0) {
|
|
514
|
+
console.warn('No existing editable listing schemas found, skipping unset/set test');
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Parse the first schema line to extract key and other details
|
|
519
|
+
// Format: "schemaFor scope key type defaultValue doc"
|
|
520
|
+
const schemaLine = schemaLines[0];
|
|
521
|
+
const parts = schemaLine.split(/\s{2,}/).map(p => p.trim());
|
|
522
|
+
const testSchemaFor = parts[0]; // Schema for column
|
|
523
|
+
const testScope = parts[1]; // Scope column
|
|
524
|
+
const testKey = parts[2]; // Key column
|
|
525
|
+
const testType = parts[3]; // Type column
|
|
526
|
+
const testDefault = parts[4] || ''; // Default value (optional)
|
|
527
|
+
const testDoc = parts[5] || ''; // Doc column (optional)
|
|
528
|
+
|
|
529
|
+
// Build the set command
|
|
530
|
+
let setCommand = `search set --marketplace ${MARKETPLACE} --key ${testKey} --scope ${testScope} --type ${testType} --schema-for ${testSchemaFor}`;
|
|
531
|
+
if (testDoc) {
|
|
532
|
+
setCommand += ` --doc "${testDoc}"`;
|
|
533
|
+
}
|
|
534
|
+
if (testDefault) {
|
|
535
|
+
setCommand += ` --default "${testDefault}"`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// 2. Run all 3 flex-cli commands first
|
|
539
|
+
const unsetFlexOutput = runCli(
|
|
540
|
+
`search unset --marketplace ${MARKETPLACE} --key ${testKey} --scope ${testScope} --schema-for ${testSchemaFor}`,
|
|
541
|
+
'flex'
|
|
542
|
+
);
|
|
543
|
+
const setFlexOutput = runCli(setCommand, 'flex');
|
|
544
|
+
const verifyFlexOutput = runCli(`search --marketplace ${MARKETPLACE}`, 'flex');
|
|
545
|
+
|
|
546
|
+
// 3. Run all 3 sharetribe-cli commands
|
|
547
|
+
const unsetShareOutput = runCli(
|
|
548
|
+
`search unset --marketplace ${MARKETPLACE} --key ${testKey} --scope ${testScope} --schema-for ${testSchemaFor}`,
|
|
549
|
+
'sharetribe'
|
|
550
|
+
);
|
|
551
|
+
const setShareOutput = runCli(setCommand, 'sharetribe');
|
|
552
|
+
const verifyShareOutput = runCli(`search --marketplace ${MARKETPLACE}`, 'sharetribe');
|
|
553
|
+
|
|
554
|
+
// 4. Do all assertions together
|
|
555
|
+
expect(unsetShareOutput).toBe(unsetFlexOutput);
|
|
556
|
+
expect(setShareOutput).toBe(setFlexOutput);
|
|
557
|
+
expect(verifyShareOutput).toBe(verifyFlexOutput);
|
|
558
|
+
}, 15000);
|
|
559
|
+
|
|
560
|
+
it('events tail can be started and stopped', () => {
|
|
561
|
+
// This test verifies events tail starts correctly with timeout
|
|
562
|
+
// We can't do full byte-by-byte comparison since tail runs indefinitely
|
|
563
|
+
const { spawn } = require('child_process');
|
|
564
|
+
|
|
565
|
+
return new Promise<void>((resolve, reject) => {
|
|
566
|
+
const flexProc = spawn('flex-cli', ['events', 'tail', '--marketplace', MARKETPLACE, '--limit', '1']);
|
|
567
|
+
const shareProc = spawn('sharetribe-cli', ['events', 'tail', '--marketplace', MARKETPLACE, '--limit', '1']);
|
|
568
|
+
|
|
569
|
+
let flexOutput = '';
|
|
570
|
+
let shareOutput = '';
|
|
571
|
+
let flexExited = false;
|
|
572
|
+
let shareExited = false;
|
|
573
|
+
|
|
574
|
+
flexProc.stdout.on('data', (data: Buffer) => {
|
|
575
|
+
flexOutput += data.toString();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
shareProc.stdout.on('data', (data: Buffer) => {
|
|
579
|
+
shareOutput += data.toString();
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
const checkBothExited = () => {
|
|
583
|
+
if (flexExited && shareExited) {
|
|
584
|
+
// Both should show "tailing" or "tail" message
|
|
585
|
+
try {
|
|
586
|
+
expect(shareOutput.toLowerCase()).toMatch(/tail|starting/);
|
|
587
|
+
resolve();
|
|
588
|
+
} catch (error) {
|
|
589
|
+
reject(error);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
flexProc.on('exit', () => {
|
|
595
|
+
flexExited = true;
|
|
596
|
+
checkBothExited();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
shareProc.on('exit', () => {
|
|
600
|
+
shareExited = true;
|
|
601
|
+
checkBothExited();
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// Wait for initial output, then kill both processes
|
|
605
|
+
setTimeout(() => {
|
|
606
|
+
flexProc.kill('SIGINT');
|
|
607
|
+
shareProc.kill('SIGINT');
|
|
608
|
+
|
|
609
|
+
// Force kill if they don't exit after SIGINT
|
|
610
|
+
setTimeout(() => {
|
|
611
|
+
if (!flexExited) flexProc.kill('SIGKILL');
|
|
612
|
+
if (!shareExited) shareProc.kill('SIGKILL');
|
|
613
|
+
|
|
614
|
+
// If still not exited after SIGKILL, resolve anyway
|
|
615
|
+
setTimeout(() => {
|
|
616
|
+
if (!flexExited || !shareExited) {
|
|
617
|
+
// Processes didn't exit cleanly, but that's okay for this test
|
|
618
|
+
resolve();
|
|
619
|
+
}
|
|
620
|
+
}, 500);
|
|
621
|
+
}, 1000);
|
|
622
|
+
}, 2000);
|
|
623
|
+
});
|
|
624
|
+
}, 10000); // 10 second timeout
|
|
625
|
+
|
|
626
|
+
it('assets pull/push workflow matches flex-cli', () => {
|
|
627
|
+
const { mkdtempSync, rmSync } = require('fs');
|
|
628
|
+
const { tmpdir } = require('os');
|
|
629
|
+
const { join } = require('path');
|
|
630
|
+
|
|
631
|
+
// Create temporary directories for both CLIs
|
|
632
|
+
const flexDir = mkdtempSync(join(tmpdir(), 'flex-assets-'));
|
|
633
|
+
const shareDir = mkdtempSync(join(tmpdir(), 'share-assets-'));
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
// Pull assets with both CLIs (this may take time if there are many assets)
|
|
637
|
+
const pullFlexOutput = runCli(
|
|
638
|
+
`assets pull --marketplace ${MARKETPLACE} --path ${flexDir}`,
|
|
639
|
+
'flex'
|
|
640
|
+
);
|
|
641
|
+
const pullShareOutput = runCli(
|
|
642
|
+
`assets pull --marketplace ${MARKETPLACE} --path ${shareDir}`,
|
|
643
|
+
'sharetribe'
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
// Both should complete successfully
|
|
647
|
+
// We can't do exact byte comparison since output may include file counts/timestamps
|
|
648
|
+
// But we verify both succeed
|
|
649
|
+
expect(pullShareOutput).toBeTruthy();
|
|
650
|
+
|
|
651
|
+
// Verify push works (should show no changes since we just pulled)
|
|
652
|
+
const pushFlexOutput = runCli(
|
|
653
|
+
`assets push --marketplace ${MARKETPLACE} --path ${flexDir}`,
|
|
654
|
+
'flex'
|
|
655
|
+
);
|
|
656
|
+
const pushShareOutput = runCli(
|
|
657
|
+
`assets push --marketplace ${MARKETPLACE} --path ${shareDir}`,
|
|
658
|
+
'sharetribe'
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
// Both should complete
|
|
662
|
+
expect(pushShareOutput).toBeTruthy();
|
|
663
|
+
|
|
664
|
+
} finally {
|
|
665
|
+
// Clean up temporary directories
|
|
666
|
+
try {
|
|
667
|
+
rmSync(flexDir, { recursive: true, force: true });
|
|
668
|
+
rmSync(shareDir, { recursive: true, force: true });
|
|
669
|
+
} catch (cleanupError) {
|
|
670
|
+
console.warn('Cleanup failed:', cleanupError);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}, 30000); // 30 second timeout for assets operations
|
|
674
|
+
|
|
675
|
+
it('listing-approval toggle workflow matches flex-cli', () => {
|
|
676
|
+
// Simple toggle test: enable → disable → enable to restore
|
|
677
|
+
// Both CLIs should produce similar output
|
|
678
|
+
|
|
679
|
+
// Enable listing approval
|
|
680
|
+
const enableFlexOutput = runCli(
|
|
681
|
+
`listing-approval enable --marketplace ${MARKETPLACE}`,
|
|
682
|
+
'flex'
|
|
683
|
+
);
|
|
684
|
+
const enableShareOutput = runCli(
|
|
685
|
+
`listing-approval enable --marketplace ${MARKETPLACE}`,
|
|
686
|
+
'sharetribe'
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
// Both should show enabled (or already enabled)
|
|
690
|
+
expect(enableShareOutput.toLowerCase()).toMatch(/enabled|already/);
|
|
691
|
+
expect(enableShareOutput.toLowerCase()).toContain('approval');
|
|
692
|
+
|
|
693
|
+
// Disable listing approval
|
|
694
|
+
const disableFlexOutput = runCli(
|
|
695
|
+
`listing-approval disable --marketplace ${MARKETPLACE}`,
|
|
696
|
+
'flex'
|
|
697
|
+
);
|
|
698
|
+
const disableShareOutput = runCli(
|
|
699
|
+
`listing-approval disable --marketplace ${MARKETPLACE}`,
|
|
700
|
+
'sharetribe'
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
// Both should show disabled (or success)
|
|
704
|
+
expect(disableShareOutput.toLowerCase()).toMatch(/disabled|success/);
|
|
705
|
+
|
|
706
|
+
// Re-enable to restore to known state
|
|
707
|
+
const restoreFlexOutput = runCli(
|
|
708
|
+
`listing-approval enable --marketplace ${MARKETPLACE}`,
|
|
709
|
+
'flex'
|
|
710
|
+
);
|
|
711
|
+
const restoreShareOutput = runCli(
|
|
712
|
+
`listing-approval enable --marketplace ${MARKETPLACE}`,
|
|
713
|
+
'sharetribe'
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
expect(restoreShareOutput.toLowerCase()).toMatch(/enabled|success/);
|
|
717
|
+
}, 15000); // 15 second timeout
|
|
718
|
+
|
|
719
|
+
// Note: notifications preview/send require interactive template selection
|
|
720
|
+
// and don't support --help, so we only test them via --help tests above
|
|
721
|
+
});
|
|
722
|
+
});
|