remove folder - untrustworthy
This commit is contained in:
@@ -1,162 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// Runs on the HOST before the container starts.
|
||||
// Validates prerequisites and sets up files that the container needs
|
||||
// without using ../ bind mounts (which break on newer Docker runtimes).
|
||||
//
|
||||
// This is Node.js (not bash) so it works on Windows without WSL.
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
// The devcontainer CLI runs initializeCommand from the workspace folder (explore/).
|
||||
// Use CWD, not __dirname, so this works both in production and in tests.
|
||||
if (!fs.existsSync('../.env')) {
|
||||
console.error(`
|
||||
❌ Missing .env file. Create it first:
|
||||
|
||||
Create a file named .env in the toolkit root with:
|
||||
ANTHROPIC_API_KEY=your-key-here
|
||||
ANTHROPIC_BASE_URL=https://app-llmproxy.dataannotation.tech/api/llm_proxy/raccoon
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Copy small files from toolkit root into explore/ so the container
|
||||
// can access them without ../ bind mounts.
|
||||
fs.copyFileSync('../.env', '.env');
|
||||
try {
|
||||
fs.copyFileSync('../toolkit.json', 'toolkit.json');
|
||||
} catch {}
|
||||
|
||||
// Link repo so the bind mount source stays within explore/.
|
||||
// Use a junction on Windows (Docker Desktop can't follow symlinks,
|
||||
// but it can follow junctions). On macOS/Linux, 'junction' is ignored
|
||||
// and creates a regular symlink.
|
||||
// Single-repo toolkits have ../repo; polyglot toolkits have ../repos (the member
|
||||
// clones) instead. Link whichever exists so the matching bind mount resolves.
|
||||
if (fs.existsSync('../repo') && !fs.existsSync('repo')) {
|
||||
fs.symlinkSync(path.resolve('../repo'), 'repo', 'junction');
|
||||
}
|
||||
if (fs.existsSync('../repos') && !fs.existsSync('repos')) {
|
||||
fs.symlinkSync(path.resolve('../repos'), 'repos', 'junction');
|
||||
}
|
||||
|
||||
// Reference-data corpus lives at the toolkit root (../data, zeta toolkits only).
|
||||
// Link it under explore/ so the corpus bind mount's source stays within explore/
|
||||
// (../ bind mounts break on newer Docker runtimes; a resolved symlink/junction works,
|
||||
// exactly as for repo/repos above). Only present when this toolkit ships a corpus.
|
||||
if (fs.existsSync('../data') && !fs.existsSync('data')) {
|
||||
fs.symlinkSync(path.resolve('../data'), 'data', 'junction');
|
||||
}
|
||||
|
||||
// A named extra instance (EXPLORE_INSTANCE set, normally by instance.js) gets
|
||||
// its OWN repo working tree, mounted at /workspace/repo in that container, so a
|
||||
// `git checkout` in one instance doesn't disturb another. A `git clone --local`
|
||||
// hardlinks the object store, so this is cheap and fully self-contained — unlike
|
||||
// a git worktree, whose gitdir lives inside the source repo and so wouldn't
|
||||
// bind-mount into the container. The devcontainer.json mount derives the dir
|
||||
// name from EXPLORE_INSTANCE (repo<instance>); create it before that mount binds.
|
||||
// post-create.sh then checks out the default commit + runs setup in the new
|
||||
// container, exactly as it does for the primary repo.
|
||||
const instance = process.env.EXPLORE_INSTANCE || '';
|
||||
if (instance) {
|
||||
try {
|
||||
if (fs.existsSync('../repos')) {
|
||||
// Polyglot toolkit: give the instance its OWN copy of every member repo at
|
||||
// repos<instance>/<member>, mounted at /workspace/repos. A git clone --local
|
||||
// hardlinks each member's object store, so this is cheap and fully isolated —
|
||||
// a member checkout in one instance never disturbs another.
|
||||
const dir = `repos${instance}`;
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
for (const member of fs.readdirSync(path.resolve('../repos'))) {
|
||||
const src = path.resolve('../repos', member);
|
||||
if (!fs.statSync(src).isDirectory()) continue;
|
||||
execSync(
|
||||
`git clone --local ${JSON.stringify(src)} ${JSON.stringify(path.join(dir, member))}`,
|
||||
{
|
||||
stdio: 'inherit',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single-repo toolkit: clone repo → repo<instance>, mounted at /workspace/repo.
|
||||
const dir = `repo${instance}`;
|
||||
if (!fs.existsSync(dir)) {
|
||||
execSync(
|
||||
`git clone --local ${JSON.stringify(path.resolve('../repo'))} ${JSON.stringify(dir)}`,
|
||||
{
|
||||
stdio: 'inherit',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`\n❌ Couldn't create the repo working tree for instance "${instance}".\n` +
|
||||
` This needs git on your PATH. Install git, then retry.\n`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Best-effort: warn if an existing container for this folder doesn't publish
|
||||
// the app ports. Docker fixes -p mappings when a container is CREATED, so a
|
||||
// container built by an older toolkit (before/with different appPort) keeps its
|
||||
// old mappings even when you re-run `up`. The only way to pick up new ports is
|
||||
// to recreate the container — so we point that out here rather than letting the
|
||||
// worker stare at a dead localhost. Wrapped so it can never block startup: any
|
||||
// failure (docker missing, odd output) is swallowed and the check is skipped.
|
||||
//
|
||||
// Skipped for named instances: they're managed by instance.js (their own ports,
|
||||
// and they carry an id-label instead of this folder's local_folder label), so
|
||||
// this folder-scoped check would only ever inspect the primary container.
|
||||
if (!instance)
|
||||
try {
|
||||
// Container ports we expect published. The browsable port is 3000 for every
|
||||
// repo; Palolo also serves its API on 3001; zeta toolkits serve the corpus
|
||||
// viewer on 3002. Read from toolkit.json when available, else assume the base pair.
|
||||
let expected = [3000, 3001];
|
||||
try {
|
||||
const tk = JSON.parse(fs.readFileSync('toolkit.json', 'utf-8'));
|
||||
expected = tk.explorePorts && tk.explorePorts.serverHost ? [3000, 3001] : [3000];
|
||||
if (tk.explorePorts && tk.explorePorts.corpusHost) expected.push(3002);
|
||||
} catch {}
|
||||
|
||||
const folder = process.cwd();
|
||||
const ids = execSync(`docker ps -aq --filter "label=devcontainer.local_folder=${folder}"`, {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
})
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean);
|
||||
|
||||
for (const id of ids) {
|
||||
const bindings = execSync(
|
||||
`docker inspect --format "{{json .HostConfig.PortBindings}}" ${id}`,
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}
|
||||
).trim();
|
||||
const missing = expected.filter((p) => !bindings.includes(`${p}/tcp`));
|
||||
if (missing.length > 0) {
|
||||
console.error(`
|
||||
⚠️ An existing container for this folder doesn't publish port(s) ${missing.join(', ')}.
|
||||
Docker fixes port mappings when a container is created, so re-running 'up'
|
||||
alone won't add them. To expose the app, recreate the container:
|
||||
|
||||
npx @devcontainers/cli up --remove-existing-container
|
||||
|
||||
Note: recreating wipes the container's Claude history — run /create-snapshot
|
||||
first if there's a conversation you want to keep.
|
||||
`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// docker unavailable or unexpected output — skip the check.
|
||||
}
|
||||
Reference in New Issue
Block a user