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.
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Search command - manage search schemas
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import {
7
+ listSearchSchemas as sdkListSearchSchemas,
8
+ setSearchSchema as sdkSetSearchSchema,
9
+ unsetSearchSchema as sdkUnsetSearchSchema,
10
+ } from 'sharetribe-flex-build-sdk';
11
+ import { printTable, printError } from '../../util/output.js';
12
+
13
+ interface SetSchemaOptions {
14
+ key: string;
15
+ scope: string;
16
+ type: string;
17
+ doc?: string;
18
+ default?: string;
19
+ schemaFor?: string;
20
+ }
21
+
22
+ interface UnsetSchemaOptions {
23
+ key: string;
24
+ scope: string;
25
+ schemaFor?: string;
26
+ }
27
+
28
+ /**
29
+ * Scope label mapping
30
+ */
31
+ const SCOPE_LABELS: Record<string, string> = {
32
+ metadata: 'Metadata',
33
+ private: 'Private data',
34
+ protected: 'Protected data',
35
+ public: 'Public data',
36
+ };
37
+
38
+ /**
39
+ * Sets a search schema field
40
+ */
41
+ async function setSearchSchema(marketplace: string, opts: SetSchemaOptions): Promise<void> {
42
+ try {
43
+ await sdkSetSearchSchema(undefined, marketplace, {
44
+ key: opts.key,
45
+ scope: opts.scope,
46
+ type: opts.type,
47
+ doc: opts.doc,
48
+ defaultValue: opts.default,
49
+ schemaFor: opts.schemaFor,
50
+ });
51
+
52
+ const schemaFor = opts.schemaFor || 'listing';
53
+ const scopeLabel = SCOPE_LABELS[opts.scope] || opts.scope;
54
+ console.log(`${scopeLabel} schema, ${opts.key} is successfully set for ${schemaFor}.`);
55
+ } catch (error) {
56
+ if (error && typeof error === 'object' && 'message' in error) {
57
+ printError(error.message as string);
58
+ } else {
59
+ printError('Failed to set search schema');
60
+ }
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Unsets a search schema field
67
+ */
68
+ async function unsetSearchSchema(marketplace: string, opts: UnsetSchemaOptions): Promise<void> {
69
+ try {
70
+ await sdkUnsetSearchSchema(undefined, marketplace, {
71
+ key: opts.key,
72
+ scope: opts.scope,
73
+ schemaFor: opts.schemaFor,
74
+ });
75
+
76
+ const schemaFor = opts.schemaFor || 'listing';
77
+ const scopeLabel = SCOPE_LABELS[opts.scope] || opts.scope;
78
+ console.log(`${scopeLabel} schema, ${opts.key} is successfully unset for ${schemaFor}.`);
79
+ } catch (error) {
80
+ if (error && typeof error === 'object' && 'message' in error) {
81
+ printError(error.message as string);
82
+ } else {
83
+ printError('Failed to unset search schema');
84
+ }
85
+ process.exit(1);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Converts default value to display string
91
+ */
92
+ function getDefaultValueLabel(value: unknown): string {
93
+ if (value === undefined || value === null) {
94
+ return '';
95
+ }
96
+
97
+ if (Array.isArray(value)) {
98
+ return value.join(', ');
99
+ }
100
+
101
+ return String(value);
102
+ }
103
+
104
+ /**
105
+ * Lists all search schemas
106
+ */
107
+ async function listSearchSchemas(marketplace: string): Promise<void> {
108
+ try {
109
+ const schemas = await sdkListSearchSchemas(undefined, marketplace);
110
+
111
+ if (schemas.length === 0) {
112
+ console.log('No search schemas found.');
113
+ return;
114
+ }
115
+
116
+ // Map and sort the data (by schema-for, scope, key)
117
+ const rows = schemas
118
+ .map((s) => ({
119
+ 'Schema for': s.schemaFor,
120
+ 'Scope': s.scope,
121
+ 'Key': s.key,
122
+ 'Type': s.type,
123
+ 'Default value': getDefaultValueLabel(s.defaultValue),
124
+ 'Doc': s.doc || '',
125
+ }))
126
+ .sort((a, b) => {
127
+ // Sort by schema-for, then scope, then key
128
+ if (a['Schema for'] !== b['Schema for']) {
129
+ return a['Schema for'].localeCompare(b['Schema for']);
130
+ }
131
+ if (a['Scope'] !== b['Scope']) {
132
+ return a['Scope'].localeCompare(b['Scope']);
133
+ }
134
+ return a['Key'].localeCompare(b['Key']);
135
+ });
136
+
137
+ // Print table using flex-cli compatible formatting
138
+ const headers = ['Schema for', 'Scope', 'Key', 'Type', 'Default value', 'Doc'];
139
+
140
+ // Calculate column widths
141
+ // flex-cli uses keywords (e.g., :version) which when stringified include the ':' prefix
142
+ // To match flex-cli widths, we add 1 to header length to simulate the ':' prefix
143
+ const widths: Record<string, number> = {};
144
+ for (const h of headers) {
145
+ widths[h] = h.length + 1;
146
+ }
147
+ for (const row of rows) {
148
+ for (const h of headers) {
149
+ const value = row[h] || '';
150
+ widths[h] = Math.max(widths[h], value.length);
151
+ }
152
+ }
153
+
154
+ // Print empty line before table
155
+ console.log('');
156
+
157
+ // Print header
158
+ // flex-cli search format: each column padded to max_width, with 2 space separator between columns
159
+ // Last column: padding with trailing space
160
+ const headerParts = headers.map((h, i) => {
161
+ const width = widths[h] || 0;
162
+ const padded = h.padEnd(width);
163
+ return i === headers.length - 1 ? padded + ' ' : padded + ' ';
164
+ });
165
+ console.log(headerParts.join(''));
166
+
167
+ // Print rows
168
+ for (const row of rows) {
169
+ const rowParts = headers.map((h, i) => {
170
+ const value = row[h] || '';
171
+ const width = widths[h] || 0;
172
+ const padded = value.padEnd(width);
173
+ return i === headers.length - 1 ? padded + ' ' : padded + ' ';
174
+ });
175
+ console.log(rowParts.join(''));
176
+ }
177
+
178
+ // Print empty line after table
179
+ console.log('');
180
+ } catch (error) {
181
+ if (error && typeof error === 'object' && 'message' in error) {
182
+ printError(error.message as string);
183
+ } else {
184
+ printError('Failed to list search schemas');
185
+ }
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Registers search commands
192
+ */
193
+ export function registerSearchCommands(program: Command): void {
194
+ const searchCmd = program
195
+ .command('search')
196
+ .description('list all search schemas')
197
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
198
+ .action(async (options) => {
199
+ const marketplace = options.marketplace || program.opts().marketplace;
200
+ if (!marketplace) {
201
+ console.error('Error: --marketplace is required');
202
+ process.exit(1);
203
+ }
204
+ await listSearchSchemas(marketplace);
205
+ });
206
+
207
+ // search set
208
+ searchCmd
209
+ .command('set')
210
+ .description('set search schema')
211
+ .requiredOption('--key <KEY>', 'schema key')
212
+ .requiredOption('--scope <SCOPE>', 'schema scope')
213
+ .requiredOption('--type <TYPE>', 'value type (enum, multi-enum, boolean, long, or text)')
214
+ .option('--doc <DOC>', 'description of the schema')
215
+ .option('--default <DEFAULT>', 'default value for search if value is not set')
216
+ .option('--schema-for <SCHEMA_FOR>', 'subject of the schema (listing, userProfile, or transaction)')
217
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
218
+ .action(async (opts) => {
219
+ const marketplace = opts.marketplace || program.opts().marketplace;
220
+ if (!marketplace) {
221
+ console.error('Error: --marketplace is required');
222
+ process.exit(1);
223
+ }
224
+ await setSearchSchema(marketplace, {
225
+ key: opts.key,
226
+ scope: opts.scope,
227
+ type: opts.type,
228
+ doc: opts.doc,
229
+ default: opts.default,
230
+ schemaFor: opts.schemaFor,
231
+ });
232
+ });
233
+
234
+ // search unset
235
+ searchCmd
236
+ .command('unset')
237
+ .description('unset search schema')
238
+ .requiredOption('--key <KEY>', 'schema key')
239
+ .requiredOption('--scope <SCOPE>', 'schema scope')
240
+ .option('--schema-for <SCHEMA_FOR>', 'subject of the schema (listing, userProfile, or transaction)')
241
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
242
+ .action(async (opts) => {
243
+ const marketplace = opts.marketplace || program.opts().marketplace;
244
+ if (!marketplace) {
245
+ console.error('Error: --marketplace is required');
246
+ process.exit(1);
247
+ }
248
+ await unsetSearchSchema(marketplace, {
249
+ key: opts.key,
250
+ scope: opts.scope,
251
+ schemaFor: opts.schemaFor,
252
+ });
253
+ });
254
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Stripe command - manage Stripe integration
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { updateStripeVersion as sdkUpdateStripeVersion, SUPPORTED_STRIPE_VERSIONS } from 'sharetribe-flex-build-sdk';
7
+ import { printError } from '../../util/output.js';
8
+ import inquirer from 'inquirer';
9
+
10
+
11
+ /**
12
+ * Prompts for version selection
13
+ */
14
+ async function promptForVersion(): Promise<string> {
15
+ const answers = await inquirer.prompt([
16
+ {
17
+ type: 'list',
18
+ name: 'version',
19
+ message: 'Select Stripe API version:',
20
+ choices: [...SUPPORTED_STRIPE_VERSIONS],
21
+ },
22
+ ]);
23
+
24
+ return answers.version;
25
+ }
26
+
27
+ /**
28
+ * Prompts for confirmation
29
+ */
30
+ async function promptForConfirmation(): Promise<boolean> {
31
+ console.log('');
32
+ console.log('WARNING: Changing Stripe API version may affect your integration.');
33
+ console.log('');
34
+ console.log('After updating the Stripe API version, you may need to:');
35
+ console.log('- Handle new Capabilities requirements');
36
+ console.log('- Update identity verification settings');
37
+ console.log('');
38
+ console.log('See Stripe documentation for details:');
39
+ console.log('https://stripe.com/docs/connect/capabilities-overview');
40
+ console.log('https://stripe.com/docs/connect/identity-verification');
41
+ console.log('');
42
+
43
+ const answers = await inquirer.prompt([
44
+ {
45
+ type: 'confirm',
46
+ name: 'confirmed',
47
+ message: 'Do you want to continue?',
48
+ default: false,
49
+ },
50
+ ]);
51
+
52
+ return answers.confirmed;
53
+ }
54
+
55
+ /**
56
+ * Updates Stripe API version
57
+ */
58
+ async function updateStripeVersion(
59
+ marketplace: string,
60
+ version?: string,
61
+ force?: boolean
62
+ ): Promise<void> {
63
+ try {
64
+ // Get version if not provided
65
+ let selectedVersion = version;
66
+ if (!selectedVersion) {
67
+ selectedVersion = await promptForVersion();
68
+ }
69
+
70
+ // Get confirmation unless --force flag is used
71
+ if (!force) {
72
+ const confirmed = await promptForConfirmation();
73
+ if (!confirmed) {
74
+ console.log('Cancelled.');
75
+ process.exit(0);
76
+ }
77
+ }
78
+
79
+ // Update via API (SDK validates version)
80
+ await sdkUpdateStripeVersion(undefined, marketplace, selectedVersion);
81
+
82
+ console.log(`Stripe API version successfully changed to ${selectedVersion}`);
83
+ } catch (error) {
84
+ if (error && typeof error === 'object' && 'message' in error) {
85
+ printError(error.message as string);
86
+ } else {
87
+ printError('Failed to update Stripe API version');
88
+ }
89
+ process.exit(1);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Registers stripe commands
95
+ */
96
+ export function registerStripeCommands(program: Command): void {
97
+ const stripeCmd = program.command('stripe').description('manage Stripe integration');
98
+
99
+ // stripe update-version
100
+ stripeCmd
101
+ .command('update-version')
102
+ .description('update Stripe API version in use')
103
+ .option('--version <VERSION>', 'Stripe API version to update to')
104
+ .option('-f, --force', 'skip confirmation prompt and force update')
105
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
106
+ .action(async (opts) => {
107
+ const marketplace = opts.marketplace || program.opts().marketplace;
108
+ if (!marketplace) {
109
+ console.error('Error: --marketplace is required');
110
+ process.exit(1);
111
+ }
112
+ await updateStripeVersion(marketplace, opts.version, opts.force);
113
+ });
114
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Version command - displays the CLI version
3
+ *
4
+ * Must match flex-cli output format exactly
5
+ */
6
+
7
+ import { readFileSync } from 'node:fs';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { dirname, join } from 'node:path';
10
+
11
+ /**
12
+ * Finds package.json by traversing up from current file
13
+ */
14
+ function findPackageJson(): string {
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ let currentDir = dirname(__filename);
17
+
18
+ // Traverse up to find package.json
19
+ while (currentDir !== '/') {
20
+ try {
21
+ const pkgPath = join(currentDir, 'package.json');
22
+ const content = readFileSync(pkgPath, 'utf-8');
23
+ return content;
24
+ } catch {
25
+ currentDir = dirname(currentDir);
26
+ }
27
+ }
28
+
29
+ throw new Error('Could not find package.json');
30
+ }
31
+
32
+ /**
33
+ * Displays the version of the CLI
34
+ *
35
+ * Output must match flex-cli exactly
36
+ */
37
+ export function version(): void {
38
+ const packageJson = JSON.parse(findPackageJson());
39
+ console.log(packageJson.version);
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Sharetribe CLI - Unofficial 100% compatible implementation
3
+ *
4
+ * Main entry point for the CLI application
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import { readFileSync } from 'node:fs';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { dirname, join } from 'node:path';
11
+ import { version } from './commands/version.js';
12
+ import { login } from './commands/login.js';
13
+ import { logout } from './commands/logout.js';
14
+ import { registerProcessCommands } from './commands/process/index.js';
15
+ import { registerSearchCommands } from './commands/search/index.js';
16
+ import { registerAssetsCommands } from './commands/assets/index.js';
17
+ import { registerNotificationsCommands } from './commands/notifications/index.js';
18
+ import { registerListingApprovalCommand } from './commands/listing-approval.js';
19
+ import { registerEventsCommand } from './commands/events/index.js';
20
+ import { registerStripeCommands } from './commands/stripe/index.js';
21
+ import { configureHelp } from './util/help-formatter.js';
22
+ import { routeProcessCommand } from './util/command-router.js';
23
+
24
+ // Get package.json for version info
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = dirname(__filename);
27
+ const packageJson = JSON.parse(
28
+ readFileSync(join(__dirname, '../package.json'), 'utf-8')
29
+ );
30
+
31
+ // Route argv to handle process subcommands
32
+ const routedArgv = routeProcessCommand(process.argv);
33
+
34
+ const program = new Command();
35
+
36
+ // Configure custom help formatter to match flex-cli
37
+ configureHelp(program);
38
+
39
+ // Configure output to add trailing newline (flex-cli behavior)
40
+ program.configureOutput({
41
+ writeOut: (str) => process.stdout.write(str + '\n'),
42
+ writeErr: (str) => process.stderr.write(str + '\n'),
43
+ });
44
+
45
+ // Configure the main program
46
+ program
47
+ .name('sharetribe-cli')
48
+ .description('CLI to interact with Sharetribe Flex')
49
+ .version(packageJson.version, '-V', 'output the version number')
50
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier');
51
+
52
+ // Register commands
53
+
54
+ // version command
55
+ program
56
+ .command('version')
57
+ .description('show version')
58
+ .action(() => {
59
+ version();
60
+ });
61
+
62
+ // login command
63
+ program
64
+ .command('login')
65
+ .description('log in with API key')
66
+ .action(async () => {
67
+ await login();
68
+ });
69
+
70
+ // logout command
71
+ program
72
+ .command('logout')
73
+ .description('logout')
74
+ .action(async () => {
75
+ await logout();
76
+ });
77
+
78
+ // Register process commands
79
+ registerProcessCommands(program);
80
+
81
+ // Register search commands
82
+ registerSearchCommands(program);
83
+
84
+ // Register assets commands
85
+ registerAssetsCommands(program);
86
+
87
+ // Register notifications commands
88
+ registerNotificationsCommands(program);
89
+
90
+ // Register listing-approval command
91
+ registerListingApprovalCommand(program);
92
+
93
+ // Register events command
94
+ registerEventsCommand(program);
95
+
96
+ // Register stripe commands
97
+ registerStripeCommands(program);
98
+
99
+ // Register custom help command (to support "help process list" syntax)
100
+ program
101
+ .command('help [command...]')
102
+ .description('display help for Flex CLI')
103
+ .action((commandPath: string[]) => {
104
+ if (!commandPath || commandPath.length === 0) {
105
+ program.outputHelp();
106
+ return;
107
+ }
108
+
109
+ // Navigate to the nested command
110
+ let targetCmd: Command = program;
111
+ for (const cmdName of commandPath) {
112
+ const subCmd = targetCmd.commands.find(c => c.name() === cmdName);
113
+ if (!subCmd) {
114
+ console.error(`Unknown command: ${commandPath.join(' ')}`);
115
+ process.exit(1);
116
+ }
117
+ targetCmd = subCmd;
118
+ }
119
+
120
+ // Show help for the target command
121
+ targetCmd.outputHelp();
122
+ });
123
+
124
+ // If no command specified, show help and exit with status 0
125
+ if (!routedArgv.slice(2).length) {
126
+ program.outputHelp();
127
+ // Don't call process.exit() - let Commander handle it naturally with exitOverride
128
+ } else {
129
+ // Parse command line arguments with routed argv
130
+ program.parse(routedArgv);
131
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Type definitions for sharetribe-cli
3
+ */
4
+
5
+ export interface CommandContext {
6
+ apiClient?: unknown; // Will be defined when API client is implemented
7
+ marketplace?: string;
8
+ apiKey?: string;
9
+ }
10
+
11
+ export interface CommandOptions {
12
+ marketplace?: string;
13
+ help?: boolean;
14
+ version?: boolean;
15
+ }
16
+
17
+ export interface ProcessDefinition {
18
+ // Will be expanded as we implement process handling
19
+ name: string;
20
+ version?: number;
21
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Custom command router to handle Commander.js limitations
3
+ *
4
+ * Commander.js cannot handle parent and child commands with the same option names.
5
+ * This router intercepts argv and routes to the correct command handler.
6
+ */
7
+
8
+ /**
9
+ * Routes process subcommands to avoid Commander parent/child option conflicts
10
+ *
11
+ * This is necessary because Commander validates parent command options before
12
+ * subcommand actions run, causing conflicts when both use --path.
13
+ */
14
+ export function routeProcessCommand(argv: string[]): string[] {
15
+ // Check if this is a process subcommand
16
+ const processIndex = argv.findIndex(arg => arg === 'process');
17
+ if (processIndex === -1) {
18
+ return argv;
19
+ }
20
+
21
+ // Check for subcommands
22
+ const nextArg = argv[processIndex + 1];
23
+ const subcommands = ['list', 'create', 'push', 'pull', 'create-alias', 'update-alias', 'delete-alias', 'deploy'];
24
+
25
+ if (nextArg && subcommands.includes(nextArg)) {
26
+ // This is a subcommand - remove 'process' from argv and make the subcommand top-level
27
+ // e.g. ['node', 'cli', 'process', 'pull', ...] => ['node', 'cli', 'process-pull', ...]
28
+ const newArgv = [
29
+ ...argv.slice(0, processIndex),
30
+ `process-${nextArg}`,
31
+ ...argv.slice(processIndex + 2)
32
+ ];
33
+
34
+ // Special handling: If this is an alias command with --version option,
35
+ // we need to filter out the global --version flag from program
36
+ // by ensuring the routed command is properly isolated
37
+ return newArgv;
38
+ }
39
+
40
+ return argv;
41
+ }