@simplens/onboard 1.0.0 → 1.0.2
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 +331 -214
- package/dist/__tests__/env-config.test.d.ts +2 -0
- package/dist/__tests__/env-config.test.d.ts.map +1 -0
- package/dist/__tests__/env-config.test.js +23 -0
- package/dist/__tests__/env-config.test.js.map +1 -0
- package/dist/__tests__/infra-prompts.test.d.ts +2 -0
- package/dist/__tests__/infra-prompts.test.d.ts.map +1 -0
- package/dist/__tests__/infra-prompts.test.js +43 -0
- package/dist/__tests__/infra-prompts.test.js.map +1 -0
- package/dist/__tests__/infra.test.d.ts +2 -0
- package/dist/__tests__/infra.test.d.ts.map +1 -0
- package/dist/__tests__/infra.test.js +14 -0
- package/dist/__tests__/infra.test.js.map +1 -0
- package/dist/__tests__/nginx.test.d.ts +2 -0
- package/dist/__tests__/nginx.test.d.ts.map +1 -0
- package/dist/__tests__/nginx.test.js +16 -0
- package/dist/__tests__/nginx.test.js.map +1 -0
- package/dist/env-config.d.ts +27 -12
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +258 -141
- package/dist/env-config.js.map +1 -1
- package/dist/index.js +341 -71
- package/dist/index.js.map +1 -1
- package/dist/infra.d.ts +17 -14
- package/dist/infra.d.ts.map +1 -1
- package/dist/infra.js +265 -176
- package/dist/infra.js.map +1 -1
- package/dist/plugins.d.ts +5 -10
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +75 -44
- package/dist/plugins.js.map +1 -1
- package/dist/services.d.ts +1 -23
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +47 -62
- package/dist/services.js.map +1 -1
- package/dist/templates.d.ts +3 -2
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +203 -198
- package/dist/templates.js.map +1 -1
- package/dist/types/domain.d.ts +2 -0
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/ui.d.ts +45 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +93 -0
- package/dist/ui.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +32 -7
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +66 -2
- package/dist/utils.js.map +1 -1
- package/dist/validators.d.ts +1 -52
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +10 -57
- package/dist/validators.js.map +1 -1
- package/package.json +3 -5
- package/src/__tests__/env-config.test.ts +28 -0
- package/src/__tests__/errors.test.ts +187 -187
- package/src/__tests__/infra-prompts.test.ts +54 -0
- package/src/__tests__/infra.test.ts +15 -0
- package/src/__tests__/utils.test.ts +142 -142
- package/src/__tests__/validators.test.ts +195 -195
- package/src/config/constants.ts +86 -86
- package/src/config/index.ts +1 -1
- package/src/env-config.ts +455 -320
- package/src/index.ts +534 -203
- package/src/infra.ts +404 -300
- package/src/plugins.ts +221 -190
- package/src/services.ts +175 -190
- package/src/templates.ts +209 -203
- package/src/types/domain.ts +129 -127
- package/src/types/errors.ts +173 -173
- package/src/types/index.ts +2 -2
- package/src/ui.ts +91 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/logger.ts +144 -118
- package/src/utils.ts +183 -105
- package/src/validators.ts +145 -192
- package/tsconfig.json +18 -18
- package/vitest.config.ts +22 -20
package/src/plugins.ts
CHANGED
|
@@ -1,190 +1,221 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { readFile, logInfo, logSuccess, logError, logWarning } from './utils.js';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { readFile, logInfo, logSuccess, logError, logWarning } from './utils.js';
|
|
5
|
+
import { multiselect, text, password } from '@clack/prompts';
|
|
6
|
+
import { handleCancel, spinner } from './ui.js';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import type { PluginInfo, SimplensConfig } from './types/domain.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fetches available SimpleNS plugins using the config-gen CLI tool.
|
|
12
|
+
* Falls back to default plugins if fetching fails.
|
|
13
|
+
*
|
|
14
|
+
* @returns Array of available plugin information
|
|
15
|
+
*/
|
|
16
|
+
export async function fetchAvailablePlugins(): Promise<PluginInfo[]> {
|
|
17
|
+
const s = spinner();
|
|
18
|
+
s.start('Fetching available plugins...');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Execute config-gen list command
|
|
22
|
+
const { stdout } = await execa('npx', ['@simplens/config-gen', 'list', '--official'], {
|
|
23
|
+
stdio: 'pipe',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Parse output to extract plugins
|
|
27
|
+
// Expected format: " @simplens/package-name Plugin Name"
|
|
28
|
+
const plugins: PluginInfo[] = [];
|
|
29
|
+
const lines = stdout.split('\n');
|
|
30
|
+
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
// Match plugin lines (starts with @simplens/)
|
|
33
|
+
const match = line.match(/^\s+(@simplens\/[\w-]+)\s+(.+)$/);
|
|
34
|
+
if (match) {
|
|
35
|
+
const [, packageName, rest] = match;
|
|
36
|
+
// Extract name and description
|
|
37
|
+
const parts = rest.split(/\s{2,}/); // Split by multiple spaces
|
|
38
|
+
plugins.push({
|
|
39
|
+
package: packageName.trim(),
|
|
40
|
+
name: parts[0]?.trim() || packageName,
|
|
41
|
+
description: parts[1]?.trim() || '',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
s.stop(`Found ${plugins.length} available plugins`);
|
|
47
|
+
return plugins;
|
|
48
|
+
} catch (error: unknown) {
|
|
49
|
+
s.stop('Could not fetch plugins list. Using defaults.');
|
|
50
|
+
logWarning('Falling back to default plugin list.');
|
|
51
|
+
// Return default plugins as fallback
|
|
52
|
+
return [
|
|
53
|
+
{ package: '@simplens/mock', name: 'Mock Provider', description: 'Mock notification provider for testing' },
|
|
54
|
+
{ package: '@simplens/nodemailer-gmail', name: 'Gmail', description: 'Send emails via Gmail' },
|
|
55
|
+
{ package: '@simplens/resend', name: 'Resend', description: 'Send emails via Resend' },
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Prompt user to select plugins
|
|
62
|
+
*/
|
|
63
|
+
export async function promptPluginSelection(availablePlugins: PluginInfo[]): Promise<string[]> {
|
|
64
|
+
if (availablePlugins.length === 0) {
|
|
65
|
+
logWarning('No plugins available to select.');
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const selected = await multiselect({
|
|
70
|
+
message: 'Select plugins to install (Space to select, Enter to confirm):',
|
|
71
|
+
options: availablePlugins.map(p => ({
|
|
72
|
+
value: p.package,
|
|
73
|
+
label: `${p.name} (${p.package})`,
|
|
74
|
+
hint: p.description,
|
|
75
|
+
})),
|
|
76
|
+
initialValues: availablePlugins
|
|
77
|
+
.filter(p => p.package === '@simplens/mock')
|
|
78
|
+
.map(p => p.package),
|
|
79
|
+
withGuide: true,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
handleCancel(selected);
|
|
83
|
+
return selected as string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generate plugin configuration using config-gen
|
|
88
|
+
*/
|
|
89
|
+
export async function generatePluginConfig(
|
|
90
|
+
targetDir: string,
|
|
91
|
+
selectedPlugins: string[]
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
if (selectedPlugins.length === 0) {
|
|
94
|
+
logInfo('No plugins selected, skipping config generation.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const s = spinner();
|
|
99
|
+
s.start(`Generating configuration for ${selectedPlugins.length} plugin(s)...`);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Execute config-gen for all selected plugins
|
|
103
|
+
// Use relative path to avoid WSL path issues when npx runs Windows binaries
|
|
104
|
+
await execa(
|
|
105
|
+
'npx',
|
|
106
|
+
['@simplens/config-gen', 'gen', ...selectedPlugins, '-o', 'simplens.config.yaml'],
|
|
107
|
+
{ cwd: targetDir, stdio: 'pipe' }
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
s.stop('Generated simplens.config.yaml');
|
|
111
|
+
} catch (error: unknown) {
|
|
112
|
+
s.error('Failed to generate plugin configuration');
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse simplens.config.yaml to extract credential keys
|
|
119
|
+
*/
|
|
120
|
+
export async function parseConfigCredentials(configPath: string): Promise<string[]> {
|
|
121
|
+
try {
|
|
122
|
+
const content = await readFile(configPath);
|
|
123
|
+
const config: any = yaml.load(content);
|
|
124
|
+
|
|
125
|
+
const credentialKeys = new Set<string>();
|
|
126
|
+
|
|
127
|
+
// Extract credential keys from providers (ONLY from credentials, not optionalConfig)
|
|
128
|
+
if (config.providers && Array.isArray(config.providers)) {
|
|
129
|
+
for (const provider of config.providers) {
|
|
130
|
+
if (provider.credentials && typeof provider.credentials === 'object') {
|
|
131
|
+
for (const [key, value] of Object.entries(provider.credentials)) {
|
|
132
|
+
// Extract env var name from ${ENV_VAR} format
|
|
133
|
+
if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {
|
|
134
|
+
const envVar = value.slice(2, -1);
|
|
135
|
+
credentialKeys.add(envVar);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// NOTE: We intentionally skip optionalConfig - those are optional!
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return Array.from(credentialKeys);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logWarning('Could not parse config file for credentials');
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generate default placeholder values for plugin credentials
|
|
152
|
+
* Used in --full mode for non-interactive setup
|
|
153
|
+
*/
|
|
154
|
+
export function generateDefaultPluginCredentials(credentialKeys: string[]): Map<string, string> {
|
|
155
|
+
const result = new Map<string, string>();
|
|
156
|
+
|
|
157
|
+
for (const key of credentialKeys) {
|
|
158
|
+
// Generate placeholder values based on key name patterns
|
|
159
|
+
if (key.toLowerCase().includes('password') || key.toLowerCase().includes('secret')) {
|
|
160
|
+
result.set(key, crypto.randomBytes(16).toString('base64'));
|
|
161
|
+
} else if (key.toLowerCase().includes('apikey') || key.toLowerCase().includes('api_key')) {
|
|
162
|
+
result.set(key, `sk_${crypto.randomBytes(24).toString('base64').slice(0, 32)}`);
|
|
163
|
+
} else if (key.toLowerCase().includes('token')) {
|
|
164
|
+
result.set(key, crypto.randomBytes(32).toString('hex'));
|
|
165
|
+
} else if (key.toLowerCase().includes('email') || key.toLowerCase().includes('user')) {
|
|
166
|
+
result.set(key, 'CHANGE_ME@example.com');
|
|
167
|
+
} else {
|
|
168
|
+
// Generic placeholder
|
|
169
|
+
result.set(key, 'CHANGE_ME');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Prompt for plugin-specific credentials
|
|
178
|
+
*/
|
|
179
|
+
export async function promptPluginCredentials(credentialKeys: string[]): Promise<Map<string, string>> {
|
|
180
|
+
if (credentialKeys.length === 0) {
|
|
181
|
+
logInfo('No plugin credentials required.');
|
|
182
|
+
return new Map();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
logInfo('Configuring plugin credentials...');
|
|
186
|
+
|
|
187
|
+
const result = new Map<string, string>();
|
|
188
|
+
|
|
189
|
+
for (const key of credentialKeys) {
|
|
190
|
+
const isSecret = key.toLowerCase().includes('password') || key.toLowerCase().includes('key');
|
|
191
|
+
|
|
192
|
+
let answer: string | symbol;
|
|
193
|
+
if (isSecret) {
|
|
194
|
+
answer = await password({
|
|
195
|
+
message: `${key}:`,
|
|
196
|
+
validate: (input: string | undefined) => {
|
|
197
|
+
if (!input || input.trim().length === 0) {
|
|
198
|
+
return `${key} is required`;
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
answer = await text({
|
|
205
|
+
message: `${key}:`,
|
|
206
|
+
validate: (input: string | undefined) => {
|
|
207
|
+
if (!input || input.trim().length === 0) {
|
|
208
|
+
return `${key} is required`;
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
handleCancel(answer);
|
|
216
|
+
result.set(key, answer as string);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
logSuccess('Plugin credentials configured');
|
|
220
|
+
return result;
|
|
221
|
+
}
|