feat: mount user repo as read-only with writable shannon overlay (#273)
* feat: mount user repo as read-only with deliverables bind-mount overlay * feat: add playground and .playwright-cli overlay mounts * feat: add filesystem context to pipeline-testing prompts * fix: use explicit REPO_PATH in filesystem prompt for clarity * fix: update filesystem prompts with playground notes and absolute screenshot paths * feat: namespace writable overlays under .shannon/ to avoid polluting host repo * refactor: rename playground to scratchpad * fix: redirect playwright-cli output to writable .shannon/ overlay * fix: pre-create .shannon/ overlay mount points for Linux compatibility * fix: exclude nested node_modules and dist from Docker build context * fix: enforce LF line endings for shell scripts on Windows
This commit is contained in:
@@ -12,7 +12,7 @@ import { ensureImage, ensureInfra, randomSuffix, spawnWorker } from '../docker.j
|
||||
import { buildEnvFlags, isRouterConfigured, loadEnv, validateCredentials } from '../env.js';
|
||||
import { getCredentialsPath, getWorkspacesDir, initHome } from '../home.js';
|
||||
import { isLocal } from '../mode.js';
|
||||
import { ensureDeliverables, resolveConfig, resolveRepo } from '../paths.js';
|
||||
import { resolveConfig, resolveRepo } from '../paths.js';
|
||||
import { displaySplash } from '../splash.js';
|
||||
|
||||
export interface StartArgs {
|
||||
@@ -42,7 +42,6 @@ export async function start(args: StartArgs): Promise<void> {
|
||||
// 3. Resolve paths
|
||||
const repo = resolveRepo(args.repo);
|
||||
const config = args.config ? resolveConfig(args.config) : undefined;
|
||||
ensureDeliverables(repo.hostPath);
|
||||
|
||||
// 4. Ensure workspaces dir is writable by container user (UID 1001)
|
||||
const workspacesDir = getWorkspacesDir();
|
||||
@@ -68,7 +67,20 @@ export async function start(args: StartArgs): Promise<void> {
|
||||
const workspace =
|
||||
args.workspace ?? `${new URL(args.url).hostname.replace(/[^a-zA-Z0-9-]/g, '-')}_shannon-${Date.now()}`;
|
||||
|
||||
// 9. Resolve credentials — mount single file to fixed container path
|
||||
// 9. Create writable overlay directories (mounted over :ro repo paths inside container)
|
||||
const workspacePath = path.join(workspacesDir, workspace);
|
||||
for (const dir of ['deliverables', 'scratchpad', '.playwright-cli']) {
|
||||
const dirPath = path.join(workspacePath, dir);
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
fs.chmodSync(dirPath, 0o777);
|
||||
}
|
||||
|
||||
// 10. Pre-create overlay mount points (Linux :ro mounts can't auto-create them)
|
||||
const shannonDir = path.join(repo.hostPath, '.shannon');
|
||||
for (const dir of ['deliverables', 'scratchpad', '.playwright-cli']) {
|
||||
fs.mkdirSync(path.join(shannonDir, dir), { recursive: true });
|
||||
}
|
||||
|
||||
const credentialsPath = getCredentialsPath();
|
||||
const hasCredentials = fs.existsSync(credentialsPath);
|
||||
|
||||
@@ -101,7 +113,7 @@ export async function start(args: StartArgs): Promise<void> {
|
||||
...(hasCredentials && { credentials: credentialsPath }),
|
||||
...(promptsDir && { promptsDir }),
|
||||
...(outputDir && { outputDir }),
|
||||
...(workspace && { workspace }),
|
||||
workspace,
|
||||
...(args.pipelineTesting && { pipelineTesting: true }),
|
||||
});
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ export interface WorkerOptions {
|
||||
credentials?: string;
|
||||
promptsDir?: string;
|
||||
outputDir?: string;
|
||||
workspace?: string;
|
||||
workspace: string;
|
||||
pipelineTesting?: boolean;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,13 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
|
||||
|
||||
// Volume mounts
|
||||
args.push('-v', `${opts.workspacesDir}:/app/workspaces`);
|
||||
args.push('-v', `${opts.repo.hostPath}:${opts.repo.containerPath}`);
|
||||
args.push('-v', `${opts.repo.hostPath}:${opts.repo.containerPath}:ro`);
|
||||
|
||||
// Writable overlays: shadow .shannon/ inside the :ro repo with workspace-backed dirs
|
||||
const workspacePath = path.join(opts.workspacesDir, opts.workspace);
|
||||
args.push('-v', `${path.join(workspacePath, 'deliverables')}:${opts.repo.containerPath}/.shannon/deliverables`);
|
||||
args.push('-v', `${path.join(workspacePath, 'scratchpad')}:${opts.repo.containerPath}/.shannon/scratchpad`);
|
||||
args.push('-v', `${path.join(workspacePath, '.playwright-cli')}:${opts.repo.containerPath}/.shannon/.playwright-cli`);
|
||||
|
||||
// Local mode: mount prompts for live editing
|
||||
if (opts.promptsDir) {
|
||||
@@ -253,9 +259,7 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
|
||||
if (opts.outputDir) {
|
||||
args.push('--output', '/app/output');
|
||||
}
|
||||
if (opts.workspace) {
|
||||
args.push('--workspace', opts.workspace);
|
||||
}
|
||||
args.push('--workspace', opts.workspace);
|
||||
if (opts.pipelineTesting) {
|
||||
args.push('--pipeline-testing');
|
||||
}
|
||||
|
||||
@@ -76,12 +76,3 @@ export function resolveConfig(configArg: string): MountPair {
|
||||
containerPath: `/app/configs/${basename}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the deliverables directory exists and is writable by the container user.
|
||||
*/
|
||||
export function ensureDeliverables(repoHostPath: string): void {
|
||||
const deliverables = path.join(repoHostPath, 'deliverables');
|
||||
fs.mkdirSync(deliverables, { recursive: true });
|
||||
fs.chmodSync(deliverables, 0o777);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user