suthep 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.
Files changed (50) hide show
  1. package/.editorconfig +17 -0
  2. package/.prettierignore +6 -0
  3. package/.prettierrc +7 -0
  4. package/.vscode/settings.json +19 -0
  5. package/LICENSE +21 -0
  6. package/README.md +214 -0
  7. package/dist/commands/deploy.js +104 -0
  8. package/dist/commands/deploy.js.map +1 -0
  9. package/dist/commands/init.js +188 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/setup.js +90 -0
  12. package/dist/commands/setup.js.map +1 -0
  13. package/dist/index.js +12 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/utils/certbot.js +27 -0
  16. package/dist/utils/certbot.js.map +1 -0
  17. package/dist/utils/config-loader.js +65 -0
  18. package/dist/utils/config-loader.js.map +1 -0
  19. package/dist/utils/deployment.js +52 -0
  20. package/dist/utils/deployment.js.map +1 -0
  21. package/dist/utils/docker.js +57 -0
  22. package/dist/utils/docker.js.map +1 -0
  23. package/dist/utils/nginx.js +154 -0
  24. package/dist/utils/nginx.js.map +1 -0
  25. package/docs/README.md +62 -0
  26. package/docs/api-reference.md +545 -0
  27. package/docs/architecture.md +367 -0
  28. package/docs/commands.md +273 -0
  29. package/docs/configuration.md +347 -0
  30. package/docs/examples.md +537 -0
  31. package/docs/getting-started.md +197 -0
  32. package/docs/troubleshooting.md +441 -0
  33. package/example/README.md +81 -0
  34. package/example/docker-compose.yml +72 -0
  35. package/example/suthep.yml +31 -0
  36. package/package.json +45 -0
  37. package/src/commands/deploy.ts +133 -0
  38. package/src/commands/init.ts +214 -0
  39. package/src/commands/setup.ts +112 -0
  40. package/src/index.ts +34 -0
  41. package/src/types/config.ts +51 -0
  42. package/src/utils/certbot.ts +82 -0
  43. package/src/utils/config-loader.ts +81 -0
  44. package/src/utils/deployment.ts +132 -0
  45. package/src/utils/docker.ts +151 -0
  46. package/src/utils/nginx.ts +143 -0
  47. package/suthep.example.yml +69 -0
  48. package/todo.md +6 -0
  49. package/tsconfig.json +26 -0
  50. package/vite.config.ts +46 -0
