File size: 7,956 Bytes
7d285f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadWorkspaceConfig = loadWorkspaceConfig;
exports.buildWorkspaceImage = buildWorkspaceImage;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const tar = __importStar(require("tar-fs"));
/**
* Parses a simple subset of Nix language from .idx/dev.nix
* Extracts pkgs.* into apt packages and env.* into env vars.
*/
function parseBasicNix(nixContent) {
var _a, _b, _c, _d;
const config = { packages: { apt: [], npm: [] }, env: {} };
// Extract packages: pkgs.nodejs_20, pkgs.python3, etc.
const pkgMatches = nixContent.matchAll(/pkgs\.([a-zA-Z0-9_\-]+)/g);
for (const match of pkgMatches) {
let pkgName = match[1];
// Soft map nix packages to common ubuntu apt packages
if (pkgName.startsWith('nodejs'))
pkgName = 'nodejs npm';
else if (pkgName.startsWith('python'))
pkgName = 'python3 python3-pip';
else if (pkgName === 'go')
pkgName = 'golang';
else if (pkgName === 'rust')
pkgName = 'rustc cargo';
// Add unique
const split = pkgName.split(' ');
for (const p of split) {
if (!((_b = (_a = config.packages) === null || _a === void 0 ? void 0 : _a.apt) === null || _b === void 0 ? void 0 : _b.includes(p))) {
(_d = (_c = config.packages) === null || _c === void 0 ? void 0 : _c.apt) === null || _d === void 0 ? void 0 : _d.push(p);
}
}
}
// Extract basic env: PORT = 3000;
const envBlockMatch = nixContent.match(/env\s*=\s*{([^}]+)}/);
if (envBlockMatch) {
const envLines = envBlockMatch[1].split('\n');
for (const line of envLines) {
const kvMatch = line.trim().match(/([a-zA-Z0-9_]+)\s*=\s*['"]?([^'";]+)['"]?\s*;/);
if (kvMatch) {
if (config.env)
config.env[kvMatch[1]] = kvMatch[2];
}
}
}
return config;
}
async function loadWorkspaceConfig(workspacePath) {
const codeverseJsonPath = path_1.default.join(/*turbopackIgnore: true*/ workspacePath, 'codeverse.json');
const idxNixPath = path_1.default.join(/*turbopackIgnore: true*/ workspacePath, '.idx', 'dev.nix');
// 1. Check for Project IDX dev.nix
try {
const nixContent = await promises_1.default.readFile(idxNixPath, 'utf-8');
return parseBasicNix(nixContent);
}
catch (_a) {
// Fallthrough if no dev.nix
}
// 2. Check for codeverse.json
try {
const jsonContent = await promises_1.default.readFile(codeverseJsonPath, 'utf-8');
return JSON.parse(jsonContent);
}
catch (err) {
const fsErr = err;
if (fsErr.code === 'ENOENT') {
// 3. Create default if none exists
const defaultConfig = {
"env": { "PORT": "3000" },
"packages": { "apt": [], "npm": [] }
};
await promises_1.default.writeFile(codeverseJsonPath, JSON.stringify(defaultConfig, null, 2));
return defaultConfig;
}
throw err;
}
}
/**
* Dynamically writes a Dockerfile and uses Docker's native layer caching
* to speed up future startups with the same packages.
*/
async function buildWorkspaceImage(workspaceId, workspacePath, onLog, docker) {
var _a, _b, _c;
onLog("Loading workspace configuration (codeverse.json or dev.nix)...");
const config = await loadWorkspaceConfig(workspacePath);
// Generate Dockerfile content
let dockerfile = `FROM codercom/code-server:latest\n`;
dockerfile += `USER root\n`;
// APT Packages
if (((_a = config.packages) === null || _a === void 0 ? void 0 : _a.apt) && config.packages.apt.length > 0) {
const aptList = config.packages.apt.join(' ');
dockerfile += `RUN apt-get update && apt-get install -y ${aptList} && rm -rf /var/lib/apt/lists/*\n`;
}
// NPM Packages
if (((_b = config.packages) === null || _b === void 0 ? void 0 : _b.npm) && config.packages.npm.length > 0) {
// Ensure nodejs and npm are present if not already installed
if (!((_c = config.packages.apt) === null || _c === void 0 ? void 0 : _c.some(p => p.includes('npm')))) {
dockerfile += `RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/*\n`;
}
const npmList = config.packages.npm.join(' ');
dockerfile += `RUN npm install -g ${npmList}\n`;
}
// Environment map (so they exist in all shells)
// We add them to /etc/environment and .bashrc to be safe
if (config.env) {
for (const [k, v] of Object.entries(config.env)) {
dockerfile += `ENV ${k}="${v}"\n`;
dockerfile += `RUN echo 'export ${k}="${v}"' >> /home/coder/.bashrc\n`;
}
}
dockerfile += `USER coder\n`;
// Set WORKDIR
dockerfile += `WORKDIR /home/coder/project\n`;
// Write to a temporary hidden dir in the workspace
const buildDir = path_1.default.join(/*turbopackIgnore: true*/ workspacePath, '.codeverse');
await promises_1.default.mkdir(buildDir, { recursive: true });
const dockerfilePath = path_1.default.join(/*turbopackIgnore: true*/ buildDir, 'Dockerfile');
await promises_1.default.writeFile(dockerfilePath, dockerfile);
const imageName = `codeverse-workspace-${workspaceId}`;
onLog(`Building image ${imageName}...`);
return new Promise((resolve, reject) => {
// Native Docker build engine with tar-fs
const pack = tar.pack(buildDir);
docker.buildImage(pack, { t: imageName }, (err, stream) => {
if (err)
return reject(err);
if (!stream)
return reject(new Error("No stream returned from Docker build"));
docker.modem.followProgress(stream, (err2) => {
if (err2)
return reject(err2);
onLog(`Image ${imageName} built successfully.`);
resolve({ imageName, config });
}, (event) => {
if (event.stream)
onLog(event.stream.trim());
else if (event.status)
onLog(event.status.trim());
else if (event.error)
onLog(`Build Error: ${event.error}`);
});
});
});
}
|