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,121 @@
1
+ /**
2
+ * Listing approval command - DEPRECATED
3
+ *
4
+ * This command is deprecated and should not be used.
5
+ * Use the Sharetribe Console instead.
6
+ */
7
+
8
+ import { Command } from 'commander';
9
+ import {
10
+ getListingApprovalStatus as sdkGetStatus,
11
+ enableListingApproval as sdkEnable,
12
+ disableListingApproval as sdkDisable,
13
+ } from 'sharetribe-flex-build-sdk';
14
+ import { printError } from '../util/output.js';
15
+
16
+ /**
17
+ * Gets current listing approval status
18
+ */
19
+ async function getStatus(marketplace: string): Promise<void> {
20
+ try {
21
+ const result = await sdkGetStatus(undefined, marketplace);
22
+
23
+ if (result.enabled) {
24
+ console.log(`Listing approvals are enabled in ${marketplace}`);
25
+ } else {
26
+ console.log(`Listing approvals are disabled in ${marketplace}`);
27
+ }
28
+ } catch (error) {
29
+ if (error && typeof error === 'object' && 'message' in error) {
30
+ printError(error.message as string);
31
+ } else {
32
+ printError('Failed to get listing approval status');
33
+ }
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Enables listing approvals
40
+ */
41
+ async function enableApprovals(marketplace: string): Promise<void> {
42
+ try {
43
+ await sdkEnable(undefined, marketplace);
44
+ console.log(`Successfully enabled listing approvals in ${marketplace}`);
45
+ } catch (error) {
46
+ if (error && typeof error === 'object' && 'message' in error) {
47
+ printError(error.message as string);
48
+ } else {
49
+ printError('Failed to enable listing approvals');
50
+ }
51
+ process.exit(1);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Disables listing approvals
57
+ */
58
+ async function disableApprovals(marketplace: string): Promise<void> {
59
+ try {
60
+ await sdkDisable(undefined, marketplace);
61
+ console.log(`Successfully disabled listing approvals in ${marketplace}`);
62
+ } catch (error) {
63
+ if (error && typeof error === 'object' && 'message' in error) {
64
+ printError(error.message as string);
65
+ } else {
66
+ printError('Failed to disable listing approvals');
67
+ }
68
+ process.exit(1);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Registers listing-approval command
74
+ */
75
+ export function registerListingApprovalCommand(program: Command): void {
76
+ const cmd = program
77
+ .command('listing-approval')
78
+ .description('manage listing approvals (DEPRECATED - use Console instead)')
79
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier');
80
+
81
+ // Default action - show status
82
+ cmd.action(async (opts) => {
83
+ console.warn('Warning: CLI command `listing-approval` is deprecated. Use Console instead.');
84
+ const marketplace = opts.marketplace || program.opts().marketplace;
85
+ if (!marketplace) {
86
+ console.error('Error: --marketplace is required');
87
+ process.exit(1);
88
+ }
89
+ await getStatus(marketplace);
90
+ });
91
+
92
+ // Enable subcommand
93
+ cmd
94
+ .command('enable')
95
+ .description('enable listing approvals')
96
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
97
+ .action(async (opts) => {
98
+ console.warn('Warning: CLI command `listing-approval` is deprecated. Use Console instead.');
99
+ const marketplace = opts.marketplace || program.opts().marketplace;
100
+ if (!marketplace) {
101
+ console.error('Error: --marketplace is required');
102
+ process.exit(1);
103
+ }
104
+ await enableApprovals(marketplace);
105
+ });
106
+
107
+ // Disable subcommand
108
+ cmd
109
+ .command('disable')
110
+ .description('disable listing approvals')
111
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
112
+ .action(async (opts) => {
113
+ console.warn('Warning: CLI command `listing-approval` is deprecated. Use Console instead.');
114
+ const marketplace = opts.marketplace || program.opts().marketplace;
115
+ if (!marketplace) {
116
+ console.error('Error: --marketplace is required');
117
+ process.exit(1);
118
+ }
119
+ await disableApprovals(marketplace);
120
+ });
121
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Login command - interactive API key authentication
3
+ *
4
+ * Must match flex-cli behavior exactly:
5
+ * - Prompt for API key
6
+ * - Store in ~/.config/flex-cli/auth.edn
7
+ * - Display admin email on success
8
+ */
9
+
10
+ import inquirer from 'inquirer';
11
+ import { writeAuth } from 'sharetribe-flex-build-sdk';
12
+
13
+ /**
14
+ * Executes the login command
15
+ *
16
+ * Prompts for API key and stores it in auth.edn
17
+ */
18
+ export async function login(): Promise<void> {
19
+ const answers = await inquirer.prompt([
20
+ {
21
+ type: 'password',
22
+ name: 'apiKey',
23
+ message: 'Enter API key:',
24
+ mask: '*',
25
+ validate: (input: string) => {
26
+ if (!input || input.trim().length === 0) {
27
+ return 'API key cannot be empty';
28
+ }
29
+ return true;
30
+ },
31
+ },
32
+ ]);
33
+
34
+ // Store the API key
35
+ writeAuth({ apiKey: answers.apiKey });
36
+
37
+ // TODO: Validate API key by making a test request to get admin email
38
+ // For now, just confirm storage
39
+ console.log('Successfully logged in.');
40
+
41
+ // Note: flex-cli displays admin email after successful login
42
+ // We'll need to implement API client to fetch this
43
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Logout command - clears authentication
3
+ *
4
+ * Must match flex-cli behavior exactly
5
+ */
6
+
7
+ import { clearAuth } from 'sharetribe-flex-build-sdk';
8
+
9
+ /**
10
+ * Executes the logout command
11
+ *
12
+ * Clears auth.edn file
13
+ */
14
+ export async function logout(): Promise<void> {
15
+ await clearAuth();
16
+ console.log('Successfully logged out.');
17
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Notifications commands - manage email notifications
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import {
7
+ sendNotification as sdkSendNotification,
8
+ previewNotification as sdkPreviewNotification,
9
+ } from 'sharetribe-flex-build-sdk';
10
+ import { printError } from '../../util/output.js';
11
+ import { readFileSync, existsSync, statSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { createServer, IncomingMessage, ServerResponse } from 'node:http';
14
+
15
+
16
+ /**
17
+ * Reads a notification template from a directory
18
+ */
19
+ function readTemplate(templatePath: string): { html: string; subject: string } {
20
+ if (!existsSync(templatePath) || !statSync(templatePath).isDirectory()) {
21
+ throw new Error(`Template directory not found: ${templatePath}`);
22
+ }
23
+
24
+ const htmlPath = join(templatePath, 'template.html');
25
+ const subjectPath = join(templatePath, 'template-subject.txt');
26
+
27
+ if (!existsSync(htmlPath)) {
28
+ throw new Error(`template.html not found in ${templatePath}`);
29
+ }
30
+
31
+ if (!existsSync(subjectPath)) {
32
+ throw new Error(`template-subject.txt not found in ${templatePath}`);
33
+ }
34
+
35
+ const html = readFileSync(htmlPath, 'utf-8');
36
+ const subject = readFileSync(subjectPath, 'utf-8').trim();
37
+
38
+ return { html, subject };
39
+ }
40
+
41
+ /**
42
+ * Reads template context JSON file
43
+ */
44
+ function readContext(contextPath?: string): unknown {
45
+ if (!contextPath) {
46
+ return undefined;
47
+ }
48
+
49
+ if (!existsSync(contextPath)) {
50
+ throw new Error(`Context file not found: ${contextPath}`);
51
+ }
52
+
53
+ const content = readFileSync(contextPath, 'utf-8');
54
+ try {
55
+ return JSON.parse(content);
56
+ } catch (error) {
57
+ throw new Error(`Invalid JSON in context file: ${error}`);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Sends a preview email to the marketplace admin
63
+ */
64
+ async function sendNotification(
65
+ marketplace: string,
66
+ templatePath: string,
67
+ contextPath?: string
68
+ ): Promise<void> {
69
+ try {
70
+ const template = readTemplate(templatePath);
71
+ const context = readContext(contextPath);
72
+
73
+ const result = await sdkSendNotification(undefined, marketplace, { template, context });
74
+
75
+ console.log(`Preview successfully sent to ${result.adminEmail}`);
76
+ } catch (error) {
77
+ if (error && typeof error === 'object' && 'message' in error) {
78
+ printError(error.message as string);
79
+ } else {
80
+ printError('Failed to send notification');
81
+ }
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Previews a notification in the browser
88
+ */
89
+ async function previewNotification(
90
+ marketplace: string,
91
+ templatePath: string,
92
+ contextPath?: string
93
+ ): Promise<void> {
94
+ try {
95
+ const template = readTemplate(templatePath);
96
+ const context = readContext(contextPath);
97
+
98
+ console.log(`Template: ${templatePath}`);
99
+ console.log(`Subject: ${template.subject}`);
100
+ console.log('');
101
+ console.log('Starting preview server at http://localhost:3535');
102
+ console.log('Press Ctrl+C to stop');
103
+ console.log('');
104
+
105
+ let previewHtml: string | null = null;
106
+
107
+ // Fetch preview from API
108
+ const fetchPreview = async () => {
109
+ try {
110
+ const result = await sdkPreviewNotification(undefined, marketplace, { template, context });
111
+
112
+ // Inject title into HTML
113
+ const html = result.html;
114
+ const titleTag = `<title>${template.subject}</title>`;
115
+
116
+ if (html.includes('<head>')) {
117
+ previewHtml = html.replace('<head>', `<head>\n${titleTag}`);
118
+ } else if (html.includes('<html>')) {
119
+ previewHtml = html.replace('<html>', `<html>\n<head>${titleTag}</head>`);
120
+ } else {
121
+ previewHtml = `<html><head>${titleTag}</head><body>${html}</body></html>`;
122
+ }
123
+ } catch (error) {
124
+ const errorMessage = error && typeof error === 'object' && 'message' in error
125
+ ? (error.message as string)
126
+ : 'Failed to preview notification';
127
+
128
+ previewHtml = `
129
+ <html>
130
+ <head><title>Error</title></head>
131
+ <body style="font-family: sans-serif; padding: 20px;">
132
+ <h1 style="color: #d32f2f;">Error</h1>
133
+ <pre style="background: #f5f5f5; padding: 15px; border-radius: 4px;">${errorMessage}</pre>
134
+ </body>
135
+ </html>
136
+ `;
137
+ }
138
+ };
139
+
140
+ // Initial fetch
141
+ await fetchPreview();
142
+
143
+ // Create HTTP server
144
+ const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
145
+ if (req.url === '/' || req.url === '') {
146
+ // Refresh preview on each request
147
+ await fetchPreview();
148
+
149
+ res.writeHead(200, { 'Content-Type': 'text/html' });
150
+ res.end(previewHtml);
151
+ } else {
152
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
153
+ res.end('Not Found');
154
+ }
155
+ });
156
+
157
+ server.listen(3535, () => {
158
+ console.log('Preview server started. Open http://localhost:3535 in your browser.');
159
+ });
160
+
161
+ // Handle graceful shutdown
162
+ const shutdown = () => {
163
+ console.log('\nShutting down preview server...');
164
+ server.close(() => {
165
+ process.exit(0);
166
+ });
167
+ };
168
+
169
+ process.on('SIGINT', shutdown);
170
+ process.on('SIGTERM', shutdown);
171
+
172
+ } catch (error) {
173
+ if (error && typeof error === 'object' && 'message' in error) {
174
+ printError(error.message as string);
175
+ } else {
176
+ printError('Failed to preview notification');
177
+ }
178
+ process.exit(1);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Registers notifications commands
184
+ */
185
+ export function registerNotificationsCommands(program: Command): void {
186
+ const notificationsCmd = program
187
+ .command('notifications')
188
+ .description('manage email notifications');
189
+
190
+ // notifications preview
191
+ notificationsCmd
192
+ .command('preview')
193
+ .description('render a preview of an email template')
194
+ .requiredOption('--template <TEMPLATE_DIR>', 'path to template directory')
195
+ .option('--context <CONTEXT_FILE>', 'path to email rendering context JSON file')
196
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
197
+ .action(async (opts) => {
198
+ const marketplace = opts.marketplace || program.opts().marketplace;
199
+ if (!marketplace) {
200
+ console.error('Error: --marketplace is required');
201
+ process.exit(1);
202
+ }
203
+ await previewNotification(marketplace, opts.template, opts.context);
204
+ });
205
+
206
+ // notifications send
207
+ notificationsCmd
208
+ .command('send')
209
+ .description('send a preview of an email template to the logged in admin')
210
+ .requiredOption('--template <TEMPLATE_DIR>', 'path to template directory')
211
+ .option('--context <CONTEXT_FILE>', 'path to email rendering context JSON file')
212
+ .option('-m, --marketplace <MARKETPLACE_ID>', 'marketplace identifier')
213
+ .action(async (opts) => {
214
+ const marketplace = opts.marketplace || program.opts().marketplace;
215
+ if (!marketplace) {
216
+ console.error('Error: --marketplace is required');
217
+ process.exit(1);
218
+ }
219
+ await sendNotification(marketplace, opts.template, opts.context);
220
+ });
221
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Process alias commands
3
+ */
4
+
5
+ import {
6
+ createAlias as sdkCreateAlias,
7
+ updateAlias as sdkUpdateAlias,
8
+ deleteAlias as sdkDeleteAlias
9
+ } from 'sharetribe-flex-build-sdk';
10
+ import { printError, printSuccess } from '../../util/output.js';
11
+
12
+ /**
13
+ * Creates a process alias
14
+ */
15
+ export async function createAlias(
16
+ marketplace: string,
17
+ processName: string,
18
+ version: number,
19
+ alias: string
20
+ ): Promise<void> {
21
+ try {
22
+ const result = await sdkCreateAlias(undefined, marketplace, processName, version, alias);
23
+
24
+ printSuccess(
25
+ `Alias ${result.alias} successfully created to point to version ${result.version}.`
26
+ );
27
+ } catch (error) {
28
+ if (error && typeof error === 'object' && 'message' in error) {
29
+ printError(error.message as string);
30
+ } else {
31
+ printError('Failed to create alias');
32
+ }
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Updates a process alias
39
+ */
40
+ export async function updateAlias(
41
+ marketplace: string,
42
+ processName: string,
43
+ version: number,
44
+ alias: string
45
+ ): Promise<void> {
46
+ try {
47
+ const result = await sdkUpdateAlias(undefined, marketplace, processName, version, alias);
48
+
49
+ printSuccess(
50
+ `Alias ${result.alias} successfully updated to point to version ${result.version}.`
51
+ );
52
+ } catch (error) {
53
+ if (error && typeof error === 'object' && 'message' in error) {
54
+ printError(error.message as string);
55
+ } else {
56
+ printError('Failed to update alias');
57
+ }
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Deletes a process alias
64
+ */
65
+ export async function deleteAlias(
66
+ marketplace: string,
67
+ processName: string,
68
+ alias: string
69
+ ): Promise<void> {
70
+ try {
71
+ const result = await sdkDeleteAlias(undefined, marketplace, processName, alias);
72
+
73
+ printSuccess(`Alias ${result.alias} successfully deleted.`);
74
+ } catch (error) {
75
+ if (error && typeof error === 'object' && 'message' in error) {
76
+ printError(error.message as string);
77
+ } else {
78
+ printError('Failed to delete alias');
79
+ }
80
+ process.exit(1);
81
+ }
82
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Combined process command - create-or-push-and-create-or-update-alias
3
+ *
4
+ * This is the enhanced "superset" feature that combines multiple operations
5
+ * into one atomic command
6
+ */
7
+
8
+ import { deployProcess as sdkDeployProcess, parseProcessFile } from 'sharetribe-flex-build-sdk';
9
+ import { printError, printSuccess } from '../../util/output.js';
10
+ import { readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ /**
14
+ * Creates or pushes a process and creates or updates an alias
15
+ *
16
+ * This is an atomic operation that:
17
+ * 1. Tries to push a new version (create-version)
18
+ * 2. If process doesn't exist, creates it
19
+ * 3. Then creates or updates the alias
20
+ */
21
+ export async function createOrPushAndCreateOrUpdateAlias(
22
+ marketplace: string,
23
+ processName: string,
24
+ path: string,
25
+ alias: string
26
+ ): Promise<void> {
27
+ try {
28
+ const processFilePath = join(path, 'process.edn');
29
+ const processContent = readFileSync(processFilePath, 'utf-8');
30
+ const processDefinition = parseProcessFile(processContent);
31
+
32
+ const result = await sdkDeployProcess(
33
+ undefined, // Use auth from file
34
+ marketplace,
35
+ {
36
+ process: processName,
37
+ alias,
38
+ path: processFilePath,
39
+ processDefinition,
40
+ }
41
+ );
42
+
43
+ if (result.processCreated) {
44
+ printSuccess(`Process ${processName} successfully created.`);
45
+ }
46
+
47
+ printSuccess(`Version ${result.version} successfully saved for process ${processName}.`);
48
+
49
+ if (result.aliasCreated) {
50
+ printSuccess(`Alias ${result.alias} successfully created to point to version ${result.version}.`);
51
+ } else {
52
+ printSuccess(`Alias ${result.alias} successfully updated to point to version ${result.version}.`);
53
+ }
54
+ } catch (error) {
55
+ if (error && typeof error === 'object' && 'message' in error) {
56
+ printError(error.message as string);
57
+ } else {
58
+ printError('Failed to create/push process and alias');
59
+ }
60
+ process.exit(1);
61
+ }
62
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Process create command
3
+ */
4
+
5
+ import { createProcess as sdkCreateProcess } from 'sharetribe-flex-build-sdk';
6
+ import { printError, printSuccess } from '../../util/output.js';
7
+ import { readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+
10
+ /**
11
+ * Creates a new transaction process
12
+ */
13
+ export async function createProcess(
14
+ marketplace: string,
15
+ processName: string,
16
+ path: string
17
+ ): Promise<void> {
18
+ try {
19
+ const processFilePath = join(path, 'process.edn');
20
+ const processContent = readFileSync(processFilePath, 'utf-8');
21
+
22
+ const result = await sdkCreateProcess(undefined, marketplace, processName, processContent);
23
+
24
+ printSuccess(
25
+ `Process ${result.name} successfully created with version ${result.version}.`
26
+ );
27
+ } catch (error) {
28
+ if (error && typeof error === 'object' && 'message' in error) {
29
+ printError(error.message as string);
30
+ } else {
31
+ printError('Failed to create process');
32
+ }
33
+ process.exit(1);
34
+ }
35
+ }