@buildepicshit/cli 0.0.4 → 0.0.6
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/package.json +1 -1
- package/src/orchestrator/core/__tests__/command.test.ts +82 -18
- package/src/orchestrator/core/command.ts +49 -33
- package/src/orchestrator/core/index.ts +0 -1
- package/src/orchestrator/core/types.ts +3 -5
- package/src/orchestrator/index.ts +6 -2
- package/src/orchestrator/presets/docker.ts +14 -23
- package/src/orchestrator/presets/index.ts +2 -2
- package/src/orchestrator/presets/nextjs.ts +15 -17
- package/src/orchestrator/utils/dna.ts +16 -7
package/package.json
CHANGED
|
@@ -133,14 +133,14 @@ describe("command identity", () => {
|
|
|
133
133
|
expect(c.identity.name).toBe("tsx");
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
it("overrides identity with .
|
|
137
|
-
const c = cmd("node server.js").
|
|
136
|
+
it("overrides identity with .withIdentity(suffix)", () => {
|
|
137
|
+
const c = cmd("node server.js").withIdentity("custom").dir("apps/api");
|
|
138
138
|
expect(c.identity.name).toBe("custom");
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
it("identity suffix propagates through builder chain", () => {
|
|
142
142
|
const c = cmd("node server.js")
|
|
143
|
-
.
|
|
143
|
+
.withIdentity("api")
|
|
144
144
|
.dir("apps/server")
|
|
145
145
|
.env({ KEY: "value" })
|
|
146
146
|
.flag("verbose");
|
|
@@ -149,13 +149,39 @@ describe("command identity", () => {
|
|
|
149
149
|
|
|
150
150
|
it("identity is immutable across branches", () => {
|
|
151
151
|
const base = cmd("node server.js");
|
|
152
|
-
const withId = base.
|
|
152
|
+
const withId = base.withIdentity("custom");
|
|
153
153
|
expect(base.identity.name).toBe("node");
|
|
154
154
|
expect(withId.identity.name).toBe("custom");
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
+
it("throws a helpful error when .identity is called as a function", () => {
|
|
158
|
+
const c = cmd("node server.js");
|
|
159
|
+
expect(() => {
|
|
160
|
+
(c.identity as unknown as (s: string) => void)("api");
|
|
161
|
+
}).toThrow(
|
|
162
|
+
'.identity("api") is not a function. To set identity, use .withIdentity("api") instead.',
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("throws a helpful error when .identity is called without args", () => {
|
|
167
|
+
const c = cmd("node server.js");
|
|
168
|
+
expect(() => {
|
|
169
|
+
(c.identity as unknown as () => void)();
|
|
170
|
+
}).toThrow(
|
|
171
|
+
".identity() is not a function. To set identity, use .withIdentity() instead.",
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("identity proxy still exposes .port() and .url()", async () => {
|
|
176
|
+
const c = cmd("node server.js").withIdentity("api");
|
|
177
|
+
expect(c.identity.name).toBe("api");
|
|
178
|
+
expect(typeof c.identity.port()).toBe("function");
|
|
179
|
+
expect(typeof c.identity.url("/health")).toBe("function");
|
|
180
|
+
expect(typeof c.identity.localhostUrl()).toBe("function");
|
|
181
|
+
});
|
|
182
|
+
|
|
157
183
|
it("identity.port() returns a token", async () => {
|
|
158
|
-
const c = cmd("node server.js").
|
|
184
|
+
const c = cmd("node server.js").withIdentity("api").dir("apps/api");
|
|
159
185
|
const token = c.identity.port();
|
|
160
186
|
expect(typeof token).toBe("function");
|
|
161
187
|
const port = Number.parseInt(
|
|
@@ -167,14 +193,14 @@ describe("command identity", () => {
|
|
|
167
193
|
});
|
|
168
194
|
|
|
169
195
|
it("identity.localhostUrl() returns a token", async () => {
|
|
170
|
-
const c = cmd("node server.js").
|
|
196
|
+
const c = cmd("node server.js").withIdentity("api");
|
|
171
197
|
const token = c.identity.localhostUrl("/health");
|
|
172
198
|
const url = await (token as (ctx: ComputeContext) => Promise<string>)(ctx);
|
|
173
199
|
expect(url).toMatch(/^http:\/\/localhost:\d+\/health$/);
|
|
174
200
|
});
|
|
175
201
|
|
|
176
202
|
it("cross-command identity access works", async () => {
|
|
177
|
-
const server = cmd("node server.js").
|
|
203
|
+
const server = cmd("node server.js").withIdentity("api").dir("apps/api");
|
|
178
204
|
const built = await cmd("next dev")
|
|
179
205
|
.env({ API_URL: server.identity.localhostUrl() })
|
|
180
206
|
.build(ctx);
|
|
@@ -182,25 +208,35 @@ describe("command identity", () => {
|
|
|
182
208
|
});
|
|
183
209
|
|
|
184
210
|
it("identity.port(name) resolves a named sub-identity", async () => {
|
|
185
|
-
const c = cmd("docker compose").
|
|
211
|
+
const c = cmd("docker compose").withIdentity("infra");
|
|
186
212
|
const dbPortToken = c.identity.port("database");
|
|
187
213
|
const infraPortToken = c.identity.port();
|
|
188
|
-
const dbPort = await (
|
|
189
|
-
|
|
214
|
+
const dbPort = await (
|
|
215
|
+
dbPortToken as (ctx: ComputeContext) => Promise<string>
|
|
216
|
+
)(ctx);
|
|
217
|
+
const infraPort = await (
|
|
218
|
+
infraPortToken as (ctx: ComputeContext) => Promise<string>
|
|
219
|
+
)(ctx);
|
|
190
220
|
// Named sub-identity should produce a different port
|
|
191
221
|
expect(dbPort).not.toBe(infraPort);
|
|
192
222
|
expect(Number.parseInt(dbPort, 10)).toBeGreaterThanOrEqual(3000);
|
|
193
223
|
});
|
|
194
224
|
|
|
195
225
|
it("identity.localhostUrl with portIdentity option", async () => {
|
|
196
|
-
const c = cmd("docker compose").
|
|
197
|
-
const urlToken = c.identity.localhostUrl("/health", {
|
|
198
|
-
|
|
226
|
+
const c = cmd("docker compose").withIdentity("infra");
|
|
227
|
+
const urlToken = c.identity.localhostUrl("/health", {
|
|
228
|
+
portIdentity: "minio",
|
|
229
|
+
});
|
|
230
|
+
const url = await (urlToken as (ctx: ComputeContext) => Promise<string>)(
|
|
231
|
+
ctx,
|
|
232
|
+
);
|
|
199
233
|
expect(url).toMatch(/^http:\/\/localhost:\d+\/health$/);
|
|
200
234
|
|
|
201
235
|
// Port in the URL should match minio identity, not infra
|
|
202
236
|
const minioPortToken = c.identity.port("minio");
|
|
203
|
-
const minioPort = await (
|
|
237
|
+
const minioPort = await (
|
|
238
|
+
minioPortToken as (ctx: ComputeContext) => Promise<string>
|
|
239
|
+
)(ctx);
|
|
204
240
|
expect(url).toBe(`http://localhost:${minioPort}/health`);
|
|
205
241
|
});
|
|
206
242
|
});
|
|
@@ -216,7 +252,7 @@ describe("multiple waitFor", () => {
|
|
|
216
252
|
|
|
217
253
|
it("accepts callback form for multiple waitFor", async () => {
|
|
218
254
|
const c = cmd("node server.js")
|
|
219
|
-
.
|
|
255
|
+
.withIdentity("api")
|
|
220
256
|
.waitFor((self) => health.http(self.identity.url("/livez")))
|
|
221
257
|
.waitFor((self) => health.tcp("localhost", self.identity.port()));
|
|
222
258
|
const built = await c.build(ctx);
|
|
@@ -227,7 +263,7 @@ describe("multiple waitFor", () => {
|
|
|
227
263
|
describe("waitFor callback form", () => {
|
|
228
264
|
it("accepts a callback that receives the command", async () => {
|
|
229
265
|
const c = cmd("node server.js")
|
|
230
|
-
.
|
|
266
|
+
.withIdentity("api")
|
|
231
267
|
.dir("apps/api")
|
|
232
268
|
.waitFor((self) => health.http(self.identity.url("/livez")));
|
|
233
269
|
|
|
@@ -240,7 +276,7 @@ describe("waitFor callback form", () => {
|
|
|
240
276
|
describe("env callback form", () => {
|
|
241
277
|
it("accepts a callback that receives the command", async () => {
|
|
242
278
|
const c = cmd("node server.js")
|
|
243
|
-
.
|
|
279
|
+
.withIdentity("api")
|
|
244
280
|
.env((self) => ({ PORT: self.identity.port() }));
|
|
245
281
|
|
|
246
282
|
const built = await c.build(ctx);
|
|
@@ -251,7 +287,7 @@ describe("env callback form", () => {
|
|
|
251
287
|
|
|
252
288
|
it("mixes callback env with record env", async () => {
|
|
253
289
|
const c = cmd("node server.js")
|
|
254
|
-
.
|
|
290
|
+
.withIdentity("api")
|
|
255
291
|
.env({ A: "1" })
|
|
256
292
|
.env((self) => ({ PORT: self.identity.port() }))
|
|
257
293
|
.env({ B: "2" });
|
|
@@ -262,3 +298,31 @@ describe("env callback form", () => {
|
|
|
262
298
|
expect(Number.parseInt(built.env.PORT, 10)).toBeGreaterThanOrEqual(3000);
|
|
263
299
|
});
|
|
264
300
|
});
|
|
301
|
+
|
|
302
|
+
describe("logs", () => {
|
|
303
|
+
it("logs({ silent: true }) builds without error", async () => {
|
|
304
|
+
const c = cmd("node server.js").logs({ silent: true });
|
|
305
|
+
const built = await c.build(ctx);
|
|
306
|
+
expect(built).toBeDefined();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("logs state propagates through builder chain", async () => {
|
|
310
|
+
const c = cmd("node server.js")
|
|
311
|
+
.logs({ silent: true })
|
|
312
|
+
.dir("apps/api")
|
|
313
|
+
.env({ KEY: "value" })
|
|
314
|
+
.flag("verbose");
|
|
315
|
+
const built = await c.build(ctx);
|
|
316
|
+
expect(built).toBeDefined();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("is immutable — logs does not affect original", async () => {
|
|
320
|
+
const base = cmd("node server.js");
|
|
321
|
+
const silent = base.logs({ silent: true });
|
|
322
|
+
// Both should build fine — they are independent instances
|
|
323
|
+
const built1 = await base.build(ctx);
|
|
324
|
+
const built2 = await silent.build(ctx);
|
|
325
|
+
expect(built1).toBeDefined();
|
|
326
|
+
expect(built2).toBeDefined();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createDeferred,
|
|
6
6
|
getRuntimeContext,
|
|
7
7
|
} from "../runner/runtime-context.js";
|
|
8
|
-
import { createIdentity } from "../utils/dna.js";
|
|
8
|
+
import { createIdentity, type Identity } from "../utils/dna.js";
|
|
9
9
|
import { resolveToken } from "./token.js";
|
|
10
10
|
import {
|
|
11
11
|
type BuiltCommand,
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
type HealthCheck,
|
|
15
15
|
type HealthCheckCallback,
|
|
16
16
|
type ICommand,
|
|
17
|
-
type IdentityAccessor,
|
|
18
17
|
isEnvCallback,
|
|
19
18
|
isLazyEnvSource,
|
|
20
19
|
type Runnable,
|
|
@@ -31,6 +30,7 @@ interface CommandState {
|
|
|
31
30
|
readonly flags: readonly { name: string; value?: Token }[];
|
|
32
31
|
readonly healthChecks: readonly HealthCheck[];
|
|
33
32
|
readonly identitySuffix: string | null;
|
|
33
|
+
readonly logsSilent: boolean;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
class Command implements ICommand {
|
|
@@ -41,24 +41,39 @@ class Command implements ICommand {
|
|
|
41
41
|
this.state = state;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
get identity():
|
|
45
|
-
const id = createIdentity(
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
get identity(): Identity {
|
|
45
|
+
const id = createIdentity(this.state.identitySuffix ?? this.deriveName());
|
|
46
|
+
|
|
47
|
+
function throwOnCall(...args: unknown[]): never {
|
|
48
|
+
const hint = typeof args[0] === "string" ? `("${args[0]}")` : "()";
|
|
49
|
+
throw new TypeError(
|
|
50
|
+
`.identity${hint} is not a function. To set identity, use .withIdentity${hint} instead. ` +
|
|
51
|
+
`.identity is a read-only property that provides access to .port() and .url().`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return new Proxy(throwOnCall, {
|
|
56
|
+
apply(_target, _thisArg, args) {
|
|
57
|
+
throwOnCall(...(args as unknown[]));
|
|
58
|
+
},
|
|
59
|
+
get(_target, prop, receiver) {
|
|
60
|
+
return Reflect.get(id, prop, receiver);
|
|
61
|
+
},
|
|
62
|
+
has(_target, prop) {
|
|
63
|
+
return prop in id;
|
|
64
|
+
},
|
|
65
|
+
}) as unknown as Identity;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
withIdentity(suffix: string): ICommand {
|
|
69
|
+
return new Command({ ...this.state, identitySuffix: suffix });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logs(options: { silent?: boolean }): ICommand {
|
|
73
|
+
return new Command({
|
|
74
|
+
...this.state,
|
|
75
|
+
logsSilent: options.silent ?? false,
|
|
56
76
|
});
|
|
57
|
-
return Object.assign(setter, {
|
|
58
|
-
localhostUrl: id.localhostUrl,
|
|
59
|
-
port: id.port,
|
|
60
|
-
url: id.url,
|
|
61
|
-
}) as IdentityAccessor;
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
flag(name: string, value?: Token): ICommand {
|
|
@@ -194,9 +209,7 @@ class Command implements ICommand {
|
|
|
194
209
|
rtx.logger.system(` env: ${JSON.stringify(built.env)}`);
|
|
195
210
|
}
|
|
196
211
|
if (this.state.healthChecks.length > 0) {
|
|
197
|
-
const types = this.state.healthChecks
|
|
198
|
-
.map((hc) => hc.type)
|
|
199
|
-
.join(", ");
|
|
212
|
+
const types = this.state.healthChecks.map((hc) => hc.type).join(", ");
|
|
200
213
|
rtx.logger.system(` waitFor: ${types}`);
|
|
201
214
|
}
|
|
202
215
|
if (this.state.dependencies.length > 0) {
|
|
@@ -226,17 +239,19 @@ class Command implements ICommand {
|
|
|
226
239
|
});
|
|
227
240
|
|
|
228
241
|
proc.onOutput((line, stream) => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
242
|
+
if (!this.state.logsSilent) {
|
|
243
|
+
rtx.logger.output(name, line, stream);
|
|
244
|
+
rtx.eventBus.emit({
|
|
245
|
+
data: {
|
|
246
|
+
id: name,
|
|
247
|
+
line,
|
|
248
|
+
serviceName: name,
|
|
249
|
+
stream,
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
},
|
|
252
|
+
type: "log",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
240
255
|
});
|
|
241
256
|
|
|
242
257
|
if (this.state.healthChecks.length > 0) {
|
|
@@ -330,5 +345,6 @@ export function cmd(base: string): ICommand {
|
|
|
330
345
|
flags: [],
|
|
331
346
|
healthChecks: [],
|
|
332
347
|
identitySuffix: null,
|
|
348
|
+
logsSilent: false,
|
|
333
349
|
});
|
|
334
350
|
}
|
|
@@ -46,10 +46,6 @@ export interface BuiltCommand {
|
|
|
46
46
|
env: Record<string, string>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export interface IdentityAccessor extends Identity {
|
|
50
|
-
(suffix: string): ICommand;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
49
|
export interface ICommand {
|
|
54
50
|
readonly __type: "command";
|
|
55
51
|
arg(value: Token): ICommand;
|
|
@@ -60,9 +56,11 @@ export interface ICommand {
|
|
|
60
56
|
dir(path: string): ICommand;
|
|
61
57
|
env(source: EnvSource): ICommand;
|
|
62
58
|
flag(name: string, value?: Token): ICommand;
|
|
63
|
-
readonly identity:
|
|
59
|
+
readonly identity: Identity;
|
|
60
|
+
logs(options: { silent?: boolean }): ICommand;
|
|
64
61
|
run(): Promise<void>;
|
|
65
62
|
waitFor(check: HealthCheck | HealthCheckCallback): ICommand;
|
|
63
|
+
withIdentity(suffix: string): ICommand;
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
// ─── Compound ────────────────────────────────────────────────────────────────
|
|
@@ -25,7 +25,6 @@ export type {
|
|
|
25
25
|
HealthCheckCallback,
|
|
26
26
|
ICommand,
|
|
27
27
|
ICompound,
|
|
28
|
-
IdentityAccessor,
|
|
29
28
|
ProcessStatus,
|
|
30
29
|
Runnable,
|
|
31
30
|
Token,
|
|
@@ -34,7 +33,12 @@ export type {
|
|
|
34
33
|
export type { Logger } from "./logger/logger.js";
|
|
35
34
|
export { createLazyLogger, createLogger } from "./logger/logger.js";
|
|
36
35
|
// Presets
|
|
37
|
-
export type {
|
|
36
|
+
export type {
|
|
37
|
+
IDockerCompose,
|
|
38
|
+
IDrizzle,
|
|
39
|
+
INextjs,
|
|
40
|
+
NextPreset,
|
|
41
|
+
} from "./presets/index.js";
|
|
38
42
|
export {
|
|
39
43
|
docker,
|
|
40
44
|
dockerCompose,
|
|
@@ -6,14 +6,16 @@ import type {
|
|
|
6
6
|
HealthCheck,
|
|
7
7
|
HealthCheckCallback,
|
|
8
8
|
ICommand,
|
|
9
|
-
IdentityAccessor,
|
|
10
9
|
Runnable,
|
|
11
10
|
Token,
|
|
12
11
|
} from "../core/types.js";
|
|
12
|
+
import type { Identity } from "../utils/dna.js";
|
|
13
13
|
|
|
14
14
|
export interface IDockerCompose extends ICommand {
|
|
15
15
|
file(path: string): IDockerCompose;
|
|
16
|
+
logs(options: { silent?: boolean }): IDockerCompose;
|
|
16
17
|
up(options?: { detach?: boolean }): IDockerCompose;
|
|
18
|
+
withIdentity(suffix: string): IDockerCompose;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
interface DockerComposeState {
|
|
@@ -49,24 +51,16 @@ class DockerCompose implements IDockerCompose {
|
|
|
49
51
|
|
|
50
52
|
// ─── ICommand delegation (returns IDockerCompose for chaining) ───────
|
|
51
53
|
|
|
52
|
-
get identity():
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
configurable: true,
|
|
63
|
-
value: innerAccessor.name,
|
|
64
|
-
});
|
|
65
|
-
return Object.assign(setter, {
|
|
66
|
-
localhostUrl: innerAccessor.localhostUrl,
|
|
67
|
-
port: innerAccessor.port,
|
|
68
|
-
url: innerAccessor.url,
|
|
69
|
-
}) as IdentityAccessor;
|
|
54
|
+
get identity(): Identity {
|
|
55
|
+
return this.inner.identity;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
withIdentity(suffix: string): IDockerCompose {
|
|
59
|
+
return new DockerCompose(this.inner.withIdentity(suffix), this.dcState);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
logs(options: { silent?: boolean }): IDockerCompose {
|
|
63
|
+
return new DockerCompose(this.inner.logs(options), this.dcState);
|
|
70
64
|
}
|
|
71
65
|
|
|
72
66
|
flag(name: string, value?: Token): IDockerCompose {
|
|
@@ -94,10 +88,7 @@ class DockerCompose implements IDockerCompose {
|
|
|
94
88
|
}
|
|
95
89
|
|
|
96
90
|
dependsOn(...deps: Runnable[]): IDockerCompose {
|
|
97
|
-
return new DockerCompose(
|
|
98
|
-
this.inner.dependsOn(...deps),
|
|
99
|
-
this.dcState,
|
|
100
|
-
);
|
|
91
|
+
return new DockerCompose(this.inner.dependsOn(...deps), this.dcState);
|
|
101
92
|
}
|
|
102
93
|
|
|
103
94
|
collectNames(): string[] {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { type IDrizzle
|
|
1
|
+
export { docker, dockerCompose, type IDockerCompose } from "./docker.js";
|
|
2
|
+
export { drizzle, type IDrizzle } from "./drizzle.js";
|
|
3
3
|
export { esbuild, esbuildWatch } from "./esbuild.js";
|
|
4
4
|
export { hono } from "./hono.js";
|
|
5
5
|
export { type INextjs, type NextPreset, next, nextjs } from "./nextjs.js";
|
|
@@ -6,12 +6,15 @@ import type {
|
|
|
6
6
|
HealthCheck,
|
|
7
7
|
HealthCheckCallback,
|
|
8
8
|
ICommand,
|
|
9
|
-
IdentityAccessor,
|
|
10
9
|
Runnable,
|
|
11
10
|
Token,
|
|
12
11
|
} from "../core/types.js";
|
|
12
|
+
import type { Identity } from "../utils/dna.js";
|
|
13
13
|
|
|
14
|
-
export interface INextjs extends ICommand {
|
|
14
|
+
export interface INextjs extends ICommand {
|
|
15
|
+
logs(options: { silent?: boolean }): INextjs;
|
|
16
|
+
withIdentity(suffix: string): INextjs;
|
|
17
|
+
}
|
|
15
18
|
|
|
16
19
|
/** @deprecated Use `INextjs` instead */
|
|
17
20
|
export type NextPreset = INextjs;
|
|
@@ -26,21 +29,16 @@ class Nextjs implements INextjs {
|
|
|
26
29
|
|
|
27
30
|
// ─── ICommand delegation (returns INextjs for chaining) ─────────────
|
|
28
31
|
|
|
29
|
-
get identity():
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return Object.assign(setter, {
|
|
40
|
-
localhostUrl: innerAccessor.localhostUrl,
|
|
41
|
-
port: innerAccessor.port,
|
|
42
|
-
url: innerAccessor.url,
|
|
43
|
-
}) as IdentityAccessor;
|
|
32
|
+
get identity(): Identity {
|
|
33
|
+
return this.inner.identity;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
withIdentity(suffix: string): INextjs {
|
|
37
|
+
return new Nextjs(this.inner.withIdentity(suffix));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
logs(options: { silent?: boolean }): INextjs {
|
|
41
|
+
return new Nextjs(this.inner.logs(options));
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
flag(name: string, value?: Token): INextjs {
|
|
@@ -12,7 +12,16 @@ const RESERVED_PORTS = new Set([
|
|
|
12
12
|
5984, // CouchDB
|
|
13
13
|
6379, // Redis
|
|
14
14
|
6380, // Redis alt
|
|
15
|
-
6660,
|
|
15
|
+
6660,
|
|
16
|
+
6661,
|
|
17
|
+
6662,
|
|
18
|
+
6663,
|
|
19
|
+
6664,
|
|
20
|
+
6665,
|
|
21
|
+
6666,
|
|
22
|
+
6667,
|
|
23
|
+
6668,
|
|
24
|
+
6669, // IRC
|
|
16
25
|
6697, // IRC over TLS (ircs-u)
|
|
17
26
|
7474, // Neo4j
|
|
18
27
|
8080, // HTTP alternate
|
|
@@ -32,23 +41,23 @@ function computePort(
|
|
|
32
41
|
): number {
|
|
33
42
|
const span = range.max - range.min + 1;
|
|
34
43
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
35
|
-
const input =
|
|
44
|
+
const input =
|
|
45
|
+
attempt === 0 ? `${cwd}:${suffix}` : `${cwd}:${suffix}:${attempt}`;
|
|
36
46
|
const hash = createHash("md5").update(input).digest("hex");
|
|
37
47
|
const num = Number.parseInt(hash.slice(0, 8), 16);
|
|
38
48
|
const port = range.min + (num % span);
|
|
39
49
|
if (!RESERVED_PORTS.has(port)) return port;
|
|
40
50
|
}
|
|
41
51
|
// Extremely unlikely fallback — just offset from the last attempt
|
|
42
|
-
const hash = createHash("md5")
|
|
52
|
+
const hash = createHash("md5")
|
|
53
|
+
.update(`${cwd}:${suffix}:fallback`)
|
|
54
|
+
.digest("hex");
|
|
43
55
|
const num = Number.parseInt(hash.slice(0, 8), 16);
|
|
44
56
|
return range.min + (num % span);
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
export interface Identity {
|
|
48
|
-
localhostUrl: (
|
|
49
|
-
path?: string,
|
|
50
|
-
options?: { portIdentity?: string },
|
|
51
|
-
) => Token;
|
|
60
|
+
localhostUrl: (path?: string, options?: { portIdentity?: string }) => Token;
|
|
52
61
|
readonly name: string;
|
|
53
62
|
port: (identityName?: string) => Token;
|
|
54
63
|
url: (path?: string, options?: { portIdentity?: string }) => Token;
|