@@ -0,0 +1,81 @@
1
+ import fs from 'fs-extra'
2
+ import yaml from 'js-yaml'
3
+ import type { DeployConfig } from '../types/config'
4
+
5
+ /**
6
+ * Load and parse a YAML configuration file
7
+ */
8
+ export async function loadConfig(filePath: string): Promise<DeployConfig> {
9
+ try {
10
+ const fileContent = await fs.readFile(filePath, 'utf8')
11
+ const config = yaml.load(fileContent) as DeployConfig
12
+
13
+ validateConfig(config)
14
+
15
+ return config
16
+ } catch (error) {
17
+ if (error instanceof Error) {
18
+ throw new Error(`Failed to load configuration from ${filePath}: ${error.message}`)
19
+ }
20
+ throw error
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Validate the configuration object
26
+ */
27
+ function validateConfig(config: any): asserts config is DeployConfig {
28
+ if (!config.project || !config.project.name) {
29
+ throw new Error('Configuration must include project.name')
30
+ }
31
+
32
+ if (!config.services || !Array.isArray(config.services) || config.services.length === 0) {
33
+ throw new Error('Configuration must include at least one service')
34
+ }
35
+
36
+ for (const service of config.services) {
37
+ if (!service.name) {
38
+ throw new Error('Each service must have a name')
39
+ }
40
+ if (!service.port) {
41
+ throw new Error(`Service ${service.name} must have a port`)
42
+ }
43
+ if (!service.domains || !Array.isArray(service.domains) || service.domains.length === 0) {
44
+ throw new Error(`Service ${service.name} must have at least one domain`)
45
+ }
46
+ }
47
+
48
+ if (!config.nginx) {
49
+ config.nginx = {
50
+ configPath: '/etc/nginx/sites-available',
51
+ reloadCommand: 'sudo nginx -t && sudo systemctl reload nginx',
52
+ }
53
+ }
54
+
55
+ if (!config.certbot) {
56
+ config.certbot = {
57
+ email: '',
58
+ staging: false,
59
+ }
60
+ }
61
+
62
+ if (!config.deployment) {
63
+ config.deployment = {
64
+ strategy: 'rolling',
65
+ healthCheckTimeout: 30000,
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Save configuration to a YAML file
72
+ */
73
+ export async function saveConfig(filePath: string, config: DeployConfig): Promise<void> {
74
+ const yamlContent = yaml.dump(config, {
75
+ indent: 2,
76
+ lineWidth: 120,
77
+ noRefs: true,
78
+ })
79
+
80
+ await fs.writeFile(filePath, yamlContent, 'utf8')
81
+ }
@@ -0,0 +1,132 @@
1
+ import type { DeploymentConfig, ServiceConfig } from '../types/config'
2
+
3
+ /**
4
+ * Perform a health check on a service endpoint
5
+ */
6
+ export async function performHealthCheck(url: string, timeout: number = 30000): Promise<boolean> {
7
+ const startTime = Date.now()
8
+ const interval = 2000 // Check every 2 seconds
9
+
10
+ while (Date.now() - startTime < timeout) {
11
+ try {
12
+ const response = await fetch(url, {
13
+ method: 'GET',
14
+ signal: AbortSignal.timeout(5000), // 5 second timeout per request
15
+ })
16
+
17
+ if (response.ok) {
18
+ return true
19
+ }
20
+ } catch (error) {
21
+ // Endpoint not ready yet, continue waiting
22
+ }
23
+
24
+ // Wait before next check
25
+ await new Promise((resolve) => setTimeout(resolve, interval))
26
+ }
27
+
28
+ return false
29
+ }
30
+
31
+ /**
32
+ * Deploy a service with zero-downtime strategy
33
+ */
34
+ export async function deployService(
35
+ service: ServiceConfig,
36
+ deploymentConfig: DeploymentConfig
37
+ ): Promise<void> {
38
+ if (deploymentConfig.strategy === 'rolling') {
39
+ await rollingDeploy(service, deploymentConfig)
40
+ } else if (deploymentConfig.strategy === 'blue-green') {
41
+ await blueGreenDeploy(service, deploymentConfig)
42
+ } else {
43
+ throw new Error(`Unknown deployment strategy: ${deploymentConfig.strategy}`)
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Rolling deployment strategy
49
+ * Gradually replaces old instances with new ones
50
+ */
51
+ async function rollingDeploy(
52
+ service: ServiceConfig,
53
+ deploymentConfig: DeploymentConfig
54
+ ): Promise<void> {
55
+ // For rolling deployment:
56
+ // 1. Start new service instance
57
+ // 2. Wait for health check
58
+ // 3. Switch traffic to new instance
59
+ // 4. Stop old instance (if applicable)
60
+
61
+ // In this implementation, we assume the service is already running
62
+ // and we're just verifying it's healthy before proceeding
63
+
64
+ if (service.healthCheck) {
65
+ const healthUrl = `http://localhost:${service.port}${service.healthCheck.path}`
66
+ const isHealthy = await performHealthCheck(healthUrl, deploymentConfig.healthCheckTimeout)
67
+
68
+ if (!isHealthy) {
69
+ throw new Error(`Service ${service.name} failed health check during rolling deployment`)
70
+ }
71
+ }
72
+
73
+ // Add a small delay to ensure service is fully ready
74
+ await new Promise((resolve) => setTimeout(resolve, 2000))
75
+ }
76
+
77
+ /**
78
+ * Blue-green deployment strategy
79
+ * Maintains two identical environments and switches between them
80
+ */
81
+ async function blueGreenDeploy(
82
+ service: ServiceConfig,
83
+ deploymentConfig: DeploymentConfig
84
+ ): Promise<void> {
85
+ // For blue-green deployment:
86
+ // 1. Deploy to "green" environment
87
+ // 2. Run health checks on green
88
+ // 3. Switch router/load balancer to green
89
+ // 4. Keep blue as backup
90
+
91
+ // This is a simplified implementation
92
+ // In production, you'd manage multiple service instances
93
+
94
+ if (service.healthCheck) {
95
+ const healthUrl = `http://localhost:${service.port}${service.healthCheck.path}`
96
+ const isHealthy = await performHealthCheck(healthUrl, deploymentConfig.healthCheckTimeout)
97
+
98
+ if (!isHealthy) {
99
+ throw new Error(`Service ${service.name} failed health check during blue-green deployment`)
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Wait for a service to become healthy
106
+ */
107
+ export async function waitForService(
108
+ service: ServiceConfig,
109
+ timeout: number = 60000
110
+ ): Promise<boolean> {
111
+ if (!service.healthCheck) {
112
+ // No health check configured, assume service is ready after a short delay
113
+ await new Promise((resolve) => setTimeout(resolve, 5000))
114
+ return true
115
+ }
116
+
117
+ const healthUrl = `http://localhost:${service.port}${service.healthCheck.path}`
118
+ return await performHealthCheck(healthUrl, timeout)
119
+ }
120
+
121
+ /**
122
+ * Gracefully shutdown a service
123
+ */
124
+ export async function gracefulShutdown(
125
+ _service: ServiceConfig,
126
+ timeout: number = 30000
127
+ ): Promise<void> {
128
+ // Send shutdown signal and wait for graceful termination
129
+ // This is a placeholder - actual implementation would depend on how services are managed
130
+
131
+ await new Promise((resolve) => setTimeout(resolve, Math.min(timeout, 5000)))
132
+ }
@@ -0,0 +1,151 @@
1
+ import { execa } from 'execa'
2
+ import type { ServiceConfig } from '../types/config'
3
+
4
+ /**
5
+ * Start or connect to a Docker container for a service
6
+ */
7
+ export async function startDockerContainer(service: ServiceConfig): Promise<void> {
8
+ if (!service.docker) {
9
+ return
10
+ }
11
+
12
+ const { image, container, port } = service.docker
13
+
14
+ try {
15
+ // Check if container exists
16
+ const { stdout: existingContainers } = await execa('docker', [
17
+ 'ps',
18
+ '-a',
19
+ '--filter',
20
+ `name=${container}`,
21
+ '--format',
22
+ '{{.Names}}',
23
+ ])
24
+
25
+ if (existingContainers.includes(container)) {
26
+ // Container exists, check if it's running
27
+ const { stdout: runningContainers } = await execa('docker', [
28
+ 'ps',
29
+ '--filter',
30
+ `name=${container}`,
31
+ '--format',
32
+ '{{.Names}}',
33
+ ])
34
+
35
+ if (!runningContainers.includes(container)) {
36
+ // Container exists but not running, start it
37
+ await execa('docker', ['start', container])
38
+ }
39
+ } else if (image) {
40
+ // Container doesn't exist and image is provided, create and run it
41
+ const args = [
42
+ 'run',
43
+ '-d',
44
+ '--name',
45
+ container,
46
+ '-p',
47
+ `${service.port}:${port}`,
48
+ '--restart',
49
+ 'unless-stopped',
50
+ ]
51
+
52
+ // Add environment variables if configured
53
+ if (service.environment) {
54
+ for (const [key, value] of Object.entries(service.environment)) {
55
+ args.push('-e', `${key}=${value}`)
56
+ }
57
+ }
58
+
59
+ args.push(image)
60
+
61
+ await execa('docker', args)
62
+ } else {
63
+ throw new Error(`Container ${container} not found and no image specified`)
64
+ }
65
+ } catch (error) {
66
+ throw new Error(
67
+ `Failed to start Docker container: ${error instanceof Error ? error.message : error}`
68
+ )
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Stop a Docker container
74
+ */
75
+ export async function stopDockerContainer(containerName: string): Promise<void> {
76
+ try {
77
+ await execa('docker', ['stop', containerName])
78
+ } catch (error) {
79
+ throw new Error(
80
+ `Failed to stop container ${containerName}: ${error instanceof Error ? error.message : error}`
81
+ )
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Remove a Docker container
87
+ */
88
+ export async function removeDockerContainer(containerName: string): Promise<void> {
89
+ try {
90
+ await execa('docker', ['rm', '-f', containerName])
91
+ } catch (error) {
92
+ throw new Error(
93
+ `Failed to remove container ${containerName}: ${
94
+ error instanceof Error ? error.message : error
95
+ }`
96
+ )
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Check if a Docker container is running
102
+ */
103
+ export async function isContainerRunning(containerName: string): Promise<boolean> {
104
+ try {
105
+ const { stdout } = await execa('docker', [
106
+ 'ps',
107
+ '--filter',
108
+ `name=${containerName}`,
109
+ '--format',
110
+ '{{.Names}}',
111
+ ])
112
+ return stdout.includes(containerName)
113
+ } catch (error) {
114
+ return false
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Get container logs
120
+ */
121
+ export async function getContainerLogs(
122
+ containerName: string,
123
+ lines: number = 100
124
+ ): Promise<string> {
125
+ try {
126
+ const { stdout } = await execa('docker', ['logs', '--tail', lines.toString(), containerName])
127
+ return stdout
128
+ } catch (error) {
129
+ throw new Error(
130
+ `Failed to get logs for container ${containerName}: ${
131
+ error instanceof Error ? error.message : error
132
+ }`
133
+ )
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Inspect a Docker container
139
+ */
140
+ export async function inspectContainer(containerName: string): Promise<any> {
141
+ try {
142
+ const { stdout } = await execa('docker', ['inspect', containerName])
143
+ return JSON.parse(stdout)[0]
144
+ } catch (error) {
145
+ throw new Error(
146
+ `Failed to inspect container ${containerName}: ${
147
+ error instanceof Error ? error.message : error
148
+ }`
149
+ )
150
+ }
151
+ }
@@ -0,0 +1,143 @@
1
+ import { execa } from 'execa'
2
+ import fs from 'fs-extra'
3
+ import path from 'path'
4
+ import type { ServiceConfig } from '../types/config'
5
+
6
+ /**
7
+ * Generate Nginx server block configuration for a service
8
+ */
9
+ export function generateNginxConfig(service: ServiceConfig, withHttps: boolean): string {
10
+ const serverNames = service.domains.join(' ')
11
+ const upstreamName = `${service.name}_backend`
12
+
13
+ let config = `# Nginx configuration for ${service.name}\n\n`
14
+
15
+ // Upstream configuration
16
+ config += `upstream ${upstreamName} {\n`
17
+ config += ` server localhost:${service.port} max_fails=3 fail_timeout=30s;\n`
18
+ config += ` keepalive 32;\n`
19
+ config += `}\n\n`
20
+
21
+ if (withHttps) {
22
+ // HTTP server - redirect to HTTPS
23
+ config += `server {\n`
24
+ config += ` listen 80;\n`
25
+ config += ` listen [::]:80;\n`
26
+ config += ` server_name ${serverNames};\n\n`
27
+ config += ` # Redirect all HTTP to HTTPS\n`
28
+ config += ` return 301 https://$server_name$request_uri;\n`
29
+ config += `}\n\n`
30
+
31
+ // HTTPS server
32
+ config += `server {\n`
33
+ config += ` listen 443 ssl http2;\n`
34
+ config += ` listen [::]:443 ssl http2;\n`
35
+ config += ` server_name ${serverNames};\n\n`
36
+
37
+ // SSL configuration
38
+ const primaryDomain = service.domains[0]
39
+ config += ` # SSL Configuration\n`
40
+ config += ` ssl_certificate /etc/letsencrypt/live/${primaryDomain}/fullchain.pem;\n`
41
+ config += ` ssl_certificate_key /etc/letsencrypt/live/${primaryDomain}/privkey.pem;\n`
42
+ config += ` ssl_protocols TLSv1.2 TLSv1.3;\n`
43
+ config += ` ssl_ciphers HIGH:!aNULL:!MD5;\n`
44
+ config += ` ssl_prefer_server_ciphers on;\n\n`
45
+ } else {
46
+ // HTTP only server
47
+ config += `server {\n`
48
+ config += ` listen 80;\n`
49
+ config += ` listen [::]:80;\n`
50
+ config += ` server_name ${serverNames};\n\n`
51
+ }
52
+
53
+ // Logging
54
+ config += ` # Logging\n`
55
+ config += ` access_log /var/log/nginx/${service.name}_access.log;\n`
56
+ config += ` error_log /var/log/nginx/${service.name}_error.log;\n\n`
57
+
58
+ // Client settings
59
+ config += ` # Client settings\n`
60
+ config += ` client_max_body_size 100M;\n\n`
61
+
62
+ // Proxy settings
63
+ config += ` # Proxy settings\n`
64
+ config += ` location / {\n`
65
+ config += ` proxy_pass http://${upstreamName};\n`
66
+ config += ` proxy_http_version 1.1;\n`
67
+ config += ` proxy_set_header Upgrade $http_upgrade;\n`
68
+ config += ` proxy_set_header Connection 'upgrade';\n`
69
+ config += ` proxy_set_header Host $host;\n`
70
+ config += ` proxy_set_header X-Real-IP $remote_addr;\n`
71
+ config += ` proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n`
72
+ config += ` proxy_set_header X-Forwarded-Proto $scheme;\n`
73
+ config += ` proxy_cache_bypass $http_upgrade;\n`
74
+ config += ` proxy_connect_timeout 60s;\n`
75
+ config += ` proxy_send_timeout 60s;\n`
76
+ config += ` proxy_read_timeout 60s;\n`
77
+ config += ` }\n`
78
+
79
+ // Health check endpoint (if configured)
80
+ if (service.healthCheck) {
81
+ config += `\n # Health check endpoint\n`
82
+ config += ` location ${service.healthCheck.path} {\n`
83
+ config += ` proxy_pass http://${upstreamName};\n`
84
+ config += ` access_log off;\n`
85
+ config += ` }\n`
86
+ }
87
+
88
+ config += `}\n`
89
+
90
+ return config
91
+ }
92
+
93
+ /**
94
+ * Enable an Nginx site by creating a symbolic link
95
+ */
96
+ export async function enableSite(siteName: string, configPath: string): Promise<void> {
97
+ const availablePath = path.join(configPath, `${siteName}.conf`)
98
+ const enabledPath = availablePath.replace('sites-available', 'sites-enabled')
99
+
100
+ // Create sites-enabled directory if it doesn't exist
101
+ await fs.ensureDir(path.dirname(enabledPath))
102
+
103
+ // Remove existing symlink if present
104
+ if (await fs.pathExists(enabledPath)) {
105
+ await fs.remove(enabledPath)
106
+ }
107
+
108
+ // Create symlink
109
+ await execa('sudo', ['ln', '-sf', availablePath, enabledPath])
110
+ }
111
+
112
+ /**
113
+ * Test and reload Nginx configuration
114
+ */
115
+ export async function reloadNginx(reloadCommand: string): Promise<void> {
116
+ try {
117
+ // Test configuration first
118
+ await execa('sudo', ['nginx', '-t'])
119
+
120
+ // Reload Nginx
121
+ const parts = reloadCommand.split(' ')
122
+ if (parts.length > 0) {
123
+ // Simple execution of provided command
124
+ await execa(parts[0], parts.slice(1), { shell: true })
125
+ }
126
+ } catch (error) {
127
+ throw new Error(`Failed to reload Nginx: ${error instanceof Error ? error.message : error}`)
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Disable an Nginx site
133
+ */
134
+ export async function disableSite(siteName: string, configPath: string): Promise<void> {
135
+ const enabledPath = path.join(
136
+ configPath.replace('sites-available', 'sites-enabled'),
137
+ `${siteName}.conf`
138
+ )
139
+
140
+ if (await fs.pathExists(enabledPath)) {
141
+ await fs.remove(enabledPath)
142
+ }
143
+ }
@@ -0,0 +1,69 @@
1
+ project:
2
+ name: my-app
3
+ version: 1.0.0
4
+
5
+ services:
6
+ # Example 1: Simple Node.js service
7
+ - name: api
8
+ port: 3000
9
+ domains:
10
+ - api.example.com
11
+ - www.api.example.com
12
+ healthCheck:
13
+ path: /health
14
+ interval: 30
15
+ environment:
16
+ NODE_ENV: production
17
+ PORT: 3000
18
+
19
+ # Example 2: Docker container service
20
+ - name: webapp
21
+ port: 8080
22
+ docker:
23
+ image: nginx:latest
24
+ container: webapp-container
25
+ port: 80
26
+ domains:
27
+ - example.com
28
+ - www.example.com
29
+ healthCheck:
30
+ path: /
31
+ interval: 30
32
+
33
+ # Example 3: Multiple subdomains with Docker
34
+ - name: dashboard
35
+ port: 5000
36
+ docker:
37
+ image: myapp/dashboard:latest
38
+ container: dashboard-container
39
+ port: 5000
40
+ domains:
41
+ - dashboard.example.com
42
+ - admin.example.com
43
+ healthCheck:
44
+ path: /api/health
45
+ interval: 60
46
+ environment:
47
+ DATABASE_URL: postgresql://localhost:5432/dashboard
48
+ REDIS_URL: redis://localhost:6379
49
+
50
+ # Example 4: Service connecting to existing Docker container
51
+ - name: database-proxy
52
+ port: 5432
53
+ docker:
54
+ container: postgres-container
55
+ port: 5432
56
+ domains:
57
+ - db.example.com
58
+
59
+ nginx:
60
+ configPath: /etc/nginx/sites-available
61
+ reloadCommand: sudo nginx -t && sudo systemctl reload nginx
62
+
63
+ certbot:
64
+ email: admin@example.com
65
+ staging: false # Set to true for testing
66
+
67
+ deployment:
68
+ strategy: rolling # Options: rolling, blue-green
69
+ healthCheckTimeout: 30000 # milliseconds
package/todo.md ADDED
@@ -0,0 +1,6 @@
1
+ build tool deploy and run project in yml service config service port to nginx multiple domain name or sub domain and can connect docker port
2
+ ✔ Automatic Nginx reverse proxy setup
3
+ ✔ Automatic HTTPS with Certbot
4
+ ✔ Zero-downtime deploy
5
+ make it in cli https://www.npmjs.com/package/commander typescript to build create project and setup by yml nginx
6
+ in save cost in run vm
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
7
+ "types": ["vite/client"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["src"]
26
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { resolve } from 'path'
2
+ import type { Plugin } from 'vite'
3
+ import { defineConfig } from 'vite'
4
+
5
+ const addShebangPlugin = (): Plugin => ({
6
+ name: 'add-shebang',
7
+ generateBundle(_options, bundle) {
8
+ if (bundle['index.js']) {
9
+ const chunk = bundle['index.js']
10
+ if (chunk.type === 'chunk') {
11
+ chunk.code = '#!/usr/bin/env node\n' + chunk.code
12
+ }
13
+ }
14
+ },
15
+ })
16
+
17
+ export default defineConfig({
18
+ plugins: [addShebangPlugin()],
19
+ build: {
20
+ outDir: 'dist',
21
+ lib: {
22
+ entry: resolve(__dirname, 'src/index.ts'),
23
+ formats: ['es'],
24
+ fileName: 'index',
25
+ },
26
+ rollupOptions: {
27
+ external: (id) => {
28
+ // Externalize all node_modules and Node.js built-ins
29
+ return (
30
+ !id.startsWith('.') &&
31
+ !id.startsWith('/') &&
32
+ !resolve(__dirname, id).startsWith(__dirname + '/src')
33
+ )
34
+ },
35
+ output: {
36
+ format: 'es',
37
+ entryFileNames: '[name].js',
38
+ preserveModules: true,
39
+ preserveModulesRoot: 'src',
40
+ },
41
+ },
42
+ target: 'node18',
43
+ minify: false,
44
+ sourcemap: true,
45
+ },
46
+ })