@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,203 @@
1
+ /**
2
+ * `slowcook port` — 0.16.0-α.8.
3
+ *
4
+ * Deterministic copy step from mock/ → src/. No LLM. Same input →
5
+ * same output. Runs as a CI step before brew on the brew PR's branch.
6
+ *
7
+ * Walks `mock/src/components/` + `mock/src/app/`, copies each file to
8
+ * the mirrored src/ path, applying small import + hook rewrites so the
9
+ * production component reads via `@/lib/data#useDataDomain` instead of
10
+ * the mock-runtime hook. Brew (α.9) writes the actual data layer.
11
+ *
12
+ * Excluded from the copy: `mock/scenarios/`, `mock/src/lib/scenario-
13
+ * registry.ts`, anything mock-only infrastructure.
14
+ *
15
+ * What "deterministic" buys us: the diff is auditable in the PR, brew
16
+ * has a clear scope (anything outside src/lib/data + src/app/api +
17
+ * supabase/migrations is off-limits), and a future tooling pass could
18
+ * re-run port to detect drift.
19
+ */
20
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, readdirSync } from "node:fs";
21
+ import { dirname, join, relative } from "node:path";
22
+ import { transformForPort, mockPathToSrcPath } from "./transform.js";
23
+ function parseArgs(argv) {
24
+ const args = {
25
+ story: "",
26
+ repoRoot: process.cwd(),
27
+ dryRun: false,
28
+ force: false,
29
+ };
30
+ for (let i = 0; i < argv.length; i++) {
31
+ const a = argv[i];
32
+ const next = argv[i + 1];
33
+ if (a === "--story" && next) {
34
+ args.story = next;
35
+ i++;
36
+ }
37
+ else if (a === "--cwd" && next) {
38
+ args.repoRoot = next;
39
+ i++;
40
+ }
41
+ else if (a === "--dry-run") {
42
+ args.dryRun = true;
43
+ }
44
+ else if (a === "--force") {
45
+ args.force = true;
46
+ }
47
+ else if (a === "--help" || a === "-h") {
48
+ printHelp();
49
+ process.exit(0);
50
+ }
51
+ }
52
+ if (!args.story) {
53
+ console.error("--story <id> is required.");
54
+ printHelp();
55
+ process.exit(64);
56
+ }
57
+ return args;
58
+ }
59
+ function printHelp() {
60
+ console.log(`
61
+ slowcook port — deterministic mock → src copy (0.16-α.8)
62
+
63
+ Walks mock/src/components/ + mock/src/app/, copies each file to the
64
+ mirrored src/ path, and applies small import + hook rewrites so the
65
+ production component reads from @/lib/data#useDataDomain instead of
66
+ the mock-runtime hook. No LLM; same input → same output.
67
+
68
+ Usage:
69
+ slowcook port --story <id> [--cwd <path>] [--dry-run] [--force]
70
+
71
+ Options:
72
+ --story <id> Story id (e.g. 017). Used in the port-provenance header.
73
+ --cwd <path> Repo root (default: cwd).
74
+ --dry-run Print planned actions; don't write.
75
+ --force Overwrite existing src/ files even if they don't
76
+ carry the @slowcook-port-from marker. Default: refuse
77
+ so a hand-edited file isn't accidentally clobbered.
78
+
79
+ What's NOT ported:
80
+ mock/scenarios/ (scenario fixtures — mock-only)
81
+ mock/src/lib/scenario-registry.ts (consumer-managed)
82
+ mock/Dockerfile, mock/package.json (mock-app shell — mock-only)
83
+
84
+ What's transformed:
85
+ import { useScenarioFixture } from "@slowcook-ai/mock-runtime";
86
+ → import { useDataDomain } from "@/lib/data";
87
+ useScenarioFixture<T>("domain")
88
+ → useDataDomain<T>("domain")
89
+ // @slowcook-mock-only lines stripped
90
+ port-provenance header prepended (// @slowcook-port-from mock/ (story-N))
91
+
92
+ Exit codes:
93
+ 0 success (or dry-run completed; or no files to port)
94
+ 2 refusing to overwrite a non-port file (use --force)
95
+ `);
96
+ }
97
+ export async function port(argv, _cliVersion) {
98
+ const args = parseArgs(argv);
99
+ const mockSrcDir = join(args.repoRoot, "mock/src");
100
+ if (!existsSync(mockSrcDir)) {
101
+ console.log(`No mock/src/ directory at ${mockSrcDir}. Run \`slowcook init mock\` first; nothing to port.`);
102
+ return;
103
+ }
104
+ const files = walkFiles(mockSrcDir);
105
+ const actions = [];
106
+ for (const absPath of files) {
107
+ const relMockPath = relative(args.repoRoot, absPath).replace(/\\/g, "/");
108
+ const destRel = mockPathToSrcPath(relMockPath);
109
+ if (!destRel)
110
+ continue;
111
+ const inputBody = readFileSync(absPath, "utf8");
112
+ const { output, rewrites } = transformForPort(inputBody, { storyId: args.story });
113
+ void output; // populated below in writePhase
114
+ actions.push(buildPlanned(args.repoRoot, relMockPath, destRel, inputBody, rewrites, args.force, args.story));
115
+ }
116
+ if (actions.length === 0) {
117
+ console.log("Nothing to port (no eligible mock/src/ files).");
118
+ return;
119
+ }
120
+ console.log(`slowcook port · story-${args.story} · cwd: ${relative(process.cwd(), args.repoRoot) || "."}`);
121
+ for (const a of actions)
122
+ printAction(a);
123
+ if (args.dryRun) {
124
+ console.log("\n--dry-run: no files written.");
125
+ return;
126
+ }
127
+ // 0.16.0-α.8 — refuse if any action is blocked. Better to surface
128
+ // the conflict than overwrite a hand-edited production file.
129
+ const blocked = actions.filter((a) => a.kind === "blocked");
130
+ if (blocked.length > 0) {
131
+ console.error(`\nRefusing to port: ${blocked.length} file(s) at the destination ` +
132
+ `path don't carry the @slowcook-port-from marker. Pass --force to overwrite, ` +
133
+ `or hand-merge first.`);
134
+ process.exit(2);
135
+ }
136
+ let written = 0;
137
+ let unchanged = 0;
138
+ for (const a of actions) {
139
+ if (a.kind === "no-op") {
140
+ unchanged += 1;
141
+ continue;
142
+ }
143
+ const inputBody = readFileSync(join(args.repoRoot, a.src), "utf8");
144
+ const { output } = transformForPort(inputBody, { storyId: args.story });
145
+ const destAbs = join(args.repoRoot, a.dest);
146
+ mkdirSync(dirname(destAbs), { recursive: true });
147
+ writeFileSync(destAbs, output, "utf8");
148
+ written += 1;
149
+ }
150
+ console.log(`\nDone. ${written} file(s) written; ${unchanged} unchanged.`);
151
+ }
152
+ function buildPlanned(repoRoot, src, dest, inputBody, rewrites, force, story) {
153
+ const destAbs = join(repoRoot, dest);
154
+ const { output } = transformForPort(inputBody, { storyId: story });
155
+ const destExists = existsSync(destAbs);
156
+ if (!destExists) {
157
+ return { src, dest, kind: "create", rewrites };
158
+ }
159
+ const existing = readFileSync(destAbs, "utf8");
160
+ if (existing === output) {
161
+ return { src, dest, kind: "no-op", rewrites: [] };
162
+ }
163
+ // Allow overwrite when the existing file is a previously-ported file
164
+ // (carries the marker) OR --force is set.
165
+ if (existing.includes("@slowcook-port-from") || force) {
166
+ return { src, dest, kind: "update", rewrites };
167
+ }
168
+ return {
169
+ src,
170
+ dest,
171
+ kind: "blocked",
172
+ rewrites: [],
173
+ reason: "destination file exists, doesn't carry @slowcook-port-from marker, --force not set",
174
+ };
175
+ }
176
+ function printAction(a) {
177
+ const tag = a.kind === "create" ? "CREATE" :
178
+ a.kind === "update" ? "UPDATE" :
179
+ a.kind === "blocked" ? "BLOCK " :
180
+ "NO-OP ";
181
+ console.log(` ${tag} ${a.src} → ${a.dest}`);
182
+ if (a.reason)
183
+ console.log(` ${a.reason}`);
184
+ for (const r of a.rewrites)
185
+ console.log(` · ${r}`);
186
+ }
187
+ function walkFiles(dir, acc = []) {
188
+ const entries = readdirSync(dir);
189
+ for (const name of entries) {
190
+ if (name.startsWith("."))
191
+ continue;
192
+ const full = join(dir, name);
193
+ const st = statSync(full);
194
+ if (st.isDirectory()) {
195
+ walkFiles(full, acc);
196
+ }
197
+ else if (/\.(tsx?|jsx?)$/.test(name)) {
198
+ acc.push(full);
199
+ }
200
+ }
201
+ return acc;
202
+ }
203
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/port/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAWrE,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,IAAI,GAAa;QACrB,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE;QACvB,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,KAAK;KACb,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,SAAS,IAAI,IAAI,EAAE,CAAC;YAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;aACnD,IAAI,CAAC,KAAK,OAAO,IAAI,IAAI,EAAE,CAAC;YAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;aACzD,IAAI,CAAC,KAAK,WAAW,EAAE,CAAC;YAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAAC,CAAC;aAC9C,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAAC,CAAC;aAC3C,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAAC,SAAS,EAAE,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAAC,CAAC;IAC1E,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC3C,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCb,CAAC,CAAC;AACH,CAAC;AAUD,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAc,EAAE,WAAmB;IAC5D,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CACT,6BAA6B,UAAU,sDAAsD,CAC9F,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC/C,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAClF,KAAK,MAAM,CAAC,CAAC,gCAAgC;QAC7C,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/G,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;QAC9D,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,CAAC,KAAK,WAAW,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAC3G,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAExC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,OAAO;IACT,CAAC;IAED,kEAAkE;IAClE,6DAA6D;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IAC5D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CACX,uBAAuB,OAAO,CAAC,MAAM,8BAA8B;YACjE,8EAA8E;YAC9E,sBAAsB,CACzB,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAAC,SAAS,IAAI,CAAC,CAAC;YAAC,SAAS;QAAC,CAAC;QACrD,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACnE,MAAM,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5C,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC,CAAC;IACf,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,qBAAqB,SAAS,aAAa,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,YAAY,CACnB,QAAgB,EAChB,GAAW,EACX,IAAY,EACZ,SAAiB,EACjB,QAAkB,EAClB,KAAc,EACd,KAAa;IAEb,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACrC,MAAM,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC/C,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACpD,CAAC;IACD,qEAAqE;IACrE,0CAA0C;IAC1C,IAAI,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,CAAC,IAAI,KAAK,EAAE,CAAC;QACtD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACjD,CAAC;IACD,OAAO;QACL,GAAG;QACH,IAAI;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,EAAE;QACZ,MAAM,EACJ,oFAAoF;KACvF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,CAAgB;IACnC,MAAM,GAAG,GACP,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;gBACjC,QAAQ,CAAC;IACX,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,SAAS,CAAC,GAAW,EAAE,MAAgB,EAAE;IAChD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;YACrB,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvB,CAAC;aAAM,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Port-time source transforms — 0.16.0-α.8.
3
+ *
4
+ * `slowcook port` is the deterministic mock → src copy step. It walks
5
+ * mock/src/components/ + mock/src/app/, copies each file to the
6
+ * mirrored src/ path, and applies a small set of import + hook
7
+ * rewrites so the production component reads its data from the real
8
+ * Supabase layer instead of the mock-runtime scenarios.
9
+ *
10
+ * The transform is intentionally narrow:
11
+ *
12
+ * import { useScenarioFixture } from "@slowcook-ai/mock-runtime";
13
+ * ↓
14
+ * import { useDataDomain } from "@/lib/data";
15
+ *
16
+ * const x = useScenarioFixture<T>("pins");
17
+ * ↓
18
+ * const x = useDataDomain<T>("pins");
19
+ *
20
+ * The new `@/lib/data#useDataDomain` is brew's territory (α.9 writes
21
+ * the real implementation against Supabase). At port-time, all that
22
+ * exists in src/lib/data/ may be a stub `useDataDomain` — brew fills
23
+ * the body. That keeps tests red until brew's done.
24
+ *
25
+ * Pure functions over strings. No fs access. Same input → same output.
26
+ */
27
+ export interface PortTransformResult {
28
+ /** New file body. */
29
+ output: string;
30
+ /** Each rewrite that fired (for the audit-trail commit message). */
31
+ rewrites: string[];
32
+ }
33
+ /**
34
+ * Apply all port-time source transforms to a single file's contents.
35
+ * Returns the transformed body + the list of rewrites that fired.
36
+ */
37
+ export declare function transformForPort(input: string, opts?: {
38
+ storyId?: string;
39
+ }): PortTransformResult;
40
+ /**
41
+ * Translate a `mock/<rest>` path into the corresponding `src/<rest>`
42
+ * path. Used by the file walker.
43
+ *
44
+ * Examples:
45
+ * mock/src/components/rewo/RewoCard.tsx → src/components/rewo/RewoCard.tsx
46
+ * mock/src/app/(main)/u/[handle]/page.tsx → src/app/(main)/u/[handle]/page.tsx
47
+ * mock/src/lib/scenario-registry.ts → null (mock-only; never ports)
48
+ * mock/scenarios/story-017.ts → null (mock-only; never ports)
49
+ */
50
+ export declare function mockPathToSrcPath(mockPath: string): string | null;
51
+ //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/commands/port/transform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAOD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,mBAAmB,CAqDhG;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAajE"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Port-time source transforms — 0.16.0-α.8.
3
+ *
4
+ * `slowcook port` is the deterministic mock → src copy step. It walks
5
+ * mock/src/components/ + mock/src/app/, copies each file to the
6
+ * mirrored src/ path, and applies a small set of import + hook
7
+ * rewrites so the production component reads its data from the real
8
+ * Supabase layer instead of the mock-runtime scenarios.
9
+ *
10
+ * The transform is intentionally narrow:
11
+ *
12
+ * import { useScenarioFixture } from "@slowcook-ai/mock-runtime";
13
+ * ↓
14
+ * import { useDataDomain } from "@/lib/data";
15
+ *
16
+ * const x = useScenarioFixture<T>("pins");
17
+ * ↓
18
+ * const x = useDataDomain<T>("pins");
19
+ *
20
+ * The new `@/lib/data#useDataDomain` is brew's territory (α.9 writes
21
+ * the real implementation against Supabase). At port-time, all that
22
+ * exists in src/lib/data/ may be a stub `useDataDomain` — brew fills
23
+ * the body. That keeps tests red until brew's done.
24
+ *
25
+ * Pure functions over strings. No fs access. Same input → same output.
26
+ */
27
+ const SCENARIO_FIXTURE_IMPORT_RE = /import\s+\{\s*useScenarioFixture\s*(?:,\s*([^}]*))?\}\s+from\s+["']@slowcook-ai\/mock-runtime["'];?\s*\n/g;
28
+ const SCENARIO_USE_RE = /\buseScenarioFixture\b/g;
29
+ /**
30
+ * Apply all port-time source transforms to a single file's contents.
31
+ * Returns the transformed body + the list of rewrites that fired.
32
+ */
33
+ export function transformForPort(input, opts) {
34
+ let output = input;
35
+ const rewrites = [];
36
+ // 1) Rewrite mock-runtime imports → src/lib/data import.
37
+ if (SCENARIO_FIXTURE_IMPORT_RE.test(output)) {
38
+ SCENARIO_FIXTURE_IMPORT_RE.lastIndex = 0;
39
+ output = output.replace(SCENARIO_FIXTURE_IMPORT_RE, (_m, otherImports) => {
40
+ if (otherImports && otherImports.trim()) {
41
+ // Other named imports stayed too — preserve them as a separate
42
+ // mock-runtime import line. Conservative: rare in mock components
43
+ // (most use only useScenarioFixture).
44
+ return (`import { useDataDomain } from "@/lib/data";\n` +
45
+ `import { ${otherImports.trim()} } from "@slowcook-ai/mock-runtime";\n`);
46
+ }
47
+ return `import { useDataDomain } from "@/lib/data";\n`;
48
+ });
49
+ rewrites.push("rewrote @slowcook-ai/mock-runtime → @/lib/data import");
50
+ }
51
+ // 2) Rewrite useScenarioFixture call sites → useDataDomain.
52
+ if (SCENARIO_USE_RE.test(output)) {
53
+ SCENARIO_USE_RE.lastIndex = 0;
54
+ output = output.replace(SCENARIO_USE_RE, "useDataDomain");
55
+ rewrites.push("rewrote useScenarioFixture(...) → useDataDomain(...)");
56
+ }
57
+ // 3) Strip the `// @slowcook-mock-only` markers so the production
58
+ // file doesn't carry mock-only annotations (vibe sometimes adds
59
+ // these to flag temporary structures).
60
+ if (/^\s*\/\/\s*@slowcook-mock-only\b.*$/m.test(output)) {
61
+ output = output.replace(/^\s*\/\/\s*@slowcook-mock-only\b.*$\n?/gm, "");
62
+ rewrites.push("stripped // @slowcook-mock-only markers");
63
+ }
64
+ // 4) Insert a port-provenance header so future readers know which
65
+ // story shipped this file. Skipped when one already exists.
66
+ if (opts?.storyId && !/@slowcook-port-from\b/.test(output)) {
67
+ const header = `// @slowcook-port-from mock/ (story-${opts.storyId})\n` +
68
+ `// Copied by \`slowcook port\` from the mock app.\n` +
69
+ `// UI shape is the design contract; brew (--mode plate) writes the\n` +
70
+ `// data layer + handlers but does NOT touch this file.\n`;
71
+ output = output.replace(/^("use client";?\s*\n)?/, (m) => (m ?? "") + header);
72
+ rewrites.push(`prepended port-provenance header (story-${opts.storyId})`);
73
+ }
74
+ return { output, rewrites };
75
+ }
76
+ /**
77
+ * Translate a `mock/<rest>` path into the corresponding `src/<rest>`
78
+ * path. Used by the file walker.
79
+ *
80
+ * Examples:
81
+ * mock/src/components/rewo/RewoCard.tsx → src/components/rewo/RewoCard.tsx
82
+ * mock/src/app/(main)/u/[handle]/page.tsx → src/app/(main)/u/[handle]/page.tsx
83
+ * mock/src/lib/scenario-registry.ts → null (mock-only; never ports)
84
+ * mock/scenarios/story-017.ts → null (mock-only; never ports)
85
+ */
86
+ export function mockPathToSrcPath(mockPath) {
87
+ if (!mockPath.startsWith("mock/"))
88
+ return null;
89
+ // Mock-only files: scenarios + scenario registry never port.
90
+ if (mockPath.startsWith("mock/scenarios/"))
91
+ return null;
92
+ if (mockPath === "mock/src/lib/scenario-registry.ts")
93
+ return null;
94
+ if (mockPath.startsWith("mock/src/lib/scenario-registry/"))
95
+ return null;
96
+ // mock/src/* → src/* ; mock/<other>/* → don't port (only src/ maps cleanly).
97
+ if (mockPath.startsWith("mock/src/")) {
98
+ return "src/" + mockPath.slice("mock/src/".length);
99
+ }
100
+ return null;
101
+ }
102
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../src/commands/port/transform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `.brewing/preview.yaml` schema + parser — 0.16.0-α.5.
3
+ *
4
+ * Lightweight config that tells `slowcook preview deploy/teardown` how
5
+ * to ssh into the consumer's box, where to put files, what port range
6
+ * to allocate from, and what URL pattern the box's reverse proxy serves.
7
+ *
8
+ * Slowcook is stateless re: hosting. Each consumer provides their own
9
+ * SSH-reachable box (Docker engine + reverse proxy with wildcard cert);
10
+ * this config tells slowcook how to reach it.
11
+ *
12
+ * Hand-parsed (no yaml dep) because the schema is small + flat. If we
13
+ * ever need anchors / multi-doc / etc, switch to the workspace's `yaml`
14
+ * package — it's already a transitive dep via cli/dependencies.
15
+ */
16
+ export interface PreviewConfig {
17
+ type: "ssh";
18
+ host: string;
19
+ user: string;
20
+ /** GitHub Actions secret NAME holding the SSH private key. */
21
+ keySecret: string;
22
+ port: number;
23
+ /** Inclusive range to allocate Docker host ports from (e.g. [4000, 4099]). */
24
+ portRange: [number, number];
25
+ /** URL template; `{port}` is substituted. e.g. https://mock-{port}.preview.example.com */
26
+ urlTemplate: string;
27
+ /** Absolute path on the box where slowcook stages PR builds. */
28
+ remoteRoot: string;
29
+ /** Path within the consumer's repo to the mock app. Default: "mock". */
30
+ mockDir: string;
31
+ }
32
+ export declare class PreviewConfigError extends Error {
33
+ constructor(message: string);
34
+ }
35
+ export declare const PREVIEW_CONFIG_PATH = ".brewing/preview.yaml";
36
+ /**
37
+ * Parse `.brewing/preview.yaml`. Returns the typed config or throws
38
+ * `PreviewConfigError` with a precise message identifying the missing
39
+ * or malformed field.
40
+ *
41
+ * Accepts either:
42
+ *
43
+ * preview:
44
+ * type: ssh
45
+ * host: ...
46
+ * ...
47
+ *
48
+ * or top-level keys (the `preview:` wrapper is optional but
49
+ * recommended; consumers will likely add a `box:` or other top-level
50
+ * sections later).
51
+ */
52
+ export declare function parsePreviewConfig(yamlText: string): PreviewConfig;
53
+ export declare function readPreviewConfig(repoRoot: string): PreviewConfig;
54
+ /**
55
+ * Build the preview URL for a given allocated port.
56
+ */
57
+ export declare function urlForPort(cfg: PreviewConfig, port: number): string;
58
+ /**
59
+ * Container name for a given PR. Single source of truth so deploy +
60
+ * teardown agree on the name.
61
+ */
62
+ export declare function containerNameForPr(pr: number): string;
63
+ /**
64
+ * Image tag for a given PR. We rebuild per-PR (each PR has its own
65
+ * scenario set), so tags don't collide.
66
+ */
67
+ export declare function imageTagForPr(pr: number): string;
68
+ /**
69
+ * Remote staging directory for a given PR. Build artifacts + the tar
70
+ * extract live here.
71
+ */
72
+ export declare function remoteDirForPr(cfg: PreviewConfig, pr: number): string;
73
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/commands/preview/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,KAAK,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAwHlE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAUjE;AAgBD;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAErE"}
@@ -0,0 +1,200 @@
1
+ /**
2
+ * `.brewing/preview.yaml` schema + parser — 0.16.0-α.5.
3
+ *
4
+ * Lightweight config that tells `slowcook preview deploy/teardown` how
5
+ * to ssh into the consumer's box, where to put files, what port range
6
+ * to allocate from, and what URL pattern the box's reverse proxy serves.
7
+ *
8
+ * Slowcook is stateless re: hosting. Each consumer provides their own
9
+ * SSH-reachable box (Docker engine + reverse proxy with wildcard cert);
10
+ * this config tells slowcook how to reach it.
11
+ *
12
+ * Hand-parsed (no yaml dep) because the schema is small + flat. If we
13
+ * ever need anchors / multi-doc / etc, switch to the workspace's `yaml`
14
+ * package — it's already a transitive dep via cli/dependencies.
15
+ */
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ export class PreviewConfigError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "PreviewConfigError";
22
+ }
23
+ }
24
+ export const PREVIEW_CONFIG_PATH = ".brewing/preview.yaml";
25
+ /**
26
+ * Parse `.brewing/preview.yaml`. Returns the typed config or throws
27
+ * `PreviewConfigError` with a precise message identifying the missing
28
+ * or malformed field.
29
+ *
30
+ * Accepts either:
31
+ *
32
+ * preview:
33
+ * type: ssh
34
+ * host: ...
35
+ * ...
36
+ *
37
+ * or top-level keys (the `preview:` wrapper is optional but
38
+ * recommended; consumers will likely add a `box:` or other top-level
39
+ * sections later).
40
+ */
41
+ export function parsePreviewConfig(yamlText) {
42
+ const lines = yamlText.split(/\r?\n/);
43
+ // Track inside-`preview:` block by indentation; allow flat top-level too.
44
+ const flat = {};
45
+ let inPreview = false;
46
+ let baseIndent = -1;
47
+ for (const raw of lines) {
48
+ const line = raw.replace(/#.*$/, ""); // strip comments
49
+ if (line.trim() === "")
50
+ continue;
51
+ const indentMatch = line.match(/^(\s*)/);
52
+ const indent = indentMatch ? indentMatch[1].length : 0;
53
+ const content = line.slice(indent);
54
+ if (indent === 0 && content.startsWith("preview:")) {
55
+ inPreview = true;
56
+ baseIndent = -1;
57
+ continue;
58
+ }
59
+ if (indent === 0 && content.includes(":") && !content.startsWith("preview:")) {
60
+ // top-level key outside preview — could be a sibling block we don't parse
61
+ inPreview = false;
62
+ const m = content.match(/^([a-zA-Z_][\w-]*)\s*:\s*(.*)$/);
63
+ if (m && m[2]?.trim()) {
64
+ flat[m[1]] = stripQuotes(m[2].trim());
65
+ }
66
+ continue;
67
+ }
68
+ if (inPreview) {
69
+ if (baseIndent === -1)
70
+ baseIndent = indent;
71
+ if (indent < baseIndent) {
72
+ inPreview = false;
73
+ continue;
74
+ }
75
+ const m = content.match(/^([a-zA-Z_][\w-]*)\s*:\s*(.*)$/);
76
+ if (m) {
77
+ flat[m[1]] = stripQuotes(m[2].trim());
78
+ }
79
+ }
80
+ }
81
+ const required = [
82
+ "type",
83
+ "host",
84
+ "user",
85
+ "keySecret",
86
+ "urlTemplate",
87
+ "remoteRoot",
88
+ ];
89
+ // Map snake_case YAML to camelCase keys.
90
+ const aliasMap = {
91
+ type: "type",
92
+ host: "host",
93
+ user: "user",
94
+ key_secret: "keySecret",
95
+ keysecret: "keySecret",
96
+ url_template: "urlTemplate",
97
+ urltemplate: "urlTemplate",
98
+ remote_root: "remoteRoot",
99
+ remoteroot: "remoteRoot",
100
+ port: "port",
101
+ port_range: "portRange",
102
+ portrange: "portRange",
103
+ mock_dir: "mockDir",
104
+ mockdir: "mockDir",
105
+ };
106
+ const cfg = {};
107
+ for (const [k, v] of Object.entries(flat)) {
108
+ const camel = aliasMap[k.toLowerCase()];
109
+ if (!camel)
110
+ continue;
111
+ if (camel === "port") {
112
+ cfg.port = parseInt(v, 10);
113
+ }
114
+ else if (camel === "portRange") {
115
+ const m = v.match(/^\[?\s*(\d+)\s*,\s*(\d+)\s*\]?$/);
116
+ if (!m) {
117
+ throw new PreviewConfigError(`preview.port_range: expected "[lo, hi]"; got ${JSON.stringify(v)}`);
118
+ }
119
+ const lo = parseInt(m[1], 10);
120
+ const hi = parseInt(m[2], 10);
121
+ if (lo > hi || lo <= 0 || hi > 65535) {
122
+ throw new PreviewConfigError(`preview.port_range: ${lo}..${hi} is not a valid port range`);
123
+ }
124
+ cfg.portRange = [lo, hi];
125
+ }
126
+ else {
127
+ // string fields
128
+ cfg[camel] = v;
129
+ }
130
+ }
131
+ for (const k of required) {
132
+ if (cfg[k] === undefined || cfg[k] === "") {
133
+ throw new PreviewConfigError(`preview.${snakeOf(k)} is required in ${PREVIEW_CONFIG_PATH}.`);
134
+ }
135
+ }
136
+ if (cfg.type !== "ssh") {
137
+ throw new PreviewConfigError(`preview.type must be "ssh" (got ${JSON.stringify(cfg.type)}). Other deploy types may ship later.`);
138
+ }
139
+ if (!cfg.urlTemplate.includes("{port}")) {
140
+ throw new PreviewConfigError(`preview.url_template must contain the literal "{port}" placeholder; got ${JSON.stringify(cfg.urlTemplate)}.`);
141
+ }
142
+ return {
143
+ type: "ssh",
144
+ host: cfg.host,
145
+ user: cfg.user,
146
+ keySecret: cfg.keySecret,
147
+ port: cfg.port ?? 22,
148
+ portRange: cfg.portRange ?? [4000, 4099],
149
+ urlTemplate: cfg.urlTemplate,
150
+ remoteRoot: cfg.remoteRoot,
151
+ mockDir: cfg.mockDir ?? "mock",
152
+ };
153
+ }
154
+ export function readPreviewConfig(repoRoot) {
155
+ const p = join(repoRoot, PREVIEW_CONFIG_PATH);
156
+ if (!existsSync(p)) {
157
+ throw new PreviewConfigError(`${PREVIEW_CONFIG_PATH} not found at ${p}. ` +
158
+ `See docs/operating-guide.md for the schema + box setup steps.`);
159
+ }
160
+ const text = readFileSync(p, "utf8");
161
+ return parsePreviewConfig(text);
162
+ }
163
+ function stripQuotes(s) {
164
+ if ((s.startsWith('"') && s.endsWith('"')) ||
165
+ (s.startsWith("'") && s.endsWith("'"))) {
166
+ return s.slice(1, -1);
167
+ }
168
+ return s;
169
+ }
170
+ function snakeOf(camel) {
171
+ return camel.replace(/([A-Z])/g, "_$1").toLowerCase();
172
+ }
173
+ /**
174
+ * Build the preview URL for a given allocated port.
175
+ */
176
+ export function urlForPort(cfg, port) {
177
+ return cfg.urlTemplate.replace("{port}", String(port));
178
+ }
179
+ /**
180
+ * Container name for a given PR. Single source of truth so deploy +
181
+ * teardown agree on the name.
182
+ */
183
+ export function containerNameForPr(pr) {
184
+ return `slowcook-mock-pr-${pr}`;
185
+ }
186
+ /**
187
+ * Image tag for a given PR. We rebuild per-PR (each PR has its own
188
+ * scenario set), so tags don't collide.
189
+ */
190
+ export function imageTagForPr(pr) {
191
+ return `slowcook-mock-pr-${pr}:latest`;
192
+ }
193
+ /**
194
+ * Remote staging directory for a given PR. Build artifacts + the tar
195
+ * extract live here.
196
+ */
197
+ export function remoteDirForPr(cfg, pr) {
198
+ return `${cfg.remoteRoot.replace(/\/+$/, "")}/pr-${pr}`;
199
+ }
200
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/commands/preview/config.ts"],"names":[],"mappings":"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"}