hostfn 0.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/LICENSE +21 -0
- package/README.md +1136 -0
- package/_conduct/specs/1.v0.spec.md +1041 -0
- package/examples/express-api/package.json +22 -0
- package/examples/express-api/src/index.ts +16 -0
- package/examples/express-api/tsconfig.json +11 -0
- package/examples/github-actions-deploy.yml +40 -0
- package/examples/monorepo-config.json +76 -0
- package/examples/monorepo-multi-server-config.json +74 -0
- package/package.json +39 -0
- package/packages/cli/package.json +40 -0
- package/packages/cli/src/__tests__/core/backup.test.ts +137 -0
- package/packages/cli/src/__tests__/core/health.test.ts +125 -0
- package/packages/cli/src/__tests__/core/lock.test.ts +173 -0
- package/packages/cli/src/__tests__/core/nginx-multi-domain.test.ts +176 -0
- package/packages/cli/src/__tests__/runtimes/pm2.test.ts +130 -0
- package/packages/cli/src/__tests__/utils/validation.test.ts +164 -0
- package/packages/cli/src/commands/deploy.ts +817 -0
- package/packages/cli/src/commands/env.ts +391 -0
- package/packages/cli/src/commands/expose.ts +438 -0
- package/packages/cli/src/commands/init.ts +192 -0
- package/packages/cli/src/commands/logs.ts +106 -0
- package/packages/cli/src/commands/rollback.ts +142 -0
- package/packages/cli/src/commands/server/info.ts +131 -0
- package/packages/cli/src/commands/server/setup.ts +200 -0
- package/packages/cli/src/commands/status.ts +149 -0
- package/packages/cli/src/config/loader.ts +66 -0
- package/packages/cli/src/config/schema.ts +140 -0
- package/packages/cli/src/core/backup.ts +128 -0
- package/packages/cli/src/core/health.ts +116 -0
- package/packages/cli/src/core/local.ts +67 -0
- package/packages/cli/src/core/lock.ts +108 -0
- package/packages/cli/src/core/nginx.ts +170 -0
- package/packages/cli/src/core/ssh.ts +335 -0
- package/packages/cli/src/core/sync.ts +138 -0
- package/packages/cli/src/core/workspace.ts +180 -0
- package/packages/cli/src/index.ts +240 -0
- package/packages/cli/src/runtimes/base.ts +144 -0
- package/packages/cli/src/runtimes/nodejs/detector.ts +157 -0
- package/packages/cli/src/runtimes/nodejs/index.ts +228 -0
- package/packages/cli/src/runtimes/nodejs/pm2.ts +71 -0
- package/packages/cli/src/runtimes/registry.ts +76 -0
- package/packages/cli/src/utils/logger.ts +86 -0
- package/packages/cli/src/utils/validation.ts +147 -0
- package/packages/cli/tsconfig.json +25 -0
- package/packages/cli/vitest.config.ts +19 -0
- package/turbo.json +24 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Logger } from './logger.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validation utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate SSH connection string format
|
|
9
|
+
*/
|
|
10
|
+
export function validateSSHConnection(connectionString: string): boolean {
|
|
11
|
+
const regex = /^([^@]+)@([^:]+)(?::(\d+))?$/;
|
|
12
|
+
|
|
13
|
+
if (!regex.test(connectionString)) {
|
|
14
|
+
Logger.error(`Invalid SSH connection string: ${connectionString}`);
|
|
15
|
+
Logger.info('Expected format: user@host or user@host:port');
|
|
16
|
+
Logger.info('Examples:');
|
|
17
|
+
Logger.log(' ubuntu@123.45.67.89');
|
|
18
|
+
Logger.log(' ubuntu@myserver.com:2222');
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate HTTP URL format
|
|
27
|
+
*/
|
|
28
|
+
export function validateHttpUrl(url: string): boolean {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(url);
|
|
31
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
32
|
+
Logger.error(`Invalid URL protocol: ${parsed.protocol}`);
|
|
33
|
+
Logger.info('Expected: http:// or https://');
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
Logger.error(`Invalid URL format: ${url}`);
|
|
39
|
+
Logger.info('Expected format: http://host:port/path');
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate environment name
|
|
46
|
+
*/
|
|
47
|
+
export function validateEnvironmentName(name: string): boolean {
|
|
48
|
+
const regex = /^[a-zA-Z0-9_-]+$/;
|
|
49
|
+
|
|
50
|
+
if (!regex.test(name)) {
|
|
51
|
+
Logger.error(`Invalid environment name: ${name}`);
|
|
52
|
+
Logger.info('Environment names can only contain: a-z, A-Z, 0-9, _, -');
|
|
53
|
+
Logger.info('Examples: production, staging, dev, prod-eu');
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate port number
|
|
62
|
+
*/
|
|
63
|
+
export function validatePort(port: number): boolean {
|
|
64
|
+
if (port < 1 || port > 65535) {
|
|
65
|
+
Logger.error(`Invalid port number: ${port}`);
|
|
66
|
+
Logger.info('Port must be between 1 and 65535');
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (port < 1024) {
|
|
71
|
+
Logger.warn(`Port ${port} is privileged (< 1024) - may require sudo`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate Node.js version format
|
|
79
|
+
*/
|
|
80
|
+
export function validateNodeVersion(version: string): boolean {
|
|
81
|
+
const regex = /^\d+(\.\d+)?(\.\d+)?$/;
|
|
82
|
+
|
|
83
|
+
if (!regex.test(version)) {
|
|
84
|
+
Logger.error(`Invalid Node.js version: ${version}`);
|
|
85
|
+
Logger.info('Expected format: 18 or 18.19 or 18.19.0');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const major = parseInt(version.split('.')[0]);
|
|
90
|
+
if (major < 14) {
|
|
91
|
+
Logger.warn(`Node.js ${version} is very old - consider using 18 or 20`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate application name
|
|
99
|
+
*/
|
|
100
|
+
export function validateAppName(name: string): boolean {
|
|
101
|
+
const regex = /^[a-zA-Z0-9_-]+$/;
|
|
102
|
+
|
|
103
|
+
if (!regex.test(name)) {
|
|
104
|
+
Logger.error(`Invalid application name: ${name}`);
|
|
105
|
+
Logger.info('Names can only contain: a-z, A-Z, 0-9, _, -');
|
|
106
|
+
Logger.info('Examples: my-app, api_server, webapp-v2');
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (name.length < 2) {
|
|
111
|
+
Logger.error('Application name must be at least 2 characters');
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (name.length > 50) {
|
|
116
|
+
Logger.error('Application name must be less than 50 characters');
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validate remote directory path
|
|
125
|
+
*/
|
|
126
|
+
export function validateRemotePath(path: string): boolean {
|
|
127
|
+
if (!path.startsWith('/')) {
|
|
128
|
+
Logger.error(`Remote path must be absolute: ${path}`);
|
|
129
|
+
Logger.info('Example: /var/www/my-app');
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (path.includes('..')) {
|
|
134
|
+
Logger.error('Remote path cannot contain ..');
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Warn about dangerous paths
|
|
139
|
+
const dangerousPaths = ['/', '/etc', '/bin', '/usr', '/var/log', '/root', '/home'];
|
|
140
|
+
if (dangerousPaths.includes(path)) {
|
|
141
|
+
Logger.error(`Refusing to deploy to system directory: ${path}`);
|
|
142
|
+
Logger.info('Use a subdirectory like /var/www/my-app');
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"outDir": "./dist",
|
|
17
|
+
"rootDir": "./src",
|
|
18
|
+
"baseUrl": "./src",
|
|
19
|
+
"paths": {
|
|
20
|
+
"@/*": ["./*"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["node_modules", "dist"]
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: 'v8',
|
|
9
|
+
reporter: ['text', 'json', 'html'],
|
|
10
|
+
exclude: [
|
|
11
|
+
'node_modules/**',
|
|
12
|
+
'dist/**',
|
|
13
|
+
'**/*.d.ts',
|
|
14
|
+
'**/*.config.*',
|
|
15
|
+
'**/index.ts',
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
package/turbo.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://turbo.build/schema.json",
|
|
3
|
+
"globalDependencies": ["**/.env.*local"],
|
|
4
|
+
"pipeline": {
|
|
5
|
+
"build": {
|
|
6
|
+
"dependsOn": ["^build"],
|
|
7
|
+
"outputs": ["dist/**"]
|
|
8
|
+
},
|
|
9
|
+
"dev": {
|
|
10
|
+
"cache": false,
|
|
11
|
+
"persistent": true
|
|
12
|
+
},
|
|
13
|
+
"lint": {
|
|
14
|
+
"outputs": []
|
|
15
|
+
},
|
|
16
|
+
"test": {
|
|
17
|
+
"dependsOn": ["build"],
|
|
18
|
+
"outputs": []
|
|
19
|
+
},
|
|
20
|
+
"clean": {
|
|
21
|
+
"cache": false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|