gitops-ai 1.0.0 → 1.2.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/LICENSE +21 -0
- package/README.md +74 -41
- package/dist/commands/bootstrap.js +641 -117
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/template-sync-wizard.d.ts +1 -0
- package/dist/commands/template-sync-wizard.js +169 -0
- package/dist/commands/template-sync-wizard.js.map +1 -0
- package/dist/commands/template-sync.d.ts +8 -0
- package/dist/commands/template-sync.js +41 -0
- package/dist/commands/template-sync.js.map +1 -0
- package/dist/core/bootstrap-runner.js +28 -11
- package/dist/core/bootstrap-runner.js.map +1 -1
- package/dist/core/cloudflare-oauth.d.ts +1 -0
- package/dist/core/cloudflare-oauth.js +311 -0
- package/dist/core/cloudflare-oauth.js.map +1 -0
- package/dist/core/dependencies.js +0 -12
- package/dist/core/dependencies.js.map +1 -1
- package/dist/core/encryption.js +1 -1
- package/dist/core/encryption.js.map +1 -1
- package/dist/core/flux.d.ts +1 -1
- package/dist/core/flux.js +57 -8
- package/dist/core/flux.js.map +1 -1
- package/dist/core/git-provider.d.ts +38 -0
- package/dist/core/git-provider.js +30 -0
- package/dist/core/git-provider.js.map +1 -0
- package/dist/core/github-oauth.d.ts +1 -0
- package/dist/core/github-oauth.js +110 -0
- package/dist/core/github-oauth.js.map +1 -0
- package/dist/core/github.d.ts +12 -0
- package/dist/core/github.js +188 -0
- package/dist/core/github.js.map +1 -0
- package/dist/core/gitlab-oauth.d.ts +1 -0
- package/dist/core/gitlab-oauth.js +194 -0
- package/dist/core/gitlab-oauth.js.map +1 -0
- package/dist/core/gitlab.d.ts +4 -9
- package/dist/core/gitlab.js +127 -56
- package/dist/core/gitlab.js.map +1 -1
- package/dist/core/kubernetes.d.ts +9 -0
- package/dist/core/kubernetes.js +51 -1
- package/dist/core/kubernetes.js.map +1 -1
- package/dist/core/template-sync.d.ts +46 -0
- package/dist/core/template-sync.js +249 -0
- package/dist/core/template-sync.js.map +1 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +17 -4
- package/dist/schemas.js +17 -3
- package/dist/schemas.js.map +1 -1
- package/package.json +32 -2
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import { isMacOS } from "../utils/platform.js";
|
|
5
|
+
const GITHUB_CLIENT_ID = "Ov23lig0eFrzARzjfqy6";
|
|
6
|
+
const OAUTH_SCOPES = "repo read:org";
|
|
7
|
+
const POLL_TIMEOUT_MS = 120_000;
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Browser opener
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
function openUrl(url) {
|
|
12
|
+
const cmd = isMacOS() ? "open" : "xdg-open";
|
|
13
|
+
try {
|
|
14
|
+
execSync(`${cmd} '${url}'`, { stdio: "ignore" });
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
/* user will see the manual URL in the terminal */
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function authBaseUrl(host) {
|
|
24
|
+
return host === "github.com" ? "https://github.com" : `https://${host}`;
|
|
25
|
+
}
|
|
26
|
+
function getClientId(host) {
|
|
27
|
+
const fromEnv = process.env.GITHUB_OAUTH_CLIENT_ID;
|
|
28
|
+
if (fromEnv)
|
|
29
|
+
return fromEnv;
|
|
30
|
+
if (host === "github.com")
|
|
31
|
+
return GITHUB_CLIENT_ID;
|
|
32
|
+
throw new Error(`No OAuth application configured for ${host}. ` +
|
|
33
|
+
`Set GITHUB_OAUTH_CLIENT_ID env var for GitHub Enterprise hosts.`);
|
|
34
|
+
}
|
|
35
|
+
async function requestDeviceCode(host, clientId) {
|
|
36
|
+
const res = await fetch(`${authBaseUrl(host)}/login/device/code`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
Accept: "application/json",
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
client_id: clientId,
|
|
44
|
+
scope: OAUTH_SCOPES,
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const body = await res.text();
|
|
49
|
+
throw new Error(`GitHub device code request failed (${res.status}): ${body}`);
|
|
50
|
+
}
|
|
51
|
+
return res.json();
|
|
52
|
+
}
|
|
53
|
+
async function pollForToken(host, clientId, deviceCode, intervalSec) {
|
|
54
|
+
const start = Date.now();
|
|
55
|
+
let interval = intervalSec * 1000;
|
|
56
|
+
while (Date.now() - start < POLL_TIMEOUT_MS) {
|
|
57
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
58
|
+
const res = await fetch(`${authBaseUrl(host)}/login/oauth/access_token`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
Accept: "application/json",
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
client_id: clientId,
|
|
66
|
+
device_code: deviceCode,
|
|
67
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok)
|
|
71
|
+
continue;
|
|
72
|
+
const data = (await res.json());
|
|
73
|
+
if (data.access_token) {
|
|
74
|
+
return data.access_token;
|
|
75
|
+
}
|
|
76
|
+
switch (data.error) {
|
|
77
|
+
case "authorization_pending":
|
|
78
|
+
break;
|
|
79
|
+
case "slow_down":
|
|
80
|
+
interval += 5000;
|
|
81
|
+
break;
|
|
82
|
+
case "expired_token":
|
|
83
|
+
throw new Error("Device code expired — please try again");
|
|
84
|
+
case "access_denied":
|
|
85
|
+
throw new Error("Authorization denied by user");
|
|
86
|
+
default:
|
|
87
|
+
if (data.error) {
|
|
88
|
+
throw new Error(`GitHub OAuth error: ${data.error_description ?? data.error}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new Error("Login timed out — no response within 2 minutes");
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Public entry point
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
export async function loginWithGitHubDevice(host) {
|
|
98
|
+
const clientId = getClientId(host);
|
|
99
|
+
const dc = await requestDeviceCode(host, clientId);
|
|
100
|
+
p.log.info(`Enter code ${pc.bold(pc.cyan(dc.user_code))} at ${pc.cyan(dc.verification_uri)}`);
|
|
101
|
+
p.log.info(pc.dim("If the browser doesn't open, visit the URL above manually."));
|
|
102
|
+
await p.text({
|
|
103
|
+
message: pc.dim("Press ") + pc.bold(pc.yellow("Enter")) + pc.dim(" to open browser…"),
|
|
104
|
+
defaultValue: "",
|
|
105
|
+
});
|
|
106
|
+
openUrl(dc.verification_uri);
|
|
107
|
+
const token = await pollForToken(host, clientId, dc.device_code, dc.interval);
|
|
108
|
+
return token;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=github-oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-oauth.js","sourceRoot":"","sources":["../../src/core/github-oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GitProvider } from "./git-provider.js";
|
|
2
|
+
export interface GitHubProvider extends GitProvider {
|
|
3
|
+
readonly type: "github";
|
|
4
|
+
}
|
|
5
|
+
export declare function createGitHubProvider(): GitHubProvider;
|
|
6
|
+
export declare function getKnownHosts(host: string): string;
|
|
7
|
+
export interface DeployKeyResult {
|
|
8
|
+
privateKey: string;
|
|
9
|
+
publicKey: string;
|
|
10
|
+
knownHosts: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createGitHubDeployKey(owner: string, repo: string, host: string, token: string): Promise<DeployKeyResult>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { readFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { exec, execSafe } from "../utils/shell.js";
|
|
4
|
+
import { log } from "../utils/log.js";
|
|
5
|
+
import { loginWithGitHubDevice } from "./github-oauth.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// REST helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
async function apiFetch(path, token, host) {
|
|
10
|
+
const apiBase = host === "github.com"
|
|
11
|
+
? "https://api.github.com"
|
|
12
|
+
: `https://${host}/api/v3`;
|
|
13
|
+
const url = `${apiBase}${path}`;
|
|
14
|
+
const res = await fetch(url, {
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${token}`,
|
|
17
|
+
Accept: "application/vnd.github+json",
|
|
18
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(`GitHub API ${path}: ${res.status} ${res.statusText}`);
|
|
23
|
+
}
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
async function apiPost(path, token, host, body) {
|
|
27
|
+
const apiBase = host === "github.com"
|
|
28
|
+
? "https://api.github.com"
|
|
29
|
+
: `https://${host}/api/v3`;
|
|
30
|
+
const url = `${apiBase}${path}`;
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${token}`,
|
|
35
|
+
Accept: "application/vnd.github+json",
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify(body),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const text = await res.text();
|
|
43
|
+
throw new Error(`GitHub API POST ${path}: ${res.status} ${text}`);
|
|
44
|
+
}
|
|
45
|
+
return res.json();
|
|
46
|
+
}
|
|
47
|
+
export function createGitHubProvider() {
|
|
48
|
+
return {
|
|
49
|
+
type: "github",
|
|
50
|
+
defaultHost: "github.com",
|
|
51
|
+
cliTool: "gh",
|
|
52
|
+
tokenLabel: "GitHub Personal Access Token (repo, read:org)",
|
|
53
|
+
async fetchCurrentUser(token, host) {
|
|
54
|
+
const u = await apiFetch("/user", token, host);
|
|
55
|
+
return { username: u.login, name: u.name ?? u.login };
|
|
56
|
+
},
|
|
57
|
+
async fetchOrganizations(token, host) {
|
|
58
|
+
const orgs = await apiFetch("/user/orgs?per_page=100", token, host);
|
|
59
|
+
return orgs.map((o) => ({
|
|
60
|
+
name: o.login,
|
|
61
|
+
path: o.login,
|
|
62
|
+
fullPath: o.login,
|
|
63
|
+
}));
|
|
64
|
+
},
|
|
65
|
+
async fetchNamespaceProjects(token, host, namespace) {
|
|
66
|
+
// Try as an org first, then fall back to user repos
|
|
67
|
+
let repos;
|
|
68
|
+
try {
|
|
69
|
+
repos = await apiFetch(`/orgs/${encodeURIComponent(namespace)}/repos?per_page=100&sort=updated&direction=desc`, token, host);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
repos = await apiFetch(`/users/${encodeURIComponent(namespace)}/repos?per_page=100&sort=updated&direction=desc`, token, host);
|
|
73
|
+
}
|
|
74
|
+
return repos.map((r) => ({
|
|
75
|
+
name: r.name,
|
|
76
|
+
description: r.description,
|
|
77
|
+
pathWithNamespace: r.full_name,
|
|
78
|
+
httpUrl: r.clone_url,
|
|
79
|
+
}));
|
|
80
|
+
},
|
|
81
|
+
async authenticate(token, host) {
|
|
82
|
+
const user = await this.fetchCurrentUser(token, host);
|
|
83
|
+
log.success(`Authenticated as: ${user.username}`);
|
|
84
|
+
return user.username;
|
|
85
|
+
},
|
|
86
|
+
async resolveNamespaceId(namespace, host, token) {
|
|
87
|
+
// GitHub doesn't use numeric namespace IDs. We return the namespace
|
|
88
|
+
// string itself so `createProject` can decide org vs user.
|
|
89
|
+
try {
|
|
90
|
+
await apiFetch(`/orgs/${encodeURIComponent(namespace)}`, token, host);
|
|
91
|
+
return namespace;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Not an org — treat as personal user namespace
|
|
95
|
+
return namespace;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
async getProject(namespace, name, host, token) {
|
|
99
|
+
try {
|
|
100
|
+
const r = await apiFetch(`/repos/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`, token, host);
|
|
101
|
+
return {
|
|
102
|
+
id: String(r.id),
|
|
103
|
+
httpUrl: r.clone_url,
|
|
104
|
+
pathWithNamespace: r.full_name,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
async createProject(name, namespaceId, host, token) {
|
|
112
|
+
// Determine if namespace is an org or the authenticated user
|
|
113
|
+
const user = await this.fetchCurrentUser(token, host);
|
|
114
|
+
const isOrg = namespaceId !== user.username;
|
|
115
|
+
let repo;
|
|
116
|
+
if (isOrg) {
|
|
117
|
+
repo = await apiPost(`/orgs/${encodeURIComponent(namespaceId)}/repos`, token, host, { name, private: true, auto_init: false });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
repo = await apiPost("/user/repos", token, host, { name, private: true, auto_init: false });
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
id: String(repo.id),
|
|
124
|
+
httpUrl: repo.clone_url,
|
|
125
|
+
pathWithNamespace: repo.full_name,
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
configureGitCredentials(token, cwd) {
|
|
129
|
+
const { exitCode, stdout } = execSafe("git remote get-url origin 2>/dev/null");
|
|
130
|
+
let host = "github.com";
|
|
131
|
+
if (exitCode === 0 && stdout) {
|
|
132
|
+
const match = stdout.match(/https:\/\/([^/]+)\//);
|
|
133
|
+
if (match)
|
|
134
|
+
host = match[1];
|
|
135
|
+
}
|
|
136
|
+
exec(`git config --local --replace-all credential.helper '!f() { echo "username=x-access-token"; echo "password=${token}"; }; f'`, { cwd });
|
|
137
|
+
log.success(`git credentials configured for ${host}`);
|
|
138
|
+
},
|
|
139
|
+
getAuthRemoteUrl(host, pathWithNamespace, token) {
|
|
140
|
+
return `https://x-access-token:${token}@${host}/${pathWithNamespace}.git`;
|
|
141
|
+
},
|
|
142
|
+
async loginWithBrowser(host) {
|
|
143
|
+
return loginWithGitHubDevice(host);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Deploy key management (SSH-based auth for Flux)
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
const GITHUB_COM_KNOWN_HOSTS = [
|
|
151
|
+
"github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
|
|
152
|
+
"github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=",
|
|
153
|
+
].join("\n");
|
|
154
|
+
export function getKnownHosts(host) {
|
|
155
|
+
if (host === "github.com")
|
|
156
|
+
return GITHUB_COM_KNOWN_HOSTS;
|
|
157
|
+
const { stdout } = execSafe(`ssh-keyscan ${host} 2>/dev/null`);
|
|
158
|
+
if (stdout?.trim())
|
|
159
|
+
return stdout.trim();
|
|
160
|
+
throw new Error(`Could not retrieve SSH host keys for ${host}. ` +
|
|
161
|
+
`Ensure ssh-keyscan is installed and the host is reachable.`);
|
|
162
|
+
}
|
|
163
|
+
export async function createGitHubDeployKey(owner, repo, host, token) {
|
|
164
|
+
const keyPath = "/tmp/flux-deploy-key";
|
|
165
|
+
try {
|
|
166
|
+
execSync(`rm -f "${keyPath}" "${keyPath}.pub"`, { stdio: "ignore" });
|
|
167
|
+
}
|
|
168
|
+
catch { /* ignore */ }
|
|
169
|
+
execSync(`ssh-keygen -t ed25519 -f "${keyPath}" -N "" -q -C "flux-system"`, { stdio: "ignore" });
|
|
170
|
+
const privateKey = readFileSync(keyPath, "utf-8");
|
|
171
|
+
const publicKey = readFileSync(`${keyPath}.pub`, "utf-8").trim();
|
|
172
|
+
try {
|
|
173
|
+
unlinkSync(keyPath);
|
|
174
|
+
}
|
|
175
|
+
catch { /* ignore */ }
|
|
176
|
+
try {
|
|
177
|
+
unlinkSync(`${keyPath}.pub`);
|
|
178
|
+
}
|
|
179
|
+
catch { /* ignore */ }
|
|
180
|
+
await apiPost(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/keys`, token, host, {
|
|
181
|
+
title: "flux-system (GitOps AI Bootstrapper)",
|
|
182
|
+
key: publicKey,
|
|
183
|
+
read_only: false,
|
|
184
|
+
});
|
|
185
|
+
log.success(`Deploy key added to ${owner}/${repo}`);
|
|
186
|
+
return { privateKey, publicKey, knownHosts: getKnownHosts(host) };
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/core/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loginWithBrowser(host: string): Promise<string>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createServer, } from "node:http";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
import { isMacOS } from "../utils/platform.js";
|
|
9
|
+
const OAUTH_SCOPES = "api read_repository write_repository";
|
|
10
|
+
const CALLBACK_PATH = "/callback";
|
|
11
|
+
const LOGIN_TIMEOUT_MS = 120_000;
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Pre-registered OAuth application per host (like Vercel, gh CLI).
|
|
14
|
+
// Register once at: https://<host>/-/user_settings/applications
|
|
15
|
+
// Name: gitops-ai
|
|
16
|
+
// Redirect URI: http://127.0.0.1/callback
|
|
17
|
+
// Confidential: No
|
|
18
|
+
// Scopes: api
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const BUILTIN_CLIENT_IDS = {
|
|
21
|
+
"gitlab.com": "0e183a7a911ca9b4e078a42bdc9e9ea6a2e821cd5303c3ceca6ce9be51f7e627",
|
|
22
|
+
};
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// PKCE helpers (RFC 7636)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
function base64url(buf) {
|
|
27
|
+
return buf
|
|
28
|
+
.toString("base64")
|
|
29
|
+
.replace(/\+/g, "-")
|
|
30
|
+
.replace(/\//g, "_")
|
|
31
|
+
.replace(/=+$/, "");
|
|
32
|
+
}
|
|
33
|
+
function generateVerifier() {
|
|
34
|
+
return base64url(randomBytes(32));
|
|
35
|
+
}
|
|
36
|
+
function computeChallenge(verifier) {
|
|
37
|
+
return base64url(createHash("sha256").update(verifier).digest());
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Browser opener
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
function openUrl(url) {
|
|
43
|
+
const cmd = isMacOS() ? "open" : "xdg-open";
|
|
44
|
+
try {
|
|
45
|
+
execSync(`${cmd} '${url}'`, { stdio: "ignore" });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* user will see the manual URL in the terminal */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Resolve client_id — built-in or env var, nothing else needed.
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
function getClientId(host) {
|
|
55
|
+
const fromEnv = process.env.GITLAB_OAUTH_APP_ID;
|
|
56
|
+
if (fromEnv)
|
|
57
|
+
return fromEnv;
|
|
58
|
+
const builtIn = BUILTIN_CLIENT_IDS[host];
|
|
59
|
+
if (builtIn)
|
|
60
|
+
return builtIn;
|
|
61
|
+
throw new Error(`No OAuth application configured for ${host}. ` +
|
|
62
|
+
`Set GITLAB_OAUTH_APP_ID env var or add the host to BUILTIN_CLIENT_IDS.`);
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Main browser-login flow
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
export async function loginWithBrowser(host) {
|
|
68
|
+
const clientId = getClientId(host);
|
|
69
|
+
const verifier = generateVerifier();
|
|
70
|
+
const challenge = computeChallenge(verifier);
|
|
71
|
+
const state = base64url(randomBytes(16));
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
let timer;
|
|
74
|
+
const server = createServer(async (req, res) => {
|
|
75
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
76
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
77
|
+
res.writeHead(404);
|
|
78
|
+
res.end();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const error = url.searchParams.get("error");
|
|
82
|
+
if (error) {
|
|
83
|
+
const desc = url.searchParams.get("error_description") ?? error;
|
|
84
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
85
|
+
res.end(htmlPage("Authentication Failed", escapeHtml(desc), true));
|
|
86
|
+
finish(new Error(`GitLab OAuth: ${desc}`));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (url.searchParams.get("state") !== state) {
|
|
90
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
91
|
+
res.end(htmlPage("Security Error", "State mismatch — possible CSRF.", true));
|
|
92
|
+
finish(new Error("OAuth state mismatch"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const code = url.searchParams.get("code");
|
|
96
|
+
if (!code) {
|
|
97
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
98
|
+
res.end(htmlPage("Error", "No authorization code received.", true));
|
|
99
|
+
finish(new Error("Missing authorization code"));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const addr = server.address();
|
|
104
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
105
|
+
const redirectUri = `http://127.0.0.1:${port}${CALLBACK_PATH}`;
|
|
106
|
+
const tokenRes = await fetch(`https://${host}/oauth/token`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
client_id: clientId,
|
|
111
|
+
code,
|
|
112
|
+
grant_type: "authorization_code",
|
|
113
|
+
redirect_uri: redirectUri,
|
|
114
|
+
code_verifier: verifier,
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
if (!tokenRes.ok) {
|
|
118
|
+
const body = await tokenRes.text();
|
|
119
|
+
throw new Error(`Token exchange failed (${tokenRes.status}): ${body}`);
|
|
120
|
+
}
|
|
121
|
+
const { access_token } = (await tokenRes.json());
|
|
122
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
123
|
+
res.end(htmlPage("Authenticated", "You can close this tab and return to the terminal.", false));
|
|
124
|
+
finish(null, access_token);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
128
|
+
res.end(htmlPage("Error", "Token exchange failed. Check the terminal.", true));
|
|
129
|
+
finish(err);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
function finish(err, token) {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
server.close();
|
|
135
|
+
if (err)
|
|
136
|
+
reject(err);
|
|
137
|
+
else
|
|
138
|
+
resolve(token);
|
|
139
|
+
}
|
|
140
|
+
server.listen(0, "127.0.0.1", () => {
|
|
141
|
+
const addr = server.address();
|
|
142
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
143
|
+
const redirectUri = `http://127.0.0.1:${port}${CALLBACK_PATH}`;
|
|
144
|
+
const authUrl = new URL(`https://${host}/oauth/authorize`);
|
|
145
|
+
authUrl.searchParams.set("client_id", clientId);
|
|
146
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
147
|
+
authUrl.searchParams.set("response_type", "code");
|
|
148
|
+
authUrl.searchParams.set("scope", OAUTH_SCOPES);
|
|
149
|
+
authUrl.searchParams.set("code_challenge", challenge);
|
|
150
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
151
|
+
authUrl.searchParams.set("state", state);
|
|
152
|
+
const urlStr = authUrl.toString();
|
|
153
|
+
p.log.info(pc.dim(`If the browser doesn't open, visit:\n${pc.cyan(urlStr)}`));
|
|
154
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
155
|
+
rl.question(`Press \x1b[1m\x1b[33mEnter\x1b[0m to open browser for GitLab authorization… `, () => {
|
|
156
|
+
rl.close();
|
|
157
|
+
openUrl(urlStr);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
timer = setTimeout(() => {
|
|
161
|
+
finish(new Error("Login timed out — no response within 2 minutes"));
|
|
162
|
+
}, LOGIN_TIMEOUT_MS);
|
|
163
|
+
server.on("error", (err) => {
|
|
164
|
+
finish(new Error(`Local OAuth server failed: ${err.message}`));
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Callback HTML pages
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
function escapeHtml(s) {
|
|
172
|
+
return s
|
|
173
|
+
.replace(/&/g, "&")
|
|
174
|
+
.replace(/</g, "<")
|
|
175
|
+
.replace(/>/g, ">")
|
|
176
|
+
.replace(/"/g, """);
|
|
177
|
+
}
|
|
178
|
+
function htmlPage(title, message, isError) {
|
|
179
|
+
const accent = isError ? "#f85149" : "#58a6ff";
|
|
180
|
+
return `<!DOCTYPE html>
|
|
181
|
+
<html><head><meta charset="utf-8"><title>GitOps AI — ${escapeHtml(title)}</title>
|
|
182
|
+
<style>
|
|
183
|
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
184
|
+
display:flex;align-items:center;justify-content:center;min-height:100vh;
|
|
185
|
+
margin:0;background:#0d1117;color:#e6edf3}
|
|
186
|
+
.c{text-align:center;padding:3rem;border-radius:12px;background:#161b22;
|
|
187
|
+
border:1px solid #30363d;max-width:420px}
|
|
188
|
+
h1{color:${accent};margin-bottom:.5rem}
|
|
189
|
+
p{color:#8b949e;line-height:1.6}
|
|
190
|
+
</style></head><body><div class="c">
|
|
191
|
+
<h1>${escapeHtml(title)}</h1><p>${message}</p>
|
|
192
|
+
</div></body></html>`;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=gitlab-oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-oauth.js","sourceRoot":"","sources":["../../src/core/gitlab-oauth.ts"],"names":[],"mappings":"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"}
|
package/dist/core/gitlab.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
id: string;
|
|
5
|
-
httpUrl: string;
|
|
6
|
-
pathWithNamespace: string;
|
|
1
|
+
import type { GitProvider } from "./git-provider.js";
|
|
2
|
+
export interface GitLabProvider extends GitProvider {
|
|
3
|
+
readonly type: "gitlab";
|
|
7
4
|
}
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function createProject(name: string, namespaceId: string, host: string): Promise<ProjectInfo>;
|
|
10
|
-
export declare function configureGitCredentials(pat: string, cwd: string): void;
|
|
5
|
+
export declare function createGitLabProvider(): GitLabProvider;
|