@astralkit/cli 2.0.1 → 2.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 +83 -14
- package/bin/index.js +14 -7
- package/package.json +1 -1
- package/src/commands/add.js +296 -27
- package/src/commands/list.js +125 -3
- package/src/commands/search.js +105 -2
- package/src/lib/api.js +53 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @astralkit/cli
|
|
2
2
|
|
|
3
|
-
Install and manage AstralKit UI components in your project.
|
|
3
|
+
Install and manage AstralKit UI components, boosters, and workflows in your project.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -11,10 +11,26 @@ npx @astralkit/cli init
|
|
|
11
11
|
# Add a component
|
|
12
12
|
npx @astralkit/cli add button-sizes
|
|
13
13
|
|
|
14
|
+
# Add a booster (feature module)
|
|
15
|
+
npx @astralkit/cli add stripe-payments --booster
|
|
16
|
+
|
|
17
|
+
# Scaffold a workflow (complete app)
|
|
18
|
+
npx @astralkit/cli add saas-starter --workflow
|
|
19
|
+
|
|
14
20
|
# Search for components
|
|
15
21
|
npx @astralkit/cli search "pricing table"
|
|
16
22
|
```
|
|
17
23
|
|
|
24
|
+
## Content Types
|
|
25
|
+
|
|
26
|
+
AstralKit distributes three types of content:
|
|
27
|
+
|
|
28
|
+
| Type | Description | Example |
|
|
29
|
+
|------|-------------|---------|
|
|
30
|
+
| **Components** | Single UI elements with framework variants | Button, Card, Data Table |
|
|
31
|
+
| **Boosters** | Drop-in feature modules (core logic + framework adapters) | Stripe Payments, Auth System |
|
|
32
|
+
| **Workflows** | Complete application scaffolds | SaaS Starter, CRM, Admin Dashboard |
|
|
33
|
+
|
|
18
34
|
## Commands
|
|
19
35
|
|
|
20
36
|
### `astralkit init`
|
|
@@ -27,9 +43,11 @@ astralkit init --framework=react
|
|
|
27
43
|
astralkit init --dir=src/components/ui
|
|
28
44
|
```
|
|
29
45
|
|
|
30
|
-
### `astralkit add <
|
|
46
|
+
### `astralkit add <name>`
|
|
47
|
+
|
|
48
|
+
Add a component, booster, or workflow to your project.
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
#### Adding Components
|
|
33
51
|
|
|
34
52
|
```bash
|
|
35
53
|
astralkit add avatar-variants
|
|
@@ -37,17 +55,51 @@ astralkit add pricing-table --framework=react
|
|
|
37
55
|
astralkit add data-table --overwrite
|
|
38
56
|
```
|
|
39
57
|
|
|
40
|
-
|
|
58
|
+
#### Adding Boosters
|
|
59
|
+
|
|
60
|
+
Boosters install framework-agnostic core files (API routes, webhooks, utilities) and framework-specific adapter files (UI components, hooks).
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
astralkit add stripe-payments --booster
|
|
64
|
+
astralkit add auth-system --booster --framework=nextjs
|
|
65
|
+
astralkit add stripe-payments --booster --overwrite
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Core files are written to `src/lib/<booster-slug>/` and adapter files to `src/components/<booster-slug>/` by default. Boosters may specify custom install paths.
|
|
69
|
+
|
|
70
|
+
#### Adding Workflows
|
|
71
|
+
|
|
72
|
+
Workflows scaffold a complete application. By default, files are created in a new `./<slug>/` directory.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
astralkit add saas-starter --workflow
|
|
76
|
+
astralkit add crm --workflow --framework=nextjs
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use `--overlay` to merge workflow files into your current project instead of creating a new directory:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
astralkit add saas-starter --workflow --overlay
|
|
83
|
+
astralkit add saas-starter --workflow --overlay --overwrite
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Options
|
|
87
|
+
|
|
41
88
|
- `-f, --framework` — Framework version (html, react, vue, nextjs, angular, svelte, astro)
|
|
42
89
|
- `--dir` — Override output directory
|
|
43
90
|
- `--overwrite` — Overwrite existing files
|
|
91
|
+
- `--booster` — Install a booster (feature module)
|
|
92
|
+
- `--workflow` — Scaffold a workflow (complete app template)
|
|
93
|
+
- `--overlay` — Merge workflow into current project (workflow only)
|
|
44
94
|
|
|
45
95
|
### `astralkit list`
|
|
46
96
|
|
|
47
|
-
List
|
|
97
|
+
List available content.
|
|
48
98
|
|
|
49
99
|
```bash
|
|
50
|
-
astralkit list
|
|
100
|
+
astralkit list # List components
|
|
101
|
+
astralkit list --boosters # List boosters
|
|
102
|
+
astralkit list --workflows # List workflows
|
|
51
103
|
astralkit list --category=forms
|
|
52
104
|
astralkit list --framework=react
|
|
53
105
|
astralkit list --pro
|
|
@@ -56,11 +108,13 @@ astralkit list --free
|
|
|
56
108
|
|
|
57
109
|
### `astralkit search <query>`
|
|
58
110
|
|
|
59
|
-
Search
|
|
111
|
+
Search by name or description.
|
|
60
112
|
|
|
61
113
|
```bash
|
|
62
114
|
astralkit search "data table"
|
|
63
115
|
astralkit search "pricing" --framework=react
|
|
116
|
+
astralkit search "stripe" --boosters
|
|
117
|
+
astralkit search "crm" --workflows
|
|
64
118
|
```
|
|
65
119
|
|
|
66
120
|
### `astralkit diff <component>`
|
|
@@ -82,7 +136,7 @@ astralkit update button-sizes --force
|
|
|
82
136
|
|
|
83
137
|
### `astralkit login`
|
|
84
138
|
|
|
85
|
-
Authenticate with AstralKit to access premium
|
|
139
|
+
Authenticate with AstralKit to access premium content. Opens your browser for secure login.
|
|
86
140
|
|
|
87
141
|
```bash
|
|
88
142
|
astralkit login
|
|
@@ -96,9 +150,9 @@ Remove stored authentication.
|
|
|
96
150
|
|
|
97
151
|
Show current authentication status and subscription plan. Verifies your token with the server.
|
|
98
152
|
|
|
99
|
-
## Premium
|
|
153
|
+
## Premium Content
|
|
100
154
|
|
|
101
|
-
Some components require a Pro subscription. When you try to add
|
|
155
|
+
Some components, boosters, and workflows require a Pro subscription. When you try to add pro content without authentication:
|
|
102
156
|
|
|
103
157
|
```bash
|
|
104
158
|
$ astralkit add pro-data-table
|
|
@@ -106,7 +160,7 @@ $ astralkit add pro-data-table
|
|
|
106
160
|
Authentication required. Run `astralkit login` to authenticate.
|
|
107
161
|
```
|
|
108
162
|
|
|
109
|
-
After logging in, pro
|
|
163
|
+
After logging in, pro content installs normally:
|
|
110
164
|
|
|
111
165
|
```bash
|
|
112
166
|
$ astralkit login
|
|
@@ -114,8 +168,22 @@ $ astralkit login
|
|
|
114
168
|
Email: you@example.com
|
|
115
169
|
Plan: pro
|
|
116
170
|
|
|
117
|
-
$ astralkit add
|
|
118
|
-
✔ Added
|
|
171
|
+
$ astralkit add stripe-payments --booster
|
|
172
|
+
✔ Added booster Stripe Payments (nextjs)
|
|
173
|
+
Core files → src/lib/stripe-payments/
|
|
174
|
+
lib/stripe.ts
|
|
175
|
+
api/webhooks/stripe/route.ts
|
|
176
|
+
Adapter files → src/components/stripe-payments/
|
|
177
|
+
components/checkout-button.tsx
|
|
178
|
+
hooks/use-subscription.ts
|
|
179
|
+
|
|
180
|
+
Install required dependencies:
|
|
181
|
+
npm install stripe @stripe/stripe-js
|
|
182
|
+
|
|
183
|
+
Add these environment variables to your .env:
|
|
184
|
+
STRIPE_SECRET_KEY=
|
|
185
|
+
STRIPE_PUBLISHABLE_KEY=
|
|
186
|
+
STRIPE_WEBHOOK_SECRET=
|
|
119
187
|
```
|
|
120
188
|
|
|
121
189
|
## Configuration
|
|
@@ -147,6 +215,7 @@ export ASTRALKIT_TOKEN="your-api-token"
|
|
|
147
215
|
|
|
148
216
|
# Commands automatically use the env var
|
|
149
217
|
astralkit add button-sizes
|
|
218
|
+
astralkit add stripe-payments --booster
|
|
150
219
|
astralkit whoami
|
|
151
220
|
```
|
|
152
221
|
|
|
@@ -189,7 +258,7 @@ If you see timeout or connection errors, check your internet connection. The CLI
|
|
|
189
258
|
## Requirements
|
|
190
259
|
|
|
191
260
|
- Node.js 18+
|
|
192
|
-
- An AstralKit account for premium
|
|
261
|
+
- An AstralKit account for premium content
|
|
193
262
|
|
|
194
263
|
## Links
|
|
195
264
|
|
package/bin/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const program = new Command();
|
|
|
18
18
|
|
|
19
19
|
program
|
|
20
20
|
.name('astralkit')
|
|
21
|
-
.description('AstralKit CLI — Install and manage UI components')
|
|
21
|
+
.description('AstralKit CLI — Install and manage UI components, boosters, and workflows')
|
|
22
22
|
.version(pkg.version);
|
|
23
23
|
|
|
24
24
|
// ─── init ────────────────────────────────────────────
|
|
@@ -31,11 +31,14 @@ program
|
|
|
31
31
|
|
|
32
32
|
// ─── add ─────────────────────────────────────────────
|
|
33
33
|
program
|
|
34
|
-
.command('add <
|
|
35
|
-
.description('Add a component to your project')
|
|
34
|
+
.command('add <name>')
|
|
35
|
+
.description('Add a component, booster, or workflow to your project')
|
|
36
36
|
.option('-f, --framework <framework>', 'Framework version (html, react, vue, nextjs)')
|
|
37
37
|
.option('--dir <directory>', 'Override output directory')
|
|
38
38
|
.option('--overwrite', 'Overwrite existing files', false)
|
|
39
|
+
.option('--booster', 'Install a booster (feature module)')
|
|
40
|
+
.option('--workflow', 'Scaffold a workflow (complete app template)')
|
|
41
|
+
.option('--overlay', 'Merge workflow into current project (workflow only)')
|
|
39
42
|
.action(addCommand);
|
|
40
43
|
|
|
41
44
|
// ─── login ───────────────────────────────────────────
|
|
@@ -57,18 +60,22 @@ program
|
|
|
57
60
|
// ─── list ────────────────────────────────────────────
|
|
58
61
|
program
|
|
59
62
|
.command('list')
|
|
60
|
-
.description('List available components')
|
|
63
|
+
.description('List available components, boosters, or workflows')
|
|
61
64
|
.option('-c, --category <category>', 'Filter by category')
|
|
62
65
|
.option('--framework <framework>', 'Filter by supported framework')
|
|
63
|
-
.option('--pro', 'Show only pro
|
|
64
|
-
.option('--free', 'Show only free
|
|
66
|
+
.option('--pro', 'Show only pro items', false)
|
|
67
|
+
.option('--free', 'Show only free items', false)
|
|
68
|
+
.option('--boosters', 'List boosters instead of components')
|
|
69
|
+
.option('--workflows', 'List workflows instead of components')
|
|
65
70
|
.action(listCommand);
|
|
66
71
|
|
|
67
72
|
// ─── search ──────────────────────────────────────────
|
|
68
73
|
program
|
|
69
74
|
.command('search <query>')
|
|
70
|
-
.description('Search for components')
|
|
75
|
+
.description('Search for components, boosters, or workflows')
|
|
71
76
|
.option('--framework <framework>', 'Filter by supported framework')
|
|
77
|
+
.option('--boosters', 'Search boosters only')
|
|
78
|
+
.option('--workflows', 'Search workflows only')
|
|
72
79
|
.action(searchCommand);
|
|
73
80
|
|
|
74
81
|
// ─── diff ────────────────────────────────────────────
|
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -2,22 +2,35 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const ora = require('ora');
|
|
5
|
-
const { getComponent } = require('../lib/api');
|
|
5
|
+
const { getComponent, getBooster, getWorkflow } = require('../lib/api');
|
|
6
6
|
const { getProjectConfig, detectFramework } = require('../lib/config');
|
|
7
7
|
const { validateSlug, validateFramework, isSafePath } = require('../lib/validators');
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Route to the correct add handler based on flags.
|
|
11
|
+
*/
|
|
12
|
+
async function addCommand(nameOrSlug, options) {
|
|
13
|
+
if (options.booster) {
|
|
14
|
+
return addBooster(nameOrSlug, options);
|
|
15
|
+
}
|
|
16
|
+
if (options.workflow) {
|
|
17
|
+
return addWorkflow(nameOrSlug, options);
|
|
18
|
+
}
|
|
19
|
+
return addComponent(nameOrSlug, options);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Add a single UI component (existing behavior).
|
|
24
|
+
*/
|
|
25
|
+
async function addComponent(componentSlug, options) {
|
|
11
26
|
if (!validateSlug(componentSlug)) process.exit(1);
|
|
12
|
-
if (!validateFramework(options.framework)) process.exit(1);
|
|
27
|
+
if (options.framework && !validateFramework(options.framework)) process.exit(1);
|
|
13
28
|
|
|
14
|
-
// Resolve framework
|
|
15
29
|
const config = getProjectConfig();
|
|
16
30
|
const framework = options.framework || config?.framework || detectFramework();
|
|
17
31
|
const componentsDir = options.dir || config?.componentsDir || 'src/components/ui';
|
|
18
32
|
const targetDir = path.resolve(process.cwd(), componentsDir, componentSlug);
|
|
19
33
|
|
|
20
|
-
// Check for existing files
|
|
21
34
|
if (fs.existsSync(targetDir) && !options.overwrite) {
|
|
22
35
|
console.log(chalk.yellow(`Component already exists at ${componentsDir}/${componentSlug}/`));
|
|
23
36
|
console.log(chalk.gray('Use --overwrite to replace existing files.'));
|
|
@@ -37,10 +50,8 @@ async function addCommand(componentSlug, options) {
|
|
|
37
50
|
return;
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
// Create component directory
|
|
41
53
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
42
54
|
|
|
43
|
-
// Write each file (with path traversal protection)
|
|
44
55
|
for (const file of data.files) {
|
|
45
56
|
if (!isSafePath(targetDir, file.path)) {
|
|
46
57
|
console.log(chalk.red(` Skipping suspicious file path: ${file.path}`));
|
|
@@ -56,20 +67,17 @@ async function addCommand(componentSlug, options) {
|
|
|
56
67
|
|
|
57
68
|
spinner.succeed(`Added ${chalk.cyan(data.name || componentSlug)} (${framework})`);
|
|
58
69
|
|
|
59
|
-
// Show files written
|
|
60
70
|
console.log(chalk.gray(` ${componentsDir}/${componentSlug}/`));
|
|
61
71
|
for (const file of data.files) {
|
|
62
72
|
console.log(chalk.gray(` ${file.path}`));
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
// Show dependencies to install
|
|
66
75
|
if (data.dependencies?.length) {
|
|
67
76
|
console.log();
|
|
68
77
|
console.log(chalk.yellow('Install required dependencies:'));
|
|
69
78
|
console.log(chalk.cyan(` npm install ${data.dependencies.join(' ')}`));
|
|
70
79
|
}
|
|
71
80
|
|
|
72
|
-
// Pro badge
|
|
73
81
|
if (data.is_pro) {
|
|
74
82
|
console.log();
|
|
75
83
|
console.log(chalk.magenta('Pro component — thank you for your subscription!'));
|
|
@@ -77,28 +85,289 @@ async function addCommand(componentSlug, options) {
|
|
|
77
85
|
|
|
78
86
|
} catch (err) {
|
|
79
87
|
spinner.fail('Failed to add component.');
|
|
88
|
+
handleAddError(err, componentSlug, framework);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Add a booster (multi-file feature module with core + adapter).
|
|
94
|
+
*/
|
|
95
|
+
async function addBooster(slug, options) {
|
|
96
|
+
if (!validateSlug(slug)) process.exit(1);
|
|
97
|
+
if (options.framework && !validateFramework(options.framework)) process.exit(1);
|
|
98
|
+
|
|
99
|
+
const config = getProjectConfig();
|
|
100
|
+
const framework = options.framework || config?.framework || detectFramework();
|
|
101
|
+
|
|
102
|
+
const spinner = ora(`Fetching booster ${slug} (${framework})...`).start();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const data = await getBooster(slug, framework);
|
|
106
|
+
|
|
107
|
+
const coreFiles = data.core_files || [];
|
|
108
|
+
const adapterFiles = data.adapter_files || [];
|
|
109
|
+
|
|
110
|
+
if (coreFiles.length === 0 && adapterFiles.length === 0) {
|
|
111
|
+
spinner.fail(`No files available for booster "${slug}" (${framework}).`);
|
|
112
|
+
if (data.frameworks?.length) {
|
|
113
|
+
console.log(chalk.gray(`Available frameworks: ${data.frameworks.join(', ')}`));
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Resolve install paths
|
|
119
|
+
const corePath = options.dir || data.install_paths?.core || `src/lib/${slug}`;
|
|
120
|
+
const adapterPath = data.install_paths?.adapter || `src/components/${slug}`;
|
|
121
|
+
const coreDir = path.resolve(process.cwd(), corePath);
|
|
122
|
+
const adapterDir = path.resolve(process.cwd(), adapterPath);
|
|
123
|
+
|
|
124
|
+
// Check for existing files
|
|
125
|
+
if (!options.overwrite) {
|
|
126
|
+
const conflicts = [];
|
|
127
|
+
for (const file of coreFiles) {
|
|
128
|
+
const fp = path.resolve(coreDir, file.path);
|
|
129
|
+
if (fs.existsSync(fp)) conflicts.push(`${corePath}/${file.path}`);
|
|
130
|
+
}
|
|
131
|
+
for (const file of adapterFiles) {
|
|
132
|
+
const fp = path.resolve(adapterDir, file.path);
|
|
133
|
+
if (fs.existsSync(fp)) conflicts.push(`${adapterPath}/${file.path}`);
|
|
134
|
+
}
|
|
135
|
+
if (conflicts.length > 0) {
|
|
136
|
+
spinner.fail('Some files already exist.');
|
|
137
|
+
console.log(chalk.yellow('Conflicting files:'));
|
|
138
|
+
for (const c of conflicts) {
|
|
139
|
+
console.log(chalk.gray(` ${c}`));
|
|
140
|
+
}
|
|
141
|
+
console.log(chalk.gray('Use --overwrite to replace existing files.'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Write core files
|
|
147
|
+
let coreCount = 0;
|
|
148
|
+
for (const file of coreFiles) {
|
|
149
|
+
if (!isSafePath(coreDir, file.path)) {
|
|
150
|
+
console.log(chalk.red(` Skipping suspicious file path: ${file.path}`));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const filePath = path.resolve(coreDir, file.path);
|
|
154
|
+
const fileDir = path.dirname(filePath);
|
|
155
|
+
if (!fs.existsSync(fileDir)) {
|
|
156
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
fs.writeFileSync(filePath, file.content);
|
|
159
|
+
coreCount++;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Write adapter files
|
|
163
|
+
let adapterCount = 0;
|
|
164
|
+
for (const file of adapterFiles) {
|
|
165
|
+
if (!isSafePath(adapterDir, file.path)) {
|
|
166
|
+
console.log(chalk.red(` Skipping suspicious file path: ${file.path}`));
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const filePath = path.resolve(adapterDir, file.path);
|
|
170
|
+
const fileDir = path.dirname(filePath);
|
|
171
|
+
if (!fs.existsSync(fileDir)) {
|
|
172
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
fs.writeFileSync(filePath, file.content);
|
|
175
|
+
adapterCount++;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
spinner.succeed(`Added booster ${chalk.cyan(data.name || slug)} (${framework})`);
|
|
179
|
+
|
|
180
|
+
// Show files written
|
|
181
|
+
if (coreCount > 0) {
|
|
182
|
+
console.log(chalk.gray(` Core files → ${corePath}/`));
|
|
183
|
+
for (const file of coreFiles) {
|
|
184
|
+
console.log(chalk.gray(` ${file.path}`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (adapterCount > 0) {
|
|
188
|
+
console.log(chalk.gray(` Adapter files → ${adapterPath}/`));
|
|
189
|
+
for (const file of adapterFiles) {
|
|
190
|
+
console.log(chalk.gray(` ${file.path}`));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Dependencies
|
|
195
|
+
if (data.dependencies?.npm?.length) {
|
|
196
|
+
console.log();
|
|
197
|
+
console.log(chalk.yellow('Install required dependencies:'));
|
|
198
|
+
console.log(chalk.cyan(` npm install ${data.dependencies.npm.join(' ')}`));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Environment variables
|
|
202
|
+
if (data.dependencies?.env?.length) {
|
|
203
|
+
console.log();
|
|
204
|
+
console.log(chalk.yellow('Add these environment variables to your .env:'));
|
|
205
|
+
for (const envVar of data.dependencies.env) {
|
|
206
|
+
console.log(chalk.gray(` ${envVar}=`));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Post-install instructions
|
|
211
|
+
if (data.post_install) {
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(chalk.white.bold('Setup instructions:'));
|
|
214
|
+
console.log(chalk.gray(data.post_install));
|
|
215
|
+
}
|
|
80
216
|
|
|
81
|
-
if (
|
|
82
|
-
console.log(
|
|
83
|
-
|
|
84
|
-
|
|
217
|
+
if (data.is_pro) {
|
|
218
|
+
console.log();
|
|
219
|
+
console.log(chalk.magenta('Pro booster — thank you for your subscription!'));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
} catch (err) {
|
|
223
|
+
spinner.fail('Failed to add booster.');
|
|
224
|
+
handleAddError(err, slug, framework);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Add a workflow (complete application scaffold).
|
|
230
|
+
*/
|
|
231
|
+
async function addWorkflow(slug, options) {
|
|
232
|
+
if (!validateSlug(slug)) process.exit(1);
|
|
233
|
+
if (options.framework && !validateFramework(options.framework)) process.exit(1);
|
|
234
|
+
|
|
235
|
+
const config = getProjectConfig();
|
|
236
|
+
const framework = options.framework || config?.framework || detectFramework();
|
|
237
|
+
|
|
238
|
+
const spinner = ora(`Fetching workflow ${slug} (${framework})...`).start();
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const data = await getWorkflow(slug, framework);
|
|
242
|
+
|
|
243
|
+
if (!data.files || data.files.length === 0) {
|
|
244
|
+
spinner.fail(`No files available for workflow "${slug}" (${framework}).`);
|
|
245
|
+
if (data.frameworks?.length) {
|
|
246
|
+
console.log(chalk.gray(`Available frameworks: ${data.frameworks.join(', ')}`));
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Determine target directory
|
|
252
|
+
let targetDir;
|
|
253
|
+
if (options.overlay) {
|
|
254
|
+
// Overlay mode: write into current project
|
|
255
|
+
targetDir = path.resolve(process.cwd());
|
|
85
256
|
} else {
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
257
|
+
// Default: create a new subdirectory
|
|
258
|
+
const dirName = options.dir || slug;
|
|
259
|
+
targetDir = path.resolve(process.cwd(), dirName);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check for conflicts in overlay mode
|
|
263
|
+
if (options.overlay && !options.overwrite) {
|
|
264
|
+
const conflicts = [];
|
|
265
|
+
for (const file of data.files) {
|
|
266
|
+
const fp = path.resolve(targetDir, file.path);
|
|
267
|
+
if (fs.existsSync(fp)) conflicts.push(file.path);
|
|
268
|
+
}
|
|
269
|
+
if (conflicts.length > 0) {
|
|
270
|
+
spinner.fail(`${conflicts.length} file(s) would be overwritten.`);
|
|
271
|
+
console.log(chalk.yellow('Conflicting files:'));
|
|
272
|
+
for (const c of conflicts.slice(0, 20)) {
|
|
273
|
+
console.log(chalk.gray(` ${c}`));
|
|
274
|
+
}
|
|
275
|
+
if (conflicts.length > 20) {
|
|
276
|
+
console.log(chalk.gray(` ... and ${conflicts.length - 20} more`));
|
|
94
277
|
}
|
|
95
|
-
|
|
96
|
-
|
|
278
|
+
console.log(chalk.gray('Use --overwrite to replace existing files.'));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check if target directory exists (non-overlay)
|
|
284
|
+
if (!options.overlay && fs.existsSync(targetDir) && !options.overwrite) {
|
|
285
|
+
spinner.fail(`Directory "${slug}/" already exists.`);
|
|
286
|
+
console.log(chalk.gray('Use --overwrite to replace, or --dir to specify a different name.'));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Write files
|
|
291
|
+
let fileCount = 0;
|
|
292
|
+
for (const file of data.files) {
|
|
293
|
+
if (!isSafePath(targetDir, file.path)) {
|
|
294
|
+
console.log(chalk.red(` Skipping suspicious file path: ${file.path}`));
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const filePath = path.resolve(targetDir, file.path);
|
|
298
|
+
const fileDir = path.dirname(filePath);
|
|
299
|
+
if (!fs.existsSync(fileDir)) {
|
|
300
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
301
|
+
}
|
|
302
|
+
fs.writeFileSync(filePath, file.content);
|
|
303
|
+
fileCount++;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const targetLabel = options.overlay ? './' : `${slug}/`;
|
|
307
|
+
spinner.succeed(`Scaffolded workflow ${chalk.cyan(data.name || slug)} (${framework}) → ${targetLabel}`);
|
|
308
|
+
|
|
309
|
+
console.log(chalk.gray(` ${fileCount} files written to ${targetLabel}`));
|
|
310
|
+
|
|
311
|
+
// Dependencies
|
|
312
|
+
if (data.dependencies?.npm?.length) {
|
|
313
|
+
console.log();
|
|
314
|
+
console.log(chalk.yellow('Install dependencies:'));
|
|
315
|
+
if (options.overlay) {
|
|
316
|
+
console.log(chalk.cyan(` npm install ${data.dependencies.npm.join(' ')}`));
|
|
317
|
+
} else {
|
|
318
|
+
console.log(chalk.cyan(` cd ${slug} && npm install`));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Environment variables
|
|
323
|
+
if (data.dependencies?.env?.length) {
|
|
324
|
+
console.log();
|
|
325
|
+
console.log(chalk.yellow('Add these environment variables to your .env:'));
|
|
326
|
+
for (const envVar of data.dependencies.env) {
|
|
327
|
+
console.log(chalk.gray(` ${envVar}=`));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Post-install instructions
|
|
332
|
+
if (data.post_install) {
|
|
333
|
+
console.log();
|
|
334
|
+
console.log(chalk.white.bold('Setup instructions:'));
|
|
335
|
+
console.log(chalk.gray(data.post_install));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (data.is_pro) {
|
|
339
|
+
console.log();
|
|
340
|
+
console.log(chalk.magenta('Pro workflow — thank you for your subscription!'));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
} catch (err) {
|
|
344
|
+
spinner.fail('Failed to add workflow.');
|
|
345
|
+
handleAddError(err, slug, framework);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Shared error handler for add commands.
|
|
351
|
+
*/
|
|
352
|
+
function handleAddError(err, slug, framework) {
|
|
353
|
+
if (err.code === 'AUTH_REQUIRED') {
|
|
354
|
+
console.log(chalk.yellow(err.message));
|
|
355
|
+
} else if (err.code === 'PRO_REQUIRED') {
|
|
356
|
+
console.log(chalk.yellow(err.message));
|
|
357
|
+
} else {
|
|
358
|
+
try {
|
|
359
|
+
const body = JSON.parse(err.message.replace(/^API error \d+: /, ''));
|
|
360
|
+
if (body.supported_frameworks) {
|
|
361
|
+
console.log(chalk.yellow(`No ${framework} version available for "${slug}".`));
|
|
362
|
+
console.log(chalk.gray(`Available frameworks: ${body.supported_frameworks.join(', ')}`));
|
|
363
|
+
process.exit(1);
|
|
97
364
|
}
|
|
98
|
-
|
|
365
|
+
} catch {
|
|
366
|
+
// Not a JSON parse error
|
|
99
367
|
}
|
|
100
|
-
|
|
368
|
+
console.error(chalk.red(err.message));
|
|
101
369
|
}
|
|
370
|
+
process.exit(1);
|
|
102
371
|
}
|
|
103
372
|
|
|
104
373
|
module.exports = { addCommand };
|
package/src/commands/list.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const ora = require('ora');
|
|
3
|
-
const { listComponents } = require('../lib/api');
|
|
3
|
+
const { listComponents, listBoosters, listWorkflows } = require('../lib/api');
|
|
4
4
|
const { validateFramework } = require('../lib/validators');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Route to the correct list handler based on flags.
|
|
8
|
+
*/
|
|
6
9
|
async function listCommand(options) {
|
|
7
|
-
// Validate --framework if provided
|
|
8
10
|
if (options.framework && !validateFramework(options.framework)) {
|
|
9
11
|
process.exit(1);
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
if (options.boosters) {
|
|
15
|
+
return listBoostersCommand(options);
|
|
16
|
+
}
|
|
17
|
+
if (options.workflows) {
|
|
18
|
+
return listWorkflowsCommand(options);
|
|
19
|
+
}
|
|
20
|
+
return listComponentsCommand(options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* List UI components (existing behavior).
|
|
25
|
+
*/
|
|
26
|
+
async function listComponentsCommand(options) {
|
|
12
27
|
const spinner = ora('Fetching components...').start();
|
|
13
28
|
|
|
14
29
|
try {
|
|
@@ -26,7 +41,6 @@ async function listCommand(options) {
|
|
|
26
41
|
return;
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
// Group by category
|
|
30
44
|
const grouped = {};
|
|
31
45
|
for (const comp of data.components) {
|
|
32
46
|
const cat = comp.category || 'Uncategorized';
|
|
@@ -61,4 +75,112 @@ async function listCommand(options) {
|
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
|
|
78
|
+
/**
|
|
79
|
+
* List available boosters.
|
|
80
|
+
*/
|
|
81
|
+
async function listBoostersCommand(options) {
|
|
82
|
+
const spinner = ora('Fetching boosters...').start();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const data = await listBoosters({
|
|
86
|
+
framework: options.framework,
|
|
87
|
+
pro: options.pro,
|
|
88
|
+
free: options.free,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
spinner.stop();
|
|
92
|
+
|
|
93
|
+
if (!data.boosters || data.boosters.length === 0) {
|
|
94
|
+
console.log(chalk.yellow('No boosters found.'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const grouped = {};
|
|
99
|
+
for (const booster of data.boosters) {
|
|
100
|
+
const cat = booster.category || 'Uncategorized';
|
|
101
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
102
|
+
grouped[cat].push(booster);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(chalk.blue.bold(`AstralKit Boosters (${data.boosters.length} total)`));
|
|
106
|
+
console.log();
|
|
107
|
+
|
|
108
|
+
for (const [category, boosters] of Object.entries(grouped)) {
|
|
109
|
+
console.log(chalk.white.bold(` ${category}`));
|
|
110
|
+
for (const b of boosters) {
|
|
111
|
+
const proTag = b.is_pro ? chalk.magenta(' PRO') : '';
|
|
112
|
+
const frameworks = b.frameworks?.length
|
|
113
|
+
? chalk.gray(` [${b.frameworks.join(', ')}]`)
|
|
114
|
+
: '';
|
|
115
|
+
console.log(` ${chalk.cyan(b.slug)}${proTag}${frameworks}`);
|
|
116
|
+
if (b.description) {
|
|
117
|
+
console.log(` ${chalk.gray(b.description)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(chalk.gray('Run'), chalk.cyan('astralkit add --booster <name>'), chalk.gray('to install.'));
|
|
124
|
+
|
|
125
|
+
} catch (err) {
|
|
126
|
+
spinner.fail('Failed to list boosters.');
|
|
127
|
+
console.error(chalk.red(err.message));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* List available workflows.
|
|
134
|
+
*/
|
|
135
|
+
async function listWorkflowsCommand(options) {
|
|
136
|
+
const spinner = ora('Fetching workflows...').start();
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const data = await listWorkflows({
|
|
140
|
+
framework: options.framework,
|
|
141
|
+
pro: options.pro,
|
|
142
|
+
free: options.free,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
spinner.stop();
|
|
146
|
+
|
|
147
|
+
if (!data.workflows || data.workflows.length === 0) {
|
|
148
|
+
console.log(chalk.yellow('No workflows found.'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const grouped = {};
|
|
153
|
+
for (const workflow of data.workflows) {
|
|
154
|
+
const cat = workflow.category || 'Uncategorized';
|
|
155
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
156
|
+
grouped[cat].push(workflow);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(chalk.blue.bold(`AstralKit Workflows (${data.workflows.length} total)`));
|
|
160
|
+
console.log();
|
|
161
|
+
|
|
162
|
+
for (const [category, workflows] of Object.entries(grouped)) {
|
|
163
|
+
console.log(chalk.white.bold(` ${category}`));
|
|
164
|
+
for (const w of workflows) {
|
|
165
|
+
const proTag = w.is_pro ? chalk.magenta(' PRO') : '';
|
|
166
|
+
const frameworks = w.frameworks?.length
|
|
167
|
+
? chalk.gray(` [${w.frameworks.join(', ')}]`)
|
|
168
|
+
: '';
|
|
169
|
+
console.log(` ${chalk.cyan(w.slug)}${proTag}${frameworks}`);
|
|
170
|
+
if (w.description) {
|
|
171
|
+
console.log(` ${chalk.gray(w.description)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
console.log();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(chalk.gray('Run'), chalk.cyan('astralkit add --workflow <name>'), chalk.gray('to scaffold.'));
|
|
178
|
+
|
|
179
|
+
} catch (err) {
|
|
180
|
+
spinner.fail('Failed to list workflows.');
|
|
181
|
+
console.error(chalk.red(err.message));
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
64
186
|
module.exports = { listCommand };
|
package/src/commands/search.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const ora = require('ora');
|
|
3
|
-
const { listComponents } = require('../lib/api');
|
|
3
|
+
const { listComponents, listBoosters, listWorkflows } = require('../lib/api');
|
|
4
4
|
const { validateFramework } = require('../lib/validators');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Route to the correct search handler based on flags.
|
|
8
|
+
*/
|
|
6
9
|
async function searchCommand(query, options) {
|
|
7
|
-
// Validate --framework if provided
|
|
8
10
|
if (options.framework && !validateFramework(options.framework)) {
|
|
9
11
|
process.exit(1);
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
if (options.boosters) {
|
|
15
|
+
return searchBoostersCommand(query, options);
|
|
16
|
+
}
|
|
17
|
+
if (options.workflows) {
|
|
18
|
+
return searchWorkflowsCommand(query, options);
|
|
19
|
+
}
|
|
20
|
+
return searchComponentsCommand(query, options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Search UI components (existing behavior).
|
|
25
|
+
*/
|
|
26
|
+
async function searchComponentsCommand(query, options) {
|
|
12
27
|
const spinner = ora(`Searching for "${query}"...`).start();
|
|
13
28
|
|
|
14
29
|
try {
|
|
@@ -50,4 +65,92 @@ async function searchCommand(query, options) {
|
|
|
50
65
|
}
|
|
51
66
|
}
|
|
52
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Search boosters.
|
|
70
|
+
*/
|
|
71
|
+
async function searchBoostersCommand(query, options) {
|
|
72
|
+
const spinner = ora(`Searching boosters for "${query}"...`).start();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const data = await listBoosters({
|
|
76
|
+
search: query,
|
|
77
|
+
framework: options.framework,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
spinner.stop();
|
|
81
|
+
|
|
82
|
+
if (!data.boosters || data.boosters.length === 0) {
|
|
83
|
+
console.log(chalk.yellow(`No boosters found for "${query}".`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(chalk.blue.bold(`Found ${data.boosters.length} booster(s):`));
|
|
88
|
+
console.log();
|
|
89
|
+
|
|
90
|
+
for (const b of data.boosters) {
|
|
91
|
+
const proTag = b.is_pro ? chalk.magenta(' PRO') : '';
|
|
92
|
+
const cat = b.category ? chalk.gray(` (${b.category})`) : '';
|
|
93
|
+
console.log(` ${chalk.cyan(b.slug)}${proTag}${cat}`);
|
|
94
|
+
if (b.description) {
|
|
95
|
+
console.log(` ${chalk.gray(b.description.length > 80 ? b.description.slice(0, 80) + '...' : b.description)}`);
|
|
96
|
+
}
|
|
97
|
+
if (b.frameworks?.length) {
|
|
98
|
+
console.log(` ${chalk.gray(`Frameworks: ${b.frameworks.join(', ')}`)}`);
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(chalk.gray('Run'), chalk.cyan('astralkit add --booster <name>'), chalk.gray('to install.'));
|
|
104
|
+
|
|
105
|
+
} catch (err) {
|
|
106
|
+
spinner.fail('Search failed.');
|
|
107
|
+
console.error(chalk.red(err.message));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Search workflows.
|
|
114
|
+
*/
|
|
115
|
+
async function searchWorkflowsCommand(query, options) {
|
|
116
|
+
const spinner = ora(`Searching workflows for "${query}"...`).start();
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const data = await listWorkflows({
|
|
120
|
+
search: query,
|
|
121
|
+
framework: options.framework,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
spinner.stop();
|
|
125
|
+
|
|
126
|
+
if (!data.workflows || data.workflows.length === 0) {
|
|
127
|
+
console.log(chalk.yellow(`No workflows found for "${query}".`));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(chalk.blue.bold(`Found ${data.workflows.length} workflow(s):`));
|
|
132
|
+
console.log();
|
|
133
|
+
|
|
134
|
+
for (const w of data.workflows) {
|
|
135
|
+
const proTag = w.is_pro ? chalk.magenta(' PRO') : '';
|
|
136
|
+
const cat = w.category ? chalk.gray(` (${w.category})`) : '';
|
|
137
|
+
console.log(` ${chalk.cyan(w.slug)}${proTag}${cat}`);
|
|
138
|
+
if (w.description) {
|
|
139
|
+
console.log(` ${chalk.gray(w.description.length > 80 ? w.description.slice(0, 80) + '...' : w.description)}`);
|
|
140
|
+
}
|
|
141
|
+
if (w.frameworks?.length) {
|
|
142
|
+
console.log(` ${chalk.gray(`Frameworks: ${w.frameworks.join(', ')}`)}`);
|
|
143
|
+
}
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(chalk.gray('Run'), chalk.cyan('astralkit add --workflow <name>'), chalk.gray('to scaffold.'));
|
|
148
|
+
|
|
149
|
+
} catch (err) {
|
|
150
|
+
spinner.fail('Search failed.');
|
|
151
|
+
console.error(chalk.red(err.message));
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
53
156
|
module.exports = { searchCommand };
|
package/src/lib/api.js
CHANGED
|
@@ -136,4 +136,56 @@ async function verifyAuth() {
|
|
|
136
136
|
return response.json();
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
/**
|
|
140
|
+
* List boosters with optional filters.
|
|
141
|
+
*/
|
|
142
|
+
async function listBoosters({ framework, search, pro, free } = {}) {
|
|
143
|
+
const params = new URLSearchParams();
|
|
144
|
+
if (framework) params.set('framework', framework);
|
|
145
|
+
if (pro) params.set('pro', 'true');
|
|
146
|
+
if (free) params.set('free', 'true');
|
|
147
|
+
if (search) params.set('search', search);
|
|
148
|
+
|
|
149
|
+
const response = await apiRequest(`/api/cli/boosters?${params}`);
|
|
150
|
+
return response.json();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get a single booster's details + files for installation.
|
|
155
|
+
*/
|
|
156
|
+
async function getBooster(slug, framework = 'nextjs') {
|
|
157
|
+
const response = await apiRequest(
|
|
158
|
+
`/api/cli/boosters/${encodeURIComponent(slug)}?framework=${encodeURIComponent(framework)}`
|
|
159
|
+
);
|
|
160
|
+
return response.json();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* List workflows with optional filters.
|
|
165
|
+
*/
|
|
166
|
+
async function listWorkflows({ framework, search, pro, free } = {}) {
|
|
167
|
+
const params = new URLSearchParams();
|
|
168
|
+
if (framework) params.set('framework', framework);
|
|
169
|
+
if (pro) params.set('pro', 'true');
|
|
170
|
+
if (free) params.set('free', 'true');
|
|
171
|
+
if (search) params.set('search', search);
|
|
172
|
+
|
|
173
|
+
const response = await apiRequest(`/api/cli/workflows?${params}`);
|
|
174
|
+
return response.json();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get a single workflow's details + files for scaffolding.
|
|
179
|
+
*/
|
|
180
|
+
async function getWorkflow(slug, framework = 'nextjs') {
|
|
181
|
+
const response = await apiRequest(
|
|
182
|
+
`/api/cli/workflows/${encodeURIComponent(slug)}?framework=${encodeURIComponent(framework)}`
|
|
183
|
+
);
|
|
184
|
+
return response.json();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
apiRequest, listComponents, getComponent, verifyAuth,
|
|
189
|
+
listBoosters, getBooster, listWorkflows, getWorkflow,
|
|
190
|
+
API_BASE, CLI_VERSION,
|
|
191
|
+
};
|