devports 0.0.1 → 1.0.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 (151) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/LICENSE +21 -0
  3. package/README.md +810 -29
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +329 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/allocate.command.d.ts +8 -0
  9. package/dist/commands/allocate.command.d.ts.map +1 -0
  10. package/dist/commands/allocate.command.js +84 -0
  11. package/dist/commands/allocate.command.js.map +1 -0
  12. package/dist/commands/base-command.d.ts +28 -0
  13. package/dist/commands/base-command.d.ts.map +1 -0
  14. package/dist/commands/base-command.js +33 -0
  15. package/dist/commands/base-command.js.map +1 -0
  16. package/dist/commands/check.command.d.ts +7 -0
  17. package/dist/commands/check.command.d.ts.map +1 -0
  18. package/dist/commands/check.command.js +44 -0
  19. package/dist/commands/check.command.js.map +1 -0
  20. package/dist/commands/completion.command.d.ts +7 -0
  21. package/dist/commands/completion.command.d.ts.map +1 -0
  22. package/dist/commands/completion.command.js +116 -0
  23. package/dist/commands/completion.command.js.map +1 -0
  24. package/dist/commands/gitignore.command.d.ts +7 -0
  25. package/dist/commands/gitignore.command.d.ts.map +1 -0
  26. package/dist/commands/gitignore.command.js +61 -0
  27. package/dist/commands/gitignore.command.js.map +1 -0
  28. package/dist/commands/index.d.ts +7 -0
  29. package/dist/commands/index.d.ts.map +1 -0
  30. package/dist/commands/index.js +35 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/info.command.d.ts +7 -0
  33. package/dist/commands/info.command.d.ts.map +1 -0
  34. package/dist/commands/info.command.js +40 -0
  35. package/dist/commands/info.command.js.map +1 -0
  36. package/dist/commands/list.command.d.ts +8 -0
  37. package/dist/commands/list.command.d.ts.map +1 -0
  38. package/dist/commands/list.command.js +165 -0
  39. package/dist/commands/list.command.js.map +1 -0
  40. package/dist/commands/release.command.d.ts +8 -0
  41. package/dist/commands/release.command.d.ts.map +1 -0
  42. package/dist/commands/release.command.js +89 -0
  43. package/dist/commands/release.command.js.map +1 -0
  44. package/dist/commands/render.command.d.ts +7 -0
  45. package/dist/commands/render.command.d.ts.map +1 -0
  46. package/dist/commands/render.command.js +53 -0
  47. package/dist/commands/render.command.js.map +1 -0
  48. package/dist/commands/reserve.command.d.ts +7 -0
  49. package/dist/commands/reserve.command.d.ts.map +1 -0
  50. package/dist/commands/reserve.command.js +42 -0
  51. package/dist/commands/reserve.command.js.map +1 -0
  52. package/dist/commands/setup.command.d.ts +7 -0
  53. package/dist/commands/setup.command.d.ts.map +1 -0
  54. package/dist/commands/setup.command.js +43 -0
  55. package/dist/commands/setup.command.js.map +1 -0
  56. package/dist/commands/status.command.d.ts +7 -0
  57. package/dist/commands/status.command.d.ts.map +1 -0
  58. package/dist/commands/status.command.js +41 -0
  59. package/dist/commands/status.command.js.map +1 -0
  60. package/dist/commands/unreserve.command.d.ts +7 -0
  61. package/dist/commands/unreserve.command.d.ts.map +1 -0
  62. package/dist/commands/unreserve.command.js +38 -0
  63. package/dist/commands/unreserve.command.js.map +1 -0
  64. package/dist/commands/worktree-add.command.d.ts +7 -0
  65. package/dist/commands/worktree-add.command.d.ts.map +1 -0
  66. package/dist/commands/worktree-add.command.js +68 -0
  67. package/dist/commands/worktree-add.command.js.map +1 -0
  68. package/dist/commands/worktree-remove.command.d.ts +7 -0
  69. package/dist/commands/worktree-remove.command.d.ts.map +1 -0
  70. package/dist/commands/worktree-remove.command.js +33 -0
  71. package/dist/commands/worktree-remove.command.js.map +1 -0
  72. package/dist/completion/bash-completion-template.d.ts +5 -0
  73. package/dist/completion/bash-completion-template.d.ts.map +1 -0
  74. package/dist/completion/bash-completion-template.js +14 -0
  75. package/dist/completion/bash-completion-template.js.map +1 -0
  76. package/dist/completion/bash.sh +208 -0
  77. package/dist/completion/completion-data.d.ts +16 -0
  78. package/dist/completion/completion-data.d.ts.map +1 -0
  79. package/dist/completion/completion-data.js +38 -0
  80. package/dist/completion/completion-data.js.map +1 -0
  81. package/dist/completion/index.d.ts +24 -0
  82. package/dist/completion/index.d.ts.map +1 -0
  83. package/dist/completion/index.js +30 -0
  84. package/dist/completion/index.js.map +1 -0
  85. package/dist/completion/shell-config.d.ts +27 -0
  86. package/dist/completion/shell-config.d.ts.map +1 -0
  87. package/dist/completion/shell-config.js +243 -0
  88. package/dist/completion/shell-config.js.map +1 -0
  89. package/dist/completion/zsh-completion-template.d.ts +5 -0
  90. package/dist/completion/zsh-completion-template.d.ts.map +1 -0
  91. package/dist/completion/zsh-completion-template.js +14 -0
  92. package/dist/completion/zsh-completion-template.js.map +1 -0
  93. package/dist/completion/zsh.sh +164 -0
  94. package/dist/config.d.ts +6 -0
  95. package/dist/config.d.ts.map +1 -0
  96. package/dist/config.js +111 -0
  97. package/dist/config.js.map +1 -0
  98. package/dist/devports-1.0.0.tgz +0 -0
  99. package/dist/execution.d.ts +31 -0
  100. package/dist/execution.d.ts.map +1 -0
  101. package/dist/execution.js +110 -0
  102. package/dist/execution.js.map +1 -0
  103. package/dist/gitignore.d.ts +22 -0
  104. package/dist/gitignore.d.ts.map +1 -0
  105. package/dist/gitignore.js +142 -0
  106. package/dist/gitignore.js.map +1 -0
  107. package/dist/index.d.ts +7 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +6 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/port-manager.d.ts +33 -0
  112. package/dist/port-manager.d.ts.map +1 -0
  113. package/dist/port-manager.js +169 -0
  114. package/dist/port-manager.js.map +1 -0
  115. package/dist/port-utils.d.ts +9 -0
  116. package/dist/port-utils.d.ts.map +1 -0
  117. package/dist/port-utils.js +38 -0
  118. package/dist/port-utils.js.map +1 -0
  119. package/dist/render.d.ts +54 -0
  120. package/dist/render.d.ts.map +1 -0
  121. package/dist/render.js +286 -0
  122. package/dist/render.js.map +1 -0
  123. package/dist/services/lock-manager.d.ts +46 -0
  124. package/dist/services/lock-manager.d.ts.map +1 -0
  125. package/dist/services/lock-manager.js +118 -0
  126. package/dist/services/lock-manager.js.map +1 -0
  127. package/dist/services/response-formatter.d.ts +45 -0
  128. package/dist/services/response-formatter.d.ts.map +1 -0
  129. package/dist/services/response-formatter.js +102 -0
  130. package/dist/services/response-formatter.js.map +1 -0
  131. package/dist/services/validation-service.d.ts +109 -0
  132. package/dist/services/validation-service.d.ts.map +1 -0
  133. package/dist/services/validation-service.js +267 -0
  134. package/dist/services/validation-service.js.map +1 -0
  135. package/dist/setup.d.ts +20 -0
  136. package/dist/setup.d.ts.map +1 -0
  137. package/dist/setup.js +243 -0
  138. package/dist/setup.js.map +1 -0
  139. package/dist/types.d.ts +29 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +18 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/validation.d.ts +69 -0
  144. package/dist/validation.d.ts.map +1 -0
  145. package/dist/validation.js +344 -0
  146. package/dist/validation.js.map +1 -0
  147. package/dist/worktree.d.ts +24 -0
  148. package/dist/worktree.d.ts.map +1 -0
  149. package/dist/worktree.js +245 -0
  150. package/dist/worktree.js.map +1 -0
  151. package/package.json +90 -6
