Import @amika/sandbox from amika-mono as a JS workspace - #330
Conversation
`@amika/sandbox`, the Amika-agnostic sandbox provider abstraction, moves out of the closed-source `amika-mono` repo and into this one. amika-mono consumes it back as a git submodule pinned to an exact commit, so this repo becomes the single source of truth for the package: changes land here and reach amika-mono through a pointer bump. The package is a clean extraction candidate. It has no `@amika/*` dependencies of its own, it already exposes a deliberate public surface through its export entries, and it already follows the `internal/` convention that keeps helpers off that surface. Establishes a JS workspace at the repo root to host it: - `pnpm-workspace.yaml` and `package.json` declare `js/*` and `eslint-rules` as members. `sdk/typescript` is deliberately excluded: it is a self-contained workspace with its own lockfile and `packageManager` pin, and its CI and npm publish workflows install from that lockfile. - `eslint-rules/` comes along because `js/sandbox/eslint.config.mjs` loads the `no-cross-package-internal` rule from it. Without it the package cannot lint in either repo. - `js/AGENTS.md` carries the workspace conventions, adapted so its examples and links resolve against this repo. - A `check-sandbox` CI job and a JS branch in `githooks/pre-commit` give the package the source-quality gates amika-mono can no longer provide for it: a superproject sees only a gitlink for a submodule, so it cannot lint or format this source. `prettier` is pinned to an exact 3.8.3 rather than the `^3.6.2` it carried in amika-mono. The two repos resolve lockfiles independently, so a range lets them pick different patch versions, and prettier's output does change across minors: on byte-identical source, `formatcheck` passed under 3.8.3 and failed under 3.9.6. An exact pin is what keeps the two repos agreeing on formatting.
A repo that declares git submodules is not usable without them, and a clone that silently leaves the submodule directory empty fails later, further away, and far more confusingly than a slow clone does. This is not hypothetical: amika-mono now carries `js/sandbox` as a submodule, so a sandbox provisioned from it needs submodule recursion or `pnpm install` breaks on a missing workspace member. Adds `recurseSubmodules` to `CloneRepoInput`, defaulting to `true` when omitted, and honors it in `buildRefreshClonedRepoScript` — the fast path that refreshes a repo already baked into the booted snapshot. The refresh runs `submodule sync --recursive` before `submodule update`: `update --init` alone would keep using the URL recorded in `.git/config`, missing a `.gitmodules` URL that changed between the baked-in commit and the one being checked out. Both run after the branch checkout, since the commits a superproject pins are a property of the checked out tree. No provider SDK exposes a submodule option on its native clone primitive — Daytona's `git.clone` takes url, path, branch, commitId, credentials, and a TLS flag, and neither `@daytonaio/sdk` nor `@daytona/api-client` mentions submodules anywhere. Honoring a `true` value therefore has to mean cloning over the exec port instead. The flag is threaded through here; `resolveCloneRepo` in amika-mono's `@amika/sandbox-provisioning` is what acts on it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4c0a08d2c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| function shellQuote(path) { | ||
| return JSON.stringify(path); |
There was a problem hiding this comment.
Quote staged paths safely for the shell
When a staged, formattable filename contains shell syntax such as $() or backticks, JSON.stringify merely surrounds it with double quotes, so the shell still performs command substitution when lint-staged executes the generated Prettier command. A valid Git path such as js/sandbox/src/$(command).ts can therefore execute arbitrary commands during commit; use POSIX-safe single-quote escaping or pass paths without constructing a shell command string.
Useful? React with 👍 / 👎.
| // a git submodule, and a formatting disagreement between the two repos | ||
| // would surface as a `formatcheck` failure in whichever one did not write | ||
| // the file. Bump both repos together. | ||
| return [`pnpm dlx prettier@3.8.3 --write ${quotedPaths}`]; |
There was a problem hiding this comment.
Use the installed workspace Prettier
For any commit staging a formattable js/ file, the hook runs pnpm dlx rather than the Prettier dependency installed by setup-repo.sh; on a fresh machine without a cached dlx package, this adds a registry dependency and can prevent offline commits even though the workspace was installed successfully. Invoke the workspace binary, for example through pnpm exec, so the hook uses the lockfile-resolved tool as intended.
AGENTS.md reference: js/AGENTS.md:L153-L159
Useful? React with 👍 / 👎.
Implements the amika-side half of amika-mono's
specs/020-sandbox-oss-submodule-migration.md.@amika/sandbox— the Amika-agnostic sandbox provider abstraction — moves out of the closed-sourceamika-monorepo and into this one. amika-mono consumes it back as a git submodule mounted atrepos/amika/, registered as a normal pnpm workspace member, so all four existing consumers keep importing@amika/sandboxthroughworkspace:*with no import-site changes.This repo becomes the single source of truth for the package: changes land here and reach amika-mono through a pointer bump.
What's here
Import @amika/sandbox from amika-mono as a JS workspacepnpm-workspace.yaml+package.jsondeclaringjs/*andeslint-rulesas members, plus a committedpnpm-lock.yaml.js/sandbox/, copied from amika-mono.eslint-rules/, which came along out of necessity —js/sandbox/eslint.config.mjsloads theno-cross-package-internalrule from it via a relative path. The spec didn't account for this; without the copy the package cannot lint in either repo, since after the move that path would point atrepos/amika/eslint-rules.js/AGENTS.md, adapted so its examples and links resolve against this repo.check-sandboxjob (format check → typecheck → lint → test) and acheck-eslint-rulesjob in CI.githooks/pre-commit, driven by a new.lintstagedrc.mjs, gated on staged JS paths so a Go-only commit doesn't pay for a pnpm resolution.setup-repo.shnow installs the JS workspace.Add a recurseSubmodules option to the clone inputsCloneRepoInputgainsrecurseSubmodules, defaulting totrue, honored inbuildRefreshClonedRepoScript. amika-mono now carries a submodule, so a sandbox provisioned from it needs recursion orpnpm installbreaks on a missing workspace member. The amika-mono side (resolveCloneRepo) lands separately.Two things worth a look
sdk/typescriptis deliberately not a workspace member. The spec called for including it. It is already a self-contained pnpm workspace — ownpnpm-workspace.yaml, ownpnpm-lock.yaml, ownpackageManagerpin (11.1.3 vs. 10.18.2 here) — and bothsdk-typescript.ymland the OIDC publish workflow install from that lockfile. Absorbing it would nest a workspace inside a workspace and orphan a published package's release pipeline, for no benefit to this migration.prettieris pinned to an exact3.8.3, not^3.6.2. The two repos resolve lockfiles independently, so a caret range lets them pick different patch versions — and prettier's output changes across minors. This bit during the migration: on byte-identical source,formatcheckpassed under 3.8.3 and failed under 3.9.6. The exact pin is what keeps both repos and both pre-commit hooks agreeing on formatting. Bump it in all four places together.Verification
Run at the repo root against this branch:
pnpm installsdk/typescriptcorrectly excluded@amika/sandboxformatcheck@amika/sandboxtypecheck@amika/sandboxlint@amika/sandboxtesteslint-rulestest