@slowcook-ai/cli 0.16.0-alpha.1 → 0.16.0-alpha.10

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.
Files changed (54) hide show
  1. package/README.md +10 -0
  2. package/dist/cli.js +18 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/brew/agent.d.ts.map +1 -1
  5. package/dist/commands/brew/agent.js +26 -3
  6. package/dist/commands/brew/agent.js.map +1 -1
  7. package/dist/commands/init/mock.js +14 -6
  8. package/dist/commands/init/mock.js.map +1 -1
  9. package/dist/commands/plate/classify.d.ts +65 -0
  10. package/dist/commands/plate/classify.d.ts.map +1 -0
  11. package/dist/commands/plate/classify.js +194 -0
  12. package/dist/commands/plate/classify.js.map +1 -0
  13. package/dist/commands/plate/index.d.ts.map +1 -1
  14. package/dist/commands/plate/index.js +121 -17
  15. package/dist/commands/plate/index.js.map +1 -1
  16. package/dist/commands/port/index.d.ts +21 -0
  17. package/dist/commands/port/index.d.ts.map +1 -0
  18. package/dist/commands/port/index.js +203 -0
  19. package/dist/commands/port/index.js.map +1 -0
  20. package/dist/commands/port/transform.d.ts +51 -0
  21. package/dist/commands/port/transform.d.ts.map +1 -0
  22. package/dist/commands/port/transform.js +102 -0
  23. package/dist/commands/port/transform.js.map +1 -0
  24. package/dist/commands/preview/config.d.ts +73 -0
  25. package/dist/commands/preview/config.d.ts.map +1 -0
  26. package/dist/commands/preview/config.js +200 -0
  27. package/dist/commands/preview/config.js.map +1 -0
  28. package/dist/commands/preview/deploy.d.ts +35 -0
  29. package/dist/commands/preview/deploy.d.ts.map +1 -0
  30. package/dist/commands/preview/deploy.js +247 -0
  31. package/dist/commands/preview/deploy.js.map +1 -0
  32. package/dist/commands/preview/index.d.ts +9 -0
  33. package/dist/commands/preview/index.d.ts.map +1 -0
  34. package/dist/commands/preview/index.js +67 -0
  35. package/dist/commands/preview/index.js.map +1 -0
  36. package/dist/commands/preview/ssh.d.ts +49 -0
  37. package/dist/commands/preview/ssh.d.ts.map +1 -0
  38. package/dist/commands/preview/ssh.js +99 -0
  39. package/dist/commands/preview/ssh.js.map +1 -0
  40. package/dist/commands/preview/teardown.d.ts +25 -0
  41. package/dist/commands/preview/teardown.d.ts.map +1 -0
  42. package/dist/commands/preview/teardown.js +164 -0
  43. package/dist/commands/preview/teardown.js.map +1 -0
  44. package/dist/commands/refine/proposals-synth.js +17 -1
  45. package/dist/commands/refine/proposals-synth.js.map +1 -1
  46. package/dist/commands/refine/spec-yaml.d.ts +100 -100
  47. package/dist/commands/vibe/emit.d.ts +6 -0
  48. package/dist/commands/vibe/emit.d.ts.map +1 -1
  49. package/dist/commands/vibe/emit.js +12 -0
  50. package/dist/commands/vibe/emit.js.map +1 -1
  51. package/dist/commands/vibe/index.d.ts.map +1 -1
  52. package/dist/commands/vibe/index.js +143 -44
  53. package/dist/commands/vibe/index.js.map +1 -1
  54. package/package.json +6 -5
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `slowcook preview deploy --pr <n>` — 0.16.0-α.5.
3
+ *
4
+ * Builds the consumer's mock app remotely on their SSH-reachable box,
5
+ * runs it as a Docker container, posts the preview URL to the PR.
6
+ *
7
+ * Flow:
8
+ * 1. Read `.brewing/preview.yaml` (host, user, key secret, port range,
9
+ * URL template, remote root)
10
+ * 2. tar the local mock/ directory (excluding node_modules + .next)
11
+ * 3. scp tarball to `${remote_root}/pr-N/`
12
+ * 4. ssh: extract, `docker build`, allocate port, `docker rm -f` old
13
+ * container if any, `docker run -d`
14
+ * 5. Compose URL via `url_template`; upsert PR comment
15
+ *
16
+ * The container always exposes 3100 internally (matches the mock's
17
+ * Dockerfile from `slowcook init mock`); the host port is allocated
18
+ * from `port_range` and substituted into `url_template`.
19
+ *
20
+ * Error semantics: any sub-step failure throws; the caller prints +
21
+ * exits non-zero. The workflow surfaces the failure as a check on the
22
+ * PR; consumers can re-run via workflow_dispatch.
23
+ */
24
+ interface ParsedArgs {
25
+ pr: number | undefined;
26
+ repoRoot: string;
27
+ keyPath: string | undefined;
28
+ owner: string | undefined;
29
+ repo: string | undefined;
30
+ dryRun: boolean;
31
+ }
32
+ export declare function parseDeployArgs(argv: string[]): ParsedArgs;
33
+ export declare function deploy(argv: string[], cliVersion: string): Promise<void>;
34
+ export {};
35
+ //# sourceMappingURL=deploy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/preview/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAsBH,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAoB1D;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0I9E"}
@@ -0,0 +1,247 @@
1
+ /**
2
+ * `slowcook preview deploy --pr <n>` — 0.16.0-α.5.
3
+ *
4
+ * Builds the consumer's mock app remotely on their SSH-reachable box,
5
+ * runs it as a Docker container, posts the preview URL to the PR.
6
+ *
7
+ * Flow:
8
+ * 1. Read `.brewing/preview.yaml` (host, user, key secret, port range,
9
+ * URL template, remote root)
10
+ * 2. tar the local mock/ directory (excluding node_modules + .next)
11
+ * 3. scp tarball to `${remote_root}/pr-N/`
12
+ * 4. ssh: extract, `docker build`, allocate port, `docker rm -f` old
13
+ * container if any, `docker run -d`
14
+ * 5. Compose URL via `url_template`; upsert PR comment
15
+ *
16
+ * The container always exposes 3100 internally (matches the mock's
17
+ * Dockerfile from `slowcook init mock`); the host port is allocated
18
+ * from `port_range` and substituted into `url_template`.
19
+ *
20
+ * Error semantics: any sub-step failure throws; the caller prints +
21
+ * exits non-zero. The workflow surfaces the failure as a check on the
22
+ * PR; consumers can re-run via workflow_dispatch.
23
+ */
24
+ import { execSync, spawnSync } from "node:child_process";
25
+ import { existsSync, statSync } from "node:fs";
26
+ import { join } from "node:path";
27
+ import { tmpdir } from "node:os";
28
+ import { readPreviewConfig, containerNameForPr, imageTagForPr, remoteDirForPr, urlForPort, } from "./config.js";
29
+ import { sshExec, scpUpload, pickRemotePort, } from "./ssh.js";
30
+ export function parseDeployArgs(argv) {
31
+ const args = {
32
+ pr: undefined,
33
+ repoRoot: process.cwd(),
34
+ keyPath: undefined,
35
+ owner: undefined,
36
+ repo: undefined,
37
+ dryRun: false,
38
+ };
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i];
41
+ const next = argv[i + 1];
42
+ if (a === "--pr" && next) {
43
+ args.pr = parseInt(next, 10);
44
+ i++;
45
+ }
46
+ else if (a === "--cwd" && next) {
47
+ args.repoRoot = next;
48
+ i++;
49
+ }
50
+ else if (a === "--ssh-key" && next) {
51
+ args.keyPath = next;
52
+ i++;
53
+ }
54
+ else if (a === "--owner" && next) {
55
+ args.owner = next;
56
+ i++;
57
+ }
58
+ else if (a === "--repo" && next) {
59
+ args.repo = next;
60
+ i++;
61
+ }
62
+ else if (a === "--dry-run") {
63
+ args.dryRun = true;
64
+ }
65
+ }
66
+ return args;
67
+ }
68
+ export async function deploy(argv, cliVersion) {
69
+ const parsed = parseDeployArgs(argv);
70
+ if (!parsed.pr || Number.isNaN(parsed.pr)) {
71
+ console.error("--pr <number> is required.");
72
+ process.exit(64);
73
+ }
74
+ const cfg = readPreviewConfig(parsed.repoRoot);
75
+ const mockPath = join(parsed.repoRoot, cfg.mockDir);
76
+ if (!existsSync(mockPath) || !statSync(mockPath).isDirectory()) {
77
+ console.error(`Mock directory ${cfg.mockDir} not found at ${mockPath}. Run \`slowcook init mock\` first.`);
78
+ process.exit(2);
79
+ }
80
+ if (parsed.dryRun) {
81
+ console.log(`slowcook preview deploy · pr ${parsed.pr} (dry-run)`);
82
+ console.log(` config: host=${cfg.host} user=${cfg.user} port=${cfg.port}`);
83
+ console.log(` remote: ${remoteDirForPr(cfg, parsed.pr)}`);
84
+ console.log(` container: ${containerNameForPr(parsed.pr)}`);
85
+ console.log(` image: ${imageTagForPr(parsed.pr)}`);
86
+ console.log(` url_template: ${cfg.urlTemplate}`);
87
+ return;
88
+ }
89
+ const keyPath = parsed.keyPath ?? process.env["SLOWCOOK_PREVIEW_SSH_KEY_PATH"];
90
+ if (!keyPath) {
91
+ console.error("SSH key path not provided. Pass --ssh-key <path> or set SLOWCOOK_PREVIEW_SSH_KEY_PATH.");
92
+ process.exit(2);
93
+ }
94
+ const target = {
95
+ host: cfg.host,
96
+ user: cfg.user,
97
+ port: cfg.port,
98
+ keyPath,
99
+ };
100
+ console.log(`slowcook preview deploy · pr ${parsed.pr} → ${cfg.user}@${cfg.host}:${cfg.port}`);
101
+ // Step 1: tar the mock directory locally. Exclude node_modules, .next,
102
+ // .turbo (we'll rebuild on the box). Keeps the tarball small + the
103
+ // remote build deterministic.
104
+ const tarball = join(tmpdir(), `slowcook-mock-pr-${parsed.pr}.tgz`);
105
+ console.log(` tar → ${tarball}`);
106
+ const tarResult = spawnSync("tar", [
107
+ "czf", tarball,
108
+ "-C", parsed.repoRoot,
109
+ "--exclude=node_modules",
110
+ "--exclude=.next",
111
+ "--exclude=.turbo",
112
+ "--exclude=out",
113
+ cfg.mockDir,
114
+ ], { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" });
115
+ if (tarResult.status !== 0) {
116
+ throw new Error(`tar failed (status ${tarResult.status}): ${tarResult.stderr}`);
117
+ }
118
+ const remoteDir = remoteDirForPr(cfg, parsed.pr);
119
+ const remoteTarball = `${remoteDir}/mock-src.tgz`;
120
+ const containerName = containerNameForPr(parsed.pr);
121
+ const imageTag = imageTagForPr(parsed.pr);
122
+ // Step 2: ensure remote dir exists, scp tarball.
123
+ console.log(` ssh mkdir -p ${remoteDir}`);
124
+ sshExec(target, `mkdir -p ${shellQuote(remoteDir)}`);
125
+ console.log(` scp → ${cfg.host}:${remoteTarball}`);
126
+ scpUpload(target, tarball, remoteTarball);
127
+ // Step 3: extract + build remotely.
128
+ // 0.16.0-α.6 — pass the review-overlay env vars as build args so the
129
+ // mock's compile-time `process.env.NEXT_PUBLIC_SLOWCOOK_*` references
130
+ // resolve to the right values (Next inlines NEXT_PUBLIC_* at build time).
131
+ const owner = parsed.owner ?? detectOwner(parsed.repoRoot) ?? "";
132
+ const repo = parsed.repo ?? detectRepo(parsed.repoRoot) ?? "";
133
+ console.log(` ssh docker build ${imageTag}`);
134
+ const buildEnv = [
135
+ `NEXT_PUBLIC_SLOWCOOK_REVIEW=1`,
136
+ `NEXT_PUBLIC_SLOWCOOK_OWNER=${shellQuote(owner)}`,
137
+ `NEXT_PUBLIC_SLOWCOOK_REPO=${shellQuote(repo)}`,
138
+ `NEXT_PUBLIC_SLOWCOOK_PR_NUMBER=${parsed.pr}`,
139
+ ].join(" ");
140
+ const buildScript = [
141
+ `cd ${shellQuote(remoteDir)}`,
142
+ `rm -rf ${shellQuote(cfg.mockDir)}`,
143
+ `tar xzf mock-src.tgz`,
144
+ `cd ${shellQuote(cfg.mockDir)}`,
145
+ // Write the env vars to .env.production so `next build` picks them up.
146
+ // (Build args via --build-arg would also work but require Dockerfile
147
+ // edits in the consumer's repo; .env.production keeps the contract
148
+ // backward-compatible with the init-mock template.)
149
+ `printf '%s\\n' ${shellQuote(buildEnv.split(" ").join("\\n"))} > .env.production`,
150
+ `docker build -t ${shellQuote(imageTag)} -f Dockerfile ..`,
151
+ ].join(" && ");
152
+ sshExec(target, buildScript);
153
+ // Step 4: stop existing container (idempotent), allocate port, run.
154
+ console.log(` ssh docker rm -f ${containerName} (if present)`);
155
+ sshExec(target, `docker rm -f ${shellQuote(containerName)} 2>/dev/null || true`);
156
+ console.log(` ssh pick free port in ${cfg.portRange[0]}..${cfg.portRange[1]}`);
157
+ const hostPort = pickRemotePort(target, cfg.portRange[0], cfg.portRange[1]);
158
+ console.log(` → ${hostPort}`);
159
+ console.log(` ssh docker run -d --name ${containerName} -p ${hostPort}:3100`);
160
+ sshExec(target, `docker run -d --name ${shellQuote(containerName)} ` +
161
+ `--restart unless-stopped ` +
162
+ `--label slowcook.pr=${parsed.pr} ` +
163
+ `-p ${hostPort}:3100 ${shellQuote(imageTag)}`);
164
+ // Step 5: compose URL + upsert PR comment.
165
+ const url = urlForPort(cfg, hostPort);
166
+ console.log(` url ${url}`);
167
+ const githubToken = process.env["GITHUB_TOKEN"];
168
+ if (githubToken && owner && repo) {
169
+ await upsertPreviewComment({
170
+ owner, repo, pr: parsed.pr, url, hostPort, cliVersion, githubToken,
171
+ });
172
+ }
173
+ else {
174
+ console.log(` (skipped PR comment: no GITHUB_TOKEN or unknown owner/repo. URL: ${url})`);
175
+ }
176
+ console.log(`Done. Preview ready at ${url}`);
177
+ }
178
+ const PREVIEW_COMMENT_MARKER = "<!-- slowcook-preview-deploy -->";
179
+ async function upsertPreviewComment(args) {
180
+ // Use Octokit transitively via dynamic import to avoid pulling it
181
+ // for the dry-run path.
182
+ const { Octokit } = await import("@octokit/rest");
183
+ const octokit = new Octokit({ auth: args.githubToken });
184
+ const body = [
185
+ PREVIEW_COMMENT_MARKER,
186
+ `## 🍳 Mockup preview ready`,
187
+ ``,
188
+ `**Live URL:** ${args.url}`,
189
+ ``,
190
+ `Open the scenario picker; click any \`?scenario=story-N\` link to deep-link into a specific story's mock.`,
191
+ ``,
192
+ `_Deployed by \`slowcook preview deploy@${args.cliVersion}\` on host port ${args.hostPort}. Updated each time the mockup branch changes._`,
193
+ ].join("\n");
194
+ const list = await octokit.rest.issues.listComments({
195
+ owner: args.owner,
196
+ repo: args.repo,
197
+ issue_number: args.pr,
198
+ per_page: 100,
199
+ });
200
+ const existing = list.data.find((c) => c.body?.includes(PREVIEW_COMMENT_MARKER));
201
+ if (existing) {
202
+ await octokit.rest.issues.updateComment({
203
+ owner: args.owner,
204
+ repo: args.repo,
205
+ comment_id: existing.id,
206
+ body,
207
+ });
208
+ console.log(` pr comment ${existing.id} updated`);
209
+ }
210
+ else {
211
+ const created = await octokit.rest.issues.createComment({
212
+ owner: args.owner,
213
+ repo: args.repo,
214
+ issue_number: args.pr,
215
+ body,
216
+ });
217
+ console.log(` pr comment ${created.data.id} created`);
218
+ }
219
+ }
220
+ function detectOwner(repoRoot) {
221
+ return detectOwnerRepo(repoRoot)?.owner;
222
+ }
223
+ function detectRepo(repoRoot) {
224
+ return detectOwnerRepo(repoRoot)?.repo;
225
+ }
226
+ function detectOwnerRepo(repoRoot) {
227
+ try {
228
+ const url = execSync("git remote get-url origin", {
229
+ cwd: repoRoot,
230
+ encoding: "utf8",
231
+ stdio: ["ignore", "pipe", "ignore"],
232
+ }).trim();
233
+ const m = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
234
+ if (m && m[1] && m[2])
235
+ return { owner: m[1], repo: m[2] };
236
+ }
237
+ catch {
238
+ // not a git repo
239
+ }
240
+ return undefined;
241
+ }
242
+ function shellQuote(s) {
243
+ // Minimal quoting for ssh-side bash. Single-quote everything; escape
244
+ // existing single-quotes via the close-quote-then-escape pattern.
245
+ return `'${s.replace(/'/g, "'\\''")}'`;
246
+ }
247
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../src/commands/preview/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `slowcook preview <subcommand>` dispatch — 0.16.0-α.5.
3
+ *
4
+ * Subcommands:
5
+ * - `slowcook preview deploy --pr <n>` — build + run + post URL
6
+ * - `slowcook preview teardown --pr <n>` — stop + remove + mark
7
+ */
8
+ export declare function preview(argv: string[], cliVersion: string): Promise<void>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/preview/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB/E"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `slowcook preview <subcommand>` dispatch — 0.16.0-α.5.
3
+ *
4
+ * Subcommands:
5
+ * - `slowcook preview deploy --pr <n>` — build + run + post URL
6
+ * - `slowcook preview teardown --pr <n>` — stop + remove + mark
7
+ */
8
+ import { deploy } from "./deploy.js";
9
+ import { teardown } from "./teardown.js";
10
+ export async function preview(argv, cliVersion) {
11
+ const sub = argv[0];
12
+ switch (sub) {
13
+ case "deploy":
14
+ await deploy(argv.slice(1), cliVersion);
15
+ return;
16
+ case "teardown":
17
+ await teardown(argv.slice(1), cliVersion);
18
+ return;
19
+ case undefined:
20
+ case "help":
21
+ case "--help":
22
+ case "-h":
23
+ printHelp();
24
+ return;
25
+ default:
26
+ console.error(`Unknown preview subcommand: ${sub}`);
27
+ printHelp();
28
+ process.exit(64);
29
+ }
30
+ }
31
+ function printHelp() {
32
+ console.log(`
33
+ slowcook preview — SSH preview deploy for the consumer's mock app (0.16-α.5)
34
+
35
+ Usage:
36
+ slowcook preview deploy --pr <n> [--ssh-key <path>] [--cwd <path>] [--owner <login>] [--repo <name>] [--dry-run]
37
+ slowcook preview teardown --pr <n> [--ssh-key <path>] [--cwd <path>] [--owner <login>] [--repo <name>] [--prune-image] [--dry-run]
38
+
39
+ Reads .brewing/preview.yaml for SSH host / user / port range / URL
40
+ template / remote root. See docs/operating-guide.md for the schema +
41
+ box setup steps.
42
+
43
+ Common options:
44
+ --pr <n> PR number to deploy/teardown for. REQUIRED.
45
+ --ssh-key <path> Path to the SSH private key file. Defaults to
46
+ SLOWCOOK_PREVIEW_SSH_KEY_PATH.
47
+ --cwd <path> Repo root (default: cwd).
48
+ --owner <login> GitHub owner (default: detect from git remote).
49
+ --repo <name> GitHub repo (default: detect from git remote).
50
+ --dry-run Print planned actions, don't ssh/scp/docker.
51
+
52
+ deploy-only:
53
+ (none)
54
+
55
+ teardown-only:
56
+ --prune-image Also \`docker rmi\` the per-PR image (frees disk).
57
+
58
+ Environment:
59
+ GITHUB_TOKEN For PR-comment upsert (otherwise skipped).
60
+ SLOWCOOK_PREVIEW_SSH_KEY_PATH Default for --ssh-key.
61
+
62
+ Workflow templates installed by \`slowcook init\`:
63
+ .github/workflows/slowcook-preview-deploy.yml fires on PR opened/synchronized with the slowcook-mockup label
64
+ .github/workflows/slowcook-preview-teardown.yml fires on PR closed
65
+ `);
66
+ }
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/preview/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc,EAAE,UAAkB;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,QAAQ;YACX,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;YACxC,OAAO;QACT,KAAK,UAAU;YACb,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;YAC1C,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,SAAS,EAAE,CAAC;YACZ,OAAO;QACT;YACE,OAAO,CAAC,KAAK,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC;YACpD,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCb,CAAC,CAAC;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Thin shell wrappers around `ssh` + `scp` so deploy/teardown can talk
3
+ * to the consumer's box without pulling in a JS ssh library.
4
+ *
5
+ * Why shell out: ops people debug `ssh` + `scp` invocations daily; if a
6
+ * deploy fails, the failing command is something they can copy + paste.
7
+ * Pulling in a JS library for marginal API ergonomics adds a maintenance
8
+ * surface for a feature that's already box-specific.
9
+ *
10
+ * All wrappers use BatchMode=yes (no interactive password prompts) and
11
+ * StrictHostKeyChecking=accept-new (first connect adds the key, future
12
+ * connects verify against it). Failures throw with stderr captured so
13
+ * the workflow logs surface the underlying error.
14
+ */
15
+ export interface SshTarget {
16
+ host: string;
17
+ user: string;
18
+ port: number;
19
+ /** Absolute path to the private key file on the runner's filesystem. */
20
+ keyPath: string;
21
+ }
22
+ export interface SshResult {
23
+ stdout: string;
24
+ stderr: string;
25
+ }
26
+ /**
27
+ * Run a remote command via ssh. Throws on non-zero exit with the
28
+ * captured stderr inlined into the error message.
29
+ */
30
+ export declare function sshExec(target: SshTarget, command: string): SshResult;
31
+ /**
32
+ * scp a local file to a remote path. Throws on non-zero exit.
33
+ */
34
+ export declare function scpUpload(target: SshTarget, localPath: string, remotePath: string): void;
35
+ /**
36
+ * Find the first free TCP port in [lo, hi] on the remote box. Uses
37
+ * `ss -ltn` (universally available on modern Linux) to enumerate
38
+ * already-bound ports.
39
+ *
40
+ * Returns the chosen port. Throws if every port in the range is in use.
41
+ */
42
+ export declare function pickRemotePort(target: SshTarget, lo: number, hi: number): number;
43
+ /**
44
+ * Get the host port a running container is publishing to (the value
45
+ * after `0.0.0.0:` in `docker port` output). Returns null if the
46
+ * container isn't running or doesn't publish 3100.
47
+ */
48
+ export declare function getContainerPort(target: SshTarget, containerName: string): number | null;
49
+ //# sourceMappingURL=ssh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../../../src/commands/preview/ssh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAkBD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAcrE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAsBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAehF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKxF"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Thin shell wrappers around `ssh` + `scp` so deploy/teardown can talk
3
+ * to the consumer's box without pulling in a JS ssh library.
4
+ *
5
+ * Why shell out: ops people debug `ssh` + `scp` invocations daily; if a
6
+ * deploy fails, the failing command is something they can copy + paste.
7
+ * Pulling in a JS library for marginal API ergonomics adds a maintenance
8
+ * surface for a feature that's already box-specific.
9
+ *
10
+ * All wrappers use BatchMode=yes (no interactive password prompts) and
11
+ * StrictHostKeyChecking=accept-new (first connect adds the key, future
12
+ * connects verify against it). Failures throw with stderr captured so
13
+ * the workflow logs surface the underlying error.
14
+ */
15
+ import { spawnSync } from "node:child_process";
16
+ /**
17
+ * Standard ssh options: non-interactive, accept-new host keys, dedicated
18
+ * key file. Returns the array form spawnSync wants.
19
+ */
20
+ function sshArgs(target, extra) {
21
+ return [
22
+ "-i", target.keyPath,
23
+ "-p", String(target.port),
24
+ "-o", "BatchMode=yes",
25
+ "-o", "StrictHostKeyChecking=accept-new",
26
+ "-o", "ConnectTimeout=15",
27
+ `${target.user}@${target.host}`,
28
+ ...extra,
29
+ ];
30
+ }
31
+ /**
32
+ * Run a remote command via ssh. Throws on non-zero exit with the
33
+ * captured stderr inlined into the error message.
34
+ */
35
+ export function sshExec(target, command) {
36
+ const result = spawnSync("ssh", sshArgs(target, [command]), {
37
+ encoding: "utf8",
38
+ stdio: ["ignore", "pipe", "pipe"],
39
+ });
40
+ if (result.error) {
41
+ throw new Error(`ssh spawn failed: ${result.error.message}`);
42
+ }
43
+ if (result.status !== 0) {
44
+ throw new Error(`ssh exited ${result.status} for command:\n ${command}\nstderr:\n${(result.stderr || "").trim()}`);
45
+ }
46
+ return { stdout: result.stdout || "", stderr: result.stderr || "" };
47
+ }
48
+ /**
49
+ * scp a local file to a remote path. Throws on non-zero exit.
50
+ */
51
+ export function scpUpload(target, localPath, remotePath) {
52
+ const result = spawnSync("scp", [
53
+ "-i", target.keyPath,
54
+ "-P", String(target.port),
55
+ "-o", "BatchMode=yes",
56
+ "-o", "StrictHostKeyChecking=accept-new",
57
+ "-o", "ConnectTimeout=15",
58
+ localPath,
59
+ `${target.user}@${target.host}:${remotePath}`,
60
+ ], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
61
+ if (result.error) {
62
+ throw new Error(`scp spawn failed: ${result.error.message}`);
63
+ }
64
+ if (result.status !== 0) {
65
+ throw new Error(`scp exited ${result.status} uploading ${localPath} → ${target.user}@${target.host}:${remotePath}\nstderr:\n${(result.stderr || "").trim()}`);
66
+ }
67
+ }
68
+ /**
69
+ * Find the first free TCP port in [lo, hi] on the remote box. Uses
70
+ * `ss -ltn` (universally available on modern Linux) to enumerate
71
+ * already-bound ports.
72
+ *
73
+ * Returns the chosen port. Throws if every port in the range is in use.
74
+ */
75
+ export function pickRemotePort(target, lo, hi) {
76
+ const cmd = `ss -ltn 'sport = :0' 2>/dev/null | awk 'NR>1 {split($4,a,":"); print a[length(a)]}' | sort -un`;
77
+ const { stdout } = sshExec(target, cmd);
78
+ const used = new Set(stdout
79
+ .split("\n")
80
+ .map((s) => parseInt(s.trim(), 10))
81
+ .filter((n) => !Number.isNaN(n)));
82
+ for (let p = lo; p <= hi; p++) {
83
+ if (!used.has(p))
84
+ return p;
85
+ }
86
+ throw new Error(`No free port in range ${lo}..${hi} on ${target.host}. Increase port_range or teardown stale containers.`);
87
+ }
88
+ /**
89
+ * Get the host port a running container is publishing to (the value
90
+ * after `0.0.0.0:` in `docker port` output). Returns null if the
91
+ * container isn't running or doesn't publish 3100.
92
+ */
93
+ export function getContainerPort(target, containerName) {
94
+ const cmd = `docker port ${containerName} 3100/tcp 2>/dev/null || true`;
95
+ const { stdout } = sshExec(target, cmd);
96
+ const m = stdout.trim().match(/:(\d+)\s*$/m);
97
+ return m ? parseInt(m[1], 10) : null;
98
+ }
99
+ //# sourceMappingURL=ssh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../../src/commands/preview/ssh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAe/C;;;GAGG;AACH,SAAS,OAAO,CAAC,MAAiB,EAAE,KAAe;IACjD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,OAAO;QACpB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACzB,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,kCAAkC;QACxC,IAAI,EAAE,mBAAmB;QACzB,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE;QAC/B,GAAG,KAAK;KACT,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAAiB,EAAE,OAAe;IACxD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE;QAC1D,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;KAClC,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,cAAc,MAAM,CAAC,MAAM,oBAAoB,OAAO,cAAc,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CACnG,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;AACtE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,MAAiB,EAAE,SAAiB,EAAE,UAAkB;IAChF,MAAM,MAAM,GAAG,SAAS,CACtB,KAAK,EACL;QACE,IAAI,EAAE,MAAM,CAAC,OAAO;QACpB,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACzB,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,kCAAkC;QACxC,IAAI,EAAE,mBAAmB;QACzB,SAAS;QACT,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,UAAU,EAAE;KAC9C,EACD,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CACxD,CAAC;IACF,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,cAAc,MAAM,CAAC,MAAM,cAAc,SAAS,MAAM,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,UAAU,cAAc,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAC7I,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAiB,EAAE,EAAU,EAAE,EAAU;IACtE,MAAM,GAAG,GAAG,gGAAgG,CAAC;IAC7G,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,MAAM;SACH,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CACnC,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,IAAI,KAAK,CACb,yBAAyB,EAAE,KAAK,EAAE,OAAO,MAAM,CAAC,IAAI,qDAAqD,CAC1G,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,aAAqB;IACvE,MAAM,GAAG,GAAG,eAAe,aAAa,+BAA+B,CAAC;IACxE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC7C,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `slowcook preview teardown --pr <n>` — 0.16.0-α.5.
3
+ *
4
+ * Removes the running container + cleans up the staging directory for
5
+ * a given PR. Idempotent — running it twice is fine. Updates the PR
6
+ * comment to reflect the teardown.
7
+ *
8
+ * Triggered by the slowcook-preview-teardown.yml workflow on
9
+ * `pull_request: closed`. Safe to also run manually via
10
+ * workflow_dispatch when a stale container needs purging.
11
+ */
12
+ interface ParsedArgs {
13
+ pr: number | undefined;
14
+ repoRoot: string;
15
+ keyPath: string | undefined;
16
+ owner: string | undefined;
17
+ repo: string | undefined;
18
+ /** Also remove the docker image (frees disk on the box). Default: keep image for fast redeploy. */
19
+ pruneImage: boolean;
20
+ dryRun: boolean;
21
+ }
22
+ export declare function parseTeardownArgs(argv: string[]): ParsedArgs;
23
+ export declare function teardown(argv: string[], cliVersion: string): Promise<void>;
24
+ export {};
25
+ //# sourceMappingURL=teardown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teardown.d.ts","sourceRoot":"","sources":["../../../src/commands/preview/teardown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAsB5D;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DhF"}