@@ -0,0 +1,33 @@
1
+ import type { PortAllocation } from './types.js';
2
+ export declare function allocatePort(project: string, service: string, type: string): Promise<number>;
3
+ /**
4
+ * Get existing port allocation for a project/service combination
5
+ */
6
+ export declare function getExistingAllocation(project: string, service: string): Promise<number | null>;
7
+ /**
8
+ * Get or allocate a port for a project/service combination
9
+ * This function first checks for existing allocations and returns them,
10
+ * only allocating new ports when needed. Used by render command.
11
+ */
12
+ export declare function getOrAllocatePort(project: string, service: string, type: string): Promise<number>;
13
+ export declare function releasePort(project: string, service?: string, all?: boolean): Promise<number>;
14
+ export declare function releasePortByNumber(port: number): Promise<boolean>;
15
+ export declare function listAllocations(filters?: {
16
+ project?: string;
17
+ type?: string;
18
+ }): PortAllocation[];
19
+ export declare function getStatus(): {
20
+ types: Record<string, {
21
+ available: number;
22
+ used: number;
23
+ next: number | null;
24
+ }>;
25
+ };
26
+ export declare function reservePort(port: number, reason: string): Promise<void>;
27
+ export declare function unreservePort(port: number): Promise<boolean>;
28
+ export declare function listReservations(): Array<{
29
+ port: number;
30
+ reason: string;
31
+ reservedAt: string;
32
+ }>;
33
+ //# sourceMappingURL=port-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-manager.d.ts","sourceRoot":"","sources":["../src/port-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAmEjB;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,GAAG,UAAQ,GACV,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAaxE;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,cAAc,EAAE,CAcnB;AAED,wBAAgB,SAAS,IAAI;IAC3B,KAAK,EAAE,MAAM,CACX,MAAM,EACN;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CACzD,CAAC;CACH,CA+BA;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB7E;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAelE;AAED,wBAAgB,gBAAgB,IAAI,KAAK,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAGD"}
@@ -0,0 +1,169 @@
1
+ import { LockManager } from './services/lock-manager.js';
2
+ import { loadConfig, loadRegistry } from './config.js';
3
+ import { findAvailablePort, checkPortInUse } from './port-utils.js';
4
+ export async function allocatePort(project, service, type) {
5
+ const config = loadConfig();
6
+ const serviceType = type;
7
+ // Validate port type exists in config
8
+ if (!config.ranges[serviceType]) {
9
+ // Provide helpful error message with valid types
10
+ const validTypes = Object.keys(config.ranges).join(', ');
11
+ throw new Error(`No port range configured for type "${serviceType}". ` +
12
+ `Valid types: ${validTypes}`);
13
+ }
14
+ return await LockManager.withRegistryLock(async (registry) => {
15
+ const range = config.ranges[serviceType];
16
+ // Check if this project/service already has an allocation
17
+ const existing = registry.allocations.find((a) => a.project === project && a.service === service);
18
+ if (existing) {
19
+ throw new Error(`Port ${existing.port} already allocated for ${project}/${service}. ` +
20
+ `Use 'devports release ${project} ${service}' first.`);
21
+ }
22
+ // Find next available port (checking both devports allocations and actual port usage)
23
+ const usedPorts = new Set([
24
+ ...registry.allocations.map((a) => a.port),
25
+ ...registry.reservations.map((r) => r.port),
26
+ ]);
27
+ const port = await findAvailablePort(range.start, range.end, usedPorts);
28
+ if (!port) {
29
+ throw new Error(`No available ports in range ${range.start}-${range.end} for ${serviceType}. ` +
30
+ `All ports are either allocated by devports or in use by other processes.`);
31
+ }
32
+ // Double-check if the port is actually in use and warn if so
33
+ const isInUse = await checkPortInUse(port);
34
+ if (isInUse) {
35
+ console.warn(`⚠️ Warning: Port ${port} is currently in use by another process. ` +
36
+ `You may need to stop that process before using this port.`);
37
+ }
38
+ // Allocate the port
39
+ const allocation = {
40
+ port,
41
+ project,
42
+ service,
43
+ type: serviceType,
44
+ allocatedAt: new Date().toISOString(),
45
+ };
46
+ registry.allocations.push(allocation);
47
+ return port;
48
+ });
49
+ }
50
+ /**
51
+ * Get existing port allocation for a project/service combination
52
+ */
53
+ export async function getExistingAllocation(project, service) {
54
+ return await LockManager.withRegistryLock((registry) => {
55
+ const existing = registry.allocations.find((a) => a.project === project && a.service === service);
56
+ return existing ? existing.port : null;
57
+ });
58
+ }
59
+ /**
60
+ * Get or allocate a port for a project/service combination
61
+ * This function first checks for existing allocations and returns them,
62
+ * only allocating new ports when needed. Used by render command.
63
+ */
64
+ export async function getOrAllocatePort(project, service, type) {
65
+ // First check if we already have an allocation
66
+ const existingPort = await getExistingAllocation(project, service);
67
+ if (existingPort !== null) {
68
+ return existingPort;
69
+ }
70
+ // No existing allocation, so allocate a new port
71
+ return await allocatePort(project, service, type);
72
+ }
73
+ export async function releasePort(project, service, all = false) {
74
+ return await LockManager.withRegistryLock((registry) => {
75
+ const before = registry.allocations.length;
76
+ if (all) {
77
+ // Release all ports for this project
78
+ registry.allocations = registry.allocations.filter((a) => a.project !== project);
79
+ }
80
+ else if (service) {
81
+ // Release specific service
82
+ registry.allocations = registry.allocations.filter((a) => !(a.project === project && a.service === service));
83
+ }
84
+ else {
85
+ throw new Error('Must specify --all or provide a service name');
86
+ }
87
+ const released = before - registry.allocations.length;
88
+ return released;
89
+ });
90
+ }
91
+ export async function releasePortByNumber(port) {
92
+ return await LockManager.withRegistryLockConditional((registry) => {
93
+ const before = registry.allocations.length;
94
+ registry.allocations = registry.allocations.filter((a) => a.port !== port);
95
+ const released = before > registry.allocations.length;
96
+ return {
97
+ result: released,
98
+ shouldSave: released, // Only save if something was actually released
99
+ };
100
+ });
101
+ }
102
+ export function listAllocations(filters) {
103
+ const registry = loadRegistry();
104
+ let allocations = registry.allocations;
105
+ if (filters?.project) {
106
+ allocations = allocations.filter((a) => a.project === filters.project);
107
+ }
108
+ if (filters?.type) {
109
+ allocations = allocations.filter((a) => a.type === filters.type);
110
+ }
111
+ return allocations.sort((a, b) => a.port - b.port);
112
+ }
113
+ export function getStatus() {
114
+ const config = loadConfig();
115
+ const registry = loadRegistry();
116
+ const usedPorts = new Set([
117
+ ...registry.allocations.map((a) => a.port),
118
+ ...registry.reservations.map((r) => r.port),
119
+ ]);
120
+ const types = {};
121
+ for (const [typeName, range] of Object.entries(config.ranges)) {
122
+ const total = range.end - range.start + 1;
123
+ const used = registry.allocations.filter((a) => a.type === typeName).length;
124
+ const available = total - used;
125
+ let next = null;
126
+ for (let p = range.start; p <= range.end; p++) {
127
+ if (!usedPorts.has(p)) {
128
+ next = p;
129
+ break;
130
+ }
131
+ }
132
+ types[typeName] = { available, used, next };
133
+ }
134
+ return { types };
135
+ }
136
+ export async function reservePort(port, reason) {
137
+ return await LockManager.withRegistryLock((registry) => {
138
+ // Check if port is already allocated or reserved
139
+ const allocated = registry.allocations.find((a) => a.port === port);
140
+ if (allocated) {
141
+ throw new Error(`Port ${port} is already allocated to ${allocated.project}/${allocated.service}`);
142
+ }
143
+ const reserved = registry.reservations.find((r) => r.port === port);
144
+ if (reserved) {
145
+ throw new Error(`Port ${port} is already reserved: ${reserved.reason}`);
146
+ }
147
+ registry.reservations.push({
148
+ port,
149
+ reason,
150
+ reservedAt: new Date().toISOString(),
151
+ });
152
+ });
153
+ }
154
+ export async function unreservePort(port) {
155
+ return await LockManager.withRegistryLockConditional((registry) => {
156
+ const before = registry.reservations.length;
157
+ registry.reservations = registry.reservations.filter((r) => r.port !== port);
158
+ const unreserved = before > registry.reservations.length;
159
+ return {
160
+ result: unreserved,
161
+ shouldSave: unreserved, // Only save if something was actually unreserved
162
+ };
163
+ });
164
+ }
165
+ export function listReservations() {
166
+ const registry = loadRegistry();
167
+ return [...registry.reservations];
168
+ }
169
+ //# sourceMappingURL=port-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-manager.js","sourceRoot":"","sources":["../src/port-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Check if a port is actually in use (not just allocated by devports)
3
+ */
4
+ export declare function checkPortInUse(port: number, host?: string): Promise<boolean>;
5
+ /**
6
+ * Find the next available port in a range that's not in actual use
7
+ */
8
+ export declare function findAvailablePort(start: number, end: number, usedPorts: Set<number>, host?: string): Promise<number | null>;
9
+ //# sourceMappingURL=port-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-utils.d.ts","sourceRoot":"","sources":["../src/port-utils.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,SAAc,GACjB,OAAO,CAAC,OAAO,CAAC,CAmBlB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,IAAI,SAAc,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYxB"}
@@ -0,0 +1,38 @@
1
+ import { createConnection } from 'net';
2
+ /**
3
+ * Check if a port is actually in use (not just allocated by devports)
4
+ */
5
+ export function checkPortInUse(port, host = 'localhost') {
6
+ return new Promise((resolve) => {
7
+ const socket = createConnection({ port, host }, () => {
8
+ // Connection successful means port is in use
9
+ socket.destroy();
10
+ resolve(true);
11
+ });
12
+ socket.on('error', () => {
13
+ // Connection failed means port is available
14
+ resolve(false);
15
+ });
16
+ // Set timeout to avoid hanging
17
+ socket.setTimeout(1000, () => {
18
+ socket.destroy();
19
+ resolve(false);
20
+ });
21
+ });
22
+ }
23
+ /**
24
+ * Find the next available port in a range that's not in actual use
25
+ */
26
+ export async function findAvailablePort(start, end, usedPorts, host = 'localhost') {
27
+ for (let port = start; port <= end; port++) {
28
+ if (usedPorts.has(port)) {
29
+ continue; // Skip devports-allocated ports
30
+ }
31
+ const inUse = await checkPortInUse(port, host);
32
+ if (!inUse) {
33
+ return port;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ //# sourceMappingURL=port-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-utils.js","sourceRoot":"","sources":["../src/port-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAC;AAEvC;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,IAAI,GAAG,WAAW;IAElB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE;YACnD,6CAA6C;YAC7C,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,4CAA4C;YAC5C,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,+BAA+B;QAC/B,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE;YAC3B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAa,EACb,GAAW,EACX,SAAsB,EACtB,IAAI,GAAG,WAAW;IAElB,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,IAAI,IAAI,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;QAC3C,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,SAAS,CAAC,gCAAgC;QAC5C,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,54 @@
1
+ export interface RenderOptions {
2
+ projectName?: string;
3
+ outputFile?: string;
4
+ }
5
+ export interface RenderResult {
6
+ content: string;
7
+ allocatedPorts: Record<string, number>;
8
+ projectName: string;
9
+ }
10
+ /**
11
+ * Render a template file by replacing {devports:type:service-name} patterns with allocated ports
12
+ */
13
+ export declare function renderFile(filePath: string, options?: RenderOptions): Promise<RenderResult>;
14
+ /**
15
+ * Process template content using magic-string for efficient manipulation
16
+ */
17
+ export declare function processTemplate(templateContent: string, ports: Record<string, number>, projectName: string, filePath?: string): string;
18
+ /**
19
+ * Extract DEVPORTS_PROJECT_NAME from template content
20
+ */
21
+ export declare function extractProjectNameFromTemplate(templateContent: string): string | null;
22
+ /**
23
+ * Detect services from template file with {devports:type:service-name} patterns
24
+ */
25
+ export declare function detectServicesFromTemplate(templateContent: string, filePath?: string): string[];
26
+ /**
27
+ * Comment style configuration for different file types
28
+ */
29
+ interface CommentStyle {
30
+ lineComment?: string;
31
+ blockStart?: string;
32
+ blockEnd?: string;
33
+ }
34
+ /**
35
+ * Represents a range in the source text
36
+ */
37
+ interface Range {
38
+ start: number;
39
+ end: number;
40
+ }
41
+ /**
42
+ * Get comment style configuration for a file based on its extension
43
+ */
44
+ export declare function getCommentStyleForFile(filePath?: string): CommentStyle;
45
+ /**
46
+ * Find all comment ranges in the content, respecting string literals
47
+ */
48
+ export declare function findCommentRanges(content: string, filePath?: string): Range[];
49
+ /**
50
+ * Make string URL/hostname safe
51
+ */
52
+ export declare function makeUrlSafe(str: string): string;
53
+ export {};
54
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAsDR;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CAuBf;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,MAAM,EACvB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,EAAE,CAqCV;AAED;;GAEG;AACH,UAAU,YAAY;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CA4EtE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,EAAE,CAqF7E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM/C"}
package/dist/render.js ADDED
@@ -0,0 +1,286 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+ import { getOrAllocatePort } from './port-manager.js';
3
+ import MagicString from 'magic-string';
4
+ /**
5
+ * Render a template file by replacing {devports:type:service-name} patterns with allocated ports
6
+ */
7
+ export async function renderFile(filePath, options = {}) {
8
+ const templateContent = readFileSync(filePath, 'utf-8');
9
+ // Extract project name from template or use provided option
10
+ let projectName = options.projectName ?? extractProjectNameFromTemplate(templateContent);
11
+ if (!projectName) {
12
+ throw new Error('No DEVPORTS_PROJECT_NAME found in template and none provided via options');
13
+ }
14
+ // Make project name URL-safe
15
+ projectName = makeUrlSafe(projectName);
16
+ // Detect services from template patterns
17
+ const services = detectServicesFromTemplate(templateContent, filePath);
18
+ // Get or allocate ports for each service (reuses existing allocations)
19
+ const allocatedPorts = {};
20
+ for (const service of services) {
21
+ const [serviceName, serviceType] = service.split(':');
22
+ const port = await getOrAllocatePort(projectName, serviceName, serviceType);
23
+ allocatedPorts[serviceName] = port;
24
+ }
25
+ // Process template content
26
+ const renderedContent = processTemplate(templateContent, allocatedPorts, projectName, filePath);
27
+ // Write to output file if specified
28
+ if (options.outputFile) {
29
+ writeFileSync(options.outputFile, renderedContent);
30
+ }
31
+ return {
32
+ content: renderedContent,
33
+ allocatedPorts,
34
+ projectName,
35
+ };
36
+ }
37
+ /**
38
+ * Process template content using magic-string for efficient manipulation
39
+ */
40
+ export function processTemplate(templateContent, ports, projectName, filePath) {
41
+ const s = new MagicString(templateContent);
42
+ const commentRanges = findCommentRanges(templateContent, filePath);
43
+ // Find all template patterns
44
+ const patterns = [
45
+ {
46
+ regex: /\{devports:([^}]+)\}/g,
47
+ replacement: (match, serviceDef) => {
48
+ const parts = serviceDef.split(':');
49
+ // Handle project syntax: {devports:project}
50
+ if (parts.length === 1 && parts[0] === 'project') {
51
+ return projectName;
52
+ }
53
+ // Handle service syntax: {devports:type:service-name}
54
+ if (parts.length !== 2) {
55
+ console.warn(`⚠️ Invalid devports pattern: ${match} - must be {devports:type:service-name} or {devports:project}`);
56
+ return match;
57
+ }
58
+ const [, serviceName] = parts;
59
+ return ports[serviceName]?.toString() || match;
60
+ },
61
+ },
62
+ ];
63
+ patterns.forEach(({ regex, replacement }) => {
64
+ let match;
65
+ regex.lastIndex = 0; // Reset regex state
66
+ while ((match = regex.exec(templateContent)) !== null) {
67
+ const start = match.index;
68
+ const end = start + match[0].length;
69
+ // Skip if inside a comment
70
+ if (commentRanges.some((range) => start >= range.start && end <= range.end)) {
71
+ continue;
72
+ }
73
+ const replacementText = typeof replacement === 'function'
74
+ ? replacement(match[0], match[1])
75
+ : replacement;
76
+ s.overwrite(start, end, replacementText);
77
+ }
78
+ });
79
+ return s.toString();
80
+ }
81
+ /**
82
+ * Extract DEVPORTS_PROJECT_NAME from template content
83
+ */
84
+ export function extractProjectNameFromTemplate(templateContent) {
85
+ const lines = templateContent.split('\n');
86
+ for (const line of lines) {
87
+ const trimmedLine = line.trim();
88
+ if (trimmedLine.startsWith('DEVPORTS_PROJECT_NAME=')) {
89
+ const value = trimmedLine.split('=')[1];
90
+ // Remove quotes if present
91
+ const cleanValue = value?.replace(/^["']|["']$/g, '') || null;
92
+ // If the value is a placeholder (like {devports:project}),
93
+ // treat it as if no project name was specified
94
+ if (cleanValue &&
95
+ cleanValue.startsWith('{') &&
96
+ cleanValue.endsWith('}')) {
97
+ return null;
98
+ }
99
+ return cleanValue;
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+ /**
105
+ * Detect services from template file with {devports:type:service-name} patterns
106
+ */
107
+ export function detectServicesFromTemplate(templateContent, filePath) {
108
+ const services = [];
109
+ const commentRanges = findCommentRanges(templateContent, filePath);
110
+ const servicePattern = /\{devports:([^}]+)\}/g;
111
+ let match;
112
+ while ((match = servicePattern.exec(templateContent)) !== null) {
113
+ const start = match.index;
114
+ const end = start + match[0].length;
115
+ // Skip if inside a comment
116
+ if (commentRanges.some((range) => start >= range.start && end <= range.end)) {
117
+ continue;
118
+ }
119
+ const serviceDef = match[1];
120
+ const parts = serviceDef.split(':');
121
+ // Skip the special {devports:project} placeholder
122
+ if (parts.length === 1 && parts[0] === 'project') {
123
+ continue;
124
+ }
125
+ if (parts.length !== 2) {
126
+ console.warn(`⚠️ Invalid devports pattern: {devports:${serviceDef}} - must be {devports:type:service-name} or {devports:project}`);
127
+ continue;
128
+ }
129
+ const [serviceType, serviceName] = parts;
130
+ services.push(`${serviceName}:${serviceType}`);
131
+ }
132
+ return [...new Set(services)];
133
+ }
134
+ /**
135
+ * Get comment style configuration for a file based on its extension
136
+ */
137
+ export function getCommentStyleForFile(filePath) {
138
+ if (!filePath) {
139
+ // Default to shell/env style comments
140
+ return { lineComment: '#' };
141
+ }
142
+ // Remove .devports suffix to get the actual file type
143
+ const actualPath = filePath.replace(/\.devports$/, '');
144
+ const ext = actualPath.split('.').pop()?.toLowerCase();
145
+ switch (ext) {
146
+ case 'env':
147
+ case 'sh':
148
+ case 'bash':
149
+ case 'zsh':
150
+ case 'conf':
151
+ case 'config':
152
+ case 'yml':
153
+ case 'yaml':
154
+ case 'py':
155
+ case 'rb':
156
+ case 'r':
157
+ return { lineComment: '#' };
158
+ case 'js':
159
+ case 'jsx':
160
+ case 'ts':
161
+ case 'tsx':
162
+ case 'java':
163
+ case 'c':
164
+ case 'cpp':
165
+ case 'cc':
166
+ case 'cxx':
167
+ case 'h':
168
+ case 'hpp':
169
+ case 'cs':
170
+ case 'php':
171
+ case 'go':
172
+ case 'rs':
173
+ case 'scala':
174
+ case 'swift':
175
+ return {
176
+ lineComment: '//',
177
+ blockStart: '/*',
178
+ blockEnd: '*/',
179
+ };
180
+ case 'json':
181
+ // JSON doesn't officially support comments, but some parsers allow //
182
+ return { lineComment: '//' };
183
+ case 'html':
184
+ case 'xml':
185
+ case 'svg':
186
+ return {
187
+ blockStart: '<!--',
188
+ blockEnd: '-->',
189
+ };
190
+ case 'sql':
191
+ return {
192
+ lineComment: '--',
193
+ blockStart: '/*',
194
+ blockEnd: '*/',
195
+ };
196
+ case 'lua':
197
+ return { lineComment: '--' };
198
+ case 'vim':
199
+ return { lineComment: '"' };
200
+ default:
201
+ // For unknown extensions, detect based on content patterns
202
+ return { lineComment: '#' }; // Most common default
203
+ }
204
+ }
205
+ /**
206
+ * Find all comment ranges in the content, respecting string literals
207
+ */
208
+ export function findCommentRanges(content, filePath) {
209
+ const commentStyle = getCommentStyleForFile(filePath);
210
+ const ranges = [];
211
+ let i = 0;
212
+ let inSingleQuote = false;
213
+ let inDoubleQuote = false;
214
+ let escaped = false;
215
+ while (i < content.length) {
216
+ const char = content[i];
217
+ // Handle escape sequences in strings
218
+ if (escaped) {
219
+ escaped = false;
220
+ i++;
221
+ continue;
222
+ }
223
+ if (char === '\\' && (inSingleQuote || inDoubleQuote)) {
224
+ escaped = true;
225
+ i++;
226
+ continue;
227
+ }
228
+ // Handle string literals
229
+ if (char === '"' && !inSingleQuote) {
230
+ inDoubleQuote = !inDoubleQuote;
231
+ i++;
232
+ continue;
233
+ }
234
+ if (char === "'" && !inDoubleQuote) {
235
+ inSingleQuote = !inSingleQuote;
236
+ i++;
237
+ continue;
238
+ }
239
+ // Skip comment detection inside string literals
240
+ if (inSingleQuote || inDoubleQuote) {
241
+ i++;
242
+ continue;
243
+ }
244
+ // Check for line comments
245
+ if (commentStyle.lineComment &&
246
+ content.slice(i).startsWith(commentStyle.lineComment)) {
247
+ const start = i;
248
+ // Find end of line
249
+ const lineEnd = content.indexOf('\n', i);
250
+ const end = lineEnd === -1 ? content.length : lineEnd;
251
+ ranges.push({ start, end });
252
+ i = end;
253
+ continue;
254
+ }
255
+ // Check for block comments
256
+ if (commentStyle.blockStart &&
257
+ content.slice(i).startsWith(commentStyle.blockStart)) {
258
+ const start = i;
259
+ const blockEndPos = content.indexOf(commentStyle.blockEnd ?? '', i + commentStyle.blockStart.length);
260
+ if (blockEndPos === -1) {
261
+ // Block comment doesn't end - goes to end of file
262
+ ranges.push({ start, end: content.length });
263
+ break;
264
+ }
265
+ else {
266
+ const end = blockEndPos + (commentStyle.blockEnd ?? '').length;
267
+ ranges.push({ start, end });
268
+ i = end;
269
+ continue;
270
+ }
271
+ }
272
+ i++;
273
+ }
274
+ return ranges;
275
+ }
276
+ /**
277
+ * Make string URL/hostname safe
278
+ */
279
+ export function makeUrlSafe(str) {
280
+ return str
281
+ .toLowerCase()
282
+ .replace(/[^a-z0-9-]/g, '-') // Replace non-alphanumeric with hyphens
283
+ .replace(/-+/g, '-') // Collapse multiple hyphens
284
+ .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
285
+ }
286
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}