mlld
Version:
mlld: llm scripting language
854 lines (850 loc) • 25.5 kB
JavaScript
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:
{ something } from /${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