roughbook 0.0.0-stage → 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/LICENSE +201 -0
- package/README.md +166 -2
- package/dist/agents/claude.js +214 -0
- package/dist/agents/claude.js.map +1 -0
- package/dist/agents/codex.js +179 -0
- package/dist/agents/codex.js.map +1 -0
- package/dist/agents/command.js +47 -0
- package/dist/agents/command.js.map +1 -0
- package/dist/agents/index.js +21 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/process.js +76 -0
- package/dist/agents/process.js.map +1 -0
- package/dist/agents/session.js +26 -0
- package/dist/agents/session.js.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/api/client.js +138 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/errors.js +40 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/outbox.js +105 -0
- package/dist/api/outbox.js.map +1 -0
- package/dist/api/types.js +4 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents.js +39 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/context.js +27 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/done.js +125 -0
- package/dist/commands/done.js.map +1 -0
- package/dist/commands/index.js +101 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.js +203 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.js +93 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.js +34 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/promote.js +105 -0
- package/dist/commands/promote.js.map +1 -0
- package/dist/commands/pull.js +45 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/revert.js +39 -0
- package/dist/commands/revert.js.map +1 -0
- package/dist/commands/review.js +73 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/run.js +147 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/shared.js +81 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/start.js +119 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.js +62 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/credentials.js +17 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/server.js +29 -0
- package/dist/config/server.js.map +1 -0
- package/dist/config/settings.js +21 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/config/userDir.js +18 -0
- package/dist/config/userDir.js.map +1 -0
- package/dist/fs/json.js +44 -0
- package/dist/fs/json.js.map +1 -0
- package/dist/fs/lock.js +38 -0
- package/dist/fs/lock.js.map +1 -0
- package/dist/io.js +26 -0
- package/dist/io.js.map +1 -0
- package/dist/launch/open.js +48 -0
- package/dist/launch/open.js.map +1 -0
- package/dist/launch/platform.js +38 -0
- package/dist/launch/platform.js.map +1 -0
- package/dist/launch/pty.js +61 -0
- package/dist/launch/pty.js.map +1 -0
- package/dist/launch/terminal.js +194 -0
- package/dist/launch/terminal.js.map +1 -0
- package/dist/launch/which.js +59 -0
- package/dist/launch/which.js.map +1 -0
- package/dist/repo/diff.js +26 -0
- package/dist/repo/diff.js.map +1 -0
- package/dist/repo/framework.js +41 -0
- package/dist/repo/framework.js.map +1 -0
- package/dist/repo/link.js +28 -0
- package/dist/repo/link.js.map +1 -0
- package/dist/repo/prep.js +447 -0
- package/dist/repo/prep.js.map +1 -0
- package/dist/repo/preview.js +144 -0
- package/dist/repo/preview.js.map +1 -0
- package/dist/repo/root.js +48 -0
- package/dist/repo/root.js.map +1 -0
- package/dist/repo/scratch.js +111 -0
- package/dist/repo/scratch.js.map +1 -0
- package/dist/runs/checks.js +160 -0
- package/dist/runs/checks.js.map +1 -0
- package/dist/runs/lifecycle.js +142 -0
- package/dist/runs/lifecycle.js.map +1 -0
- package/dist/runs/promote.js +185 -0
- package/dist/runs/promote.js.map +1 -0
- package/dist/runs/pull.js +182 -0
- package/dist/runs/pull.js.map +1 -0
- package/dist/runs/reviews.js +34 -0
- package/dist/runs/reviews.js.map +1 -0
- package/dist/runs/slots.js +96 -0
- package/dist/runs/slots.js.map +1 -0
- package/dist/runs/snapshot.js +265 -0
- package/dist/runs/snapshot.js.map +1 -0
- package/dist/runs/store.js +38 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/runs/types.js +3 -0
- package/dist/runs/types.js.map +1 -0
- package/dist/runs/watch.js +42 -0
- package/dist/runs/watch.js.map +1 -0
- package/dist/screenshot/browser.js +163 -0
- package/dist/screenshot/browser.js.map +1 -0
- package/dist/screenshot/iteration.js +37 -0
- package/dist/screenshot/iteration.js.map +1 -0
- package/dist/server/app.js +321 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/security.js +44 -0
- package/dist/server/security.js.map +1 -0
- package/dist/server/studio.js +618 -0
- package/dist/server/studio.js.map +1 -0
- package/dist/server/terminals.js +142 -0
- package/dist/server/terminals.js.map +1 -0
- package/dist/ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/ui/assets/index-D-TSXfvz.css +1 -0
- package/dist/ui/assets/index-HGV8FKwI.js +27 -0
- package/dist/ui/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/ui/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/ui/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist/ui/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist/ui/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/dist/ui/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/dist/ui/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/dist/ui/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/dist/ui/index.html +14 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +68 -4
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
// Prepares a repo for the studio: a rough/ workspace that TypeScript and ESLint ignore, so an
|
|
2
|
+
// iteration can never break the app's build; the development-only preview route; and git told
|
|
3
|
+
// what to keep. Planning only reads; applying writes exactly what the plan showed, so `init` can
|
|
4
|
+
// show every change first. Ported from roughbook src/lib/delivery/init.ts (v2-local), which kept
|
|
5
|
+
// rough/ out of git; now committing it is the default, since git is its only record.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { renderRepoLink } from './link.js';
|
|
9
|
+
import { PREVIEW_WRAPPER, REGISTRY_FILE, detectAppDir, isOurPreviewFile, listIterations, previewRouteDir, previewRouteFiles, renderPreviewRegistry } from './preview.js';
|
|
10
|
+
import { LINK_FILE, ROUGH_DIR, STATE_DIR, isHomeOrAbove } from './root.js';
|
|
11
|
+
const GITIGNORE_HEADER = '# Roughbook';
|
|
12
|
+
const OLD_GITIGNORE_HEADER = '# Roughbook (local only)';
|
|
13
|
+
const OURS = 'Written by `roughbook init`';
|
|
14
|
+
function readme(committed) {
|
|
15
|
+
return `# rough/
|
|
16
|
+
|
|
17
|
+
Roughbook's design workspace for this repo: design iterations, the prompts that
|
|
18
|
+
made them, and the link to this repo's Roughbook project (\`roughbook.json\`).
|
|
19
|
+
|
|
20
|
+
TypeScript and ESLint ignore this folder, so an iteration can never fail the
|
|
21
|
+
app's build, and nothing in the app imports from it. \`roughbook promote\` turns
|
|
22
|
+
a chosen iteration into a real component.
|
|
23
|
+
|
|
24
|
+
${committed
|
|
25
|
+
? `This folder is committed: git is the only record of these iterations, since
|
|
26
|
+
Roughbook's server keeps only their review decisions. Run state
|
|
27
|
+
(\`${STATE_DIR}/\`) and screenshots (\`.screenshots/\`) are not committed.`
|
|
28
|
+
: `This folder is not committed (see .gitignore), so nothing backs these
|
|
29
|
+
iterations up. Run \`roughbook init\` again to commit it.`}
|
|
30
|
+
|
|
31
|
+
${OURS}; it rewrites this file.
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
/** The iteration rules every agent reads when it works in rough/, whichever agent it is. */
|
|
35
|
+
export const ROUGH_AGENTS = `# Working in rough/
|
|
36
|
+
|
|
37
|
+
This folder is Roughbook's design workspace. Each iteration is a folder
|
|
38
|
+
\`<page>/<component>/iteration-NN/\` whose \`index.tsx\` default-exports the design.
|
|
39
|
+
Each run's instructions are in \`<page>/<component>/prompts/\`.
|
|
40
|
+
|
|
41
|
+
Rules for any agent working here:
|
|
42
|
+
|
|
43
|
+
- Write only inside the iteration folder your instructions name.
|
|
44
|
+
- Never edit an existing iteration. A new idea is a new iteration, and a change
|
|
45
|
+
to one is a fork or refinement that Roughbook creates for you.
|
|
46
|
+
- An iteration never imports from another iteration. It may import the app's
|
|
47
|
+
own components, styles and utilities.
|
|
48
|
+
- Nothing outside rough/ imports from rough/.
|
|
49
|
+
- Do not install dependencies, change the app's configuration, or commit.
|
|
50
|
+
|
|
51
|
+
${OURS}; it rewrites this file.
|
|
52
|
+
`;
|
|
53
|
+
/** Claude Code reads CLAUDE.md, so it points at the same rules. */
|
|
54
|
+
export const ROUGH_CLAUDE = '@AGENTS.md\n';
|
|
55
|
+
/** Why a folder cannot be prepared, or nothing. */
|
|
56
|
+
export function checkRepo(repo, home) {
|
|
57
|
+
if (!path.isAbsolute(repo))
|
|
58
|
+
return ['The repo path must be absolute.'];
|
|
59
|
+
if (isHomeOrAbove(repo, home)) {
|
|
60
|
+
return ['That is your home folder, or contains it. Run roughbook init inside the app’s repo.'];
|
|
61
|
+
}
|
|
62
|
+
let stat;
|
|
63
|
+
try {
|
|
64
|
+
stat = fs.statSync(repo);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return ['That folder does not exist.'];
|
|
68
|
+
}
|
|
69
|
+
if (!stat.isDirectory())
|
|
70
|
+
return ['That path is a file, not a folder.'];
|
|
71
|
+
if (!fs.existsSync(path.join(repo, 'package.json'))) {
|
|
72
|
+
return ['This folder has no package.json, so it is not an app the studio can work in.'];
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
function read(repo, file) {
|
|
77
|
+
try {
|
|
78
|
+
return fs.readFileSync(path.join(repo, file), 'utf8');
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** JSON with comments and trailing commas, as tsconfig and .eslintrc allow, made parseable. */
|
|
85
|
+
export function stripJsonc(text) {
|
|
86
|
+
let out = '';
|
|
87
|
+
let inString = false;
|
|
88
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
89
|
+
const char = text[i];
|
|
90
|
+
const next = text[i + 1];
|
|
91
|
+
if (inString) {
|
|
92
|
+
out += char;
|
|
93
|
+
if (char === '\\') {
|
|
94
|
+
out += next ?? '';
|
|
95
|
+
i += 1;
|
|
96
|
+
}
|
|
97
|
+
else if (char === '"') {
|
|
98
|
+
inString = false;
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (char === '"') {
|
|
103
|
+
inString = true;
|
|
104
|
+
out += char;
|
|
105
|
+
}
|
|
106
|
+
else if (char === '/' && next === '/') {
|
|
107
|
+
while (i < text.length && text[i] !== '\n')
|
|
108
|
+
i += 1;
|
|
109
|
+
out += '\n';
|
|
110
|
+
}
|
|
111
|
+
else if (char === '/' && next === '*') {
|
|
112
|
+
i += 2;
|
|
113
|
+
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/'))
|
|
114
|
+
i += 1;
|
|
115
|
+
i += 1;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
out += char;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out.replace(/,(\s*[}\]])/g, '$1');
|
|
122
|
+
}
|
|
123
|
+
function parseJsonc(text) {
|
|
124
|
+
try {
|
|
125
|
+
const value = JSON.parse(stripJsonc(text));
|
|
126
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
127
|
+
? value
|
|
128
|
+
: null;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** A path pattern written any of the usual ways: "rough", "./rough", "rough/", "/rough/**". */
|
|
135
|
+
function normalizePattern(entry) {
|
|
136
|
+
return entry
|
|
137
|
+
.trim()
|
|
138
|
+
.replace(/^\.?\//, '')
|
|
139
|
+
.replace(/\/(\*\*(\/\*)?)?$/, '')
|
|
140
|
+
.replace(/\/$/, '');
|
|
141
|
+
}
|
|
142
|
+
function covers(entry, target) {
|
|
143
|
+
return normalizePattern(entry) === normalizePattern(target);
|
|
144
|
+
}
|
|
145
|
+
/** A list written one item per line gets the new item on its own line. */
|
|
146
|
+
function afterComma(space) {
|
|
147
|
+
return space.includes('\n') ? space : ' ';
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Brings .gitignore to `wanted` (entries written as "/path/" or "/file") and takes out any line
|
|
151
|
+
* in `unwanted`: a repo prepared before rough/ was committed ignores the whole folder.
|
|
152
|
+
*/
|
|
153
|
+
export function planGitignore(current, wanted, unwanted = []) {
|
|
154
|
+
const lines = (current ?? '').split(/\r?\n/);
|
|
155
|
+
const kept = lines
|
|
156
|
+
.filter(line => !unwanted.some(entry => covers(line, entry)))
|
|
157
|
+
// v2's header said "local only", which is no longer true once rough/ is committed.
|
|
158
|
+
.map(line => (line.trim() === OLD_GITIGNORE_HEADER ? GITIGNORE_HEADER : line));
|
|
159
|
+
const removed = lines.length - kept.length;
|
|
160
|
+
// Ignoring the whole of rough/ already covers what is inside it.
|
|
161
|
+
const ignoresRough = kept.some(line => covers(line, ROUGH_DIR));
|
|
162
|
+
const missing = wanted.filter(entry => !kept.some(line => covers(line, entry)) &&
|
|
163
|
+
!(ignoresRough && normalizePattern(entry).startsWith(`${ROUGH_DIR}/`)));
|
|
164
|
+
const body = kept.join('\n').replace(/\n*$/, '');
|
|
165
|
+
let after = body ? `${body}\n` : '';
|
|
166
|
+
if (missing.length) {
|
|
167
|
+
const hasHeader = kept.some(line => line.trim() === GITIGNORE_HEADER);
|
|
168
|
+
after = hasHeader
|
|
169
|
+
? `${body}\n${missing.join('\n')}\n`
|
|
170
|
+
: `${body}${body ? '\n\n' : ''}${GITIGNORE_HEADER}\n${missing.join('\n')}\n`;
|
|
171
|
+
}
|
|
172
|
+
const renamed = lines.some(line => line.trim() === OLD_GITIGNORE_HEADER);
|
|
173
|
+
if (!missing.length && !removed && !renamed)
|
|
174
|
+
return null;
|
|
175
|
+
const parts = [
|
|
176
|
+
removed ? `stop ignoring ${ROUGH_DIR}/, so it is committed` : '',
|
|
177
|
+
missing.length ? `ignore ${missing.join(', ')}` : ''
|
|
178
|
+
].filter(Boolean);
|
|
179
|
+
return {
|
|
180
|
+
file: '.gitignore',
|
|
181
|
+
action: current === null ? 'create' : 'edit',
|
|
182
|
+
summary: parts.length
|
|
183
|
+
? `${parts.join('; ').replace(/^./, char => char.toUpperCase())}.`
|
|
184
|
+
: 'Rename the Roughbook section.',
|
|
185
|
+
after
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Adds folders to tsconfig's exclude with a small text edit, keeping the file's own formatting
|
|
190
|
+
* and comments. A manual step when the edit cannot be made safely. An exclude that did not exist
|
|
191
|
+
* gets node_modules too, since setting exclude replaces TypeScript's default of excluding it.
|
|
192
|
+
*/
|
|
193
|
+
export function planTsconfig(current, folders) {
|
|
194
|
+
if (current === null)
|
|
195
|
+
return null;
|
|
196
|
+
const list = (items) => items.map(item => JSON.stringify(item)).join(', ');
|
|
197
|
+
const parsed = parseJsonc(current);
|
|
198
|
+
if (!parsed) {
|
|
199
|
+
return {
|
|
200
|
+
manual: `tsconfig.json could not be read. Add ${list(folders)} to its "exclude" list by hand.`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const exclude = Array.isArray(parsed.exclude) ? parsed.exclude : null;
|
|
204
|
+
const missing = folders.filter(folder => !exclude?.some(item => typeof item === 'string' && covers(item, folder)));
|
|
205
|
+
if (!missing.length)
|
|
206
|
+
return null;
|
|
207
|
+
const items = missing.map(folder => JSON.stringify(folder));
|
|
208
|
+
let after;
|
|
209
|
+
if (exclude) {
|
|
210
|
+
after = current.replace(/("exclude"\s*:\s*\[)(\s*)/, (_match, open, space) => exclude.length
|
|
211
|
+
? `${open}${space}${items.map(item => `${item},${afterComma(space)}`).join('')}`
|
|
212
|
+
: `${open}${space}${items.join(', ')}`);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const end = current.lastIndexOf('}');
|
|
216
|
+
if (end === -1) {
|
|
217
|
+
return {
|
|
218
|
+
manual: `Add "exclude": ["node_modules", ${list(missing)}] to tsconfig.json by hand.`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const head = current.slice(0, end).replace(/\s*$/, '');
|
|
222
|
+
const needsComma = !head.endsWith('{') && !head.endsWith(',');
|
|
223
|
+
after = `${head}${needsComma ? ',' : ''}\n "exclude": ["node_modules", ${items.join(', ')}]\n${current.slice(end)}`;
|
|
224
|
+
}
|
|
225
|
+
const check = parseJsonc(after);
|
|
226
|
+
const ok = Array.isArray(check?.exclude) &&
|
|
227
|
+
missing.every(folder => check.exclude.includes(folder));
|
|
228
|
+
if (!ok)
|
|
229
|
+
return { manual: `Add ${list(missing)} to the "exclude" list in tsconfig.json by hand.` };
|
|
230
|
+
return {
|
|
231
|
+
file: 'tsconfig.json',
|
|
232
|
+
action: 'edit',
|
|
233
|
+
summary: `Exclude ${missing.map(folder => `${folder}/`).join(' and ')} from type checking, so an iteration can never fail the build.`,
|
|
234
|
+
after
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const TAILWIND_IMPORT = /^\s*@import\s+["']tailwindcss["'][^;\n]*;?[^\n]*$/m;
|
|
238
|
+
const TAILWIND_CONFIGS = [
|
|
239
|
+
'tailwind.config.js',
|
|
240
|
+
'tailwind.config.ts',
|
|
241
|
+
'tailwind.config.mjs',
|
|
242
|
+
'tailwind.config.cjs'
|
|
243
|
+
];
|
|
244
|
+
function cssFiles(dir) {
|
|
245
|
+
if (!fs.existsSync(dir))
|
|
246
|
+
return [];
|
|
247
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
248
|
+
const full = path.join(dir, entry.name);
|
|
249
|
+
if (entry.isDirectory()) {
|
|
250
|
+
return entry.name === 'node_modules' || entry.name.startsWith('.') ? [] : cssFiles(full);
|
|
251
|
+
}
|
|
252
|
+
return entry.name.endsWith('.css') ? [full] : [];
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Tailwind v4 finds classes by scanning the repo and skips gitignored folders, so classes used
|
|
257
|
+
* only in an ignored rough/ would never be generated. One @source line in the stylesheet that
|
|
258
|
+
* imports Tailwind fixes that, committed or not. Tailwind v3 is configured in JavaScript, so that
|
|
259
|
+
* is left to the designer.
|
|
260
|
+
*/
|
|
261
|
+
export function planTailwindSource(repo, appDir) {
|
|
262
|
+
const v3 = TAILWIND_CONFIGS.find(name => fs.existsSync(path.join(repo, name)));
|
|
263
|
+
for (const file of [
|
|
264
|
+
...cssFiles(path.join(repo, appDir)),
|
|
265
|
+
...cssFiles(path.join(repo, 'src', 'styles')),
|
|
266
|
+
...cssFiles(path.join(repo, 'styles'))
|
|
267
|
+
]) {
|
|
268
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
269
|
+
const match = TAILWIND_IMPORT.exec(text);
|
|
270
|
+
if (!match)
|
|
271
|
+
continue;
|
|
272
|
+
const toRough = path
|
|
273
|
+
.relative(path.dirname(file), path.join(repo, ROUGH_DIR))
|
|
274
|
+
.split(path.sep)
|
|
275
|
+
.join('/');
|
|
276
|
+
const escaped = toRough.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
277
|
+
if (new RegExp(`@source\\s+["']${escaped}`).test(text))
|
|
278
|
+
return null;
|
|
279
|
+
const end = match.index + match[0].length;
|
|
280
|
+
return {
|
|
281
|
+
file: path.relative(repo, file).split(path.sep).join('/'),
|
|
282
|
+
action: 'edit',
|
|
283
|
+
summary: `Let Tailwind generate the classes used in ${ROUGH_DIR}/.`,
|
|
284
|
+
after: `${text.slice(0, end)}\n@source "${toRough}";${text.slice(end)}`
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (v3) {
|
|
288
|
+
return {
|
|
289
|
+
manual: `Add "./${ROUGH_DIR}/**/*.{ts,tsx}" to "content" in ${v3}, so previews are styled.`
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const FLAT_CONFIGS = [
|
|
295
|
+
'eslint.config.mjs',
|
|
296
|
+
'eslint.config.js',
|
|
297
|
+
'eslint.config.ts',
|
|
298
|
+
'eslint.config.cjs'
|
|
299
|
+
];
|
|
300
|
+
export function planEslint(files) {
|
|
301
|
+
const flat = FLAT_CONFIGS.find(name => files[name] != null);
|
|
302
|
+
if (flat) {
|
|
303
|
+
const text = files[flat];
|
|
304
|
+
if (/["'`]\.?\/?rough\//.test(text))
|
|
305
|
+
return null;
|
|
306
|
+
for (const anchor of [/(globalIgnores\(\s*\[)(\s*)/, /(ignores\s*:\s*\[)(\s*)/]) {
|
|
307
|
+
if (anchor.test(text)) {
|
|
308
|
+
return {
|
|
309
|
+
file: flat,
|
|
310
|
+
action: 'edit',
|
|
311
|
+
summary: `Ignore ${ROUGH_DIR}/ in ESLint.`,
|
|
312
|
+
after: text.replace(anchor, (_match, open, space) => `${open}${space}"${ROUGH_DIR}/**",${afterComma(space)}`)
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { manual: `Add "${ROUGH_DIR}/**" to the ignores in ${flat} by hand.` };
|
|
317
|
+
}
|
|
318
|
+
const ignoreFile = files['.eslintignore'];
|
|
319
|
+
if (ignoreFile != null) {
|
|
320
|
+
if (/^\/?rough\/?(\*\*)?$/m.test(ignoreFile))
|
|
321
|
+
return null;
|
|
322
|
+
return {
|
|
323
|
+
file: '.eslintignore',
|
|
324
|
+
action: 'edit',
|
|
325
|
+
summary: `Ignore ${ROUGH_DIR}/ in ESLint.`,
|
|
326
|
+
after: `${ignoreFile}${ignoreFile.endsWith('\n') || !ignoreFile ? '' : '\n'}${ROUGH_DIR}/\n`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (files['.eslintrc.json'] != null) {
|
|
330
|
+
return { manual: `Add "${ROUGH_DIR}/**" to "ignorePatterns" in .eslintrc.json by hand.` };
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
/** A file the studio owns: written when missing, rewritten when ours and different. */
|
|
335
|
+
function ownFile(repo, file, content, summary, isOurs) {
|
|
336
|
+
const current = read(repo, file);
|
|
337
|
+
if (current === content)
|
|
338
|
+
return null;
|
|
339
|
+
if (current === null)
|
|
340
|
+
return { file, action: 'create', summary, after: content };
|
|
341
|
+
if (!isOurs(current))
|
|
342
|
+
return { manual: `${file} exists and was not written by Roughbook, so it was left alone.` };
|
|
343
|
+
return { file, action: 'edit', summary, after: content };
|
|
344
|
+
}
|
|
345
|
+
export function planPrep(repo, options) {
|
|
346
|
+
const problems = checkRepo(repo);
|
|
347
|
+
if (problems.length)
|
|
348
|
+
return { repo, problems, changes: [], manual: [], before: {} };
|
|
349
|
+
const root = path.resolve(repo);
|
|
350
|
+
const committed = options.link.roughCommitted;
|
|
351
|
+
const changes = [];
|
|
352
|
+
const manual = [];
|
|
353
|
+
const add = (result) => {
|
|
354
|
+
if (!result)
|
|
355
|
+
return;
|
|
356
|
+
if ('manual' in result)
|
|
357
|
+
manual.push(result.manual);
|
|
358
|
+
else
|
|
359
|
+
changes.push(result);
|
|
360
|
+
};
|
|
361
|
+
const appDir = detectAppDir(root);
|
|
362
|
+
const link = { ...options.link, appDir };
|
|
363
|
+
add(ownFile(root, `${ROUGH_DIR}/README.md`, readme(committed), `Explain the ${ROUGH_DIR}/ workspace.`,
|
|
364
|
+
// v2's README said the folder was never committed.
|
|
365
|
+
text => text.includes(OURS) || text.startsWith("# rough/\n\nRoughbook's local design workspace")));
|
|
366
|
+
add(ownFile(root, `${ROUGH_DIR}/AGENTS.md`, ROUGH_AGENTS, 'Give every agent the iteration rules.', text => text.includes(OURS)));
|
|
367
|
+
add(ownFile(root, `${ROUGH_DIR}/CLAUDE.md`, ROUGH_CLAUDE, 'Point Claude Code at the same rules.', text => text.trim() === ROUGH_CLAUDE.trim()));
|
|
368
|
+
const linkFile = `${ROUGH_DIR}/${LINK_FILE}`;
|
|
369
|
+
const linkText = renderRepoLink(link);
|
|
370
|
+
const currentLink = read(root, linkFile);
|
|
371
|
+
if (currentLink !== linkText) {
|
|
372
|
+
changes.push({
|
|
373
|
+
file: linkFile,
|
|
374
|
+
action: currentLink === null ? 'create' : 'edit',
|
|
375
|
+
summary: `Link this repo to the project ${link.project}.`,
|
|
376
|
+
after: linkText
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const ignoredFromBuild = [ROUGH_DIR];
|
|
380
|
+
const gitWanted = committed
|
|
381
|
+
? [
|
|
382
|
+
`/${ROUGH_DIR}/${STATE_DIR}/`,
|
|
383
|
+
`/${ROUGH_DIR}/.screenshots/`,
|
|
384
|
+
`/${ROUGH_DIR}/${REGISTRY_FILE}`
|
|
385
|
+
]
|
|
386
|
+
: [`/${ROUGH_DIR}/`];
|
|
387
|
+
if (!appDir) {
|
|
388
|
+
manual.push('No App Router folder (app/ or src/app/) was found, so previews cannot render through this app. They need the Next.js App Router.');
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
const routeDir = previewRouteDir(appDir);
|
|
392
|
+
ignoredFromBuild.push(routeDir);
|
|
393
|
+
gitWanted.push(`/${routeDir}/`);
|
|
394
|
+
for (const route of previewRouteFiles(appDir)) {
|
|
395
|
+
const current = read(root, route.file);
|
|
396
|
+
if (current === route.content)
|
|
397
|
+
continue;
|
|
398
|
+
if (current !== null && !isOurPreviewFile(current)) {
|
|
399
|
+
manual.push(`${route.file} exists and was not written by Roughbook, so it was left alone.`);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
changes.push({
|
|
403
|
+
file: route.file,
|
|
404
|
+
action: current === null ? 'create' : 'edit',
|
|
405
|
+
summary: current === null
|
|
406
|
+
? 'Add the development-only preview route.'
|
|
407
|
+
: 'Update the preview route.',
|
|
408
|
+
after: route.content
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (read(root, `${ROUGH_DIR}/preview.tsx`) === null) {
|
|
412
|
+
changes.push({
|
|
413
|
+
file: `${ROUGH_DIR}/preview.tsx`,
|
|
414
|
+
action: 'create',
|
|
415
|
+
summary: "Add the wrapper for the app's providers.",
|
|
416
|
+
after: PREVIEW_WRAPPER
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
add(planTailwindSource(root, appDir));
|
|
420
|
+
if (read(root, `${ROUGH_DIR}/${REGISTRY_FILE}`) === null) {
|
|
421
|
+
changes.push({
|
|
422
|
+
file: `${ROUGH_DIR}/${REGISTRY_FILE}`,
|
|
423
|
+
action: 'create',
|
|
424
|
+
summary: 'Add the registry of iterations the route can render.',
|
|
425
|
+
after: renderPreviewRegistry(listIterations(root))
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
add(planGitignore(read(root, '.gitignore'), gitWanted, committed ? [ROUGH_DIR] : []));
|
|
430
|
+
add(planTsconfig(read(root, 'tsconfig.json'), ignoredFromBuild));
|
|
431
|
+
add(planEslint(Object.fromEntries([...FLAT_CONFIGS, '.eslintignore', '.eslintrc.json'].map(name => [name, read(root, name)]))));
|
|
432
|
+
const before = Object.fromEntries(changes
|
|
433
|
+
.filter(change => change.action === 'edit')
|
|
434
|
+
.map(change => [change.file, read(root, change.file) ?? '']));
|
|
435
|
+
return { repo: root, problems, changes, manual, before };
|
|
436
|
+
}
|
|
437
|
+
/** Writes a plan. Each file is written whole, as the plan showed it, with `\n` line endings. */
|
|
438
|
+
export function applyPrep(plan) {
|
|
439
|
+
if (plan.problems.length)
|
|
440
|
+
throw new Error(plan.problems[0]);
|
|
441
|
+
for (const change of plan.changes) {
|
|
442
|
+
const file = path.join(plan.repo, ...change.file.split('/'));
|
|
443
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
444
|
+
fs.writeFileSync(file, change.after);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
//# sourceMappingURL=prep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prep.js","sourceRoot":"","sources":["../../src/repo/prep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Iterations live in the repo's rough/ and render through the repo's own dev server, so they can
|
|
2
|
+
// use its components, fonts and tokens. Three pieces make that work:
|
|
3
|
+
//
|
|
4
|
+
// <app>/rough-preview/[...slug]/page.tsx the route (development only, gitignored)
|
|
5
|
+
// rough/preview-registry.ts one loader per iteration, generated (gitignored)
|
|
6
|
+
// rough/preview.tsx wraps iterations in the app's providers
|
|
7
|
+
//
|
|
8
|
+
// The route loads the registry only when NODE_ENV is "development", inside a branch the bundler
|
|
9
|
+
// removes from production builds, so a broken iteration can never fail the app's build. The
|
|
10
|
+
// folder is "rough-preview", not "_rough": Next.js treats folders starting with "_" as private.
|
|
11
|
+
// Ported from roughbook src/lib/delivery/preview.ts (v2-local).
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { ROUGH_DIR } from './root.js';
|
|
15
|
+
export const PREVIEW_ROUTE = 'rough-preview';
|
|
16
|
+
export const REGISTRY_FILE = 'preview-registry.ts';
|
|
17
|
+
const MARKER = 'Roughbook preview route';
|
|
18
|
+
export const ITERATION = /^iteration-\d{2,}$/;
|
|
19
|
+
export const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
20
|
+
/** Where the App Router lives in this repo, or null when it has none. */
|
|
21
|
+
export function detectAppDir(repo) {
|
|
22
|
+
for (const candidate of ['src/app', 'app']) {
|
|
23
|
+
if (fs.existsSync(path.join(repo, candidate)))
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
export function previewRouteDir(appDir) {
|
|
29
|
+
return `${appDir}/${PREVIEW_ROUTE}`;
|
|
30
|
+
}
|
|
31
|
+
/** An iteration's folder, relative to the repo, with forward slashes. */
|
|
32
|
+
export function iterationDir(page, component, iteration) {
|
|
33
|
+
return `${ROUGH_DIR}/${page}/${component}/${iteration}`;
|
|
34
|
+
}
|
|
35
|
+
export function previewUrl(devUrl, page, component, iteration) {
|
|
36
|
+
return `${devUrl.replace(/\/$/, '')}/${PREVIEW_ROUTE}/${page}/${component}/${iteration}`;
|
|
37
|
+
}
|
|
38
|
+
/** The route's two files. Imports climb from <app>/rough-preview/[...slug]/ to the repo root. */
|
|
39
|
+
export function previewRouteFiles(appDir) {
|
|
40
|
+
const up = '../'.repeat(appDir.split('/').length + 2);
|
|
41
|
+
const dir = `${previewRouteDir(appDir)}/[...slug]`;
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
file: `${dir}/page.tsx`,
|
|
45
|
+
content: `// ${MARKER}. Development only, not committed (see .gitignore).
|
|
46
|
+
// Renders one iteration from ${ROUGH_DIR}/ with this app's own layout, styles and components.
|
|
47
|
+
import { notFound } from "next/navigation";
|
|
48
|
+
import { Ready } from "./ready";
|
|
49
|
+
|
|
50
|
+
export default async function RoughPreview({ params }: { params: Promise<{ slug: string[] }> }) {
|
|
51
|
+
// The bundler drops this branch from production builds, so nothing in ${ROUGH_DIR}/ is ever built for production.
|
|
52
|
+
const registry = process.env.NODE_ENV === "development" ? await import("${up}${ROUGH_DIR}/preview-registry") : null;
|
|
53
|
+
if (!registry) notFound();
|
|
54
|
+
const key = (await params).slug.join("/");
|
|
55
|
+
const load = registry.loaders[key];
|
|
56
|
+
if (!load) notFound();
|
|
57
|
+
const { default: Iteration } = await load();
|
|
58
|
+
const { Preview } = registry;
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<Preview>
|
|
62
|
+
<Iteration />
|
|
63
|
+
</Preview>
|
|
64
|
+
<Ready id={key} />
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
`
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
file: `${dir}/ready.tsx`,
|
|
72
|
+
content: `"use client";
|
|
73
|
+
// ${MARKER}. Tells Roughbook the iteration rendered, so it does not fall back to a screenshot.
|
|
74
|
+
import { useEffect } from "react";
|
|
75
|
+
|
|
76
|
+
export function Ready({ id }: { id: string }) {
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
window.parent.postMessage({ roughbook: "ready", id }, "*");
|
|
79
|
+
}, [id]);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
`
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
export const PREVIEW_WRAPPER = `// Wraps every iteration previewed through this app. Add the providers your
|
|
87
|
+
// components need (theme, i18n, query client), as your root layout does.
|
|
88
|
+
import type { ReactNode } from "react";
|
|
89
|
+
|
|
90
|
+
export default function Preview({ children }: { children: ReactNode }) {
|
|
91
|
+
return <>{children}</>;
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
function subdirs(dir) {
|
|
95
|
+
if (!fs.existsSync(dir))
|
|
96
|
+
return [];
|
|
97
|
+
return fs
|
|
98
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
99
|
+
.filter(entry => entry.isDirectory())
|
|
100
|
+
.map(entry => entry.name)
|
|
101
|
+
.sort();
|
|
102
|
+
}
|
|
103
|
+
/** Every iteration under rough/<page>/<component>/iteration-NN/ with an index.tsx, as keys. */
|
|
104
|
+
export function listIterations(repo) {
|
|
105
|
+
const root = path.join(repo, ROUGH_DIR);
|
|
106
|
+
const keys = [];
|
|
107
|
+
for (const page of subdirs(root).filter(name => SLUG.test(name))) {
|
|
108
|
+
for (const component of subdirs(path.join(root, page)).filter(name => SLUG.test(name))) {
|
|
109
|
+
for (const iteration of subdirs(path.join(root, page, component)).filter(name => ITERATION.test(name))) {
|
|
110
|
+
if (fs.existsSync(path.join(root, page, component, iteration, 'index.tsx'))) {
|
|
111
|
+
keys.push(`${page}/${component}/${iteration}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return keys;
|
|
117
|
+
}
|
|
118
|
+
export function renderPreviewRegistry(keys) {
|
|
119
|
+
const lines = keys.map(key => ` ${JSON.stringify(key)}: () => import(${JSON.stringify(`./${key}/index`)}),`);
|
|
120
|
+
return `// Generated by Roughbook from the iterations in this folder. Do not edit.
|
|
121
|
+
import type { ComponentType } from "react";
|
|
122
|
+
|
|
123
|
+
export { default as Preview } from "./preview";
|
|
124
|
+
|
|
125
|
+
export const loaders: Record<string, () => Promise<{ default: ComponentType }>> = {
|
|
126
|
+
${lines.map(line => `${line}\n`).join('')}};
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
/** Rewrites rough/preview-registry.ts when the iterations changed. Returns whether it wrote. */
|
|
130
|
+
export function writePreviewRegistry(repo) {
|
|
131
|
+
const file = path.join(repo, ROUGH_DIR, REGISTRY_FILE);
|
|
132
|
+
const next = renderPreviewRegistry(listIterations(repo));
|
|
133
|
+
const current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
134
|
+
if (current === next)
|
|
135
|
+
return false;
|
|
136
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
137
|
+
fs.writeFileSync(file, next);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
/** Whether a file is one Roughbook wrote for the preview route, so it may be updated. */
|
|
141
|
+
export function isOurPreviewFile(text) {
|
|
142
|
+
return Boolean(text?.includes(MARKER));
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/repo/preview.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,qEAAqE;AACrE,EAAE;AACF,sFAAsF;AACtF,+FAA+F;AAC/F,sFAAsF;AACtF,EAAE;AACF,gGAAgG;AAChG,4FAA4F;AAC5F,gGAAgG;AAChG,gEAAgE;AAChE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAC7C,MAAM,CAAC,MAAM,aAAa,GAAG,qBAAqB,CAAC;AACnD,MAAM,MAAM,GAAG,yBAAyB,CAAC;AACzC,MAAM,CAAC,MAAM,SAAS,GAAG,oBAAoB,CAAC;AAC9C,MAAM,CAAC,MAAM,IAAI,GAAG,4BAA4B,CAAC;AAEjD,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,KAAK,MAAM,SAAS,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC;QAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;IAClE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,OAAO,GAAG,MAAM,IAAI,aAAa,EAAE,CAAC;AACtC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,SAAiB,EAAE,SAAiB;IAC7E,OAAO,GAAG,SAAS,IAAI,IAAI,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,MAAc,EACd,IAAY,EACZ,SAAiB,EACjB,SAAiB;IAEjB,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,aAAa,IAAI,IAAI,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;AAC3F,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC;IACnD,OAAO;QACL;YACE,IAAI,EAAE,GAAG,GAAG,WAAW;YACvB,OAAO,EAAE,MAAM,MAAM;gCACK,SAAS;;;;;2EAKkC,SAAS;4EACR,EAAE,GAAG,SAAS;;;;;;;;;;;;;;;;CAgBzF;SACI;QACD;YACE,IAAI,EAAE,GAAG,GAAG,YAAY;YACxB,OAAO,EAAE;KACV,MAAM;;;;;;;;;CASV;SACI;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;CAO9B,CAAC;AAEF,SAAS,OAAO,CAAC,GAAW;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,OAAO,EAAE;SACN,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SACzC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;SACpC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SACxB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACxC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACjE,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACvF,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAC9E,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CACrB,EAAE,CAAC;gBACF,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;oBAC5E,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC,CAAC;gBACjD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAc;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CACtF,CAAC;IACF,OAAO;;;;;;EAMP,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;CACxC,CAAC;AACF,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,qBAAqB,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACnC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,gBAAgB,CAAC,IAAmB;IAClD,OAAO,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC"}
|