@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.
Files changed (40) hide show
  1. package/README.md +439 -0
  2. package/dist/main.js +109 -2
  3. package/dist/main.js.map +2 -2
  4. package/dist/types/docker/docker.d.ts.map +1 -1
  5. package/package.json +31 -8
  6. package/build.js +0 -22
  7. package/src/add/component/component.ts +0 -496
  8. package/src/add/component/index.ts +0 -1
  9. package/src/add/index.ts +0 -3
  10. package/src/add/module/index.ts +0 -1
  11. package/src/add/module/module.ts +0 -545
  12. package/src/add/new/index.ts +0 -198
  13. package/src/config/config.ts +0 -141
  14. package/src/config/index.ts +0 -1
  15. package/src/deploy/deploy.ts +0 -592
  16. package/src/deploy/index.ts +0 -1
  17. package/src/dev/index.ts +0 -1
  18. package/src/dev/project.ts +0 -45
  19. package/src/dev/server.ts +0 -191
  20. package/src/docker/docker.ts +0 -592
  21. package/src/docker/index.ts +0 -1
  22. package/src/macros/expand_macros.ts +0 -791
  23. package/src/macros/index.ts +0 -2
  24. package/src/macros/macro_executer.ts +0 -189
  25. package/src/main.ts +0 -106
  26. package/src/plugins/analyzers/graph.ts +0 -279
  27. package/src/plugins/css/index.ts +0 -25
  28. package/src/plugins/generators/generate_controller.ts +0 -308
  29. package/src/plugins/generators/generate_rsc.ts +0 -274
  30. package/src/plugins/generators/generate_server_component.ts +0 -455
  31. package/src/plugins/generators/tsx_server_stub.ts +0 -315
  32. package/src/plugins/index.ts +0 -3
  33. package/src/plugins/my.ts +0 -282
  34. package/src/plugins/transformers/j2d.ts +0 -1080
  35. package/src/store/index.ts +0 -1
  36. package/src/store/store.ts +0 -44
  37. package/src/utils/cases.ts +0 -15
  38. package/src/utils/create.ts +0 -26
  39. package/src/utils/index.ts +0 -2
  40. package/tsconfig.json +0 -27
@@ -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
- }
@@ -1 +0,0 @@
1
- export * from "./docker";