@simplens/onboard 1.0.8 → 1.0.10

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.
@@ -1,144 +0,0 @@
1
- import chalk from 'chalk';
2
- import { appendFile as fsAppendFile } from 'fs/promises';
3
-
4
- /**
5
- * Logging level
6
- */
7
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
8
-
9
- /**
10
- * Logger configuration
11
- */
12
- interface LoggerConfig {
13
- verbose: boolean;
14
- debug: boolean;
15
- silent: boolean;
16
- logFile?: string;
17
- }
18
-
19
- let loggerConfig: LoggerConfig = {
20
- verbose: false,
21
- debug: false,
22
- silent: false,
23
- };
24
-
25
- function formatTag(tag: string, color: (text: string) => string): string {
26
- return color(`[${tag.toUpperCase().padEnd(7)}]`);
27
- }
28
-
29
- function printLogLine(tag: 'debug' | 'info' | 'ok' | 'warn' | 'error', message: string): void {
30
- if (loggerConfig.silent) return;
31
-
32
- switch (tag) {
33
- case 'debug':
34
- console.log(`${formatTag('debug', chalk.gray)} ${chalk.gray(message)}`);
35
- return;
36
- case 'info':
37
- console.log(`${formatTag('info', chalk.blueBright)} ${message}`);
38
- return;
39
- case 'ok':
40
- console.log(`${formatTag('ok', chalk.greenBright)} ${message}`);
41
- return;
42
- case 'warn':
43
- console.log(`${formatTag('warn', chalk.yellowBright)} ${message}`);
44
- return;
45
- case 'error':
46
- console.log(`${formatTag('error', chalk.redBright)} ${message}`);
47
- return;
48
- }
49
- }
50
-
51
- /**
52
- * Initialize logger with configuration
53
- */
54
- export function initLogger(config: Partial<LoggerConfig>): void {
55
- loggerConfig = { ...loggerConfig, ...config };
56
- }
57
-
58
- /**
59
- * Get current logger configuration
60
- */
61
- export function getLoggerConfig(): Readonly<LoggerConfig> {
62
- return { ...loggerConfig };
63
- }
64
-
65
- /**
66
- * Write log message to file if configured
67
- */
68
- async function writeToLogFile(level: LogLevel, message: string): Promise<void> {
69
- if (!loggerConfig.logFile) return;
70
-
71
- try {
72
- const timestamp = new Date().toISOString();
73
- const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
74
- await fsAppendFile(loggerConfig.logFile, logEntry, 'utf-8');
75
- } catch (error) {
76
- // Silently fail on log file errors
77
- }
78
- }
79
-
80
- /**
81
- * Log debug message (only shown with --debug flag)
82
- */
83
- export function logDebug(message: string): void {
84
- if (loggerConfig.debug) {
85
- printLogLine('debug', message);
86
- }
87
- writeToLogFile('debug', message);
88
- }
89
-
90
- /**
91
- * Log verbose message (shown with --verbose or --debug)
92
- */
93
- export function logVerbose(message: string): void {
94
- if (!loggerConfig.silent && (loggerConfig.verbose || loggerConfig.debug)) {
95
- console.log(`${formatTag('verbose', chalk.cyanBright)} ${message}`);
96
- }
97
- writeToLogFile('info', message);
98
- }
99
-
100
- /**
101
- * Log info message (always displayed)
102
- */
103
- export function logInfo(message: string): void {
104
- printLogLine('info', message);
105
- writeToLogFile('info', message);
106
- }
107
-
108
- /**
109
- * Log success message (always displayed)
110
- */
111
- export function logSuccess(message: string): void {
112
- printLogLine('ok', message);
113
- writeToLogFile('info', message);
114
- }
115
-
116
- /**
117
- * Log warning message (always displayed)
118
- */
119
- export function logWarning(message: string): void {
120
- printLogLine('warn', message);
121
- writeToLogFile('warn', message);
122
- }
123
-
124
- /**
125
- * Log error message (always displayed)
126
- */
127
- export function logError(message: string): void {
128
- printLogLine('error', message);
129
- writeToLogFile('error', message);
130
- }
131
-
132
- /**
133
- * Log command execution (debug level)
134
- */
135
- export function logCommand(command: string, args: string[]): void {
136
- logDebug(`Executing: ${command} ${args.join(' ')}`);
137
- }
138
-
139
- /**
140
- * Log file operation (debug level)
141
- */
142
- export function logFileOperation(operation: string, filePath: string): void {
143
- logDebug(`${operation}: ${filePath}`);
144
- }
package/src/utils.ts DELETED
@@ -1,183 +0,0 @@
1
- import chalk from 'chalk';
2
- import { execa } from 'execa';
3
- import fs from 'fs/promises';
4
- import path from 'path';
5
- import figlet from 'figlet';
6
- import { getLoggerConfig } from './utils/logger.js';
7
-
8
- // Re-export logger functions for backward compatibility
9
- export {
10
- logSuccess,
11
- logError,
12
- logWarning,
13
- logInfo,
14
- logDebug,
15
- logVerbose,
16
- logCommand,
17
- logFileOperation,
18
- initLogger,
19
- getLoggerConfig,
20
- } from './utils/logger.js';
21
-
22
- const MAX_TUI_WIDTH = 78;
23
- const MIN_TUI_WIDTH = 52;
24
-
25
- type AccentColor = 'blue' | 'cyan' | 'green' | 'yellow' | 'red' | 'gray';
26
-
27
- function getTerminalWidth(): number {
28
- const columns = process.stdout.columns ?? MAX_TUI_WIDTH;
29
- return Math.max(MIN_TUI_WIDTH, Math.min(MAX_TUI_WIDTH, columns));
30
- }
31
-
32
- function accent(text: string, color: AccentColor): string {
33
- switch (color) {
34
- case 'blue':
35
- return chalk.blueBright(text);
36
- case 'cyan':
37
- return chalk.cyanBright(text);
38
- case 'green':
39
- return chalk.greenBright(text);
40
- case 'yellow':
41
- return chalk.yellowBright(text);
42
- case 'red':
43
- return chalk.redBright(text);
44
- case 'gray':
45
- default:
46
- return chalk.gray(text);
47
- }
48
- }
49
-
50
- export function divider(color: AccentColor = 'gray', char: string = '─'): string {
51
- return accent(char.repeat(getTerminalWidth()), color);
52
- }
53
-
54
- export function printStepHeader(step: number, total: number, title: string): void {
55
- if (getLoggerConfig().silent) return;
56
-
57
- const filled = '■'.repeat(Math.max(0, step));
58
- const empty = '·'.repeat(Math.max(0, total - step));
59
- const progress = chalk.gray(`${filled}${empty}`);
60
-
61
- console.log(`\n${divider()}`);
62
- console.log(`${accent(`[${step}/${total}]`, 'cyan')} ${chalk.whiteBright(title)} ${progress}`);
63
- console.log(divider());
64
- }
65
-
66
- export function printSummaryCard(
67
- title: string,
68
- rows: Array<{ label: string; value: string }>
69
- ): void {
70
- if (getLoggerConfig().silent) return;
71
-
72
- const labelWidth = Math.max(...rows.map(row => row.label.length), 0);
73
-
74
- console.log(`\n${accent(title, 'cyan')}`);
75
- console.log(divider());
76
- for (const row of rows) {
77
- const label = chalk.gray(row.label.padEnd(labelWidth));
78
- console.log(`${label} ${chalk.white(row.value)}`);
79
- }
80
- console.log(`${divider()}\n`);
81
- }
82
-
83
- export function printCommandHints(title: string, commands: string[]): void {
84
- if (getLoggerConfig().silent) return;
85
-
86
- console.log(accent(title, 'cyan'));
87
- for (const command of commands) {
88
- console.log(` ${accent('›', 'gray')} ${chalk.white(command)}`);
89
- }
90
- console.log('');
91
- }
92
-
93
- /**
94
- * Display SimpleNS banner in blue using figlet
95
- */
96
- export function displayBanner(): void {
97
- if (getLoggerConfig().silent) return;
98
-
99
- const simpleNS = figlet.textSync('SimpleNS', {
100
- font: 'Standard',
101
- horizontalLayout: 'default',
102
- });
103
-
104
- const onboard = figlet.textSync('Onboard', {
105
- font: 'Standard',
106
- horizontalLayout: 'default',
107
- });
108
-
109
- console.log('');
110
- console.log(divider('blue', '═'));
111
- console.log(chalk.blueBright(simpleNS));
112
- console.log(chalk.cyanBright(onboard));
113
- console.log(chalk.gray('SimpleNS local setup assistant'));
114
- console.log(divider('blue', '═'));
115
- console.log('');
116
- }
117
-
118
- /**
119
- * Execute a shell command with error handling
120
- */
121
- export async function executeCommand(
122
- command: string,
123
- args: string[],
124
- options?: { cwd?: string; silent?: boolean }
125
- ): Promise<{ stdout: string; stderr: string }> {
126
- // Import dynamically to avoid circular dependency
127
- const { logCommand } = await import('./utils/logger.js');
128
-
129
- logCommand(command, args);
130
-
131
- try {
132
- const result = await execa(command, args, {
133
- cwd: options?.cwd || process.cwd(),
134
- stdio: options?.silent ? 'pipe' : 'inherit',
135
- });
136
- return { stdout: result.stdout, stderr: result.stderr };
137
- } catch (error: any) {
138
- throw new Error(`Command failed: ${command} ${args.join(' ')}\n${error.message}`);
139
- }
140
- }
141
-
142
- /**
143
- * Check if file exists
144
- */
145
- export async function fileExists(filePath: string): Promise<boolean> {
146
- try {
147
- await fs.access(filePath);
148
- return true;
149
- } catch {
150
- return false;
151
- }
152
- }
153
-
154
- /**
155
- * Write file content
156
- */
157
- export async function writeFile(filePath: string, content: string): Promise<void> {
158
- const { logFileOperation } = await import('./utils/logger.js');
159
-
160
- logFileOperation('Writing file', filePath);
161
-
162
- const dir = path.dirname(filePath);
163
- await fs.mkdir(dir, { recursive: true });
164
- await fs.writeFile(filePath, content, 'utf-8');
165
- }
166
-
167
- /**
168
- * Read file content
169
- */
170
- export async function readFile(filePath: string): Promise<string> {
171
- return await fs.readFile(filePath, 'utf-8');
172
- }
173
-
174
- /**
175
- * Append content to file
176
- */
177
- export async function appendFile(filePath: string, content: string): Promise<void> {
178
- // Ensure parent directory exists, similar to writeFile
179
- const dir = path.dirname(filePath);
180
- await fs.mkdir(dir, { recursive: true });
181
-
182
- await fs.appendFile(filePath, content, 'utf-8');
183
- }
package/src/validators.ts DELETED
@@ -1,196 +0,0 @@
1
- import { execa } from 'execa';
2
- import { logSuccess, logWarning } from './utils.js';
3
- import { spinner } from './ui.js';
4
- import {
5
- DockerNotInstalledError,
6
- DockerNotRunningError,
7
- DockerPermissionError,
8
- } from './types/errors.js';
9
- import { VALIDATION } from './config/constants.js';
10
-
11
- export type OSType = 'windows' | 'linux' | 'darwin';
12
-
13
- /**
14
- * Validate a public domain used for ACME/Certbot.
15
- * Accepts FQDN only (no protocol, path, query, port, or wildcard).
16
- */
17
- export function validatePublicDomain(input: string): true | string {
18
- const value = input.trim().toLowerCase();
19
-
20
- if (!value) {
21
- return 'Domain is required';
22
- }
23
-
24
- if (
25
- value.includes('://') ||
26
- value.includes('/') ||
27
- value.includes('?') ||
28
- value.includes('#') ||
29
- value.includes(':') ||
30
- value.includes('*')
31
- ) {
32
- return 'Enter only a domain name (example: app.example.com)';
33
- }
34
-
35
- // RFC-ish practical FQDN validation
36
- const domainRegex =
37
- /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
38
-
39
- if (!domainRegex.test(value)) {
40
- return 'Invalid domain format (example: app.example.com)';
41
- }
42
-
43
- return true;
44
- }
45
-
46
- /**
47
- * Validate email format for Let's Encrypt registration.
48
- */
49
- export function validateEmailAddress(input: string): true | string {
50
- const value = input.trim();
51
-
52
- if (!value) {
53
- return 'Email is required';
54
- }
55
-
56
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
57
- if (!emailRegex.test(value)) {
58
- return 'Invalid email format (example: admin@example.com)';
59
- }
60
-
61
- return true;
62
- }
63
-
64
- /**
65
- * Checks if Docker is installed on the system by running `docker --version`.
66
- *
67
- * @throws {DockerNotInstalledError} When Docker is not found in PATH or not installed
68
- */
69
- export async function checkDockerInstalled(): Promise<void> {
70
- try {
71
- await execa('docker', ['--version']);
72
- } catch (error: unknown) {
73
- throw new DockerNotInstalledError();
74
- }
75
- }
76
-
77
- /**
78
- * Checks if the Docker daemon is running by executing `docker ps`.
79
- * Provides specific error types based on the failure reason.
80
- *
81
- * @throws {DockerPermissionError} When user lacks permissions to access Docker socket
82
- * @throws {DockerNotRunningError} When Docker daemon is not running or unreachable
83
- */
84
- export async function checkDockerRunning(): Promise<void> {
85
- try {
86
- await execa('docker', ['ps']);
87
- } catch (error: unknown) {
88
- const errorMessage = (error as Error).message?.toLowerCase() || '';
89
-
90
- if (errorMessage.includes('permission denied') || errorMessage.includes('eacces')) {
91
- throw new DockerPermissionError();
92
- }
93
-
94
- if (errorMessage.includes('cannot connect') || errorMessage.includes('is the docker daemon running')) {
95
- throw new DockerNotRunningError();
96
- }
97
-
98
- // Generic docker error
99
- throw new DockerNotRunningError();
100
- }
101
- }
102
-
103
- /**
104
- * Detects the operating system platform.
105
- *
106
- * @returns OS type: 'windows', 'darwin' (macOS), or 'linux'
107
- * @note Defaults to 'linux' for unknown platforms
108
- */
109
- export function detectOS(): OSType {
110
- const platform = process.platform;
111
- if (platform === 'win32') return 'windows';
112
- if (platform === 'linux') return 'linux';
113
- if (platform === 'darwin') return 'darwin';
114
- return 'linux'; // Default fallback
115
- }
116
-
117
- /**
118
- * Validates all system prerequisites before starting the onboarding process.
119
- * Checks Docker installation, daemon status, and detects the operating system.
120
- * Uses clack spinners for visual feedback.
121
- *
122
- * @throws {DockerNotInstalledError} If Docker is not installed
123
- * @throws {DockerNotRunningError} If Docker daemon is not running
124
- * @throws {DockerPermissionError} If user lacks Docker permissions
125
- */
126
- export async function validatePrerequisites(): Promise<void> {
127
- logSuccess('Running prerequisite checks...');
128
-
129
- // Check Docker installation
130
- const s = spinner();
131
- s.start('Checking Docker installation...');
132
- try {
133
- await checkDockerInstalled();
134
- s.stop('Docker installation detected');
135
- } catch (error) {
136
- s.error('Docker installation check failed');
137
- throw error;
138
- }
139
-
140
- // Check Docker daemon
141
- s.start('Checking Docker daemon status...');
142
- try {
143
- await checkDockerRunning();
144
- s.stop('Docker daemon is running');
145
- } catch (error) {
146
- s.error('Docker daemon check failed');
147
- throw error;
148
- }
149
-
150
- // Detect OS
151
- const os = detectOS();
152
- logSuccess(`Detected OS: ${os}`);
153
-
154
- if (os === 'linux') {
155
- logWarning('Linux detected: You may need to provide your machine IP for infra services.');
156
- }
157
- }
158
-
159
- /**
160
- * Validates environment variable values based on the variable name/type.
161
- * Performs format-specific validation for URLs, ports, and security credentials.
162
- *
163
- * @param key - Environment variable name (e.g., 'MONGO_URI', 'PORT', 'API_KEY')
164
- * @param value - Value to validate
165
- * @returns `true` if valid, `false` otherwise
166
- */
167
- export function validateEnvValue(key: string, value: string): boolean {
168
- // URL validation
169
- if (key.includes('URL') || key.includes('URI')) {
170
- if (!value) return false;
171
- // Basic URL format check
172
- if (key === 'MONGO_URI' && !value.includes('mongodb://')) {
173
- return false;
174
- }
175
- if (key === 'REDIS_URL' && !value.includes('redis://')) {
176
- return false;
177
- }
178
- }
179
-
180
- // Port validation
181
- if (key.includes('PORT')) {
182
- const port = parseInt(value, 10);
183
- if (isNaN(port) || port < VALIDATION.PORT_MIN || port > VALIDATION.PORT_MAX) {
184
- return false;
185
- }
186
- }
187
-
188
- // API Key validation (should not be empty for security)
189
- if (key.includes('API_KEY') || key.includes('SECRET') || key.includes('PASSWORD')) {
190
- if (!value || value.length < VALIDATION.MIN_PASSWORD_LENGTH) {
191
- return false;
192
- }
193
- }
194
-
195
- return true;
196
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "rootDir": "./src",
7
- "outDir": "./dist",
8
- "esModuleInterop": true,
9
- "resolveJsonModule": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "strict": true,
12
- "skipLibCheck": true,
13
- "declaration": true,
14
- "declarationMap": true,
15
- "sourceMap": true
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
19
- }
package/vitest.config.ts DELETED
@@ -1,22 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: 'node',
7
- include: ['src/**/*.{test,spec}.{js,ts}'],
8
- exclude: ['node_modules/**', 'dist/**'],
9
- coverage: {
10
- provider: 'v8',
11
- reporter: ['text', 'json', 'html'],
12
- exclude: [
13
- 'node_modules/**',
14
- 'dist/**',
15
- '**/*.test.ts',
16
- '**/*.spec.ts',
17
- 'src/templates.ts', // Large constant file
18
- ],
19
- },
20
- testTimeout: 10000,
21
- },
22
- });