@kithinji/pod 1.0.20 → 1.0.22
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 +439 -0
- package/dist/main.js +109 -2
- package/dist/main.js.map +2 -2
- package/dist/types/docker/docker.d.ts.map +1 -1
- package/package.json +31 -8
- package/build.js +0 -22
- package/src/add/component/component.ts +0 -496
- package/src/add/component/index.ts +0 -1
- package/src/add/index.ts +0 -3
- package/src/add/module/index.ts +0 -1
- package/src/add/module/module.ts +0 -545
- package/src/add/new/index.ts +0 -198
- package/src/config/config.ts +0 -141
- package/src/config/index.ts +0 -1
- package/src/deploy/deploy.ts +0 -592
- package/src/deploy/index.ts +0 -1
- package/src/dev/index.ts +0 -1
- package/src/dev/project.ts +0 -45
- package/src/dev/server.ts +0 -191
- package/src/docker/docker.ts +0 -592
- package/src/docker/index.ts +0 -1
- package/src/macros/expand_macros.ts +0 -791
- package/src/macros/index.ts +0 -2
- package/src/macros/macro_executer.ts +0 -189
- package/src/main.ts +0 -106
- package/src/plugins/analyzers/graph.ts +0 -279
- package/src/plugins/css/index.ts +0 -25
- package/src/plugins/generators/generate_controller.ts +0 -308
- package/src/plugins/generators/generate_rsc.ts +0 -274
- package/src/plugins/generators/generate_server_component.ts +0 -455
- package/src/plugins/generators/tsx_server_stub.ts +0 -315
- package/src/plugins/index.ts +0 -3
- package/src/plugins/my.ts +0 -282
- package/src/plugins/transformers/j2d.ts +0 -1080
- package/src/store/index.ts +0 -1
- package/src/store/store.ts +0 -44
- package/src/utils/cases.ts +0 -15
- package/src/utils/create.ts +0 -26
- package/src/utils/index.ts +0 -2
- package/tsconfig.json +0 -27
package/src/docker/docker.ts
DELETED
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
import fs from "fs-extra";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import prompts from "prompts";
|
|
4
|
-
import yaml from "js-yaml";
|
|
5
|
-
|
|
6
|
-
interface PackageJson {
|
|
7
|
-
name: string;
|
|
8
|
-
dependencies?: Record<string, string>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface DockerService {
|
|
12
|
-
name: string;
|
|
13
|
-
needsTunnel?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function dockerize(env: "dev" | "prod" = "prod") {
|
|
17
|
-
const cwd = process.cwd();
|
|
18
|
-
const packageJsonPath = path.join(cwd, "package.json");
|
|
19
|
-
|
|
20
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
21
|
-
throw new Error("package.json not found. Are you in a Pod project?");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const packageJson: PackageJson = await fs.readJSON(packageJsonPath);
|
|
25
|
-
const projectName = packageJson.name;
|
|
26
|
-
|
|
27
|
-
const detectedServices = detectServices(packageJson);
|
|
28
|
-
const selectedServices = await selectServices(detectedServices);
|
|
29
|
-
|
|
30
|
-
await restructureProject(cwd, projectName);
|
|
31
|
-
await createDockerfile(cwd, projectName);
|
|
32
|
-
|
|
33
|
-
if (env === "prod") {
|
|
34
|
-
await setupProduction(cwd, projectName, selectedServices);
|
|
35
|
-
} else {
|
|
36
|
-
await setupDevelopment(cwd, projectName, selectedServices);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Write environment variables to .env if they don't exist
|
|
40
|
-
await writeEnvVars(cwd, selectedServices, env);
|
|
41
|
-
|
|
42
|
-
printNextSteps(projectName, env, selectedServices);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function detectServices(packageJson: PackageJson): DockerService[] {
|
|
46
|
-
const deps = packageJson.dependencies || {};
|
|
47
|
-
const services: DockerService[] = [];
|
|
48
|
-
|
|
49
|
-
if (deps.pg || deps.postgres) services.push({ name: "postgres" });
|
|
50
|
-
if (deps.mysql || deps.mysql2) services.push({ name: "mysql" });
|
|
51
|
-
if (deps.redis || deps.ioredis) services.push({ name: "redis" });
|
|
52
|
-
if (deps.mongodb || deps.mongoose) services.push({ name: "mongodb" });
|
|
53
|
-
|
|
54
|
-
return services;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function selectServices(
|
|
58
|
-
detected: DockerService[]
|
|
59
|
-
): Promise<DockerService[]> {
|
|
60
|
-
if (detected.length === 0) return [];
|
|
61
|
-
|
|
62
|
-
const response = await prompts({
|
|
63
|
-
type: "multiselect",
|
|
64
|
-
name: "services",
|
|
65
|
-
message: "Select services to include:",
|
|
66
|
-
choices: detected.map((s) => ({
|
|
67
|
-
title: s.name,
|
|
68
|
-
value: s.name,
|
|
69
|
-
selected: true,
|
|
70
|
-
})),
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
if (!response.services || response.services.length === 0) return [];
|
|
74
|
-
return detected.filter((s) => response.services.includes(s.name));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function restructureProject(cwd: string, projectName: string) {
|
|
78
|
-
const nestedDir = path.join(cwd, projectName);
|
|
79
|
-
|
|
80
|
-
if (fs.existsSync(nestedDir)) {
|
|
81
|
-
console.log("⚠️ Project already restructured, skipping...");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
await fs.ensureDir(nestedDir);
|
|
86
|
-
|
|
87
|
-
const items = await fs.readdir(cwd);
|
|
88
|
-
const toMove = items.filter((item) => item !== projectName);
|
|
89
|
-
|
|
90
|
-
for (const item of toMove) {
|
|
91
|
-
const src = path.join(cwd, item);
|
|
92
|
-
const dest = path.join(nestedDir, item);
|
|
93
|
-
await fs.move(src, dest, { overwrite: true });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const envSrc = path.join(nestedDir, ".env");
|
|
97
|
-
const envDest = path.join(cwd, ".env");
|
|
98
|
-
|
|
99
|
-
if (fs.existsSync(envSrc)) {
|
|
100
|
-
await fs.move(envSrc, envDest, { overwrite: true });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function writeEnvVars(
|
|
105
|
-
cwd: string,
|
|
106
|
-
services: DockerService[],
|
|
107
|
-
env: string
|
|
108
|
-
) {
|
|
109
|
-
const envPath = path.join(cwd, ".env");
|
|
110
|
-
let existingEnv: Record<string, string> = {};
|
|
111
|
-
let existingContent = "";
|
|
112
|
-
|
|
113
|
-
// Read existing .env file if it exists
|
|
114
|
-
if (fs.existsSync(envPath)) {
|
|
115
|
-
existingContent = await fs.readFile(envPath, "utf8");
|
|
116
|
-
existingEnv = parseEnvFile(existingContent);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const newVars: string[] = [];
|
|
120
|
-
|
|
121
|
-
// Add HOST variable for production
|
|
122
|
-
if (env === "prod" && !existingEnv.HOST) {
|
|
123
|
-
newVars.push("HOST=example.com");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Add service-specific variables
|
|
127
|
-
for (const service of services) {
|
|
128
|
-
const serviceVars = getEnvVars(service.name);
|
|
129
|
-
for (const varLine of serviceVars) {
|
|
130
|
-
const [key] = varLine.split("=");
|
|
131
|
-
if (!existingEnv[key]) {
|
|
132
|
-
newVars.push(varLine);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Add tunnel-specific variables for dev mode
|
|
137
|
-
if (env === "dev" && service.needsTunnel) {
|
|
138
|
-
const remoteHostKey = `${service.name.toUpperCase()}_REMOTE_HOST`;
|
|
139
|
-
const remotePortKey = `${service.name.toUpperCase()}_REMOTE_PORT`;
|
|
140
|
-
|
|
141
|
-
if (!existingEnv[remoteHostKey]) {
|
|
142
|
-
newVars.push(`${remoteHostKey}=user@remote-server.com`);
|
|
143
|
-
}
|
|
144
|
-
if (!existingEnv[remotePortKey]) {
|
|
145
|
-
newVars.push(`${remotePortKey}=${getDefaultPort(service.name)}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Write new variables to .env file
|
|
151
|
-
if (newVars.length > 0) {
|
|
152
|
-
const separator =
|
|
153
|
-
existingContent && !existingContent.endsWith("\n") ? "\n" : "";
|
|
154
|
-
const newContent =
|
|
155
|
-
existingContent +
|
|
156
|
-
separator +
|
|
157
|
-
(existingContent ? "\n" : "") +
|
|
158
|
-
newVars.join("\n") +
|
|
159
|
-
"\n";
|
|
160
|
-
await fs.writeFile(envPath, newContent);
|
|
161
|
-
console.log(
|
|
162
|
-
`✅ Added ${newVars.length} new environment variable(s) to .env`
|
|
163
|
-
);
|
|
164
|
-
} else {
|
|
165
|
-
console.log("✅ All required environment variables already exist in .env");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function parseEnvFile(content: string): Record<string, string> {
|
|
170
|
-
const env: Record<string, string> = {};
|
|
171
|
-
const lines = content.split("\n");
|
|
172
|
-
|
|
173
|
-
for (const line of lines) {
|
|
174
|
-
const trimmed = line.trim();
|
|
175
|
-
// Skip empty lines and comments
|
|
176
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
177
|
-
|
|
178
|
-
const equalIndex = trimmed.indexOf("=");
|
|
179
|
-
if (equalIndex > 0) {
|
|
180
|
-
const key = trimmed.substring(0, equalIndex).trim();
|
|
181
|
-
const value = trimmed.substring(equalIndex + 1).trim();
|
|
182
|
-
env[key] = value;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return env;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function createDockerfile(cwd: string, projectName: string) {
|
|
190
|
-
const dockerfilePath = path.join(cwd, projectName, "Dockerfile");
|
|
191
|
-
const dockerignorePath = path.join(cwd, projectName, ".dockerignore");
|
|
192
|
-
|
|
193
|
-
const dockerfile = `FROM node:18-alpine
|
|
194
|
-
|
|
195
|
-
WORKDIR /app
|
|
196
|
-
|
|
197
|
-
COPY package*.json ./
|
|
198
|
-
|
|
199
|
-
RUN npm install
|
|
200
|
-
|
|
201
|
-
COPY . .
|
|
202
|
-
|
|
203
|
-
EXPOSE 8080
|
|
204
|
-
|
|
205
|
-
CMD ["npm", "run", "dev"]
|
|
206
|
-
`;
|
|
207
|
-
|
|
208
|
-
const dockerignore = `# Dependencies
|
|
209
|
-
node_modules
|
|
210
|
-
npm-debug.log
|
|
211
|
-
yarn-error.log
|
|
212
|
-
package-lock.json
|
|
213
|
-
yarn.lock
|
|
214
|
-
|
|
215
|
-
# Environment files
|
|
216
|
-
.env
|
|
217
|
-
.env.*
|
|
218
|
-
|
|
219
|
-
# Git
|
|
220
|
-
.git
|
|
221
|
-
.gitignore
|
|
222
|
-
|
|
223
|
-
# IDE
|
|
224
|
-
.vscode
|
|
225
|
-
.idea
|
|
226
|
-
*.swp
|
|
227
|
-
*.swo
|
|
228
|
-
*~
|
|
229
|
-
|
|
230
|
-
# OS
|
|
231
|
-
.DS_Store
|
|
232
|
-
Thumbs.db
|
|
233
|
-
|
|
234
|
-
# Testing
|
|
235
|
-
coverage
|
|
236
|
-
.nyc_output
|
|
237
|
-
*.test.js
|
|
238
|
-
*.spec.js
|
|
239
|
-
__tests__
|
|
240
|
-
|
|
241
|
-
# Build files
|
|
242
|
-
dist
|
|
243
|
-
public
|
|
244
|
-
|
|
245
|
-
# Logs
|
|
246
|
-
logs
|
|
247
|
-
*.log
|
|
248
|
-
|
|
249
|
-
# Documentation
|
|
250
|
-
README.md
|
|
251
|
-
docs
|
|
252
|
-
*.md
|
|
253
|
-
|
|
254
|
-
# Docker
|
|
255
|
-
Dockerfile
|
|
256
|
-
.dockerignore
|
|
257
|
-
docker-compose*.yml
|
|
258
|
-
|
|
259
|
-
# Misc
|
|
260
|
-
.cache
|
|
261
|
-
tmp
|
|
262
|
-
temp
|
|
263
|
-
`;
|
|
264
|
-
|
|
265
|
-
await fs.writeFile(dockerfilePath, dockerfile);
|
|
266
|
-
await fs.writeFile(dockerignorePath, dockerignore);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async function setupProduction(
|
|
270
|
-
cwd: string,
|
|
271
|
-
projectName: string,
|
|
272
|
-
services: DockerService[]
|
|
273
|
-
) {
|
|
274
|
-
const compose: any = {
|
|
275
|
-
services: {
|
|
276
|
-
traefik: {
|
|
277
|
-
image: "traefik:v2.10",
|
|
278
|
-
command: [
|
|
279
|
-
"--api.insecure=true",
|
|
280
|
-
"--providers.docker=true",
|
|
281
|
-
"--providers.docker.exposedbydefault=false",
|
|
282
|
-
"--entrypoints.web.address=:80",
|
|
283
|
-
"--entrypoints.websecure.address=:443",
|
|
284
|
-
"--certificatesresolvers.myresolver.acme.tlschallenge=true",
|
|
285
|
-
"--certificatesresolvers.myresolver.acme.email=admin@example.com",
|
|
286
|
-
"--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json",
|
|
287
|
-
],
|
|
288
|
-
labels: [
|
|
289
|
-
"traefik.enable=true",
|
|
290
|
-
"traefik.http.routers.dashboard.rule=Host(`traefik.${HOST}`)",
|
|
291
|
-
"traefik.http.routers.dashboard.entrypoints=websecure",
|
|
292
|
-
"traefik.http.routers.dashboard.tls.certresolver=myresolver",
|
|
293
|
-
"traefik.http.routers.dashboard.service=api@internal",
|
|
294
|
-
],
|
|
295
|
-
ports: ["80:80", "443:443"],
|
|
296
|
-
volumes: [
|
|
297
|
-
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
|
298
|
-
"./letsencrypt:/letsencrypt",
|
|
299
|
-
],
|
|
300
|
-
networks: ["web"],
|
|
301
|
-
env_file: [".env"],
|
|
302
|
-
},
|
|
303
|
-
[projectName]: {
|
|
304
|
-
build: {
|
|
305
|
-
context: `./${projectName}`,
|
|
306
|
-
dockerfile: "Dockerfile",
|
|
307
|
-
},
|
|
308
|
-
labels: [
|
|
309
|
-
"traefik.enable=true",
|
|
310
|
-
"traefik.http.routers.app.rule=Host(`app.${HOST}`)",
|
|
311
|
-
"traefik.http.routers.app.entrypoints=websecure",
|
|
312
|
-
"traefik.http.routers.app.tls.certresolver=myresolver",
|
|
313
|
-
"traefik.http.services.app.loadbalancer.server.port=8080",
|
|
314
|
-
],
|
|
315
|
-
env_file: [".env"],
|
|
316
|
-
networks: ["web"],
|
|
317
|
-
volumes: [`./${projectName}:/app`, `/app/node_modules`],
|
|
318
|
-
command: "npm run dev",
|
|
319
|
-
depends_on: [],
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
networks: {
|
|
323
|
-
web: {
|
|
324
|
-
driver: "bridge",
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
volumes: {},
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
for (const service of services) {
|
|
331
|
-
const config = getServiceConfig(service.name);
|
|
332
|
-
compose.services[service.name] = config.service;
|
|
333
|
-
if (config.volume) {
|
|
334
|
-
compose.volumes[config.volume.name] = {};
|
|
335
|
-
}
|
|
336
|
-
compose.services[projectName].depends_on.push(service.name);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const composePath = path.join(cwd, "docker-compose.yml");
|
|
340
|
-
await fs.writeFile(
|
|
341
|
-
composePath,
|
|
342
|
-
yaml.dump(compose, { indent: 2, lineWidth: -1 })
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async function setupDevelopment(
|
|
347
|
-
cwd: string,
|
|
348
|
-
projectName: string,
|
|
349
|
-
services: DockerService[]
|
|
350
|
-
) {
|
|
351
|
-
const existingCompose = path.join(cwd, "docker-compose.yml");
|
|
352
|
-
|
|
353
|
-
let existingServices: DockerService[] = [];
|
|
354
|
-
|
|
355
|
-
if (fs.existsSync(existingCompose)) {
|
|
356
|
-
const content = await fs.readFile(existingCompose, "utf8");
|
|
357
|
-
const existing: any = yaml.load(content);
|
|
358
|
-
if (existing.services) {
|
|
359
|
-
existingServices = Object.keys(existing.services)
|
|
360
|
-
.filter((s) => ["postgres", "mysql", "redis", "mongodb"].includes(s))
|
|
361
|
-
.map((name) => ({ name }));
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const servicesToTunnel: DockerService[] = [];
|
|
366
|
-
|
|
367
|
-
if (existingServices.length > 0) {
|
|
368
|
-
const { tunnel } = await prompts({
|
|
369
|
-
type: "confirm",
|
|
370
|
-
name: "tunnel",
|
|
371
|
-
message: "Tunnel to remote database services?",
|
|
372
|
-
initial: false,
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
if (tunnel) {
|
|
376
|
-
const { selected } = await prompts({
|
|
377
|
-
type: "multiselect",
|
|
378
|
-
name: "selected",
|
|
379
|
-
message: "Select services to tunnel:",
|
|
380
|
-
choices: existingServices.map((s) => ({
|
|
381
|
-
title: s.name,
|
|
382
|
-
value: s.name,
|
|
383
|
-
})),
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
if (selected && selected.length > 0) {
|
|
387
|
-
servicesToTunnel.push(
|
|
388
|
-
...existingServices
|
|
389
|
-
.filter((s) => selected.includes(s.name))
|
|
390
|
-
.map((s) => ({ ...s, needsTunnel: true }))
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
for (const service of servicesToTunnel) {
|
|
397
|
-
await createTunnelService(cwd, service.name);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const compose: any = {
|
|
401
|
-
services: {
|
|
402
|
-
[projectName]: {
|
|
403
|
-
build: {
|
|
404
|
-
context: `./${projectName}`,
|
|
405
|
-
dockerfile: "Dockerfile",
|
|
406
|
-
},
|
|
407
|
-
ports: ["8080:8080"],
|
|
408
|
-
env_file: [".env"],
|
|
409
|
-
volumes: [`./${projectName}:/app`, `/app/node_modules`],
|
|
410
|
-
command: "npm run dev",
|
|
411
|
-
depends_on: [],
|
|
412
|
-
},
|
|
413
|
-
},
|
|
414
|
-
networks: {
|
|
415
|
-
default: {
|
|
416
|
-
driver: "bridge",
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
for (const service of servicesToTunnel) {
|
|
422
|
-
const tunnelName = `${service.name}-tunnel`;
|
|
423
|
-
compose.services[tunnelName] = {
|
|
424
|
-
build: `./${tunnelName}`,
|
|
425
|
-
environment: [
|
|
426
|
-
`REMOTE_HOST=\${${service.name.toUpperCase()}_REMOTE_HOST}`,
|
|
427
|
-
`REMOTE_PORT=\${${service.name.toUpperCase()}_REMOTE_PORT:-${getDefaultPort(
|
|
428
|
-
service.name
|
|
429
|
-
)}}`,
|
|
430
|
-
`LOCAL_PORT=${getDefaultPort(service.name)}`,
|
|
431
|
-
],
|
|
432
|
-
volumes: [`./${service.name}.pem:/ssh/${service.name}.pem:ro`],
|
|
433
|
-
};
|
|
434
|
-
compose.services[projectName].depends_on.push(tunnelName);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const devComposePath = path.join(cwd, "docker-compose.dev.yml");
|
|
438
|
-
await fs.writeFile(
|
|
439
|
-
devComposePath,
|
|
440
|
-
yaml.dump(compose, { indent: 2, lineWidth: -1 })
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async function createTunnelService(projectDir: string, serviceName: string) {
|
|
445
|
-
const tunnelDir = path.join(projectDir, `${serviceName}-tunnel`);
|
|
446
|
-
await fs.ensureDir(tunnelDir);
|
|
447
|
-
|
|
448
|
-
const dockerfile = `FROM alpine:latest
|
|
449
|
-
|
|
450
|
-
RUN apk add --no-cache openssh-client
|
|
451
|
-
|
|
452
|
-
COPY tunnel.sh /tunnel.sh
|
|
453
|
-
RUN chmod +x /tunnel.sh
|
|
454
|
-
|
|
455
|
-
CMD ["/tunnel.sh"]
|
|
456
|
-
`;
|
|
457
|
-
|
|
458
|
-
const tunnelScript = `#!/bin/sh
|
|
459
|
-
|
|
460
|
-
SSH_KEY="/ssh/${serviceName}.pem"
|
|
461
|
-
REMOTE_HOST=\${REMOTE_HOST}
|
|
462
|
-
REMOTE_PORT=\${REMOTE_PORT:-${getDefaultPort(serviceName)}}
|
|
463
|
-
LOCAL_PORT=\${LOCAL_PORT:-${getDefaultPort(serviceName)}}
|
|
464
|
-
|
|
465
|
-
chmod 600 $SSH_KEY
|
|
466
|
-
|
|
467
|
-
echo "Starting SSH tunnel for ${serviceName}..."
|
|
468
|
-
echo "Remote: $REMOTE_HOST:$REMOTE_PORT -> Local: $LOCAL_PORT"
|
|
469
|
-
|
|
470
|
-
ssh -i $SSH_KEY \\
|
|
471
|
-
-N -L 0.0.0.0:$LOCAL_PORT:localhost:$REMOTE_PORT \\
|
|
472
|
-
-o StrictHostKeyChecking=no \\
|
|
473
|
-
-o ServerAliveInterval=60 \\
|
|
474
|
-
$REMOTE_HOST
|
|
475
|
-
`;
|
|
476
|
-
|
|
477
|
-
await fs.writeFile(path.join(tunnelDir, "Dockerfile"), dockerfile);
|
|
478
|
-
await fs.writeFile(path.join(tunnelDir, "tunnel.sh"), tunnelScript);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function getServiceConfig(serviceName: string) {
|
|
482
|
-
const configs: Record<string, any> = {
|
|
483
|
-
postgres: {
|
|
484
|
-
service: {
|
|
485
|
-
image: "postgres:15-alpine",
|
|
486
|
-
environment: [
|
|
487
|
-
"POSTGRES_USER=${DB_USER}",
|
|
488
|
-
"POSTGRES_PASSWORD=${DB_PASSWORD}",
|
|
489
|
-
"POSTGRES_DB=${DB_NAME}",
|
|
490
|
-
],
|
|
491
|
-
volumes: ["postgres_data:/var/lib/postgresql/data"],
|
|
492
|
-
networks: ["web"],
|
|
493
|
-
},
|
|
494
|
-
volume: { name: "postgres_data" },
|
|
495
|
-
},
|
|
496
|
-
mysql: {
|
|
497
|
-
service: {
|
|
498
|
-
image: "mysql:8",
|
|
499
|
-
environment: [
|
|
500
|
-
"MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}",
|
|
501
|
-
"MYSQL_DATABASE=${DB_NAME}",
|
|
502
|
-
"MYSQL_USER=${DB_USER}",
|
|
503
|
-
"MYSQL_PASSWORD=${DB_PASSWORD}",
|
|
504
|
-
],
|
|
505
|
-
volumes: ["mysql_data:/var/lib/mysql"],
|
|
506
|
-
networks: ["web"],
|
|
507
|
-
},
|
|
508
|
-
volume: { name: "mysql_data" },
|
|
509
|
-
},
|
|
510
|
-
redis: {
|
|
511
|
-
service: {
|
|
512
|
-
image: "redis:7-alpine",
|
|
513
|
-
volumes: ["redis_data:/data"],
|
|
514
|
-
networks: ["web"],
|
|
515
|
-
},
|
|
516
|
-
volume: { name: "redis_data" },
|
|
517
|
-
},
|
|
518
|
-
mongodb: {
|
|
519
|
-
service: {
|
|
520
|
-
image: "mongo:6",
|
|
521
|
-
environment: [
|
|
522
|
-
"MONGO_INITDB_ROOT_USERNAME=${DB_USER}",
|
|
523
|
-
"MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD}",
|
|
524
|
-
],
|
|
525
|
-
volumes: ["mongo_data:/data/db"],
|
|
526
|
-
networks: ["web"],
|
|
527
|
-
},
|
|
528
|
-
volume: { name: "mongo_data" },
|
|
529
|
-
},
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
return configs[serviceName];
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function getEnvVars(serviceName: string): string[] {
|
|
536
|
-
const vars: Record<string, string[]> = {
|
|
537
|
-
postgres: [
|
|
538
|
-
"DB_HOST=postgres",
|
|
539
|
-
"DB_PORT=5432",
|
|
540
|
-
"DB_USER=myuser",
|
|
541
|
-
"DB_PASSWORD=mypassword",
|
|
542
|
-
"DB_NAME=mydb",
|
|
543
|
-
],
|
|
544
|
-
mysql: [
|
|
545
|
-
"DB_HOST=mysql",
|
|
546
|
-
"DB_PORT=3306",
|
|
547
|
-
"DB_USER=myuser",
|
|
548
|
-
"DB_PASSWORD=mypassword",
|
|
549
|
-
"DB_NAME=mydb",
|
|
550
|
-
"DB_ROOT_PASSWORD=rootpassword",
|
|
551
|
-
],
|
|
552
|
-
redis: ["REDIS_HOST=redis", "REDIS_PORT=6379"],
|
|
553
|
-
mongodb: [
|
|
554
|
-
"MONGO_HOST=mongodb",
|
|
555
|
-
"MONGO_PORT=27017",
|
|
556
|
-
"MONGO_USER=myuser",
|
|
557
|
-
"MONGO_PASSWORD=mypassword",
|
|
558
|
-
],
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
return vars[serviceName] || [];
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function getDefaultPort(service: string): number {
|
|
565
|
-
const ports: Record<string, number> = {
|
|
566
|
-
postgres: 5432,
|
|
567
|
-
mysql: 3306,
|
|
568
|
-
redis: 6379,
|
|
569
|
-
mongodb: 27017,
|
|
570
|
-
};
|
|
571
|
-
return ports[service] || 3000;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function printNextSteps(
|
|
575
|
-
projectName: string,
|
|
576
|
-
env: string,
|
|
577
|
-
services: DockerService[]
|
|
578
|
-
) {
|
|
579
|
-
console.log(`\n✅ Done! Next steps:\n`);
|
|
580
|
-
|
|
581
|
-
if (env === "prod") {
|
|
582
|
-
console.log(` # Review and edit .env with your settings`);
|
|
583
|
-
console.log(` docker-compose up -d`);
|
|
584
|
-
console.log(` # Access at https://app.\${HOST}\n`);
|
|
585
|
-
} else {
|
|
586
|
-
console.log(` # Review and edit .env with your settings`);
|
|
587
|
-
if (services.some((s) => s.needsTunnel)) {
|
|
588
|
-
console.log(` # Add SSH keys: {service}.pem`);
|
|
589
|
-
}
|
|
590
|
-
console.log(` docker-compose -f docker-compose.dev.yml up -d\n`);
|
|
591
|
-
}
|
|
592
|
-
}
|
package/src/docker/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./docker";
|