@dk/jolly 0.1.7 → 0.1.9

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.
Files changed (48) hide show
  1. package/README.md +16 -92
  2. package/package.json +26 -24
  3. package/src/index.ts +2095 -0
  4. package/src/lib/cloud-api.ts +527 -0
  5. package/src/lib/env-file.ts +68 -0
  6. package/src/lib/saleor-url.ts +37 -0
  7. package/.env.example +0 -3
  8. package/.mcp.json +0 -7
  9. package/.sisyphus/boulder.json +0 -13
  10. package/.sisyphus/notepads/saleor-agent-cli/decisions.md +0 -11
  11. package/.sisyphus/notepads/saleor-agent-cli/issues.md +0 -6
  12. package/.sisyphus/notepads/saleor-agent-cli/learnings.md +0 -6
  13. package/.sisyphus/plans/saleor-agent-cli.md +0 -600
  14. package/AGENTS.md +0 -46
  15. package/bun.lock +0 -123
  16. package/bunfig.toml +0 -8
  17. package/dist/agent.js +0 -258
  18. package/dist/bootstrap.js +0 -183
  19. package/dist/index.js +0 -721
  20. package/src/agents/index.ts +0 -1
  21. package/src/agents/setup.ts +0 -210
  22. package/src/api/auth.ts +0 -75
  23. package/src/api/client.ts +0 -151
  24. package/src/api/endpoints.ts +0 -8
  25. package/src/api/index.ts +0 -4
  26. package/src/cli/agent.ts +0 -26
  27. package/src/cli/bootstrap.ts +0 -24
  28. package/src/cli/commands/agent.ts +0 -40
  29. package/src/cli/commands/app.ts +0 -61
  30. package/src/cli/commands/config.ts +0 -38
  31. package/src/cli/commands/store.ts +0 -75
  32. package/src/cli/index.ts +0 -16
  33. package/src/commands/app.ts +0 -126
  34. package/src/commands/index.ts +0 -1
  35. package/src/commands/store.ts +0 -64
  36. package/src/test/command-handlers.test.ts +0 -232
  37. package/src/test/e2e-flows.test.ts +0 -231
  38. package/src/test/entry-points.test.ts +0 -126
  39. package/src/test/error-handling.test.ts +0 -137
  40. package/src/test/helpers.ts +0 -49
  41. package/src/test/index.ts +0 -1
  42. package/src/test/mocks.ts +0 -172
  43. package/src/test/setup.ts +0 -29
  44. package/src/tui/components.ts +0 -77
  45. package/src/tui/index.ts +0 -3
  46. package/src/tui/renderer.ts +0 -34
  47. package/src/tui/theme.ts +0 -38
  48. package/tsconfig.json +0 -20
