git-daemon 0.1.1 → 0.1.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/.eslintrc.cjs +1 -1
- package/README.md +33 -6
- package/config.schema.json +1 -1
- package/design.md +3 -3
- package/dist/app.js +326 -0
- package/dist/approvals.js +27 -0
- package/dist/cli-test-clone.js +122 -0
- package/dist/config.js +107 -0
- package/dist/context.js +58 -0
- package/dist/daemon.js +34 -0
- package/dist/deps.js +101 -0
- package/dist/errors.js +43 -0
- package/dist/git.js +156 -0
- package/dist/jobs.js +163 -0
- package/dist/logger.js +77 -0
- package/dist/os.js +42 -0
- package/dist/pairing.js +41 -0
- package/dist/process.js +46 -0
- package/dist/security.js +73 -0
- package/dist/setup.js +165 -0
- package/dist/tokens.js +88 -0
- package/dist/tools.js +43 -0
- package/dist/types.js +2 -0
- package/dist/validation.js +62 -0
- package/dist/workspace.js +79 -0
- package/openapi.yaml +1 -1
- package/package.json +4 -1
- package/src/app.ts +24 -1
- package/src/cli-test-clone.ts +154 -0
- package/src/config.ts +1 -1
- package/src/daemon.ts +19 -0
- package/src/jobs.ts +9 -3
- package/src/logger.ts +27 -5
- package/src/process.ts +3 -2
- package/src/setup.ts +165 -0
- package/src/types.ts +20 -17
- package/src/typings/pino-pretty.d.ts +9 -0
- package/tests/app.test.js +103 -0
- package/tsconfig.json +2 -1
- package/vitest.config.js +9 -0
package/dist/logger.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createHttpLogger = exports.createLogger = void 0;
|
|
40
|
+
const path_1 = __importDefault(require("path"));
|
|
41
|
+
const fs_1 = require("fs");
|
|
42
|
+
const pino_1 = __importDefault(require("pino"));
|
|
43
|
+
const pino_http_1 = __importDefault(require("pino-http"));
|
|
44
|
+
const rotating_file_stream_1 = require("rotating-file-stream");
|
|
45
|
+
const createLogger = async (configDir, logging, enabled = true) => {
|
|
46
|
+
const level = process.env.GIT_DAEMON_LOG_LEVEL || "info";
|
|
47
|
+
const logToStdout = process.env.GIT_DAEMON_LOG_STDOUT === "1";
|
|
48
|
+
const prettyStdout = logToStdout && process.env.GIT_DAEMON_LOG_PRETTY !== "0";
|
|
49
|
+
const logDir = path_1.default.join(configDir, logging.directory);
|
|
50
|
+
await fs_1.promises.mkdir(logDir, { recursive: true });
|
|
51
|
+
const stream = (0, rotating_file_stream_1.createStream)("daemon.log", {
|
|
52
|
+
size: `${logging.maxBytes}B`,
|
|
53
|
+
maxFiles: logging.maxFiles,
|
|
54
|
+
path: logDir,
|
|
55
|
+
});
|
|
56
|
+
if (!logToStdout) {
|
|
57
|
+
return (0, pino_1.default)({ enabled, level }, stream);
|
|
58
|
+
}
|
|
59
|
+
const streams = [{ stream }];
|
|
60
|
+
if (prettyStdout) {
|
|
61
|
+
const { default: pretty } = await Promise.resolve().then(() => __importStar(require("pino-pretty")));
|
|
62
|
+
streams.push({
|
|
63
|
+
stream: pretty({
|
|
64
|
+
colorize: true,
|
|
65
|
+
translateTime: "SYS:standard",
|
|
66
|
+
ignore: "pid,hostname",
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
streams.push({ stream: process.stdout });
|
|
72
|
+
}
|
|
73
|
+
return (0, pino_1.default)({ enabled, level }, pino_1.default.multistream(streams));
|
|
74
|
+
};
|
|
75
|
+
exports.createLogger = createLogger;
|
|
76
|
+
const createHttpLogger = (logger) => (0, pino_http_1.default)({ logger: logger });
|
|
77
|
+
exports.createHttpLogger = createHttpLogger;
|
package/dist/os.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openTarget = void 0;
|
|
4
|
+
const execa_1 = require("execa");
|
|
5
|
+
const openTarget = async (target, resolvedPath) => {
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
if (target === "folder") {
|
|
8
|
+
if (platform === "darwin") {
|
|
9
|
+
await (0, execa_1.execa)("open", [resolvedPath]);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (platform === "win32") {
|
|
13
|
+
await (0, execa_1.execa)("cmd", ["/c", "start", "", resolvedPath]);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await (0, execa_1.execa)("xdg-open", [resolvedPath]);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (target === "terminal") {
|
|
20
|
+
if (platform === "darwin") {
|
|
21
|
+
await (0, execa_1.execa)("open", ["-a", "Terminal", resolvedPath]);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (platform === "win32") {
|
|
25
|
+
await (0, execa_1.execa)("cmd", [
|
|
26
|
+
"/c",
|
|
27
|
+
"start",
|
|
28
|
+
"",
|
|
29
|
+
"cmd.exe",
|
|
30
|
+
"/k",
|
|
31
|
+
"cd",
|
|
32
|
+
"/d",
|
|
33
|
+
resolvedPath,
|
|
34
|
+
]);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await (0, execa_1.execa)("x-terminal-emulator", ["--working-directory", resolvedPath]);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await (0, execa_1.execa)("code", [resolvedPath]);
|
|
41
|
+
};
|
|
42
|
+
exports.openTarget = openTarget;
|
package/dist/pairing.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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.PairingManager = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const PAIRING_TTL_MS = 10 * 60 * 1000;
|
|
9
|
+
class PairingManager {
|
|
10
|
+
constructor(tokenStore, ttlDays) {
|
|
11
|
+
this.codes = new Map();
|
|
12
|
+
this.tokenStore = tokenStore;
|
|
13
|
+
this.ttlDays = ttlDays;
|
|
14
|
+
}
|
|
15
|
+
start(origin) {
|
|
16
|
+
const code = crypto_1.default.randomBytes(4).toString("hex");
|
|
17
|
+
const expiresAt = Date.now() + PAIRING_TTL_MS;
|
|
18
|
+
this.codes.set(origin, { code, expiresAt });
|
|
19
|
+
return {
|
|
20
|
+
step: "start",
|
|
21
|
+
instructions: "Enter this code in the Git Daemon pairing prompt within 10 minutes.",
|
|
22
|
+
code,
|
|
23
|
+
expiresAt: new Date(expiresAt).toISOString(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async confirm(origin, code) {
|
|
27
|
+
const entry = this.codes.get(origin);
|
|
28
|
+
if (!entry || entry.code !== code || entry.expiresAt < Date.now()) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
this.codes.delete(origin);
|
|
32
|
+
const { token, expiresAt } = await this.tokenStore.issueToken(origin, this.ttlDays);
|
|
33
|
+
return {
|
|
34
|
+
step: "confirm",
|
|
35
|
+
accessToken: token,
|
|
36
|
+
tokenType: "Bearer",
|
|
37
|
+
expiresAt,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.PairingManager = PairingManager;
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
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.runCommand = void 0;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
9
|
+
const attachLineReader = (stream, onLine) => {
|
|
10
|
+
if (!stream) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
let buffer = "";
|
|
14
|
+
stream.on("data", (chunk) => {
|
|
15
|
+
buffer += chunk.toString();
|
|
16
|
+
const lines = buffer.split(/\r?\n/);
|
|
17
|
+
buffer = lines.pop() ?? "";
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
if (line.length > 0) {
|
|
20
|
+
onLine(line);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
stream.on("end", () => {
|
|
25
|
+
if (buffer.length > 0) {
|
|
26
|
+
onLine(buffer);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
const runCommand = async (ctx, command, args, options) => {
|
|
31
|
+
const subprocess = (0, execa_1.execa)(command, args, {
|
|
32
|
+
...options,
|
|
33
|
+
stdout: "pipe",
|
|
34
|
+
stderr: "pipe",
|
|
35
|
+
});
|
|
36
|
+
const pid = subprocess.pid;
|
|
37
|
+
if (pid) {
|
|
38
|
+
ctx.setCancel(() => new Promise((resolve) => {
|
|
39
|
+
(0, tree_kill_1.default)(pid, "SIGTERM", () => resolve());
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
attachLineReader(subprocess.stdout, ctx.logStdout);
|
|
43
|
+
attachLineReader(subprocess.stderr, ctx.logStderr);
|
|
44
|
+
await subprocess;
|
|
45
|
+
};
|
|
46
|
+
exports.runCommand = runCommand;
|
package/dist/security.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authGuard = exports.loopbackGuard = exports.hostGuard = exports.originGuard = exports.getOrigin = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const ALLOWED_HOSTS = new Set(["127.0.0.1", "localhost"]);
|
|
6
|
+
const getOrigin = (req) => req.headers.origin || "";
|
|
7
|
+
exports.getOrigin = getOrigin;
|
|
8
|
+
const isLoopbackAddress = (address) => {
|
|
9
|
+
if (!address) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (address === "127.0.0.1" || address === "::1") {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return address.startsWith("::ffff:127.0.0.1");
|
|
16
|
+
};
|
|
17
|
+
const originGuard = (allowlist) => {
|
|
18
|
+
return (req, res, next) => {
|
|
19
|
+
const origin = (0, exports.getOrigin)(req);
|
|
20
|
+
if (!origin || !allowlist.includes(origin)) {
|
|
21
|
+
return next((0, errors_1.originNotAllowed)());
|
|
22
|
+
}
|
|
23
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
24
|
+
res.setHeader("Vary", "Origin");
|
|
25
|
+
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
|
|
26
|
+
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
|
|
27
|
+
res.setHeader("Access-Control-Max-Age", "600");
|
|
28
|
+
if (req.method === "OPTIONS") {
|
|
29
|
+
res.status(204).end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
next();
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
exports.originGuard = originGuard;
|
|
36
|
+
const hostGuard = () => {
|
|
37
|
+
return (req, _res, next) => {
|
|
38
|
+
const host = req.headers.host?.split(":")[0];
|
|
39
|
+
if (!host || !ALLOWED_HOSTS.has(host)) {
|
|
40
|
+
return next((0, errors_1.originNotAllowed)());
|
|
41
|
+
}
|
|
42
|
+
next();
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
exports.hostGuard = hostGuard;
|
|
46
|
+
const loopbackGuard = () => {
|
|
47
|
+
return (req, _res, next) => {
|
|
48
|
+
if (!isLoopbackAddress(req.socket.remoteAddress)) {
|
|
49
|
+
return next((0, errors_1.originNotAllowed)());
|
|
50
|
+
}
|
|
51
|
+
next();
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
exports.loopbackGuard = loopbackGuard;
|
|
55
|
+
const authGuard = (tokenStore) => {
|
|
56
|
+
return (req, _res, next) => {
|
|
57
|
+
const origin = (0, exports.getOrigin)(req);
|
|
58
|
+
const auth = req.headers.authorization;
|
|
59
|
+
if (!auth) {
|
|
60
|
+
return next((0, errors_1.authRequired)());
|
|
61
|
+
}
|
|
62
|
+
const match = auth.match(/^Bearer (.+)$/i);
|
|
63
|
+
if (!match) {
|
|
64
|
+
return next((0, errors_1.authInvalid)());
|
|
65
|
+
}
|
|
66
|
+
const token = match[1];
|
|
67
|
+
if (!tokenStore.verifyToken(origin, token)) {
|
|
68
|
+
return next((0, errors_1.authInvalid)());
|
|
69
|
+
}
|
|
70
|
+
next();
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
exports.authGuard = authGuard;
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const path_1 = __importDefault(require("path"));
|
|
40
|
+
const os_1 = __importDefault(require("os"));
|
|
41
|
+
const fs_1 = require("fs");
|
|
42
|
+
const fsSync = __importStar(require("fs"));
|
|
43
|
+
const readline_1 = __importDefault(require("readline"));
|
|
44
|
+
const config_1 = require("./config");
|
|
45
|
+
const expandHome = (input) => {
|
|
46
|
+
if (input === "~") {
|
|
47
|
+
return os_1.default.homedir();
|
|
48
|
+
}
|
|
49
|
+
if (input.startsWith("~/")) {
|
|
50
|
+
return path_1.default.join(os_1.default.homedir(), input.slice(2));
|
|
51
|
+
}
|
|
52
|
+
return input;
|
|
53
|
+
};
|
|
54
|
+
const pathExists = async (target) => {
|
|
55
|
+
try {
|
|
56
|
+
const stats = await fs_1.promises.stat(target);
|
|
57
|
+
return stats.isDirectory();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const createPromptInterface = () => {
|
|
64
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
65
|
+
return readline_1.default.createInterface({
|
|
66
|
+
input: process.stdin,
|
|
67
|
+
output: process.stdout,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const ttyPath = process.platform === "win32" ? "CON" : "/dev/tty";
|
|
71
|
+
try {
|
|
72
|
+
const input = fsSync.createReadStream(ttyPath, { encoding: "utf8" });
|
|
73
|
+
const output = fsSync.createWriteStream(ttyPath);
|
|
74
|
+
return readline_1.default.createInterface({ input, output });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const askQuestion = (rl, question) => new Promise((resolve) => {
|
|
81
|
+
rl.question(question, (answer) => resolve(answer));
|
|
82
|
+
});
|
|
83
|
+
const promptForWorkspace = async (rl, initialValue) => {
|
|
84
|
+
while (true) {
|
|
85
|
+
const answer = await askQuestion(rl, `Workspace root directory (absolute path) [${initialValue}]: `);
|
|
86
|
+
const trimmed = answer.trim();
|
|
87
|
+
const value = trimmed.length > 0 ? trimmed : initialValue;
|
|
88
|
+
if (!value) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const expanded = expandHome(value);
|
|
92
|
+
if (!path_1.default.isAbsolute(expanded)) {
|
|
93
|
+
console.log("Workspace root must be an absolute path.");
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const promptYesNo = async (rl, message, defaultValue) => {
|
|
100
|
+
const suffix = defaultValue ? "[Y/n]" : "[y/N]";
|
|
101
|
+
const answer = await askQuestion(rl, `${message} ${suffix} `);
|
|
102
|
+
const normalized = answer.trim().toLowerCase();
|
|
103
|
+
if (!normalized) {
|
|
104
|
+
return defaultValue;
|
|
105
|
+
}
|
|
106
|
+
return normalized === "y" || normalized === "yes";
|
|
107
|
+
};
|
|
108
|
+
const readWorkspaceArg = () => {
|
|
109
|
+
const args = process.argv.slice(2);
|
|
110
|
+
const flagIndex = args.findIndex((arg) => arg === "--workspace");
|
|
111
|
+
if (flagIndex >= 0 && args[flagIndex + 1]) {
|
|
112
|
+
return args[flagIndex + 1];
|
|
113
|
+
}
|
|
114
|
+
const inline = args.find((arg) => arg.startsWith("--workspace="));
|
|
115
|
+
if (inline) {
|
|
116
|
+
return inline.split("=").slice(1).join("=");
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
const setup = async () => {
|
|
121
|
+
const configDir = (0, config_1.getConfigDir)();
|
|
122
|
+
const config = await (0, config_1.loadConfig)(configDir);
|
|
123
|
+
console.log(`[Git Daemon setup] config=${configDir}`);
|
|
124
|
+
const provided = process.env.GIT_DAEMON_WORKSPACE_ROOT || readWorkspaceArg();
|
|
125
|
+
let workspaceInput = provided?.trim();
|
|
126
|
+
let rl = null;
|
|
127
|
+
if (!workspaceInput) {
|
|
128
|
+
rl = createPromptInterface();
|
|
129
|
+
if (!rl) {
|
|
130
|
+
console.error("No interactive prompt available. Use GIT_DAEMON_WORKSPACE_ROOT=/path or --workspace=/path.");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
workspaceInput = await promptForWorkspace(rl, config.workspaceRoot ?? process.cwd());
|
|
134
|
+
}
|
|
135
|
+
if (!workspaceInput) {
|
|
136
|
+
console.error("Workspace root was not provided.");
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const expanded = expandHome(workspaceInput);
|
|
140
|
+
const resolved = path_1.default.resolve(expanded);
|
|
141
|
+
if (!(await pathExists(resolved))) {
|
|
142
|
+
if (!rl) {
|
|
143
|
+
rl = createPromptInterface();
|
|
144
|
+
}
|
|
145
|
+
if (!rl) {
|
|
146
|
+
console.error(`Directory does not exist: ${resolved}`);
|
|
147
|
+
console.error("Create it manually, then rerun setup.");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const create = await promptYesNo(rl, `Directory does not exist. Create ${resolved}?`, true);
|
|
151
|
+
if (!create) {
|
|
152
|
+
console.log("Setup aborted. Workspace root not saved.");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
await fs_1.promises.mkdir(resolved, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
config.workspaceRoot = resolved;
|
|
158
|
+
await (0, config_1.saveConfig)(configDir, config);
|
|
159
|
+
console.log(`Workspace root set to ${resolved}`);
|
|
160
|
+
rl?.close();
|
|
161
|
+
};
|
|
162
|
+
setup().catch((err) => {
|
|
163
|
+
console.error("Setup failed", err);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
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.TokenStore = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
const TOKEN_BYTES = 32;
|
|
11
|
+
const HASH_BYTES = 32;
|
|
12
|
+
class TokenStore {
|
|
13
|
+
constructor(configDir) {
|
|
14
|
+
this.entries = [];
|
|
15
|
+
this.tokensPath = (0, config_1.getTokensPath)(configDir);
|
|
16
|
+
}
|
|
17
|
+
async load() {
|
|
18
|
+
try {
|
|
19
|
+
const raw = await fs_1.promises.readFile(this.tokensPath, "utf8");
|
|
20
|
+
const data = JSON.parse(raw);
|
|
21
|
+
this.entries = Array.isArray(data.entries) ? data.entries : [];
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (err.code !== "ENOENT") {
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
this.entries = [];
|
|
28
|
+
await this.save();
|
|
29
|
+
}
|
|
30
|
+
this.pruneExpired();
|
|
31
|
+
}
|
|
32
|
+
async save() {
|
|
33
|
+
const payload = { entries: this.entries };
|
|
34
|
+
await fs_1.promises.writeFile(this.tokensPath, JSON.stringify(payload, null, 2));
|
|
35
|
+
}
|
|
36
|
+
pruneExpired() {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
this.entries = this.entries.filter((entry) => {
|
|
39
|
+
const expiresAt = Date.parse(entry.expiresAt);
|
|
40
|
+
return Number.isNaN(expiresAt) || expiresAt > now;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
getActiveToken(origin) {
|
|
44
|
+
this.pruneExpired();
|
|
45
|
+
return this.entries.find((entry) => entry.origin === origin);
|
|
46
|
+
}
|
|
47
|
+
async issueToken(origin, ttlDays) {
|
|
48
|
+
const token = crypto_1.default.randomBytes(TOKEN_BYTES).toString("base64url");
|
|
49
|
+
const salt = crypto_1.default.randomBytes(16).toString("base64url");
|
|
50
|
+
const tokenHash = crypto_1.default
|
|
51
|
+
.scryptSync(token, salt, HASH_BYTES)
|
|
52
|
+
.toString("base64url");
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const expiresAt = new Date(now.getTime() + ttlDays * 24 * 60 * 60 * 1000);
|
|
55
|
+
const entry = {
|
|
56
|
+
origin,
|
|
57
|
+
tokenHash,
|
|
58
|
+
salt,
|
|
59
|
+
createdAt: now.toISOString(),
|
|
60
|
+
expiresAt: expiresAt.toISOString(),
|
|
61
|
+
};
|
|
62
|
+
this.entries = this.entries.filter((item) => item.origin !== origin);
|
|
63
|
+
this.entries.push(entry);
|
|
64
|
+
await this.save();
|
|
65
|
+
return {
|
|
66
|
+
token,
|
|
67
|
+
expiresAt: entry.expiresAt,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async revokeToken(origin) {
|
|
71
|
+
this.entries = this.entries.filter((item) => item.origin !== origin);
|
|
72
|
+
await this.save();
|
|
73
|
+
}
|
|
74
|
+
verifyToken(origin, token) {
|
|
75
|
+
this.pruneExpired();
|
|
76
|
+
const entry = this.entries.find((item) => item.origin === origin);
|
|
77
|
+
if (!entry) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const derived = crypto_1.default.scryptSync(token, entry.salt, HASH_BYTES);
|
|
81
|
+
const stored = Buffer.from(entry.tokenHash, "base64url");
|
|
82
|
+
if (stored.length !== derived.length) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return crypto_1.default.timingSafeEqual(stored, derived);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.TokenStore = TokenStore;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isToolInstalled = exports.detectCapabilities = void 0;
|
|
4
|
+
const execa_1 = require("execa");
|
|
5
|
+
const detect = async (command, args = ["--version"]) => {
|
|
6
|
+
try {
|
|
7
|
+
const result = await (0, execa_1.execa)(command, args);
|
|
8
|
+
const version = result.stdout.trim();
|
|
9
|
+
return { installed: true, version };
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
if (err.code === "ENOENT") {
|
|
13
|
+
return { installed: false };
|
|
14
|
+
}
|
|
15
|
+
return { installed: false };
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const detectCapabilities = async () => {
|
|
19
|
+
const [git, node, npm, pnpm, yarn, code] = await Promise.all([
|
|
20
|
+
detect("git", ["--version"]),
|
|
21
|
+
detect("node", ["--version"]),
|
|
22
|
+
detect("npm", ["--version"]),
|
|
23
|
+
detect("pnpm", ["--version"]),
|
|
24
|
+
detect("yarn", ["--version"]),
|
|
25
|
+
detect("code", ["--version"]),
|
|
26
|
+
]);
|
|
27
|
+
return {
|
|
28
|
+
tools: {
|
|
29
|
+
git,
|
|
30
|
+
node,
|
|
31
|
+
npm,
|
|
32
|
+
pnpm,
|
|
33
|
+
yarn,
|
|
34
|
+
code,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
exports.detectCapabilities = detectCapabilities;
|
|
39
|
+
const isToolInstalled = async (command) => {
|
|
40
|
+
const info = await detect(command, ["--version"]);
|
|
41
|
+
return info.installed;
|
|
42
|
+
};
|
|
43
|
+
exports.isToolInstalled = isToolInstalled;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.depsInstallRequestSchema = exports.osOpenRequestSchema = exports.gitStatusQuerySchema = exports.gitFetchRequestSchema = exports.gitCloneRequestSchema = exports.pairRequestSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const MAX_PATH_LENGTH = 4096;
|
|
6
|
+
const isValidRepoUrl = (value) => {
|
|
7
|
+
if (value.startsWith("file://")) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (value.startsWith("/") ||
|
|
11
|
+
value.startsWith("./") ||
|
|
12
|
+
value.startsWith("../")) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (/^git@[^:]+:.+/.test(value)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (/^https:\/\/[^/]+\/.+/.test(value)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (/^ssh:\/\/[^/]+\/.+/.test(value)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
};
|
|
26
|
+
exports.pairRequestSchema = zod_1.z.discriminatedUnion("step", [
|
|
27
|
+
zod_1.z.object({
|
|
28
|
+
step: zod_1.z.literal("start"),
|
|
29
|
+
}),
|
|
30
|
+
zod_1.z.object({
|
|
31
|
+
step: zod_1.z.literal("confirm"),
|
|
32
|
+
code: zod_1.z.string().min(1),
|
|
33
|
+
}),
|
|
34
|
+
]);
|
|
35
|
+
exports.gitCloneRequestSchema = zod_1.z.object({
|
|
36
|
+
repoUrl: zod_1.z.string().min(1).refine(isValidRepoUrl),
|
|
37
|
+
destRelative: zod_1.z.string().min(1).max(MAX_PATH_LENGTH),
|
|
38
|
+
options: zod_1.z
|
|
39
|
+
.object({
|
|
40
|
+
branch: zod_1.z.string().min(1).optional(),
|
|
41
|
+
depth: zod_1.z.number().int().min(1).optional(),
|
|
42
|
+
})
|
|
43
|
+
.optional(),
|
|
44
|
+
});
|
|
45
|
+
exports.gitFetchRequestSchema = zod_1.z.object({
|
|
46
|
+
repoPath: zod_1.z.string().min(1).max(MAX_PATH_LENGTH),
|
|
47
|
+
remote: zod_1.z.string().min(1).optional(),
|
|
48
|
+
prune: zod_1.z.boolean().optional(),
|
|
49
|
+
});
|
|
50
|
+
exports.gitStatusQuerySchema = zod_1.z.object({
|
|
51
|
+
repoPath: zod_1.z.string().min(1).max(MAX_PATH_LENGTH),
|
|
52
|
+
});
|
|
53
|
+
exports.osOpenRequestSchema = zod_1.z.object({
|
|
54
|
+
target: zod_1.z.enum(["folder", "terminal", "vscode"]),
|
|
55
|
+
path: zod_1.z.string().min(1).max(MAX_PATH_LENGTH),
|
|
56
|
+
});
|
|
57
|
+
exports.depsInstallRequestSchema = zod_1.z.object({
|
|
58
|
+
repoPath: zod_1.z.string().min(1).max(MAX_PATH_LENGTH),
|
|
59
|
+
manager: zod_1.z.enum(["auto", "npm", "pnpm", "yarn"]).optional(),
|
|
60
|
+
mode: zod_1.z.enum(["auto", "ci", "install"]).optional(),
|
|
61
|
+
safer: zod_1.z.boolean().optional(),
|
|
62
|
+
});
|