@ushawarma/stack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ <img src="assets/icon.svg" width="64" height="64" alt="">
2
+
3
+ # stack
4
+
5
+ Reusable, agent-safe development stacks.
6
+
7
+ Pick your tools and services, publish them as a **bundle** in a git repo, and use that bundle from any
8
+ project. Each project gets its own pinned, independent instance. stack composes bundles and generates
9
+ config for existing tools ([mise](https://mise.jdx.dev) installs tools; Pitchfork, via `mise daemons`,
10
+ runs services) instead of replacing them.
11
+
12
+ > Status: early prototype, tested end to end on Linux against real mise + Pitchfork and an OCI
13
+ > registry. See [docs/DESIGN.md](docs/DESIGN.md) for what is and isn't covered yet.
14
+
15
+ ## Quick look
16
+
17
+ A bundle is a git repo with a `bundle.toml` and whatever files it needs:
18
+
19
+ ```toml
20
+ # bundle.toml
21
+ [bundle]
22
+ name = "pybase"
23
+ version = "1.0.0"
24
+
25
+ [tools]
26
+ python = "3.13"
27
+ uv = "latest"
28
+
29
+ [services.postgres]
30
+ preset = "postgres"
31
+ version = "17"
32
+
33
+ [tasks.seed]
34
+ run = "psql \"$DATABASE_URL\" -f {{bundle_dir}}/fixtures/seed.sql"
35
+ services = ["postgres"]
36
+
37
+ [paths]
38
+ bin = ["bin"] # bundle-shipped CLIs go on PATH
39
+ ```
40
+
41
+ A project uses bundles from git, an OCI registry, or a local path in `stack.toml`:
42
+
43
+ ```toml
44
+ [[use]]
45
+ bundle = "git+https://github.com/acme/pybase?ref=v1"
46
+
47
+ [[use]]
48
+ bundle = "oci:ghcr.io/acme/obs:2.0.0"
49
+
50
+ [tasks.test]
51
+ run = "uv sync -q && uv run pytest -q"
52
+ services = ["postgres", "redis"]
53
+
54
+ [override.env]
55
+ LOG_LEVEL = "warn" # both bundles set LOG_LEVEL; the project must choose
56
+ ```
57
+
58
+ ```sh
59
+ stack compile # resolve, lock, assign ports, write .config/mise/conf.d/stack.toml
60
+ stack up --ttl 30m # start services, verify each is *this* instance, record a session
61
+ stack exec --require postgres -- pytest
62
+ stack down # succeeds only once the processes are confirmed gone
63
+ ```
64
+
65
+ ## Guarantees
66
+
67
+ - **Pinned by commit.** `stack.lock` records each bundle's commit and content hash. Moving a tag
68
+ upstream changes nothing until `stack compile --update`, which reports what moved.
69
+ - **No silent conflicts.** If two layers define the same key differently, compile fails with every
70
+ conflict listed. Only `[override.*]` resolves one, and the output records what it replaced.
71
+ - **Bundles carry files.** `{{bundle_dir}}` and `paths.bin` resolve to the bundle's own files.
72
+ - **Instance values stay out of bundles.** Bundles cannot pin ports. Each checkout gets its own
73
+ ports from a machine-wide registry (40000-49999, never service defaults), stable across restarts.
74
+ - **Never the wrong instance.** Before every `exec`, each service is checked live. Postgres and
75
+ Redis are confirmed over the app's own `DATABASE_URL`/`REDIS_URL` to be this checkout's server
76
+ (by data directory). Endpoints of unverified services are poisoned (host replaced with
77
+ `unverified.stack.invalid`) so apps with hardcoded fallbacks fail loudly instead of reaching some
78
+ other server. `--require` makes the command refuse to run instead.
79
+ - **Verified generations.** Session records fingerprint the complete compiled configuration and
80
+ assigned ports. Changed bundles, project overrides, or ports make service checks unavailable
81
+ until `stack up` restarts and verifies the new generation.
82
+ - **Owned lifetimes.** Sessions can lease on a TTL or a runner's PID. Active commands protect
83
+ TTL sessions until completion; `stack gc` (and every `stack up`) reclaims expired idle ones.
84
+ `down` retains ownership records if discovery or cleanup fails, and reports success only after
85
+ recorded processes and ports are gone.
86
+ - **Honest failures.** `up` reports the steps it completed, whether anything changed, and whether
87
+ retrying is safe.
88
+ - **Agent-friendly.** `--json` emits one object on stdout; errors have a stable `code`, a `hint`
89
+ and `details`. `stack mcp` serves the same contract over MCP. Git never prompts.
90
+
91
+ ## Commands
92
+
93
+ | Command | Does |
94
+ |---|---|
95
+ | `stack compile [--update \| --locked] [--reassign-ports]` | Resolve, lock, assign ports, write provider config |
96
+ | `stack inspect` | Show the composed stack, origins and ports; writes nothing |
97
+ | `stack up [--ttl 30m] [--owner-pid N]` | Start services, verify them, record a session |
98
+ | `stack status` | Verify every service now; session and lease state (exit 1 if unhealthy) |
99
+ | `stack exec [--require S \| --require-all] -- <cmd>` | Run with tools and env; unverified endpoints poisoned |
100
+ | `stack down` | Stop services and confirm they are gone |
101
+ | `stack renew` / `stack gc` | Renew this session's lease / reclaim expired sessions machine-wide |
102
+ | `stack publish <dir> oci:<registry>/<repo>:<tag>` | Publish a bundle as an OCI artifact |
103
+ | `stack mcp` | MCP server (stdio) exposing the same operations |
104
+
105
+ All accept `-C <dir>` and `--json`. `exec -C` runs in the selected project directory.
106
+ Registry credentials: `STACK_OCI_USERNAME` / `STACK_OCI_PASSWORD`. External token-service origins
107
+ require explicit approval in `STACK_OCI_AUTH_REALMS`, a comma-separated list such as
108
+ `https://auth.docker.io`. Credentials and authorization headers are never forwarded to external upload
109
+ origins or authentication redirects. HTTPS cannot redirect authentication to HTTP. Plain HTTP
110
+ is used only for loopback registries, or elsewhere with exactly `STACK_OCI_PLAIN_HTTP=1`.
111
+
112
+ MCP execution is bounded on Unix: at most 64 KiB of each output stream is retained, and the
113
+ command's process group is terminated on timeout or completion. Detached children cannot keep
114
+ output collection waiting for EOF. Services and sessions are still tested end to end on Linux.
115
+
116
+ ## Install
117
+
118
+ Once published, install the CLI with:
119
+
120
+ ```sh
121
+ npm install -g @ushawarma/stack
122
+ stack --version
123
+ ```
124
+
125
+ Prebuilt binaries cover macOS 13+ and Linux with glibc 2.39+, on x64 and arm64.
126
+ Node.js 22.14+ is required. See [docs/RELEASING.md](docs/RELEASING.md) for CI checks,
127
+ trusted publishing setup, release tags, and recovery.
128
+
129
+ ## Develop
130
+
131
+ ```sh
132
+ cargo test # unit + integration tests (real git repos in temp dirs)
133
+ cargo run -- -C examples/app inspect
134
+ tests/e2e/run.sh # Docker: real mise + Pitchfork + OCI registry, all scenarios
135
+ ```
136
+
137
+ `eval/` holds the competitor evaluation (Flox, devbox, devenv, mise) that shaped this design:
138
+ [eval/REPORT.md](eval/REPORT.md).
package/bin/stack.cjs ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const { spawn } = require('node:child_process');
4
+ const path = require('node:path');
5
+ const fs = require('node:fs');
6
+
7
+ const platform = `${process.platform}-${process.arch}`;
8
+ const binary = path.join(__dirname, '..', 'binaries', platform, 'stack');
9
+ if (!['darwin-x64', 'darwin-arm64', 'linux-x64', 'linux-arm64'].includes(platform)) {
10
+ console.error(`stack: unsupported platform ${platform}. Build from source: https://github.com/usharma123/stack`);
11
+ process.exit(1);
12
+ }
13
+ if (process.platform === 'linux' && !process.report.getReport().header.glibcVersionRuntime) {
14
+ console.error('stack: Linux requires glibc 2.39 or newer. Alpine/musl is not supported by this package.');
15
+ process.exit(1);
16
+ }
17
+ if (!fs.existsSync(binary)) {
18
+ console.error(`stack: missing packaged binary for ${platform}. Reinstall @ushawarma/stack.`);
19
+ process.exit(1);
20
+ }
21
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
22
+ const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
23
+ for (const signal of signals) process.on(signal, () => child.kill(signal));
24
+ child.on('error', error => {
25
+ console.error(`stack: cannot start ${platform} binary: ${error.message}`);
26
+ process.exitCode = 1;
27
+ });
28
+ child.on('exit', (code, signal) => {
29
+ if (signal) {
30
+ process.removeAllListeners(signal);
31
+ process.kill(process.pid, signal);
32
+ } else process.exitCode = code ?? 1;
33
+ });
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,10 @@
1
+ {
2
+ "commit": "9fb4def5ec41cfeab7f876730c2f3f298e51f72b",
3
+ "version": "0.1.0",
4
+ "hashes": {
5
+ "linux-x64": "7fb9a80ab9f5b666c1f55694496ae9a814807f4f76264747da5b8ca325b0db5f",
6
+ "linux-arm64": "97553cc8008ef4bf40907903c5f881d580ab8a94d031c6296596bf99287b2edd",
7
+ "darwin-x64": "9922cd11a03a5fd7aef39a01864e2962cebce4ef1366763d1a54b35728f70914",
8
+ "darwin-arm64": "367c9fbac21523993a6ead949d8a22d070e40d31298ad93b1c49be722f1decd4"
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@ushawarma/stack",
3
+ "version": "0.1.0",
4
+ "description": "Reusable, agent-safe development stacks",
5
+ "repository": { "type": "git", "url": "git+https://github.com/usharma123/stack.git" },
6
+ "homepage": "https://github.com/usharma123/stack#readme",
7
+ "bin": { "stack": "bin/stack.cjs" },
8
+ "files": ["bin/stack.cjs", "binaries/", "build-info.json", "README.md"],
9
+ "engines": { "node": ">=22.14" },
10
+ "os": ["darwin", "linux"],
11
+ "cpu": ["x64", "arm64"],
12
+ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" }
13
+ }