@@ -1,75 +0,0 @@
1
- import type { CommandModule } from 'yargs';
2
- import { createStore, listStores, createEnvironment } from '../../commands/store.js';
3
- import { promptAndSaveToken, getToken } from '../../api/auth.js';
4
-
5
- async function ensureToken() {
6
- if (!process.env.SALEOR_CLOUD_TOKEN) {
7
- await promptAndSaveToken();
8
- }
9
- }
10
-
11
- export const storeCommands: CommandModule = {
12
- command: 'store <action>',
13
- describe: 'Manage Saleor Cloud stores',
14
- builder: (yargs) =>
15
- yargs
16
- .command({
17
- command: 'create',
18
- describe: 'Create a new Saleor Cloud store',
19
- builder: (yargs) =>
20
- yargs
21
- .option('name', {
22
- alias: 'n',
23
- type: 'string',
24
- description: 'Store name',
25
- demandOption: true,
26
- })
27
- .option('region', {
28
- alias: 'r',
29
- type: 'string',
30
- description: 'Region (e.g., us-east-1)',
31
- default: 'us-east-1',
32
- }),
33
- handler: async (argv) => {
34
- await ensureToken();
35
- await createStore(argv.name, argv.region);
36
- },
37
- })
38
- .command({
39
- command: 'list',
40
- describe: 'List your Saleor Cloud stores',
41
- builder: (yargs) => yargs,
42
- handler: async () => {
43
- await ensureToken();
44
- await listStores();
45
- },
46
- })
47
- .command({
48
- command: 'env <action>',
49
- describe: 'Manage store environments',
50
- builder: (yargs) =>
51
- yargs
52
- .command({
53
- command: 'create',
54
- describe: 'Create a new environment',
55
- builder: (yargs) =>
56
- yargs
57
- .option('store', {
58
- alias: 's',
59
- type: 'string',
60
- description: 'Store ID',
61
- demandOption: true,
62
- })
63
- .option('name', {
64
- alias: 'n',
65
- type: 'string',
66
- description: 'Environment name',
67
- demandOption: true,
68
- }),
69
- handler: async (argv) => {
70
- await ensureToken();
71
- await createEnvironment(argv.store as string, argv.name as string);
72
- },
73
- }),
74
- }),
75
- };
package/src/cli/index.ts DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env node
2
- import yargs from 'yargs';
3
- import { hideBin } from 'yargs/helpers';
4
- import { storeCommands } from './commands/store.js';
5
- import { appCommands } from './commands/app.js';
6
- import { agentCommands } from './commands/agent.js';
7
- import { configCommands } from './commands/config.js';
8
-
9
- yargs(hideBin(process.argv))
10
- .command(storeCommands)
11
- .command(appCommands)
12
- .command(agentCommands)
13
- .command(configCommands)
14
- .demandCommand(1, 'You must provide a command')
15
- .strict()
16
- .parse();
@@ -1,126 +0,0 @@
1
- import { SaleorCloudClient } from '../api/client.js';
2
- import { requireToken } from '../api/auth.js';
3
- import { success, error, info } from '../tui/components.js';
4
-
5
- type AppType = 'dashboard-extension' | 'payment' | 'webhook';
6
- type PaymentProvider = 'dummy' | 'stripe';
7
-
8
- const APP_TEMPLATES = {
9
- 'dashboard-extension': {
10
- repo: 'https://github.com/saleor/saleor-app-sdk',
11
- description: 'Dashboard Extension App',
12
- },
13
- payment: {
14
- repo: 'https://github.com/saleor/saleor-apps',
15
- description: 'Payment App',
16
- },
17
- webhook: {
18
- repo: 'https://github.com/saleor/saleor-webhook-template',
19
- description: 'Webhook Handler',
20
- },
21
- };
22
-
23
- const PAYMENT_APP_URLS = {
24
- dummy: 'https://dummy-payment.saleor.io',
25
- stripe: 'https://stripe-payment.saleor.io',
26
- };
27
-
28
- export async function createApp(
29
- name: string,
30
- type: AppType,
31
- environmentId?: string,
32
- provider?: PaymentProvider
33
- ): Promise<void> {
34
- console.log(info(`Creating ${type} app: ${name}`));
35
-
36
- if (type === 'payment') {
37
- const paymentProvider = provider || 'dummy';
38
- console.log(info(`Payment provider: ${paymentProvider}`));
39
- console.log(info(`Using hosted payment app: ${PAYMENT_APP_URLS[paymentProvider]}`));
40
-
41
- if (environmentId) {
42
- await registerHostedApp(environmentId, name, paymentProvider);
43
- } else {
44
- console.log(success(`\nPayment app "${name}" configured to use ${PAYMENT_APP_URLS[paymentProvider]}`));
45
- console.log(info(`\nTo complete setup:`));
46
- console.log(info(`1. Go to your dashboard at https://cloud.saleor.io`));
47
- console.log(info(`2. Navigate to Apps > Third party apps`));
48
- console.log(info(`3. Add the ${paymentProvider} payment app from ${PAYMENT_APP_URLS[paymentProvider]}`));
49
- }
50
- return;
51
- }
52
-
53
- const template = APP_TEMPLATES[type];
54
- console.log(info(`Cloning template from: ${template.repo}`));
55
-
56
- try {
57
- const { spawn } = await import('child_process');
58
- const child = spawn('git', ['clone', template.repo, name], {
59
- stdio: 'inherit',
60
- });
61
-
62
- child.on('close', (code) => {
63
- if (code === 0) {
64
- console.log(success(`\n${type} app "${name}" created successfully!`));
65
- console.log(info(`\nTo get started:`));
66
- console.log(info(`cd ${name}`));
67
- console.log(info(`npm install`));
68
- console.log(info(`npm run dev`));
69
-
70
- if (environmentId) {
71
- console.log(info(`\nRegistering app with environment ${environmentId}...`));
72
- registerLocalApp(environmentId, name, type);
73
- }
74
- } else {
75
- console.log(error(`Failed to clone template (exit code: ${code})`));
76
- process.exit(1);
77
- }
78
- });
79
- } catch (err) {
80
- console.log(error(`Failed to create app: ${err}`));
81
- process.exit(1);
82
- }
83
- }
84
-
85
- async function registerHostedApp(
86
- environmentId: string,
87
- name: string,
88
- provider: PaymentProvider
89
- ): Promise<void> {
90
- const token = requireToken();
91
- const client = new SaleorCloudClient(token);
92
-
93
- console.log(info(`Registering hosted ${provider} payment app with environment...`));
94
-
95
- try {
96
- const result = await client.registerApp(environmentId, 'payment', name);
97
- console.log(success(`Payment app registered successfully!`));
98
- console.log(info(`App ID: ${result.app.id}`));
99
- console.log(info(`Payment URL: ${PAYMENT_APP_URLS[provider]}`));
100
- } catch (err) {
101
- console.log(error(`Failed to register app: ${err}`));
102
- process.exit(1);
103
- }
104
- }
105
-
106
- async function registerLocalApp(
107
- environmentId: string,
108
- name: string,
109
- type: AppType
110
- ): Promise<void> {
111
- const token = requireToken();
112
- const client = new SaleorCloudClient(token);
113
-
114
- try {
115
- const result = await client.registerApp(environmentId, type, name);
116
- console.log(success(`App registered with environment!`));
117
- console.log(info(`App ID: ${result.app.id}`));
118
- } catch (err) {
119
- console.log(warning(`Could not register app automatically: ${err}`));
120
- console.log(info(`You can register manually in the dashboard.`));
121
- }
122
- }
123
-
124
- function warning(msg: string): void {
125
- console.log(`\x1b[33m${msg}\x1b[0m`);
126
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,64 +0,0 @@
1
- import { SaleorCloudClient } from '../api/client.js';
2
- import { requireToken } from '../api/auth.js';
3
- import { success, error, info } from '../tui/components.js';
4
-
5
- export async function createStore(name: string, region: string): Promise<void> {
6
- const token = requireToken();
7
- const client = new SaleorCloudClient(token);
8
-
9
- console.log(info(`Creating store: ${name} in ${region}...`));
10
-
11
- try {
12
- const result = await client.createStore(name, region);
13
- console.log(success(`Store created successfully!`));
14
- console.log(info(`Project slug: ${result.project.slug}`));
15
- console.log(info(`Dashboard: https://cloud.saleor.io/organizations/default/projects/${result.project.slug}`));
16
- } catch (err) {
17
- console.log(error(`Failed to create store: ${err}`));
18
- process.exit(1);
19
- }
20
- }
21
-
22
- export async function listStores(): Promise<void> {
23
- const token = requireToken();
24
- const client = new SaleorCloudClient(token);
25
-
26
- console.log(info('Fetching organizations...'));
27
-
28
- try {
29
- const { organizations } = await client.getOrganizations();
30
-
31
- if (organizations.length === 0) {
32
- console.log(info('No organizations found. Create one at https://cloud.saleor.io'));
33
- return;
34
- }
35
-
36
- console.log(success(`Found ${organizations.length} organization(s):\n`));
37
- for (const org of organizations) {
38
- console.log(` ${org.name} (${org.slug})`);
39
- console.log(` Email: ${org.owner_email}`);
40
- console.log(` Created: ${new Date(org.created).toLocaleDateString()}`);
41
- console.log();
42
- }
43
- } catch (err) {
44
- console.log(error(`Failed to list stores: ${err}`));
45
- process.exit(1);
46
- }
47
- }
48
-
49
- export async function createEnvironment(organizationSlug: string, name: string): Promise<void> {
50
- const token = requireToken();
51
- const client = new SaleorCloudClient(token);
52
-
53
- console.log(info(`Creating environment: ${name} for organization ${organizationSlug}...`));
54
-
55
- try {
56
- const { environment } = await client.createEnvironment(organizationSlug, 'default', name, 'us-east-1');
57
- console.log(success(`Environment created successfully!`));
58
- console.log(info(`Environment key: ${environment.key}`));
59
- console.log(info(`Domain: ${environment.domain}`));
60
- } catch (err) {
61
- console.log(error(`Failed to create environment: ${err}`));
62
- process.exit(1);
63
- }
64
- }
@@ -1,232 +0,0 @@
1
- import { describe, expect, mock, beforeEach, afterEach } from 'bun:test';
2
- import { Given, When, Then } from './helpers';
3
- import {
4
- fixtures, captureConsole, mockFetch, mockFetchError,
5
- mockProcessExit, withToken, withoutToken,
6
- } from './mocks';
7
-
8
- const tuiCalls: { fn: string; msg: string }[] = [];
9
- mock.module('../tui/components', () => ({
10
- info: (msg: string) => { tuiCalls.push({ fn: 'info', msg }); return msg; },
11
- success: (msg: string) => { tuiCalls.push({ fn: 'success', msg }); return msg; },
12
- error: (msg: string) => { tuiCalls.push({ fn: 'error', msg }); return msg; },
13
- warning: (msg: string) => { tuiCalls.push({ fn: 'warning', msg }); return msg; },
14
- spinner: () => ({ stop: () => {} }),
15
- }));
16
-
17
- describe('Command Handlers', () => {
18
- let console_: ReturnType<typeof captureConsole>;
19
- let exitSpy: ReturnType<typeof mockProcessExit>;
20
- const originalFetch = globalThis.fetch;
21
-
22
- beforeEach(() => {
23
- console_ = captureConsole();
24
- exitSpy = mockProcessExit();
25
- tuiCalls.length = 0;
26
- withToken();
27
- });
28
-
29
- afterEach(() => {
30
- console_.restore();
31
- exitSpy.mockRestore();
32
- globalThis.fetch = originalFetch;
33
- withoutToken();
34
- });
35
-
36
- describe('Store Handlers', () => {
37
- Given('a valid token and successful API', () => {
38
- When('calling createStore', () => {
39
- Then('it should call POST /organizations/:slug/projects with name and region', async () => {
40
- const fetchMock = mockFetch({
41
- '/organizations': { organizations: [fixtures.organization] },
42
- '/organizations/my-org/projects': { project: fixtures.project },
43
- });
44
-
45
- const { createStore } = await import('../commands/store');
46
- await createStore('my-store', 'us-east-1');
47
-
48
- expect(fetchMock).toHaveBeenCalled();
49
- const createCall = fetchMock.mock.calls.find(([url]: [string]) =>
50
- (url as string).includes('/organizations/my-org/projects')
51
- );
52
- expect(createCall).toBeDefined();
53
- expect(createCall?.[1].method).toBe('POST');
54
- expect(JSON.parse(createCall?.[1].body)).toEqual({ name: 'my-store', region: 'us-east-1' });
55
- });
56
- });
57
-
58
- When('calling listStores with results', () => {
59
- Then('it should display organization names and slugs', async () => {
60
- mockFetch({
61
- '/organizations': { organizations: [fixtures.organization, fixtures.organization2] },
62
- });
63
-
64
- const { listStores } = await import('../commands/store');
65
- await listStores();
66
-
67
- const allOutput = console_.logs.join('\n');
68
- expect(allOutput).toContain('My Organization');
69
- expect(allOutput).toContain('my-org');
70
- expect(allOutput).toContain('Other Organization');
71
- expect(allOutput).toContain('other-org');
72
- });
73
- });
74
-
75
- When('calling listStores with empty results', () => {
76
- Then('it should display a helpful message', async () => {
77
- mockFetch({
78
- '/organizations': { organizations: [] },
79
- });
80
-
81
- const { listStores } = await import('../commands/store');
82
- await listStores();
83
-
84
- const infoMessages = tuiCalls.filter(c => c.fn === 'info').map(c => c.msg).join('\n');
85
- expect(infoMessages).toContain('No organizations found');
86
- });
87
- });
88
-
89
- When('calling createEnvironment', () => {
90
- Then('it should call POST /organizations/:slug/projects/:slug/environments', async () => {
91
- const fetchMock = mockFetch({
92
- '/organizations/my-org/projects/default/environments': { environment: fixtures.environment },
93
- });
94
-
95
- const { createEnvironment } = await import('../commands/store');
96
- await createEnvironment('my-org', 'staging');
97
-
98
- expect(fetchMock).toHaveBeenCalled();
99
- const createCall = fetchMock.mock.calls.find(([url]: [string]) =>
100
- (url as string).includes('/organizations/my-org/projects/default/environments')
101
- );
102
- expect(createCall).toBeDefined();
103
- expect(createCall?.[1].method).toBe('POST');
104
- expect(JSON.parse(createCall?.[1].body)).toEqual({ name: 'staging', region: 'us-east-1' });
105
- });
106
- });
107
- });
108
-
109
- Given('a valid token but failing API', () => {
110
- When('calling createStore and API returns 500', () => {
111
- Then('it should print error and exit 1', async () => {
112
- mockFetchError(500, 'Internal Server Error');
113
-
114
- const { createStore } = await import('../commands/store');
115
-
116
- try {
117
- await createStore('fail-store', 'us-east-1');
118
- } catch {}
119
-
120
- expect(exitSpy).toHaveBeenCalledWith(1);
121
- });
122
- });
123
-
124
- When('calling listStores and API returns 401', () => {
125
- Then('it should print error and exit 1', async () => {
126
- mockFetchError(401, 'Unauthorized');
127
-
128
- const { listStores } = await import('../commands/store');
129
-
130
- try {
131
- await listStores();
132
- } catch {}
133
-
134
- expect(exitSpy).toHaveBeenCalledWith(1);
135
- });
136
- });
137
-
138
- When('calling createEnvironment and API returns 404', () => {
139
- Then('it should print error and exit 1', async () => {
140
- mockFetchError(404, 'Not Found');
141
-
142
- const { createEnvironment } = await import('../commands/store');
143
-
144
- try {
145
- await createEnvironment('bad-org', 'staging');
146
- } catch {}
147
-
148
- expect(exitSpy).toHaveBeenCalledWith(1);
149
- });
150
- });
151
- });
152
-
153
- Given('no token is set', () => {
154
- When('calling createStore', () => {
155
- Then('it should exit with code 1 from requireToken', async () => {
156
- withoutToken();
157
-
158
- const { createStore } = await import('../commands/store');
159
-
160
- try {
161
- await createStore('no-token-store', 'us-east-1');
162
- } catch {}
163
-
164
- expect(exitSpy).toHaveBeenCalledWith(1);
165
- });
166
- });
167
-
168
- When('calling listStores', () => {
169
- Then('it should exit with code 1 from requireToken', async () => {
170
- withoutToken();
171
-
172
- const { listStores } = await import('../commands/store');
173
-
174
- try {
175
- await listStores();
176
- } catch {}
177
-
178
- expect(exitSpy).toHaveBeenCalledWith(1);
179
- });
180
- });
181
- });
182
- });
183
-
184
- describe('App Handlers', () => {
185
- Given('a payment app with no environment', () => {
186
- When('calling createApp', () => {
187
- Then('it should print hosted payment app instructions', async () => {
188
- const { createApp } = await import('../commands/app');
189
- await createApp('pay-app', 'payment', undefined, 'stripe');
190
-
191
- const allTuiOutput = tuiCalls.map(c => c.msg).join('\n');
192
- expect(allTuiOutput).toContain('payment');
193
- expect(allTuiOutput).toContain('stripe');
194
- });
195
- });
196
- });
197
-
198
- Given('a payment app with environment and valid token', () => {
199
- When('calling createApp', () => {
200
- Then('it should register the hosted app via API', async () => {
201
- const fetchMock = mockFetch({
202
- '/environments/staging/apps': { app: fixtures.app },
203
- });
204
-
205
- const { createApp } = await import('../commands/app');
206
- await createApp('pay-app', 'payment', 'staging', 'dummy');
207
-
208
- expect(fetchMock).toHaveBeenCalledTimes(1);
209
- const [url, opts] = fetchMock.mock.calls[0];
210
- expect(url).toContain('/environments/staging/apps');
211
- expect(opts.method).toBe('POST');
212
- });
213
- });
214
- });
215
-
216
- Given('a payment app with environment but failing API', () => {
217
- When('calling createApp', () => {
218
- Then('it should print error and exit 1', async () => {
219
- mockFetchError(500, 'Internal Server Error');
220
-
221
- const { createApp } = await import('../commands/app');
222
-
223
- try {
224
- await createApp('pay-app', 'payment', 'staging', 'dummy');
225
- } catch {}
226
-
227
- expect(exitSpy).toHaveBeenCalledWith(1);
228
- });
229
- });
230
- });
231
- });
232
- });