UNPKG

mlld

Version:

mlld: llm scripting language

854 lines (850 loc) 25.5 kB
import { logger } from './chunk-M3R2H5KU.mjs'; import { MlldError, ErrorSeverity, MlldResolutionError } from './chunk-V5XE5YB5.mjs'; import { __name, __publicField } from './chunk-NJQT543K.mjs'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; import * as fs2 from 'fs'; var _GitHubAuthService = class _GitHubAuthService { constructor(config = {}) { __publicField(this, "config"); __publicField(this, "serviceName"); __publicField(this, "accountName"); __publicField(this, "fallbackTokenPath"); __publicField(this, "clientId"); __publicField(this, "octokitModule"); __publicField(this, "keytarModule"); this.config = { clientId: process.env.MLLD_GITHUB_CLIENT_ID || "Ov23liFeqioeJmD9xZOP", serviceName: "mlld-cli", accountName: "github-token", fallbackStorage: true, ...config }; this.clientId = this.config.clientId; this.serviceName = this.config.serviceName; this.accountName = this.config.accountName; this.fallbackTokenPath = path.join(os.homedir(), ".mlld", "auth.json"); } /** * Get singleton instance of GitHubAuthService */ static getInstance(config) { if (!_GitHubAuthService.instance) { _GitHubAuthService.instance = new _GitHubAuthService(config); } return _GitHubAuthService.instance; } /** * Dynamically load keytar module (optional dependency) */ async getKeytarModule() { if (this.keytarModule === void 0) { try { this.keytarModule = await import('./keytar-VJN3OYQZ.mjs'); } catch { this.keytarModule = null; } } return this.keytarModule; } /** * Dynamically load Octokit module */ async getOctokitModule() { if (!this.octokitModule) { this.octokitModule = await import('@octokit/rest'); } return this.octokitModule; } /** * Get authenticated Octokit instance */ async getOctokit() { const token = await this.getStoredToken(); if (!token) { throw new MlldError("Not authenticated. Please run: mlld auth login", { code: "AUTH_NOT_AUTHENTICATED", severity: ErrorSeverity.Fatal }); } const { Octokit } = await this.getOctokitModule(); return new Octokit({ auth: token, log: { debug: /* @__PURE__ */ __name(() => { }, "debug"), info: /* @__PURE__ */ __name(() => { }, "info"), warn: console.warn, error: console.error } }); } /** * Check if user is currently authenticated */ async isAuthenticated() { try { const token = await this.getStoredToken(); if (!token) return false; const { Octokit } = await this.getOctokitModule(); const octokit = new Octokit({ auth: token, log: { debug: /* @__PURE__ */ __name(() => { }, "debug"), info: /* @__PURE__ */ __name(() => { }, "info"), warn: console.warn, error: console.error } }); await octokit.users.getAuthenticated(); return true; } catch { return false; } } /** * Get current authenticated GitHub user */ async getGitHubUser() { try { const octokit = await this.getOctokit(); const { data } = await octokit.users.getAuthenticated(); return data; } catch { return null; } } /** * Perform authentication using OAuth Device Flow */ async authenticate() { try { const existingUser = await this.getGitHubUser(); if (existingUser) { return { success: true, user: existingUser, token: await this.getStoredToken() }; } console.log("\u{1F510} Starting GitHub authentication...\n"); console.log("\u{1F4C4} By publishing to the mlld registry, you agree to license your"); console.log(" modules under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication."); console.log(" Learn more: https://creativecommons.org/public-domain/cc0/\n"); const deviceFlow = await this.initiateDeviceFlow(); console.log(`Please visit: ${deviceFlow.verification_uri}`); console.log(`And enter code: ${deviceFlow.user_code} `); if (deviceFlow.verification_uri_complete) { console.log("Or open the direct link:"); console.log(`${deviceFlow.verification_uri_complete} `); } console.log("Waiting for authentication..."); const result = await this.pollForToken(deviceFlow); if (result.success) { console.log(`\u2705 Successfully authenticated as ${result.user.login}`); return result; } else { console.error(`\u274C Authentication failed: ${result.error}`); return result; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, error: `Authentication failed: ${errorMessage}` }; } } /** * Logout - remove stored credentials */ async logout() { try { const keytar = await this.getKeytarModule(); if (keytar) { try { await keytar.deletePassword(this.serviceName, this.accountName); } catch { } } if (this.config.fallbackStorage) { try { await fs.unlink(this.fallbackTokenPath); } catch { } } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new MlldError(`Logout failed: ${errorMessage}`, { code: "AUTH_LOGOUT_FAILED", severity: ErrorSeverity.Fatal }); } } /** * Get stored authentication token */ async getStoredToken() { const keytar = await this.getKeytarModule(); if (keytar) { try { const token = await keytar.getPassword(this.serviceName, this.accountName); if (token) return token; } catch { } } if (this.config.fallbackStorage) { try { const authData = await fs.readFile(this.fallbackTokenPath, "utf8"); const parsed = JSON.parse(authData); return parsed.token || null; } catch { } } return null; } /** * Store authentication token securely */ async storeToken(token) { let storedInKeychain = false; const keytar = await this.getKeytarModule(); if (keytar) { try { await keytar.setPassword(this.serviceName, this.accountName, token); storedInKeychain = true; } catch (error) { console.warn("\u26A0\uFE0F Could not store token in system keychain, using fallback storage"); } } else { console.warn("\u26A0\uFE0F Keytar not available, using fallback storage"); } if (!storedInKeychain && this.config.fallbackStorage) { try { await fs.mkdir(path.dirname(this.fallbackTokenPath), { recursive: true }); const authData = { token, stored_at: (/* @__PURE__ */ new Date()).toISOString(), service: this.serviceName }; await fs.writeFile( this.fallbackTokenPath, JSON.stringify(authData, null, 2), { mode: 384 } // Readable only by owner ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new MlldError(`Failed to store authentication token: ${errorMessage}`, { code: "AUTH_TOKEN_STORAGE_FAILED", severity: ErrorSeverity.Fatal }); } } } /** * Initiate GitHub OAuth Device Flow */ async initiateDeviceFlow() { try { const response = await fetch("https://github.com/login/device/code", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ client_id: this.clientId, scope: "gist public_repo" }) }); if (!response.ok) { const error = await response.text(); throw new MlldError(`Failed to initiate device flow: ${error}`, { code: "AUTH_DEVICE_FLOW_FAILED", severity: ErrorSeverity.Fatal }); } return await response.json(); } catch (error) { if (error instanceof MlldError) { throw error; } throw new MlldError(`Failed to initiate device flow: ${error instanceof Error ? error.message : String(error)}`, { code: "AUTH_DEVICE_FLOW_ERROR", severity: ErrorSeverity.Fatal, cause: error }); } } /** * Poll for authentication token */ async pollForToken(deviceFlow) { const startTime = Date.now(); const expirationTime = startTime + deviceFlow.expires_in * 1e3; let interval = deviceFlow.interval * 1e3; while (Date.now() < expirationTime) { await this.sleep(interval); try { const response = await fetch("https://github.com/login/oauth/access_token", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ client_id: this.clientId, device_code: deviceFlow.device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }) }); const data = await response.json(); if (data.access_token) { await this.storeToken(data.access_token); const { Octokit } = await this.getOctokitModule(); const octokit = new Octokit({ auth: data.access_token, log: { debug: /* @__PURE__ */ __name(() => { }, "debug"), info: /* @__PURE__ */ __name(() => { }, "info"), warn: console.warn, error: console.error } }); const { data: user } = await octokit.users.getAuthenticated(); return { success: true, user, token: data.access_token }; } else if (data.error === "authorization_pending") { continue; } else if (data.error === "slow_down") { interval += 5e3; continue; } else if (data.error === "access_denied") { return { success: false, error: "Authentication was denied by user" }; } else if (data.error === "expired_token") { return { success: false, error: "Authentication code expired. Please try again." }; } else { return { success: false, error: data.error_description || data.error || "Unknown error" }; } } catch (error) { if (Date.now() + interval >= expirationTime) { const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, error: `Network error during authentication: ${errorMessage}` }; } } } return { success: false, error: "Authentication timed out. Please try again." }; } /** * Utility function to sleep for a given number of milliseconds */ sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } }; __name(_GitHubAuthService, "GitHubAuthService"); __publicField(_GitHubAuthService, "instance", null); var GitHubAuthService = _GitHubAuthService; var _GitHubResolver = class _GitHubResolver { constructor() { __publicField(this, "name", "GITHUB"); __publicField(this, "description", "Resolves modules from GitHub repositories"); __publicField(this, "type", "input"); __publicField(this, "capabilities", { io: { read: true, write: false, list: true }, contexts: { import: true, path: true, output: false }, supportedContentTypes: [ "module", "data", "text" ], defaultContentType: "text", priority: 20, cache: { strategy: "persistent", ttl: { duration: 300 } // 5 minutes } }); __publicField(this, "cache", /* @__PURE__ */ new Map()); __publicField(this, "defaultCacheTimeout", 3e5); __publicField(this, "authService"); this.authService = GitHubAuthService.getInstance(); } /** * Check if this resolver can handle the reference */ canResolve(ref, config) { return !!config?.repository; } /** * Resolve a reference to GitHub content */ async resolve(ref, config) { if (!config?.repository) { throw new MlldResolutionError("GitHubResolver requires repository in configuration", { reference: ref }); } const [owner, repo] = config.repository.split("/"); if (!owner || !repo) { throw new MlldResolutionError('Invalid repository format. Expected "owner/repo"', { reference: ref, repository: config.repository }); } const path3 = this.buildPath(ref, config); const cacheKey = `${config.repository}:${config.branch || "default"}:${path3}`; const cached = this.getCached(cacheKey, config.cacheTimeout); if (cached) { const contentType = await this.detectContentType(path3, cached.content); const metadata = { source: `github://${config.repository}/${path3}`, timestamp: /* @__PURE__ */ new Date(), taint: [], author: owner }; return { content: cached.content, contentType, mx: metadata, metadata }; } try { const token = await this.getAuthToken(config); const { content, etag } = await this.fetchFromGitHub(owner, repo, path3, config, cached?.etag, token); this.cache.set(cacheKey, { content, timestamp: Date.now(), etag }); const contentType = await this.detectContentType(path3, content); const metadata = { source: `github://${config.repository}/${path3}`, timestamp: /* @__PURE__ */ new Date(), taint: [], author: owner, mimeType: this.getMimeType(path3) }; return { content, contentType, mx: metadata, metadata }; } catch (error) { const err = error; if (err.status === 404) { const localPath = this.getLocalPath(ref, config); const localExists = localPath && fs2.existsSync(localPath); if (localExists) { const prefix = config.prefix || "@private/"; logger.debug(`GitHubResolver: Module ${ref} not found in repo, but local version exists at ${localPath}`); throw new MlldResolutionError(`Module '${prefix}${ref}' not found in repository ${config.repository}. However, a local version exists at: ${localPath} To test locally before publishing: @import { something } from @local/${ref} Ready to publish? Run: mlld publish ${localPath}`, { code: "MODULE_NOT_FOUND_BUT_LOCAL_EXISTS", details: { reference: ref, repository: config.repository, path: path3, hasLocal: true, localPath } }); } else { throw new MlldResolutionError(`File not found in repository: ${path3}`, { code: "FILE_NOT_FOUND", details: { reference: ref, repository: config.repository, path: path3 } }); } } if (err.status === 401 || err.status === 403) { throw new MlldResolutionError(`GitHub authentication required. Run 'mlld auth login' to authenticate`, { reference: ref, repository: config.repository, path: path3 }); } throw new MlldResolutionError(`Failed to fetch from GitHub: ${err.message || "Unknown error"}`, { reference: ref, repository: config.repository, path: path3, originalError: error }); } } /** * List files in a GitHub directory */ async list(prefix, config) { if (!config?.repository) { return []; } const [owner, repo] = config.repository.split("/"); if (!owner || !repo) { return []; } const path3 = this.buildPath(prefix, config); const token = await this.getAuthToken(config); const branch = await this.resolveBranch(owner, repo, config, token); try { const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path3}?ref=${branch}`; const response = await this.githubFetch(url, token); if (!response.ok) { return []; } const items = await response.json(); if (!Array.isArray(items)) { return []; } return items.map((item) => ({ path: `${prefix}/${item.name}`, type: item.type === "dir" ? "directory" : "file", size: item.size, lastModified: new Date(item.sha) // Using SHA as a proxy for last modified })); } catch { return []; } } /** * Validate configuration */ validateConfig(config) { const errors = []; if (!config || typeof config !== "object") { errors.push("config must be an object"); return errors; } const cfg = config; if (!cfg.repository) { errors.push("repository is required"); } else if (typeof cfg.repository !== "string") { errors.push("repository must be a string"); } else if (!cfg.repository.includes("/")) { errors.push('repository must be in format "owner/repo"'); } if (cfg.token !== void 0 && typeof cfg.token !== "string") { errors.push("token must be a string"); } if (cfg.branch !== void 0 && typeof cfg.branch !== "string") { errors.push("branch must be a string"); } if (cfg.basePath !== void 0 && typeof cfg.basePath !== "string") { errors.push("basePath must be a string"); } if (cfg.useRawApi !== void 0 && typeof cfg.useRawApi !== "boolean") { errors.push("useRawApi must be a boolean"); } if (cfg.cacheTimeout !== void 0) { if (typeof cfg.cacheTimeout !== "number" || cfg.cacheTimeout < 0) { errors.push("cacheTimeout must be a non-negative number"); } } return errors; } /** * Check access - depends on whether repository is public/private */ async checkAccess(ref, operation, config) { if (operation === "write") { return false; } if (!config?.repository) { return false; } const token = await this.getAuthToken(config); if (token) { return true; } const [owner, repo] = config.repository.split("/"); try { const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`); return response.ok; } catch { return false; } } /** * Get authentication token from auth service or config fallback */ async getAuthToken(config) { try { const authToken = await this.authService.getStoredToken(); if (authToken) { return authToken; } } catch { } if (config?.token) { return config.token; } if (process.env.GITHUB_TOKEN) { return process.env.GITHUB_TOKEN; } return null; } /** * Build the full path within the repository */ buildPath(ref, config) { const parts = []; if (config.basePath) { parts.push(config.basePath.replace(/^\/|\/$/g, "")); } if (ref) { let modulePath = ref.replace(/^\/|\/$/g, ""); if (modulePath && !modulePath.includes(".")) { modulePath += ".mld.md"; } parts.push(modulePath); } return parts.filter((p) => p.length > 0).join("/"); } /** * Get cached content if available */ getCached(key, timeout) { const cached = this.cache.get(key); if (!cached) return null; const maxAge = timeout || this.defaultCacheTimeout; if (Date.now() - cached.timestamp > maxAge) { this.cache.delete(key); return null; } return { content: cached.content, etag: cached.etag }; } /** * Fetch content from GitHub */ async fetchFromGitHub(owner, repo, path3, config, etag, token) { const branch = await this.resolveBranch(owner, repo, config, token); if (config.useRawApi !== false) { const url2 = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path3}`; const headers = {}; if (token) { headers["Authorization"] = `token ${token}`; } if (etag) { headers["If-None-Match"] = etag; } const response2 = await fetch(url2, { headers }); if (response2.status === 304) { const cached = this.cache.get(`${config.repository}:${branch}:${path3}`); if (cached) { return { content: cached.content, etag: cached.etag }; } } if (!response2.ok) { const error = new Error(`GitHub API error: ${response2.statusText}`); error.status = response2.status; throw error; } return { content: await response2.text(), etag: response2.headers.get("etag") || void 0 }; } const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path3}?ref=${branch}`; const response = await this.githubFetch(url, token, etag); if (response.status === 304) { const cached = this.cache.get(`${config.repository}:${branch}:${path3}`); if (cached) { return { content: cached.content, etag: cached.etag }; } } if (!response.ok) { const error = new Error(`GitHub API error: ${response.statusText}`); error.status = response.status; throw error; } const data = await response.json(); if (data.type !== "file") { throw new Error(`Path is not a file: ${path3}`); } if (!data.content) { throw new Error(`No content found for file: ${path3}`); } const content = Buffer.from(data.content, "base64").toString("utf8"); return { content, etag: response.headers.get("etag") || void 0 }; } /** * Resolve the branch to use */ async resolveBranch(owner, repo, config, token) { if (config.branch) { return config.branch; } const cacheKey = `${owner}/${repo}:default-branch`; const cached = this.cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < 864e5) { return cached.content; } try { const response = await this.githubFetch(`https://api.github.com/repos/${owner}/${repo}`, token); if (response.ok) { const data = await response.json(); const defaultBranch = data.default_branch || "main"; this.cache.set(cacheKey, { content: defaultBranch, timestamp: Date.now() }); return defaultBranch; } } catch { } return "main"; } /** * Make a GitHub API request with proper headers */ async githubFetch(url, token, etag) { const headers = { "Accept": "application/vnd.github.v3+json", "User-Agent": "mlld-resolver" }; if (token) { headers["Authorization"] = `token ${token}`; } if (etag) { headers["If-None-Match"] = etag; } return fetch(url, { headers }); } /** * Get MIME type based on file extension */ getMimeType(path3) { const ext = path3.split(".").pop()?.toLowerCase(); const mimeTypes = { "mld": "text/x-mlld", "mlld": "text/x-mlld", "md": "text/markdown", "txt": "text/plain", "json": "application/json", "yaml": "text/yaml", "yml": "text/yaml", "js": "text/javascript", "ts": "text/typescript", "py": "text/x-python", "sh": "text/x-shellscript" }; return mimeTypes[ext || ""] || "text/plain"; } /** * Detect content type based on file extension and content */ async detectContentType(filePath, content) { if (filePath.endsWith(".mld") || filePath.endsWith(".mld.md") || filePath.endsWith(".mlld") || filePath.endsWith(".mlld.md")) { return "module"; } if (filePath.endsWith(".json")) { return "data"; } try { const { parse } = await import('./parser-6HNWFG6W.mjs'); const result = await parse(content); if (result.success && this.hasModuleExports(result.ast)) { return "module"; } } catch { } try { JSON.parse(content); return "data"; } catch { } return "text"; } /** * Check if AST has module exports */ hasModuleExports(ast) { if (!ast || !Array.isArray(ast)) return false; return ast.some((node) => node && node.type === "Directive" && [ "var", "exe", "path" ].includes(node.kind)); } /** * Get the local path where this module might exist */ getLocalPath(ref, config) { if (!config.basePath) return null; const modulePath = this.buildPath(ref, config); return path.join(process.cwd(), modulePath); } }; __name(_GitHubResolver, "GitHubResolver"); var GitHubResolver = _GitHubResolver; export { GitHubAuthService, GitHubResolver }; //# sourceMappingURL=chunk-YUPK4GL5.mjs.map //# sourceMappingURL=chunk-YUPK4GL5.mjs.map