kandev 0.65.0 → 0.67.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.
- package/README.md +2 -10
- package/bin/cli.js +3 -1
- package/bin/native-shim.js +94 -0
- package/package.json +14 -16
- package/dist/args.js +0 -123
- package/dist/backup.js +0 -98
- package/dist/bundle.js +0 -80
- package/dist/cli.js +0 -155
- package/dist/constants.js +0 -54
- package/dist/dev.js +0 -157
- package/dist/github.js +0 -221
- package/dist/health.js +0 -71
- package/dist/kandev-env.js +0 -28
- package/dist/platform.js +0 -53
- package/dist/ports.js +0 -142
- package/dist/process.js +0 -122
- package/dist/run.js +0 -283
- package/dist/runtime.js +0 -76
- package/dist/service/args.js +0 -131
- package/dist/service/config.js +0 -111
- package/dist/service/health_check.js +0 -84
- package/dist/service/index.js +0 -70
- package/dist/service/install_helpers.js +0 -51
- package/dist/service/launchctl.js +0 -73
- package/dist/service/linux.js +0 -165
- package/dist/service/macos.js +0 -221
- package/dist/service/metadata.js +0 -46
- package/dist/service/paths.js +0 -168
- package/dist/service/self_update.js +0 -223
- package/dist/service/stale_check.js +0 -78
- package/dist/service/templates.js +0 -229
- package/dist/shared.js +0 -214
- package/dist/start.js +0 -188
- package/dist/supervisor/backend.js +0 -83
- package/dist/supervisor/child.js +0 -64
- package/dist/supervisor/control.js +0 -97
- package/dist/supervisor/manifest.js +0 -74
- package/dist/supervisor/paths.js +0 -33
- package/dist/version.js +0 -57
- package/dist/web.js +0 -68
package/dist/constants.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DEV_KANDEV_DOTDIR = exports.DATA_DIR = exports.CACHE_DIR = exports.KANDEV_TASKS_DIR = exports.KANDEV_HOME_DIR = exports.KANDEV_DOTDIR = exports.HEALTH_TIMEOUT_MS_DEV = exports.HEALTH_TIMEOUT_MS_RELEASE = exports.RANDOM_PORT_RETRIES = exports.RANDOM_PORT_MAX = exports.RANDOM_PORT_MIN = exports.DEFAULT_AGENTCTL_PORT = exports.DEFAULT_WEB_PORT = exports.DEFAULT_BACKEND_PORT = void 0;
|
|
7
|
-
exports.resolveKandevHomeDir = resolveKandevHomeDir;
|
|
8
|
-
exports.resolveDataDir = resolveDataDir;
|
|
9
|
-
exports.resolveCacheDir = resolveCacheDir;
|
|
10
|
-
exports.resolveDatabasePath = resolveDatabasePath;
|
|
11
|
-
exports.devKandevHome = devKandevHome;
|
|
12
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
13
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
-
// Default service ports (will auto-fallback if busy).
|
|
15
|
-
// Clustered near the web port (37429) to avoid collisions with commonly used
|
|
16
|
-
// ports (8080, 9090, 9999, etc.) while keeping the numbers memorable.
|
|
17
|
-
exports.DEFAULT_BACKEND_PORT = 38429;
|
|
18
|
-
exports.DEFAULT_WEB_PORT = 37429;
|
|
19
|
-
exports.DEFAULT_AGENTCTL_PORT = 39429;
|
|
20
|
-
// Random fallback range for port selection.
|
|
21
|
-
exports.RANDOM_PORT_MIN = 10000;
|
|
22
|
-
exports.RANDOM_PORT_MAX = 60000;
|
|
23
|
-
exports.RANDOM_PORT_RETRIES = 10;
|
|
24
|
-
// Backend healthcheck timeout during startup.
|
|
25
|
-
exports.HEALTH_TIMEOUT_MS_RELEASE = 45000;
|
|
26
|
-
exports.HEALTH_TIMEOUT_MS_DEV = 600000;
|
|
27
|
-
// Kandev root directory. Single source of truth for the dotdir name and
|
|
28
|
-
// everything derived from it (data, tasks, bin). Dev mode uses a separate
|
|
29
|
-
// root under the repo (see DEV_KANDEV_DOTDIR).
|
|
30
|
-
exports.KANDEV_DOTDIR = ".kandev";
|
|
31
|
-
exports.KANDEV_HOME_DIR = node_path_1.default.join(node_os_1.default.homedir(), exports.KANDEV_DOTDIR);
|
|
32
|
-
exports.KANDEV_TASKS_DIR = node_path_1.default.join(exports.KANDEV_HOME_DIR, "tasks");
|
|
33
|
-
// Local user cache/data directories for release bundles and DB.
|
|
34
|
-
exports.CACHE_DIR = node_path_1.default.join(exports.KANDEV_HOME_DIR, "bin");
|
|
35
|
-
exports.DATA_DIR = node_path_1.default.join(exports.KANDEV_HOME_DIR, "data");
|
|
36
|
-
function resolveKandevHomeDir(env = process.env) {
|
|
37
|
-
return env.KANDEV_HOME_DIR?.trim() || exports.KANDEV_HOME_DIR;
|
|
38
|
-
}
|
|
39
|
-
function resolveDataDir(env = process.env) {
|
|
40
|
-
return node_path_1.default.join(resolveKandevHomeDir(env), "data");
|
|
41
|
-
}
|
|
42
|
-
function resolveCacheDir(env = process.env) {
|
|
43
|
-
return node_path_1.default.join(resolveKandevHomeDir(env), "bin");
|
|
44
|
-
}
|
|
45
|
-
function resolveDatabasePath(env = process.env) {
|
|
46
|
-
return env.KANDEV_DATABASE_PATH?.trim() || node_path_1.default.join(resolveDataDir(env), "kandev.db");
|
|
47
|
-
}
|
|
48
|
-
// Dev-mode root: an isolated kandev home inside the repo so that running
|
|
49
|
-
// `make dev` from inside a kandev-spawned task workspace does not touch the
|
|
50
|
-
// user's production state.
|
|
51
|
-
exports.DEV_KANDEV_DOTDIR = ".kandev-dev";
|
|
52
|
-
function devKandevHome(repoRoot) {
|
|
53
|
-
return node_path_1.default.join(repoRoot, exports.DEV_KANDEV_DOTDIR);
|
|
54
|
-
}
|
package/dist/dev.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.runDev = runDev;
|
|
7
|
-
exports.resolveDevBackendEnv = resolveDevBackendEnv;
|
|
8
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
const backup_1 = require("./backup");
|
|
11
|
-
const constants_1 = require("./constants");
|
|
12
|
-
const health_1 = require("./health");
|
|
13
|
-
const kandev_env_1 = require("./kandev-env");
|
|
14
|
-
const process_1 = require("./process");
|
|
15
|
-
const shared_1 = require("./shared");
|
|
16
|
-
const backend_1 = require("./supervisor/backend");
|
|
17
|
-
const web_1 = require("./web");
|
|
18
|
-
async function runDev({ repoRoot, backendPort, webPort }) {
|
|
19
|
-
const ports = await (0, shared_1.pickPorts)(backendPort, webPort);
|
|
20
|
-
const { dbPath, extra } = resolveDevBackendEnv(repoRoot);
|
|
21
|
-
if ((0, backup_1.isProductionDb)(dbPath)) {
|
|
22
|
-
try {
|
|
23
|
-
const backupPath = (0, backup_1.backupProductionDb)(dbPath);
|
|
24
|
-
if (backupPath) {
|
|
25
|
-
const name = node_path_1.default.basename(backupPath);
|
|
26
|
-
console.log(`[kandev] backed up production db → ${name}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
-
// Abort rather than continue: the backup exists precisely to protect the
|
|
32
|
-
// production db before dev mode touches it. Proceeding on failure would
|
|
33
|
-
// remove the safety guarantee that justified introducing this guard.
|
|
34
|
-
throw new Error(`failed to back up production db (${message}); aborting dev startup`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const backendEnv = (0, shared_1.buildBackendEnv)({ ports, extra });
|
|
38
|
-
const webEnv = (0, shared_1.buildWebEnv)({ ports, debug: true });
|
|
39
|
-
const logLevel = process.env.KANDEV_LOGGING_LEVEL?.trim() || process.env.KANDEV_LOG_LEVEL?.trim() || "info";
|
|
40
|
-
const webUrl = `http://localhost:${ports.webPort}`;
|
|
41
|
-
(0, shared_1.logStartupInfo)({
|
|
42
|
-
header: "dev mode: using local repo",
|
|
43
|
-
ports,
|
|
44
|
-
primary: "web",
|
|
45
|
-
dbPath,
|
|
46
|
-
logLevel,
|
|
47
|
-
});
|
|
48
|
-
const supervisor = (0, process_1.createProcessSupervisor)();
|
|
49
|
-
supervisor.attachSignalHandlers();
|
|
50
|
-
const { cmd: backendCmd, args: backendArgs } = withWinjobWrap(repoRoot, "make", [
|
|
51
|
-
"-C",
|
|
52
|
-
node_path_1.default.join("apps", "backend"),
|
|
53
|
-
"dev",
|
|
54
|
-
]);
|
|
55
|
-
const backend = await (0, backend_1.launchRestartableBackend)({
|
|
56
|
-
command: backendCmd,
|
|
57
|
-
args: backendArgs,
|
|
58
|
-
cwd: repoRoot,
|
|
59
|
-
env: backendEnv,
|
|
60
|
-
homeDir: backendEnv.KANDEV_HOME_DIR ?? (0, constants_1.devKandevHome)(repoRoot),
|
|
61
|
-
ports,
|
|
62
|
-
mode: "dev",
|
|
63
|
-
stdio: "inherit",
|
|
64
|
-
supervisor,
|
|
65
|
-
});
|
|
66
|
-
const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_DEV);
|
|
67
|
-
console.log("[kandev] starting backend...");
|
|
68
|
-
await (0, health_1.waitForHealth)(ports.backendUrl, backend.proc, healthTimeoutMs);
|
|
69
|
-
console.log(`[kandev] backend ready at ${ports.backendUrl}`);
|
|
70
|
-
console.log("[kandev] starting web...");
|
|
71
|
-
const webProc = (0, web_1.launchWebApp)({
|
|
72
|
-
command: "pnpm",
|
|
73
|
-
args: ["-C", "apps", "--filter", "@kandev/web", "dev"],
|
|
74
|
-
cwd: repoRoot,
|
|
75
|
-
env: webEnv,
|
|
76
|
-
supervisor,
|
|
77
|
-
label: "web",
|
|
78
|
-
});
|
|
79
|
-
await (0, health_1.waitForUrlReady)(webUrl, webProc, healthTimeoutMs);
|
|
80
|
-
console.log(`[kandev] open: ${webUrl}`);
|
|
81
|
-
(0, web_1.openBrowser)(webUrl);
|
|
82
|
-
}
|
|
83
|
-
// Computes the dev-mode backend env. Dev mode always roots kandev under
|
|
84
|
-
// <repo>/.kandev-dev so state is isolated from the user's production ~/.kandev
|
|
85
|
-
// and so `make clean-db` (which removes .kandev-dev/) matches what `make dev`
|
|
86
|
-
// writes.
|
|
87
|
-
//
|
|
88
|
-
// When invoked from inside a kandev task workspace, any KANDEV_DATABASE_PATH
|
|
89
|
-
// is assumed to be leaked from the parent backend and is ignored. In a normal
|
|
90
|
-
// shell, an explicit KANDEV_DATABASE_PATH is honored as an escape hatch.
|
|
91
|
-
function resolveDevBackendEnv(repoRoot) {
|
|
92
|
-
// Profile-selector only: the backend reads profiles.yaml at startup
|
|
93
|
-
// and applies the matching `dev:` values (mock agent, pprof,
|
|
94
|
-
// feature flags, etc.) to its own env. We don't repeat those here —
|
|
95
|
-
// profiles.yaml at the repo root is the single source of truth.
|
|
96
|
-
// See docs/decisions/0007-runtime-feature-flags.md.
|
|
97
|
-
const baseExtra = {
|
|
98
|
-
KANDEV_DEBUG_DEV_MODE: "true",
|
|
99
|
-
};
|
|
100
|
-
const devHome = (0, constants_1.devKandevHome)(repoRoot);
|
|
101
|
-
// Display only; the backend derives its own DB path from KANDEV_HOME_DIR
|
|
102
|
-
// via ResolvedDataDir(). Both resolve to the same location.
|
|
103
|
-
const devDbPath = node_path_1.default.join(devHome, "data", "kandev.db");
|
|
104
|
-
if ((0, kandev_env_1.isInsideKandevTask)(repoRoot)) {
|
|
105
|
-
console.log("[kandev] task workspace detected → using local dev state");
|
|
106
|
-
return {
|
|
107
|
-
dbPath: devDbPath,
|
|
108
|
-
extra: {
|
|
109
|
-
...baseExtra,
|
|
110
|
-
KANDEV_HOME_DIR: devHome,
|
|
111
|
-
// Clear a parent-leaked DB path so the backend uses the HomeDir-derived default.
|
|
112
|
-
KANDEV_DATABASE_PATH: "",
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
const override = process.env.KANDEV_DATABASE_PATH;
|
|
117
|
-
if (override) {
|
|
118
|
-
return {
|
|
119
|
-
dbPath: override,
|
|
120
|
-
extra: { ...baseExtra, KANDEV_DATABASE_PATH: override },
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
dbPath: devDbPath,
|
|
125
|
-
extra: {
|
|
126
|
-
...baseExtra,
|
|
127
|
-
KANDEV_HOME_DIR: devHome,
|
|
128
|
-
KANDEV_DATABASE_PATH: "",
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
// withWinjobWrap on Windows prepends apps/backend/bin/winjob.exe to a spawn
|
|
133
|
-
// command so the child runs inside a Job Object configured with
|
|
134
|
-
// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. That makes "kill the whole backend
|
|
135
|
-
// subtree" a kernel-level guarantee tied to winjob's process exit, instead of
|
|
136
|
-
// relying on the bash → make → pnpm → node → make → sh → kandev signal chain
|
|
137
|
-
// (which drops Ctrl-C at multiple links because MSYS bash, native Win32
|
|
138
|
-
// processes, and Node disagree on signal propagation semantics).
|
|
139
|
-
//
|
|
140
|
-
// On Unix this is a passthrough — POSIX process groups already give us
|
|
141
|
-
// reliable cascading termination.
|
|
142
|
-
//
|
|
143
|
-
// If the winjob binary isn't built yet (the user ran `make dev` before
|
|
144
|
-
// `make -C apps/backend build-winjob`), we fall back to a direct spawn and
|
|
145
|
-
// emit a one-line note. The supervisor's tree-kill still handles the happy
|
|
146
|
-
// path; users only notice the gap if Ctrl-C drops mid-chain.
|
|
147
|
-
function withWinjobWrap(repoRoot, cmd, args) {
|
|
148
|
-
if (process.platform !== "win32")
|
|
149
|
-
return { cmd, args };
|
|
150
|
-
const winjob = node_path_1.default.join(repoRoot, "apps", "backend", "bin", "winjob.exe");
|
|
151
|
-
if (!node_fs_1.default.existsSync(winjob)) {
|
|
152
|
-
console.warn(`[kandev] winjob.exe not built — Ctrl-C may leak processes on Windows. ` +
|
|
153
|
-
`Run \`make -C apps/backend build-winjob\` once to enable.`);
|
|
154
|
-
return { cmd, args };
|
|
155
|
-
}
|
|
156
|
-
return { cmd: winjob, args: [cmd, ...args] };
|
|
157
|
-
}
|
package/dist/github.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getRelease = getRelease;
|
|
7
|
-
exports.readSha256 = readSha256;
|
|
8
|
-
exports.ensureAsset = ensureAsset;
|
|
9
|
-
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
-
const node_https_1 = __importDefault(require("node:https"));
|
|
12
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
-
// Allow overriding the GitHub repo for forks/testing.
|
|
14
|
-
const OWNER = process.env.KANDEV_GITHUB_OWNER || "kdlbs";
|
|
15
|
-
const REPO = process.env.KANDEV_GITHUB_REPO || "kandev";
|
|
16
|
-
const WEB_BASE = `https://github.com/${OWNER}/${REPO}`;
|
|
17
|
-
function authHeaders() {
|
|
18
|
-
if (process.env.KANDEV_GITHUB_TOKEN) {
|
|
19
|
-
return { Authorization: `Bearer ${process.env.KANDEV_GITHUB_TOKEN}` };
|
|
20
|
-
}
|
|
21
|
-
return {};
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Resolve the latest release tag by following the redirect from
|
|
25
|
-
* github.com/{owner}/{repo}/releases/latest.
|
|
26
|
-
*
|
|
27
|
-
* Uses github.com (not api.github.com) so it is not subject to
|
|
28
|
-
* the REST API rate limit (60 req/hour per IP).
|
|
29
|
-
*/
|
|
30
|
-
function resolveLatestTag() {
|
|
31
|
-
const url = `${WEB_BASE}/releases/latest`;
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const req = node_https_1.default.get(url, {
|
|
34
|
-
headers: { "User-Agent": "kandev-npx", ...authHeaders() },
|
|
35
|
-
// Do not follow redirects — we just need the Location header.
|
|
36
|
-
followRedirect: false,
|
|
37
|
-
}, (res) => {
|
|
38
|
-
// Drain the response body to free the socket.
|
|
39
|
-
res.resume();
|
|
40
|
-
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
41
|
-
// Location: https://github.com/{owner}/{repo}/releases/tag/v0.2
|
|
42
|
-
const match = res.headers.location.match(/\/releases\/tag\/(.+)$/);
|
|
43
|
-
if (match) {
|
|
44
|
-
return resolve(match[1]);
|
|
45
|
-
}
|
|
46
|
-
return reject(new Error(`Could not parse tag from redirect: ${res.headers.location}`));
|
|
47
|
-
}
|
|
48
|
-
// GitHub returns 200 if there's only one release (no redirect).
|
|
49
|
-
// In that case we need to parse the page — but this is uncommon.
|
|
50
|
-
// Fall back to a HEAD request on the resolved URL.
|
|
51
|
-
if (res.statusCode === 200 && res.headers.location) {
|
|
52
|
-
const match = res.headers.location.match(/\/releases\/tag\/(.+)$/);
|
|
53
|
-
if (match)
|
|
54
|
-
return resolve(match[1]);
|
|
55
|
-
}
|
|
56
|
-
reject(new Error(`Failed to resolve latest release (HTTP ${res.statusCode})`));
|
|
57
|
-
});
|
|
58
|
-
req.setTimeout(5000, () => {
|
|
59
|
-
req.destroy(new Error("Request timed out resolving latest release"));
|
|
60
|
-
});
|
|
61
|
-
req.on("error", reject);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Verify that a specific release tag exists.
|
|
66
|
-
*/
|
|
67
|
-
function verifyTagExists(tag) {
|
|
68
|
-
const url = `${WEB_BASE}/releases/tag/${tag}`;
|
|
69
|
-
return new Promise((resolve, reject) => {
|
|
70
|
-
const req = node_https_1.default.request(url, {
|
|
71
|
-
method: "HEAD",
|
|
72
|
-
headers: { "User-Agent": "kandev-npx", ...authHeaders() },
|
|
73
|
-
}, (res) => {
|
|
74
|
-
res.resume();
|
|
75
|
-
// GitHub returns 200 for the tag page, or 302 redirect to the tag page.
|
|
76
|
-
if (res.statusCode === 200 || res.statusCode === 301 || res.statusCode === 302) {
|
|
77
|
-
return resolve();
|
|
78
|
-
}
|
|
79
|
-
reject(new Error(`Release tag '${tag}' not found (HTTP ${res.statusCode})`));
|
|
80
|
-
});
|
|
81
|
-
req.setTimeout(5000, () => {
|
|
82
|
-
req.destroy(new Error(`Request timed out verifying tag '${tag}'`));
|
|
83
|
-
});
|
|
84
|
-
req.on("error", reject);
|
|
85
|
-
req.end();
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Get release info. Uses github.com web URLs (not api.github.com)
|
|
90
|
-
* to avoid REST API rate limits.
|
|
91
|
-
*/
|
|
92
|
-
async function getRelease(version) {
|
|
93
|
-
if (version) {
|
|
94
|
-
await verifyTagExists(version);
|
|
95
|
-
return { tag_name: version };
|
|
96
|
-
}
|
|
97
|
-
const tag = await resolveLatestTag();
|
|
98
|
-
return { tag_name: tag };
|
|
99
|
-
}
|
|
100
|
-
// -- Asset downloading --------------------------------------------------------
|
|
101
|
-
function readSha256(pathToSha) {
|
|
102
|
-
if (!node_fs_1.default.existsSync(pathToSha)) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
const content = node_fs_1.default.readFileSync(pathToSha, "utf8").trim();
|
|
106
|
-
const first = content.split(/\s+/)[0];
|
|
107
|
-
return first?.toLowerCase() || null;
|
|
108
|
-
}
|
|
109
|
-
function downloadFile(url, destPath, expectedSha256, onProgress) {
|
|
110
|
-
const tempPath = `${destPath}.tmp`;
|
|
111
|
-
return new Promise((resolve, reject) => {
|
|
112
|
-
const file = node_fs_1.default.createWriteStream(tempPath);
|
|
113
|
-
const hash = node_crypto_1.default.createHash("sha256");
|
|
114
|
-
const cleanup = () => {
|
|
115
|
-
try {
|
|
116
|
-
node_fs_1.default.unlinkSync(tempPath);
|
|
117
|
-
}
|
|
118
|
-
catch { }
|
|
119
|
-
};
|
|
120
|
-
const handleResponse = (res) => {
|
|
121
|
-
// Follow redirects (GitHub returns 302 to signed S3/CDN URL).
|
|
122
|
-
// Strip auth header on redirect to avoid S3 rejecting it.
|
|
123
|
-
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
124
|
-
const redirectReq = node_https_1.default.get(res.headers.location, { headers: { "User-Agent": "kandev-npx" } }, handleResponse);
|
|
125
|
-
redirectReq.setTimeout(30000, () => {
|
|
126
|
-
redirectReq.destroy(new Error(`Request timed out downloading ${url}`));
|
|
127
|
-
});
|
|
128
|
-
redirectReq.on("error", (err) => {
|
|
129
|
-
file.close();
|
|
130
|
-
cleanup();
|
|
131
|
-
reject(err);
|
|
132
|
-
});
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (res.statusCode !== 200) {
|
|
136
|
-
file.close();
|
|
137
|
-
cleanup();
|
|
138
|
-
return reject(new Error(`HTTP ${res.statusCode} downloading ${url}`));
|
|
139
|
-
}
|
|
140
|
-
const totalSize = parseInt(res.headers["content-length"] || "0", 10);
|
|
141
|
-
let downloadedSize = 0;
|
|
142
|
-
res.on("data", (chunk) => {
|
|
143
|
-
downloadedSize += chunk.length;
|
|
144
|
-
hash.update(chunk);
|
|
145
|
-
if (onProgress)
|
|
146
|
-
onProgress(downloadedSize, totalSize);
|
|
147
|
-
});
|
|
148
|
-
res.pipe(file);
|
|
149
|
-
file.on("finish", () => {
|
|
150
|
-
file.close();
|
|
151
|
-
const actualSha256 = hash.digest("hex");
|
|
152
|
-
if (expectedSha256 && actualSha256 !== expectedSha256) {
|
|
153
|
-
cleanup();
|
|
154
|
-
return reject(new Error(`Checksum mismatch: expected ${expectedSha256}, got ${actualSha256}`));
|
|
155
|
-
}
|
|
156
|
-
try {
|
|
157
|
-
node_fs_1.default.renameSync(tempPath, destPath);
|
|
158
|
-
resolve(destPath);
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
cleanup();
|
|
162
|
-
reject(err);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
};
|
|
166
|
-
const req = node_https_1.default.get(url, {
|
|
167
|
-
headers: {
|
|
168
|
-
"User-Agent": "kandev-npx",
|
|
169
|
-
...authHeaders(),
|
|
170
|
-
},
|
|
171
|
-
}, handleResponse);
|
|
172
|
-
req.setTimeout(30000, () => {
|
|
173
|
-
req.destroy(new Error(`Request timed out downloading ${url}`));
|
|
174
|
-
});
|
|
175
|
-
req.on("error", (err) => {
|
|
176
|
-
file.close();
|
|
177
|
-
cleanup();
|
|
178
|
-
reject(err);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Ensure a release asset is downloaded and cached.
|
|
184
|
-
*
|
|
185
|
-
* Downloads directly from github.com/{owner}/{repo}/releases/download/{tag}/{asset}
|
|
186
|
-
* instead of the API, avoiding rate limits.
|
|
187
|
-
*/
|
|
188
|
-
async function ensureAsset(tag, assetName, cacheDir, onProgress) {
|
|
189
|
-
node_fs_1.default.mkdirSync(cacheDir, { recursive: true });
|
|
190
|
-
const destPath = node_path_1.default.join(cacheDir, assetName);
|
|
191
|
-
const shaPath = `${destPath}.sha256`;
|
|
192
|
-
// Download sha256 checksum if not already cached.
|
|
193
|
-
let expectedSha = readSha256(shaPath);
|
|
194
|
-
if (!expectedSha) {
|
|
195
|
-
const shaUrl = `${WEB_BASE}/releases/download/${tag}/${assetName}.sha256`;
|
|
196
|
-
try {
|
|
197
|
-
await downloadFile(shaUrl, shaPath);
|
|
198
|
-
expectedSha = readSha256(shaPath);
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
// sha256 file may not exist for this release — continue without it.
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
// Return cached tarball if it exists and checksum matches.
|
|
205
|
-
if (node_fs_1.default.existsSync(destPath)) {
|
|
206
|
-
if (!expectedSha) {
|
|
207
|
-
return destPath;
|
|
208
|
-
}
|
|
209
|
-
const hash = node_crypto_1.default.createHash("sha256");
|
|
210
|
-
const file = node_fs_1.default.readFileSync(destPath);
|
|
211
|
-
hash.update(file);
|
|
212
|
-
if (hash.digest("hex") === expectedSha) {
|
|
213
|
-
return destPath;
|
|
214
|
-
}
|
|
215
|
-
node_fs_1.default.unlinkSync(destPath);
|
|
216
|
-
}
|
|
217
|
-
// Download the asset.
|
|
218
|
-
const assetUrl = `${WEB_BASE}/releases/download/${tag}/${assetName}`;
|
|
219
|
-
await downloadFile(assetUrl, destPath, expectedSha, onProgress);
|
|
220
|
-
return destPath;
|
|
221
|
-
}
|
package/dist/health.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.delay = delay;
|
|
4
|
-
exports.resolveHealthTimeoutMs = resolveHealthTimeoutMs;
|
|
5
|
-
exports.waitForHealth = waitForHealth;
|
|
6
|
-
exports.waitForUrlReady = waitForUrlReady;
|
|
7
|
-
function delay(ms) {
|
|
8
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Resolves the health check timeout, allowing override via environment variable.
|
|
12
|
-
*
|
|
13
|
-
* The KANDEV_HEALTH_TIMEOUT_MS environment variable can override the default
|
|
14
|
-
* timeout for waiting on backend health checks. This is useful for slower
|
|
15
|
-
* machines or debugging scenarios where the backend takes longer to start.
|
|
16
|
-
*
|
|
17
|
-
* @param defaultMs - Default timeout in milliseconds if env var is not set
|
|
18
|
-
* @returns The resolved timeout in milliseconds
|
|
19
|
-
*/
|
|
20
|
-
function resolveHealthTimeoutMs(defaultMs) {
|
|
21
|
-
const raw = process.env.KANDEV_HEALTH_TIMEOUT_MS;
|
|
22
|
-
if (!raw) {
|
|
23
|
-
return defaultMs;
|
|
24
|
-
}
|
|
25
|
-
const parsed = Number(raw);
|
|
26
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
27
|
-
return defaultMs;
|
|
28
|
-
}
|
|
29
|
-
return Math.floor(parsed);
|
|
30
|
-
}
|
|
31
|
-
async function waitForHealth(baseUrl, proc, timeoutMs, onFailure) {
|
|
32
|
-
const deadline = Date.now() + timeoutMs;
|
|
33
|
-
const healthUrl = `${baseUrl}/health`;
|
|
34
|
-
while (Date.now() < deadline) {
|
|
35
|
-
if (proc.exitCode !== null) {
|
|
36
|
-
onFailure?.();
|
|
37
|
-
throw new Error(`Backend exited (code ${proc.exitCode}) before healthcheck passed`);
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
const res = await fetch(healthUrl);
|
|
41
|
-
if (res.ok) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
// ignore until timeout
|
|
47
|
-
}
|
|
48
|
-
await delay(300);
|
|
49
|
-
}
|
|
50
|
-
onFailure?.();
|
|
51
|
-
throw new Error(`Backend healthcheck timed out after ${timeoutMs}ms at ${healthUrl}. ` +
|
|
52
|
-
`If your machine is slow on first run (antivirus scan, cold disk), ` +
|
|
53
|
-
`set KANDEV_HEALTH_TIMEOUT_MS=120000 and retry.`);
|
|
54
|
-
}
|
|
55
|
-
async function waitForUrlReady(url, proc, timeoutMs) {
|
|
56
|
-
const deadline = Date.now() + timeoutMs;
|
|
57
|
-
while (Date.now() < deadline) {
|
|
58
|
-
if (proc.exitCode !== null) {
|
|
59
|
-
throw new Error("Web process exited before URL became reachable");
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
await fetch(url);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// ignore until timeout
|
|
67
|
-
}
|
|
68
|
-
await delay(300);
|
|
69
|
-
}
|
|
70
|
-
throw new Error(`Web URL readiness timed out after ${timeoutMs}ms (${url})`);
|
|
71
|
-
}
|
package/dist/kandev-env.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.isInsideKandevTask = isInsideKandevTask;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
const constants_1 = require("./constants");
|
|
9
|
-
/**
|
|
10
|
-
* Returns true when the current process looks like it was spawned inside a
|
|
11
|
-
* kandev-created task workspace. Two signals:
|
|
12
|
-
* 1. The parent kandev backend exports KANDEV_TASK_ID into every task shell.
|
|
13
|
-
* 2. Task worktrees live under ~/.kandev/tasks/ (see KANDEV_TASKS_DIR).
|
|
14
|
-
*
|
|
15
|
-
* Used by dev mode to auto-isolate the backend onto a local dev root so that
|
|
16
|
-
* `make dev` never mutates the user's production state.
|
|
17
|
-
*
|
|
18
|
-
* Note: the path-prefix fallback is a defensive secondary signal for nested
|
|
19
|
-
* shells where KANDEV_TASK_ID was stripped. It is case-sensitive and does not
|
|
20
|
-
* resolve symlinks, so a realpath'd repoRoot may miss a symlinked HOME on
|
|
21
|
-
* macOS / Windows. KANDEV_TASK_ID remains the primary guarantee.
|
|
22
|
-
*/
|
|
23
|
-
function isInsideKandevTask(repoRoot) {
|
|
24
|
-
if (process.env.KANDEV_TASK_ID) {
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
return repoRoot.startsWith(constants_1.KANDEV_TASKS_DIR + node_path_1.default.sep);
|
|
28
|
-
}
|
package/dist/platform.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getBinaryName = getBinaryName;
|
|
4
|
-
exports.getPlatformDir = getPlatformDir;
|
|
5
|
-
const node_child_process_1 = require("node:child_process");
|
|
6
|
-
const isWindows = process.platform === "win32";
|
|
7
|
-
function getBinaryName(base) {
|
|
8
|
-
return isWindows ? `${base}.exe` : base;
|
|
9
|
-
}
|
|
10
|
-
function getEffectiveArch() {
|
|
11
|
-
const platform = process.platform;
|
|
12
|
-
const nodeArch = process.arch;
|
|
13
|
-
if (platform === "darwin") {
|
|
14
|
-
if (nodeArch === "arm64")
|
|
15
|
-
return "arm64";
|
|
16
|
-
try {
|
|
17
|
-
const translated = (0, node_child_process_1.execSync)("sysctl -in sysctl.proc_translated", {
|
|
18
|
-
encoding: "utf8",
|
|
19
|
-
}).trim();
|
|
20
|
-
if (translated === "1")
|
|
21
|
-
return "arm64";
|
|
22
|
-
}
|
|
23
|
-
catch { }
|
|
24
|
-
return "x64";
|
|
25
|
-
}
|
|
26
|
-
if (/arm/i.test(nodeArch))
|
|
27
|
-
return "arm64";
|
|
28
|
-
if (platform === "win32") {
|
|
29
|
-
const pa = process.env.PROCESSOR_ARCHITECTURE || "";
|
|
30
|
-
const paw = process.env.PROCESSOR_ARCHITEW6432 || "";
|
|
31
|
-
if (/arm/i.test(pa) || /arm/i.test(paw))
|
|
32
|
-
return "arm64";
|
|
33
|
-
}
|
|
34
|
-
return "x64";
|
|
35
|
-
}
|
|
36
|
-
function getPlatformDir() {
|
|
37
|
-
const platform = process.platform;
|
|
38
|
-
const arch = getEffectiveArch();
|
|
39
|
-
if (platform === "linux" && arch === "x64")
|
|
40
|
-
return "linux-x64";
|
|
41
|
-
if (platform === "linux" && arch === "arm64")
|
|
42
|
-
return "linux-arm64";
|
|
43
|
-
if (platform === "darwin" && arch === "x64")
|
|
44
|
-
return "macos-x64";
|
|
45
|
-
if (platform === "darwin" && arch === "arm64")
|
|
46
|
-
return "macos-arm64";
|
|
47
|
-
if (platform === "win32" && arch === "x64")
|
|
48
|
-
return "windows-x64";
|
|
49
|
-
// Windows ARM64 runs x64 binaries via emulation — no native arm64 build yet
|
|
50
|
-
if (platform === "win32" && arch === "arm64")
|
|
51
|
-
return "windows-x64";
|
|
52
|
-
throw new Error(`Unsupported platform: ${platform}-${arch}`);
|
|
53
|
-
}
|