@gachlab/devup 0.9.1 → 0.9.3
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/CHANGELOG.md +22 -0
- package/dist/control-plane/client.d.ts.map +1 -1
- package/dist/control-plane/socket-server.d.ts.map +1 -1
- package/dist/index.js +47 -13
- package/dist/index.js.map +1 -1
- package/dist/process/port-conflicts.d.ts +5 -3
- package/dist/process/port-conflicts.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to `@gachlab/devup` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.9.3] — 2026-05-22
|
|
9
|
+
|
|
10
|
+
Critical hotfix for `devup down` against the VS Code extension.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`devup down` no longer gets SIGKILL'd because the control-plane socket hangs on streaming clients.** The control-plane server's `close()` awaited every client to disconnect on its own, but long-lived streaming subscriptions (`status.follow`, `logs.follow` — exactly what the VS Code extension uses) never close until the daemon tells them to. Result: `devup down` waited the full 10 s grace, then SIGKILL'd the daemon. SIGKILL skips the cleanup handler → all spawned services orphaned to init, ports left busy, next `devup up -d` hits EADDRINUSE on every port. Fix: track every active client socket and `destroy()` them before awaiting `server.close()`. Clean shutdowns now complete in milliseconds even with the extension connected.
|
|
14
|
+
- **Pre-boot port-conflict scan now covers web services too.** They were skipped on the assumption that dev servers handle retry themselves, but in daemon mode the user wants devup to own the configured ports — same as APIs. If a web port is held by a stray Vite/ng-serve from a previous run, the scan now flags it and offers to take it over.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- New unit test asserts `socket.close()` completes in under 2 s with an active `logs.follow` subscription.
|
|
18
|
+
|
|
19
|
+
## [0.9.2] — 2026-05-22
|
|
20
|
+
|
|
21
|
+
Critical hotfix. **All 0.9.x users should upgrade immediately.**
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Bundle's top-level `main()` would fire when devup was imported as a library.** When a user's `devup.config.ts` did `import { defineConfig } from '@gachlab/devup'`, Node loaded our compiled `dist/index.js` to satisfy the import — and the bundle's top-level `main()` invocation ran, starting a SECOND, concurrent devup process. Symptoms: every line of output duplicated, three or more racing instances of the same service spawning in milliseconds, ports clobbered, the daemon's socket created but never bound, `devup ctl ping` ECONNREFUSED. Fix: guard `main()` with a `realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)` check so it only fires when the script is invoked directly (as the `devup` binary), not when imported.
|
|
25
|
+
- **`devup ctl` crashed with an unhandled `error` event on ECONNREFUSED.** The socket-error handler was attached to the connection but Node's `readline.createInterface` re-forwarded the error through its own emitter, which had no listener, so the process crashed instead of reporting cleanly. Now we attach the handler on both the socket and the readline interface.
|
|
26
|
+
|
|
27
|
+
### Why this matters
|
|
28
|
+
Every devup invocation that loaded a config file (i.e. everything except `--version` / `--help`) was silently running two instances of itself. That is the root cause of every weird behaviour reported against 0.9.0 / 0.9.1: duplicated prompts, daemons that "die" right after starting, sockets that exist but refuse connections, port-takeover prompts firing for ports the user expected devup to own.
|
|
29
|
+
|
|
8
30
|
## [0.9.1] — 2026-05-22
|
|
9
31
|
|
|
10
32
|
Hotfix for two issues reported moments after 0.9.0 hit:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/control-plane/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAMhF;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CACrB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/control-plane/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAMhF;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CACrB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;+FAC+F;AAC/F,wBAAgB,UAAU,CACxB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,EACrC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAC7B,MAAM,IAAI,CA0BZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-server.d.ts","sourceRoot":"","sources":["../../src/control-plane/socket-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAMlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;oBAQoB;AAEpB,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACpC,iCAAiC;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,8BAA8B;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D;2CACuC;IACvC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC3F,2EAA2E;IAC3E,WAAW,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,UAAU,EACf,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC1D,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"socket-server.d.ts","sourceRoot":"","sources":["../../src/control-plane/socket-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAMlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;oBAQoB;AAEpB,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACpC,iCAAiC;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,8BAA8B;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D;2CACuC;IACvC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC3F,2EAA2E;IAC3E,WAAW,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,UAAU,EACf,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC1D,OAAO,CAAC,kBAAkB,CAAC,CA+C7B"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import React7 from "react";
|
|
5
5
|
import { render } from "ink";
|
|
6
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
6
|
+
import { readFileSync as readFileSync4, realpathSync } from "fs";
|
|
7
7
|
import { dirname as dirname7, join as join10 } from "path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9
9
|
import { homedir as homedir5 } from "os";
|
|
@@ -720,7 +720,12 @@ async function startSocketServer(projectName, ctx, opts = {}) {
|
|
|
720
720
|
} catch {
|
|
721
721
|
}
|
|
722
722
|
}
|
|
723
|
-
const
|
|
723
|
+
const activeClients = /* @__PURE__ */ new Set();
|
|
724
|
+
const server = createServer((socket) => {
|
|
725
|
+
activeClients.add(socket);
|
|
726
|
+
socket.once("close", () => activeClients.delete(socket));
|
|
727
|
+
handleClient(socket, ctx);
|
|
728
|
+
});
|
|
724
729
|
await new Promise((resolve4, reject) => {
|
|
725
730
|
server.once("error", reject);
|
|
726
731
|
server.listen(path, () => {
|
|
@@ -737,6 +742,8 @@ async function startSocketServer(projectName, ctx, opts = {}) {
|
|
|
737
742
|
server,
|
|
738
743
|
path,
|
|
739
744
|
async close() {
|
|
745
|
+
for (const sock of activeClients) sock.destroy();
|
|
746
|
+
activeClients.clear();
|
|
740
747
|
await new Promise((resolve4) => server.close(() => resolve4()));
|
|
741
748
|
if (existsSync5(path)) {
|
|
742
749
|
try {
|
|
@@ -884,17 +891,31 @@ Start it with \`devup\` first.`
|
|
|
884
891
|
}
|
|
885
892
|
function sendRpc(socketPath, method, params = {}) {
|
|
886
893
|
return new Promise((resolve4, reject) => {
|
|
894
|
+
let settled = false;
|
|
895
|
+
const fail = (err) => {
|
|
896
|
+
if (!settled) {
|
|
897
|
+
settled = true;
|
|
898
|
+
reject(err);
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
const ok = (v) => {
|
|
902
|
+
if (!settled) {
|
|
903
|
+
settled = true;
|
|
904
|
+
resolve4(v);
|
|
905
|
+
}
|
|
906
|
+
};
|
|
887
907
|
const c = createConnection(socketPath);
|
|
888
|
-
c.on("error", reject);
|
|
889
908
|
const rl = createInterface2({ input: c });
|
|
909
|
+
c.on("error", fail);
|
|
910
|
+
rl.on("error", fail);
|
|
890
911
|
rl.once("line", (l) => {
|
|
891
912
|
c.end();
|
|
892
913
|
try {
|
|
893
914
|
const msg = JSON.parse(l);
|
|
894
|
-
if (msg.error)
|
|
895
|
-
else
|
|
915
|
+
if (msg.error) fail(new Error(msg.error.message ?? String(msg.error)));
|
|
916
|
+
else ok(msg.result);
|
|
896
917
|
} catch (e) {
|
|
897
|
-
|
|
918
|
+
fail(e);
|
|
898
919
|
}
|
|
899
920
|
});
|
|
900
921
|
c.write(JSON.stringify({ id: 1, method, params }) + "\n");
|
|
@@ -904,7 +925,9 @@ function openStream(socketPath, method, params, onFrame, onError) {
|
|
|
904
925
|
const c = createConnection(socketPath);
|
|
905
926
|
const rl = createInterface2({ input: c });
|
|
906
927
|
let ackDone = false;
|
|
907
|
-
|
|
928
|
+
const onErr = (err) => onError?.(err);
|
|
929
|
+
c.on("error", onErr);
|
|
930
|
+
rl.on("error", onErr);
|
|
908
931
|
c.write(JSON.stringify({ id: 1, method, params }) + "\n");
|
|
909
932
|
rl.on("line", (l) => {
|
|
910
933
|
try {
|
|
@@ -2567,9 +2590,8 @@ function parseLsof(stdout) {
|
|
|
2567
2590
|
return null;
|
|
2568
2591
|
}
|
|
2569
2592
|
async function scanPortConflicts(services) {
|
|
2570
|
-
const apis = services.filter((s) => s.type === "api");
|
|
2571
2593
|
const conflicts = [];
|
|
2572
|
-
for (const svc of
|
|
2594
|
+
for (const svc of services) {
|
|
2573
2595
|
const bindable = await isPortBindable(svc.port);
|
|
2574
2596
|
if (bindable) continue;
|
|
2575
2597
|
const holder = await findPortHolder(svc.port);
|
|
@@ -4207,10 +4229,22 @@ function askYesNo(question) {
|
|
|
4207
4229
|
process.stdin.once("end", onEnd);
|
|
4208
4230
|
});
|
|
4209
4231
|
}
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4232
|
+
function isInvokedDirectly() {
|
|
4233
|
+
const argvPath = process.argv[1];
|
|
4234
|
+
if (!argvPath) return false;
|
|
4235
|
+
const moduleFile = fileURLToPath2(import.meta.url);
|
|
4236
|
+
try {
|
|
4237
|
+
return realpathSync(argvPath) === moduleFile;
|
|
4238
|
+
} catch {
|
|
4239
|
+
return argvPath === moduleFile;
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
if (isInvokedDirectly()) {
|
|
4243
|
+
main().catch((e) => {
|
|
4244
|
+
console.error(e);
|
|
4245
|
+
process.exit(1);
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4214
4248
|
export {
|
|
4215
4249
|
defineConfig
|
|
4216
4250
|
};
|