uilint 0.2.8 → 0.2.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.
- package/dist/chunk-2RNDQVEK.js +176 -0
- package/dist/chunk-2RNDQVEK.js.map +1 -0
- package/dist/chunk-FRNXXIEM.js +197 -0
- package/dist/chunk-FRNXXIEM.js.map +1 -0
- package/dist/index.js +323 -3820
- package/dist/index.js.map +1 -1
- package/dist/install-ui-KI7YHOVZ.js +4011 -0
- package/dist/install-ui-KI7YHOVZ.js.map +1 -0
- package/dist/plan-PX7FFJ25.js +337 -0
- package/dist/plan-PX7FFJ25.js.map +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/install/constants.ts
|
|
4
|
+
var GENSTYLEGUIDE_COMMAND_MD = `# React Style Guide Generator
|
|
5
|
+
|
|
6
|
+
Analyze the React UI codebase to produce a **prescriptive, semantic** style guide. Focus on consistency, intent, and relationships\u2014not specific values.
|
|
7
|
+
|
|
8
|
+
## Philosophy
|
|
9
|
+
|
|
10
|
+
1. **Identify the intended architecture** from the best patterns in use
|
|
11
|
+
2. **Prescribe semantic rules** \u2014 about consistency and relationships, not pixels
|
|
12
|
+
3. **Stay general** \u2014 "primary buttons should be visually consistent" not "buttons use px-4"
|
|
13
|
+
4. **Focus on intent** \u2014 what should FEEL the same, not what values to use
|
|
14
|
+
|
|
15
|
+
## Analysis Steps
|
|
16
|
+
|
|
17
|
+
### 1. Detect the Stack
|
|
18
|
+
- Framework: Next.js (App Router? Pages?), Vite, CRA
|
|
19
|
+
- Component system: shadcn, MUI, Chakra, Radix, custom
|
|
20
|
+
- Styling: Tailwind, CSS Modules, styled-components
|
|
21
|
+
- Forms: react-hook-form, Formik, native
|
|
22
|
+
- State: React context, Zustand, Redux, Jotai
|
|
23
|
+
|
|
24
|
+
### 2. Identify Best Patterns
|
|
25
|
+
Examine the **best-written** components. Look at:
|
|
26
|
+
- \`components/ui/*\` \u2014 the design system
|
|
27
|
+
- Recently modified files \u2014 current standards
|
|
28
|
+
- Shared layouts \u2014 structural patterns
|
|
29
|
+
|
|
30
|
+
### 3. Infer Visual Hierarchy & Intent
|
|
31
|
+
Understand the design language:
|
|
32
|
+
- What distinguishes primary vs secondary actions?
|
|
33
|
+
- How is visual hierarchy established?
|
|
34
|
+
- What creates consistency across similar elements?
|
|
35
|
+
|
|
36
|
+
## Output Format
|
|
37
|
+
|
|
38
|
+
Generate at \`<nextjs app root>/.uilint/styleguide.md\`:
|
|
39
|
+
\`\`\`yaml
|
|
40
|
+
# Stack
|
|
41
|
+
framework:
|
|
42
|
+
styling:
|
|
43
|
+
components:
|
|
44
|
+
component_path:
|
|
45
|
+
forms:
|
|
46
|
+
|
|
47
|
+
# Component Usage (MUST use these)
|
|
48
|
+
use:
|
|
49
|
+
buttons:
|
|
50
|
+
inputs:
|
|
51
|
+
modals:
|
|
52
|
+
cards:
|
|
53
|
+
feedback:
|
|
54
|
+
icons:
|
|
55
|
+
links:
|
|
56
|
+
|
|
57
|
+
# Semantic Rules (consistency & relationships)
|
|
58
|
+
semantics:
|
|
59
|
+
hierarchy:
|
|
60
|
+
- <e.g., "primary actions must be visually distinct from secondary">
|
|
61
|
+
- <e.g., "destructive actions should be visually cautionary">
|
|
62
|
+
- <e.g., "page titles should be visually heavier than section titles">
|
|
63
|
+
consistency:
|
|
64
|
+
- <e.g., "all primary buttons should share the same visual weight">
|
|
65
|
+
- <e.g., "form inputs should have uniform height and padding">
|
|
66
|
+
- <e.g., "card padding should be consistent across the app">
|
|
67
|
+
- <e.g., "interactive elements should have consistent hover/focus states">
|
|
68
|
+
spacing:
|
|
69
|
+
- <e.g., "use the spacing scale \u2014 no arbitrary values">
|
|
70
|
+
- <e.g., "related elements should be closer than unrelated">
|
|
71
|
+
- <e.g., "section spacing should be larger than element spacing">
|
|
72
|
+
layout:
|
|
73
|
+
- <e.g., "use gap for sibling spacing, not margin">
|
|
74
|
+
- <e.g., "containers should have consistent max-width and padding">
|
|
75
|
+
|
|
76
|
+
# Patterns (structural, not values)
|
|
77
|
+
patterns:
|
|
78
|
+
forms: <e.g., "FormField + Controller + zod schema">
|
|
79
|
+
conditionals: <e.g., "cn() for class merging">
|
|
80
|
+
loading: <e.g., "Skeleton for content, Spinner for actions">
|
|
81
|
+
errors: <e.g., "ErrorBoundary at route, inline for forms">
|
|
82
|
+
responsive: <e.g., "mobile-first, standard breakpoints only">
|
|
83
|
+
|
|
84
|
+
# Component Authoring
|
|
85
|
+
authoring:
|
|
86
|
+
- <e.g., "forwardRef for interactive components">
|
|
87
|
+
- <e.g., "variants via CVA or component props, not className overrides">
|
|
88
|
+
- <e.g., "extract when used 2+ times">
|
|
89
|
+
- <e.g., "'use client' only when needed">
|
|
90
|
+
|
|
91
|
+
# Forbidden
|
|
92
|
+
forbidden:
|
|
93
|
+
- <e.g., "inline style={{}}">
|
|
94
|
+
- <e.g., "raw HTML elements when component exists">
|
|
95
|
+
- <e.g., "arbitrary values \u2014 use scale">
|
|
96
|
+
- <e.g., "className overrides that break visual consistency">
|
|
97
|
+
- <e.g., "one-off spacing that doesn't match siblings">
|
|
98
|
+
|
|
99
|
+
# Legacy (if migration in progress)
|
|
100
|
+
legacy:
|
|
101
|
+
- <e.g., "old: CSS modules \u2192 new: Tailwind">
|
|
102
|
+
- <e.g., "old: Formik \u2192 new: react-hook-form">
|
|
103
|
+
|
|
104
|
+
# Conventions
|
|
105
|
+
conventions:
|
|
106
|
+
-
|
|
107
|
+
-
|
|
108
|
+
-
|
|
109
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
## Rules
|
|
112
|
+
|
|
113
|
+
- **Semantic over specific**: "consistent padding" not "p-4"
|
|
114
|
+
- **Relationships over absolutes**: "heavier than" not "font-bold"
|
|
115
|
+
- **Intent over implementation**: "visually distinct" not "blue background"
|
|
116
|
+
- **Prescriptive**: Define target state, not current state
|
|
117
|
+
- **Terse**: No prose. Fragments and short phrases only.
|
|
118
|
+
- **Actionable**: Every rule should be human-verifiable
|
|
119
|
+
- **Omit if N/A**: Skip sections that don't apply
|
|
120
|
+
- **Max 5 items** per section \u2014 highest impact only
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
// src/utils/skill-loader.ts
|
|
124
|
+
import { readFileSync, readdirSync, statSync, existsSync } from "fs";
|
|
125
|
+
import { join, dirname, relative } from "path";
|
|
126
|
+
import { fileURLToPath } from "url";
|
|
127
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
128
|
+
var __dirname = dirname(__filename);
|
|
129
|
+
function getSkillsDir() {
|
|
130
|
+
const devPath = join(__dirname, "..", "..", "skills");
|
|
131
|
+
const prodPath = join(__dirname, "..", "skills");
|
|
132
|
+
if (existsSync(devPath)) {
|
|
133
|
+
return devPath;
|
|
134
|
+
}
|
|
135
|
+
if (existsSync(prodPath)) {
|
|
136
|
+
return prodPath;
|
|
137
|
+
}
|
|
138
|
+
throw new Error(
|
|
139
|
+
"Could not find skills directory. This is a bug in uilint installation."
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function collectFiles(dir, baseDir) {
|
|
143
|
+
const files = [];
|
|
144
|
+
const entries = readdirSync(dir);
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const fullPath = join(dir, entry);
|
|
147
|
+
const stat = statSync(fullPath);
|
|
148
|
+
if (stat.isDirectory()) {
|
|
149
|
+
files.push(...collectFiles(fullPath, baseDir));
|
|
150
|
+
} else if (stat.isFile()) {
|
|
151
|
+
const relativePath = relative(baseDir, fullPath);
|
|
152
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
153
|
+
files.push({ relativePath, content });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return files;
|
|
157
|
+
}
|
|
158
|
+
function loadSkill(name) {
|
|
159
|
+
const skillsDir = getSkillsDir();
|
|
160
|
+
const skillDir = join(skillsDir, name);
|
|
161
|
+
if (!existsSync(skillDir)) {
|
|
162
|
+
throw new Error(`Skill "${name}" not found in ${skillsDir}`);
|
|
163
|
+
}
|
|
164
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
165
|
+
if (!existsSync(skillMdPath)) {
|
|
166
|
+
throw new Error(`Skill "${name}" is missing SKILL.md`);
|
|
167
|
+
}
|
|
168
|
+
const files = collectFiles(skillDir, skillDir);
|
|
169
|
+
return { name, files };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
GENSTYLEGUIDE_COMMAND_MD,
|
|
174
|
+
loadSkill
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=chunk-2RNDQVEK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/install/constants.ts","../src/utils/skill-loader.ts"],"sourcesContent":["/**\n * Constants for the install command\n *\n * Contains file content templates for commands.\n */\n\n// ============================================================================\n// Cursor Commands\n// ============================================================================\n\nexport const GENSTYLEGUIDE_COMMAND_MD = `# React Style Guide Generator\n\nAnalyze the React UI codebase to produce a **prescriptive, semantic** style guide. Focus on consistency, intent, and relationships—not specific values.\n\n## Philosophy\n\n1. **Identify the intended architecture** from the best patterns in use\n2. **Prescribe semantic rules** — about consistency and relationships, not pixels\n3. **Stay general** — \"primary buttons should be visually consistent\" not \"buttons use px-4\"\n4. **Focus on intent** — what should FEEL the same, not what values to use\n\n## Analysis Steps\n\n### 1. Detect the Stack\n- Framework: Next.js (App Router? Pages?), Vite, CRA\n- Component system: shadcn, MUI, Chakra, Radix, custom\n- Styling: Tailwind, CSS Modules, styled-components\n- Forms: react-hook-form, Formik, native\n- State: React context, Zustand, Redux, Jotai\n\n### 2. Identify Best Patterns\nExamine the **best-written** components. Look at:\n- \\`components/ui/*\\` — the design system\n- Recently modified files — current standards\n- Shared layouts — structural patterns\n\n### 3. Infer Visual Hierarchy & Intent\nUnderstand the design language:\n- What distinguishes primary vs secondary actions?\n- How is visual hierarchy established?\n- What creates consistency across similar elements?\n\n## Output Format\n\nGenerate at \\`<nextjs app root>/.uilint/styleguide.md\\`:\n\\`\\`\\`yaml\n# Stack\nframework: \nstyling: \ncomponents: \ncomponent_path: \nforms: \n\n# Component Usage (MUST use these)\nuse:\n buttons: \n inputs: \n modals: \n cards: \n feedback: \n icons: \n links: \n\n# Semantic Rules (consistency & relationships)\nsemantics:\n hierarchy:\n - <e.g., \"primary actions must be visually distinct from secondary\">\n - <e.g., \"destructive actions should be visually cautionary\">\n - <e.g., \"page titles should be visually heavier than section titles\">\n consistency:\n - <e.g., \"all primary buttons should share the same visual weight\">\n - <e.g., \"form inputs should have uniform height and padding\">\n - <e.g., \"card padding should be consistent across the app\">\n - <e.g., \"interactive elements should have consistent hover/focus states\">\n spacing:\n - <e.g., \"use the spacing scale — no arbitrary values\">\n - <e.g., \"related elements should be closer than unrelated\">\n - <e.g., \"section spacing should be larger than element spacing\">\n layout:\n - <e.g., \"use gap for sibling spacing, not margin\">\n - <e.g., \"containers should have consistent max-width and padding\">\n\n# Patterns (structural, not values)\npatterns:\n forms: <e.g., \"FormField + Controller + zod schema\">\n conditionals: <e.g., \"cn() for class merging\">\n loading: <e.g., \"Skeleton for content, Spinner for actions\">\n errors: <e.g., \"ErrorBoundary at route, inline for forms\">\n responsive: <e.g., \"mobile-first, standard breakpoints only\">\n\n# Component Authoring\nauthoring:\n - <e.g., \"forwardRef for interactive components\">\n - <e.g., \"variants via CVA or component props, not className overrides\">\n - <e.g., \"extract when used 2+ times\">\n - <e.g., \"'use client' only when needed\">\n\n# Forbidden\nforbidden:\n - <e.g., \"inline style={{}}\">\n - <e.g., \"raw HTML elements when component exists\">\n - <e.g., \"arbitrary values — use scale\">\n - <e.g., \"className overrides that break visual consistency\">\n - <e.g., \"one-off spacing that doesn't match siblings\">\n\n# Legacy (if migration in progress)\nlegacy:\n - <e.g., \"old: CSS modules → new: Tailwind\">\n - <e.g., \"old: Formik → new: react-hook-form\">\n\n# Conventions\nconventions:\n - \n - \n - \n\\`\\`\\`\n\n## Rules\n\n- **Semantic over specific**: \"consistent padding\" not \"p-4\"\n- **Relationships over absolutes**: \"heavier than\" not \"font-bold\"\n- **Intent over implementation**: \"visually distinct\" not \"blue background\"\n- **Prescriptive**: Define target state, not current state\n- **Terse**: No prose. Fragments and short phrases only.\n- **Actionable**: Every rule should be human-verifiable\n- **Omit if N/A**: Skip sections that don't apply\n- **Max 5 items** per section — highest impact only\n`;\n\n","/**\n * Skill Loader Utility\n *\n * Loads Agent Skill files from the bundled skills directory for installation\n * into user projects. Skills follow the Agent Skills specification\n * (agentskills.io).\n */\n\nimport { readFileSync, readdirSync, statSync, existsSync } from \"fs\";\nimport { join, dirname, relative } from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Represents a file in a skill directory\n */\nexport interface SkillFile {\n /** Relative path within the skill directory */\n relativePath: string;\n /** File content */\n content: string;\n}\n\n/**\n * Represents a complete skill ready for installation\n */\nexport interface Skill {\n /** Skill name (directory name) */\n name: string;\n /** All files in the skill */\n files: SkillFile[];\n}\n\n/**\n * Get the path to the bundled skills directory\n */\nfunction getSkillsDir(): string {\n // In development: packages/uilint/skills/\n // In production (installed): node_modules/uilint/dist/ -> ../skills/\n const devPath = join(__dirname, \"..\", \"..\", \"skills\");\n const prodPath = join(__dirname, \"..\", \"skills\");\n\n if (existsSync(devPath)) {\n return devPath;\n }\n if (existsSync(prodPath)) {\n return prodPath;\n }\n\n throw new Error(\n \"Could not find skills directory. This is a bug in uilint installation.\"\n );\n}\n\n/**\n * Recursively collect all files in a directory\n */\nfunction collectFiles(dir: string, baseDir: string): SkillFile[] {\n const files: SkillFile[] = [];\n const entries = readdirSync(dir);\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n files.push(...collectFiles(fullPath, baseDir));\n } else if (stat.isFile()) {\n const relativePath = relative(baseDir, fullPath);\n const content = readFileSync(fullPath, \"utf-8\");\n files.push({ relativePath, content });\n }\n }\n\n return files;\n}\n\n/**\n * Load a specific skill by name\n */\nexport function loadSkill(name: string): Skill {\n const skillsDir = getSkillsDir();\n const skillDir = join(skillsDir, name);\n\n if (!existsSync(skillDir)) {\n throw new Error(`Skill \"${name}\" not found in ${skillsDir}`);\n }\n\n const skillMdPath = join(skillDir, \"SKILL.md\");\n if (!existsSync(skillMdPath)) {\n throw new Error(`Skill \"${name}\" is missing SKILL.md`);\n }\n\n const files = collectFiles(skillDir, skillDir);\n\n return { name, files };\n}\n\n/**\n * Load all available skills\n */\nexport function loadAllSkills(): Skill[] {\n const skillsDir = getSkillsDir();\n const entries = readdirSync(skillsDir);\n const skills: Skill[] = [];\n\n for (const entry of entries) {\n const skillDir = join(skillsDir, entry);\n const stat = statSync(skillDir);\n\n if (stat.isDirectory()) {\n const skillMdPath = join(skillDir, \"SKILL.md\");\n if (existsSync(skillMdPath)) {\n skills.push(loadSkill(entry));\n }\n }\n }\n\n return skills;\n}\n\n/**\n * Get the list of available skill names\n */\nexport function getAvailableSkillNames(): string[] {\n const skillsDir = getSkillsDir();\n const entries = readdirSync(skillsDir);\n const names: string[] = [];\n\n for (const entry of entries) {\n const skillDir = join(skillsDir, entry);\n const stat = statSync(skillDir);\n\n if (stat.isDirectory()) {\n const skillMdPath = join(skillDir, \"SKILL.md\");\n if (existsSync(skillMdPath)) {\n names.push(entry);\n }\n }\n }\n\n return names;\n}\n"],"mappings":";;;AAUO,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACFxC,SAAS,cAAc,aAAa,UAAU,kBAAkB;AAChE,SAAS,MAAM,SAAS,gBAAgB;AACxC,SAAS,qBAAqB;AAE9B,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AAyBpC,SAAS,eAAuB;AAG9B,QAAM,UAAU,KAAK,WAAW,MAAM,MAAM,QAAQ;AACpD,QAAM,WAAW,KAAK,WAAW,MAAM,QAAQ;AAE/C,MAAI,WAAW,OAAO,GAAG;AACvB,WAAO;AAAA,EACT;AACA,MAAI,WAAW,QAAQ,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAKA,SAAS,aAAa,KAAa,SAA8B;AAC/D,QAAM,QAAqB,CAAC;AAC5B,QAAM,UAAU,YAAY,GAAG;AAE/B,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAW,KAAK,KAAK,KAAK;AAChC,UAAM,OAAO,SAAS,QAAQ;AAE9B,QAAI,KAAK,YAAY,GAAG;AACtB,YAAM,KAAK,GAAG,aAAa,UAAU,OAAO,CAAC;AAAA,IAC/C,WAAW,KAAK,OAAO,GAAG;AACxB,YAAM,eAAe,SAAS,SAAS,QAAQ;AAC/C,YAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,YAAM,KAAK,EAAE,cAAc,QAAQ,CAAC;AAAA,IACtC;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,UAAU,MAAqB;AAC7C,QAAM,YAAY,aAAa;AAC/B,QAAM,WAAW,KAAK,WAAW,IAAI;AAErC,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,MAAM,UAAU,IAAI,kBAAkB,SAAS,EAAE;AAAA,EAC7D;AAEA,QAAM,cAAc,KAAK,UAAU,UAAU;AAC7C,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,UAAM,IAAI,MAAM,UAAU,IAAI,uBAAuB;AAAA,EACvD;AAEA,QAAM,QAAQ,aAAa,UAAU,QAAQ;AAE7C,SAAO,EAAE,MAAM,MAAM;AACvB;","names":[]}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/prompts.ts
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
function getCLIVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pkgPath = join(__dirname, "..", "..", "package.json");
|
|
13
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
14
|
+
return pkg.version || "0.0.0";
|
|
15
|
+
} catch {
|
|
16
|
+
return "0.0.0";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function intro2(title) {
|
|
20
|
+
const version = getCLIVersion();
|
|
21
|
+
const header = pc.bold(pc.cyan("\u25C6 UILint")) + pc.dim(` v${version}`);
|
|
22
|
+
console.log();
|
|
23
|
+
p.intro(title ? `${header} ${pc.dim("\xB7")} ${title}` : header);
|
|
24
|
+
}
|
|
25
|
+
function outro2(message) {
|
|
26
|
+
p.outro(pc.green(message));
|
|
27
|
+
}
|
|
28
|
+
function cancel2(message = "Operation cancelled.") {
|
|
29
|
+
p.cancel(pc.yellow(message));
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
function handleCancel(value) {
|
|
33
|
+
if (p.isCancel(value)) {
|
|
34
|
+
cancel2();
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
async function withSpinner(message, fn) {
|
|
40
|
+
const s = p.spinner();
|
|
41
|
+
s.start(message);
|
|
42
|
+
try {
|
|
43
|
+
const result = fn.length >= 1 ? await fn(s) : await fn();
|
|
44
|
+
s.stop(pc.green("\u2713 ") + message);
|
|
45
|
+
return result;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
s.stop(pc.red("\u2717 ") + message);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function createSpinner() {
|
|
52
|
+
return p.spinner();
|
|
53
|
+
}
|
|
54
|
+
function note2(message, title) {
|
|
55
|
+
p.note(message, title);
|
|
56
|
+
}
|
|
57
|
+
function log2(message) {
|
|
58
|
+
p.log.message(message);
|
|
59
|
+
}
|
|
60
|
+
function logInfo(message) {
|
|
61
|
+
p.log.info(message);
|
|
62
|
+
}
|
|
63
|
+
function logSuccess(message) {
|
|
64
|
+
p.log.success(message);
|
|
65
|
+
}
|
|
66
|
+
function logWarning(message) {
|
|
67
|
+
p.log.warn(message);
|
|
68
|
+
}
|
|
69
|
+
function logError(message) {
|
|
70
|
+
p.log.error(message);
|
|
71
|
+
}
|
|
72
|
+
async function select2(options) {
|
|
73
|
+
const result = await p.select({
|
|
74
|
+
message: options.message,
|
|
75
|
+
options: options.options,
|
|
76
|
+
initialValue: options.initialValue
|
|
77
|
+
});
|
|
78
|
+
return handleCancel(result);
|
|
79
|
+
}
|
|
80
|
+
async function confirm2(options) {
|
|
81
|
+
const result = await p.confirm({
|
|
82
|
+
message: options.message,
|
|
83
|
+
initialValue: options.initialValue ?? true
|
|
84
|
+
});
|
|
85
|
+
return handleCancel(result);
|
|
86
|
+
}
|
|
87
|
+
async function multiselect2(options) {
|
|
88
|
+
const result = await p.multiselect({
|
|
89
|
+
message: options.message,
|
|
90
|
+
options: options.options,
|
|
91
|
+
required: options.required,
|
|
92
|
+
initialValues: options.initialValues
|
|
93
|
+
});
|
|
94
|
+
return handleCancel(result);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/utils/next-detect.ts
|
|
98
|
+
import { existsSync, readdirSync } from "fs";
|
|
99
|
+
import { join as join2 } from "path";
|
|
100
|
+
function fileExists(projectPath, relPath) {
|
|
101
|
+
return existsSync(join2(projectPath, relPath));
|
|
102
|
+
}
|
|
103
|
+
function detectNextAppRouter(projectPath) {
|
|
104
|
+
const roots = ["app", join2("src", "app")];
|
|
105
|
+
const candidates = [];
|
|
106
|
+
let chosenRoot = null;
|
|
107
|
+
for (const root of roots) {
|
|
108
|
+
if (existsSync(join2(projectPath, root))) {
|
|
109
|
+
chosenRoot = root;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!chosenRoot) return null;
|
|
114
|
+
const entryCandidates = [
|
|
115
|
+
join2(chosenRoot, "layout.tsx"),
|
|
116
|
+
join2(chosenRoot, "layout.jsx"),
|
|
117
|
+
join2(chosenRoot, "layout.ts"),
|
|
118
|
+
join2(chosenRoot, "layout.js"),
|
|
119
|
+
// Fallbacks (less ideal, but can work):
|
|
120
|
+
join2(chosenRoot, "page.tsx"),
|
|
121
|
+
join2(chosenRoot, "page.jsx")
|
|
122
|
+
];
|
|
123
|
+
for (const rel of entryCandidates) {
|
|
124
|
+
if (fileExists(projectPath, rel)) candidates.push(rel);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
appRoot: chosenRoot,
|
|
128
|
+
appRootAbs: join2(projectPath, chosenRoot),
|
|
129
|
+
candidates
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
133
|
+
"node_modules",
|
|
134
|
+
".git",
|
|
135
|
+
".next",
|
|
136
|
+
"dist",
|
|
137
|
+
"build",
|
|
138
|
+
"out",
|
|
139
|
+
".turbo",
|
|
140
|
+
".vercel",
|
|
141
|
+
".cursor",
|
|
142
|
+
"coverage",
|
|
143
|
+
".uilint"
|
|
144
|
+
]);
|
|
145
|
+
function findNextAppRouterProjects(rootDir, options) {
|
|
146
|
+
const maxDepth = options?.maxDepth ?? 4;
|
|
147
|
+
const ignoreDirs = options?.ignoreDirs ?? DEFAULT_IGNORE_DIRS;
|
|
148
|
+
const results = [];
|
|
149
|
+
const visited = /* @__PURE__ */ new Set();
|
|
150
|
+
function walk(dir, depth) {
|
|
151
|
+
if (depth > maxDepth) return;
|
|
152
|
+
if (visited.has(dir)) return;
|
|
153
|
+
visited.add(dir);
|
|
154
|
+
const detection = detectNextAppRouter(dir);
|
|
155
|
+
if (detection) {
|
|
156
|
+
results.push({ projectPath: dir, detection });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
let entries = [];
|
|
160
|
+
try {
|
|
161
|
+
entries = readdirSync(dir, { withFileTypes: true }).map((d) => ({
|
|
162
|
+
name: d.name,
|
|
163
|
+
isDirectory: d.isDirectory()
|
|
164
|
+
}));
|
|
165
|
+
} catch {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
for (const ent of entries) {
|
|
169
|
+
if (!ent.isDirectory) continue;
|
|
170
|
+
if (ignoreDirs.has(ent.name)) continue;
|
|
171
|
+
if (ent.name.startsWith(".") && ent.name !== ".") continue;
|
|
172
|
+
walk(join2(dir, ent.name), depth + 1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
walk(rootDir, 0);
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
pc,
|
|
181
|
+
intro2 as intro,
|
|
182
|
+
outro2 as outro,
|
|
183
|
+
withSpinner,
|
|
184
|
+
createSpinner,
|
|
185
|
+
note2 as note,
|
|
186
|
+
log2 as log,
|
|
187
|
+
logInfo,
|
|
188
|
+
logSuccess,
|
|
189
|
+
logWarning,
|
|
190
|
+
logError,
|
|
191
|
+
select2 as select,
|
|
192
|
+
confirm2 as confirm,
|
|
193
|
+
multiselect2 as multiselect,
|
|
194
|
+
detectNextAppRouter,
|
|
195
|
+
findNextAppRouterProjects
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=chunk-FRNXXIEM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/prompts.ts","../src/utils/next-detect.ts"],"sourcesContent":["/**\n * Shared clack/prompts utilities for UILint CLI\n * Provides branded intro/outro, spinners, and common UI patterns\n */\n\nimport * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\n\n/**\n * Get the CLI version from package.json\n */\nfunction getCLIVersion(): string {\n try {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, \"..\", \"..\", \"package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as {\n version?: string;\n };\n return pkg.version || \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/**\n * Branded UILint intro with logo and version\n */\nexport function intro(title?: string): void {\n const version = getCLIVersion();\n const header = pc.bold(pc.cyan(\"◆ UILint\")) + pc.dim(` v${version}`);\n \n console.log();\n p.intro(title ? `${header} ${pc.dim(\"·\")} ${title}` : header);\n}\n\n/**\n * Styled outro with next steps\n */\nexport function outro(message: string): void {\n p.outro(pc.green(message));\n}\n\n/**\n * Cancel message when user exits\n */\nexport function cancel(message = \"Operation cancelled.\"): void {\n p.cancel(pc.yellow(message));\n process.exit(0);\n}\n\n/**\n * Check if user cancelled a prompt\n */\nexport function isCancel(value: unknown): value is symbol {\n return p.isCancel(value);\n}\n\n/**\n * Handle cancel check - exits if cancelled\n */\nexport function handleCancel<T>(value: T | symbol): T {\n if (p.isCancel(value)) {\n cancel();\n process.exit(0);\n }\n return value as T;\n}\n\n/**\n * Spinner wrapper with automatic error handling\n */\nexport async function withSpinner<T>(\n message: string,\n fn: (() => Promise<T>) | ((spinner: ReturnType<typeof p.spinner>) => Promise<T>)\n): Promise<T> {\n const s = p.spinner();\n s.start(message);\n try {\n const result =\n fn.length >= 1\n ? await (fn as (spinner: ReturnType<typeof p.spinner>) => Promise<T>)(s)\n : await (fn as () => Promise<T>)();\n s.stop(pc.green(\"✓ \") + message);\n return result;\n } catch (error) {\n s.stop(pc.red(\"✗ \") + message);\n throw error;\n }\n}\n\n/**\n * Spinner that can be updated\n */\nexport function createSpinner() {\n return p.spinner();\n}\n\n/**\n * Display a note box\n */\nexport function note(message: string, title?: string): void {\n p.note(message, title);\n}\n\n/**\n * Display a log message\n */\nexport function log(message: string): void {\n p.log.message(message);\n}\n\n/**\n * Display an info message\n */\nexport function logInfo(message: string): void {\n p.log.info(message);\n}\n\n/**\n * Display a success message\n */\nexport function logSuccess(message: string): void {\n p.log.success(message);\n}\n\n/**\n * Display a warning message\n */\nexport function logWarning(message: string): void {\n p.log.warn(message);\n}\n\n/**\n * Display an error message\n */\nexport function logError(message: string): void {\n p.log.error(message);\n}\n\n/**\n * Display a step message\n */\nexport function logStep(message: string): void {\n p.log.step(message);\n}\n\n/**\n * Select prompt wrapper\n */\nexport async function select<T extends string>(options: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n initialValue?: T;\n}): Promise<T> {\n const result = await p.select({\n message: options.message,\n options: options.options as { value: T; label: string; hint?: string }[],\n initialValue: options.initialValue,\n } as Parameters<typeof p.select>[0]);\n return handleCancel(result) as T;\n}\n\n/**\n * Confirm prompt wrapper\n */\nexport async function confirm(options: {\n message: string;\n initialValue?: boolean;\n}): Promise<boolean> {\n const result = await p.confirm({\n message: options.message,\n initialValue: options.initialValue ?? true,\n });\n return handleCancel(result);\n}\n\n/**\n * Text input prompt wrapper\n */\nexport async function text(options: {\n message: string;\n placeholder?: string;\n defaultValue?: string;\n validate?: (value: string) => string | Error | undefined;\n}): Promise<string> {\n const result = await p.text(options);\n return handleCancel(result);\n}\n\n/**\n * Multiselect prompt wrapper\n */\nexport async function multiselect<T extends string>(options: {\n message: string;\n options: Array<{ value: T; label: string; hint?: string }>;\n required?: boolean;\n initialValues?: T[];\n}): Promise<T[]> {\n const result = await p.multiselect({\n message: options.message,\n options: options.options as { value: T; label: string; hint?: string }[],\n required: options.required,\n initialValues: options.initialValues,\n } as Parameters<typeof p.multiselect>[0]);\n return handleCancel(result) as T[];\n}\n\n/**\n * Group of tasks displayed together\n */\nexport async function group<T extends Record<string, unknown>>(\n prompts: p.PromptGroup<T>,\n options?: p.PromptGroupOptions<T>\n): Promise<T> {\n const result = await p.group(prompts, options);\n return result;\n}\n\n// Re-export picocolors for consistent styling\nexport { pc };\n","import { existsSync, readdirSync } from \"fs\";\nimport { join } from \"path\";\n\nexport interface NextAppRouterDetection {\n /**\n * Relative path to the Next App Router root dir (either \"app\" or \"src/app\").\n */\n appRoot: string;\n /**\n * Absolute path to the App Router root dir.\n */\n appRootAbs: string;\n /**\n * Candidate entry files (relative paths) that are good injection targets.\n */\n candidates: string[];\n}\n\nfunction fileExists(projectPath: string, relPath: string): boolean {\n return existsSync(join(projectPath, relPath));\n}\n\nexport function detectNextAppRouter(\n projectPath: string\n): NextAppRouterDetection | null {\n const roots = [\"app\", join(\"src\", \"app\")];\n const candidates: string[] = [];\n\n let chosenRoot: string | null = null;\n for (const root of roots) {\n if (existsSync(join(projectPath, root))) {\n chosenRoot = root;\n break;\n }\n }\n\n if (!chosenRoot) return null;\n\n // Prioritize layout files (Next App Router canonical integration point).\n const entryCandidates = [\n join(chosenRoot, \"layout.tsx\"),\n join(chosenRoot, \"layout.jsx\"),\n join(chosenRoot, \"layout.ts\"),\n join(chosenRoot, \"layout.js\"),\n // Fallbacks (less ideal, but can work):\n join(chosenRoot, \"page.tsx\"),\n join(chosenRoot, \"page.jsx\"),\n ];\n\n for (const rel of entryCandidates) {\n if (fileExists(projectPath, rel)) candidates.push(rel);\n }\n\n // If nothing exists, still return detection so routes can be installed.\n return {\n appRoot: chosenRoot,\n appRootAbs: join(projectPath, chosenRoot),\n candidates,\n };\n}\n\nexport interface NextAppRouterProjectMatch {\n /**\n * Absolute path to the Next project root (dir containing app/ or src/app/).\n */\n projectPath: string;\n detection: NextAppRouterDetection;\n}\n\nconst DEFAULT_IGNORE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \".next\",\n \"dist\",\n \"build\",\n \"out\",\n \".turbo\",\n \".vercel\",\n \".cursor\",\n \"coverage\",\n \".uilint\",\n]);\n\n/**\n * Best-effort monorepo discovery for Next.js App Router apps.\n *\n * Walks down from `rootDir` looking for directories that contain `app/` or\n * `src/app/`. Skips common large/irrelevant dirs.\n */\nexport function findNextAppRouterProjects(\n rootDir: string,\n options?: { maxDepth?: number; ignoreDirs?: Set<string> }\n): NextAppRouterProjectMatch[] {\n const maxDepth = options?.maxDepth ?? 4;\n const ignoreDirs = options?.ignoreDirs ?? DEFAULT_IGNORE_DIRS;\n const results: NextAppRouterProjectMatch[] = [];\n const visited = new Set<string>();\n\n function walk(dir: string, depth: number) {\n if (depth > maxDepth) return;\n if (visited.has(dir)) return;\n visited.add(dir);\n\n const detection = detectNextAppRouter(dir);\n if (detection) {\n results.push({ projectPath: dir, detection });\n // Don't descend further once we found a project root (avoid nested hits).\n return;\n }\n\n let entries: Array<{ name: string; isDirectory: boolean }> = [];\n try {\n entries = readdirSync(dir, { withFileTypes: true }).map((d) => ({\n name: d.name,\n isDirectory: d.isDirectory(),\n }));\n } catch {\n return;\n }\n\n for (const ent of entries) {\n if (!ent.isDirectory) continue;\n if (ignoreDirs.has(ent.name)) continue;\n // Skip hidden dirs by default (except `src` which matters)\n if (ent.name.startsWith(\".\") && ent.name !== \".\") continue;\n walk(join(dir, ent.name), depth + 1);\n }\n }\n\n walk(rootDir, 0);\n return results;\n}\n"],"mappings":";;;AAKA,YAAY,OAAO;AACnB,OAAO,QAAQ;AACf,SAAS,oBAAoB;AAC7B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAK9B,SAAS,gBAAwB;AAC/B,MAAI;AACF,UAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,UAAM,UAAU,KAAK,WAAW,MAAM,MAAM,cAAc;AAC1D,UAAM,MAAM,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AAGrD,WAAO,IAAI,WAAW;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAASA,OAAM,OAAsB;AAC1C,QAAM,UAAU,cAAc;AAC9B,QAAM,SAAS,GAAG,KAAK,GAAG,KAAK,eAAU,CAAC,IAAI,GAAG,IAAI,KAAK,OAAO,EAAE;AAEnE,UAAQ,IAAI;AACZ,EAAE,QAAM,QAAQ,GAAG,MAAM,IAAI,GAAG,IAAI,MAAG,CAAC,IAAI,KAAK,KAAK,MAAM;AAC9D;AAKO,SAASC,OAAM,SAAuB;AAC3C,EAAE,QAAM,GAAG,MAAM,OAAO,CAAC;AAC3B;AAKO,SAASC,QAAO,UAAU,wBAA8B;AAC7D,EAAE,SAAO,GAAG,OAAO,OAAO,CAAC;AAC3B,UAAQ,KAAK,CAAC;AAChB;AAYO,SAAS,aAAgB,OAAsB;AACpD,MAAM,WAAS,KAAK,GAAG;AACrB,IAAAC,QAAO;AACP,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAKA,eAAsB,YACpB,SACA,IACY;AACZ,QAAM,IAAM,UAAQ;AACpB,IAAE,MAAM,OAAO;AACf,MAAI;AACF,UAAM,SACJ,GAAG,UAAU,IACT,MAAO,GAA6D,CAAC,IACrE,MAAO,GAAwB;AACrC,MAAE,KAAK,GAAG,MAAM,SAAI,IAAI,OAAO;AAC/B,WAAO;AAAA,EACT,SAAS,OAAO;AACd,MAAE,KAAK,GAAG,IAAI,SAAI,IAAI,OAAO;AAC7B,UAAM;AAAA,EACR;AACF;AAKO,SAAS,gBAAgB;AAC9B,SAAS,UAAQ;AACnB;AAKO,SAASC,MAAK,SAAiB,OAAsB;AAC1D,EAAE,OAAK,SAAS,KAAK;AACvB;AAKO,SAASC,KAAI,SAAuB;AACzC,EAAE,MAAI,QAAQ,OAAO;AACvB;AAKO,SAAS,QAAQ,SAAuB;AAC7C,EAAE,MAAI,KAAK,OAAO;AACpB;AAKO,SAAS,WAAW,SAAuB;AAChD,EAAE,MAAI,QAAQ,OAAO;AACvB;AAKO,SAAS,WAAW,SAAuB;AAChD,EAAE,MAAI,KAAK,OAAO;AACpB;AAKO,SAAS,SAAS,SAAuB;AAC9C,EAAE,MAAI,MAAM,OAAO;AACrB;AAYA,eAAsBC,QAAyB,SAIhC;AACb,QAAM,SAAS,MAAQ,SAAO;AAAA,IAC5B,SAAS,QAAQ;AAAA,IACjB,SAAS,QAAQ;AAAA,IACjB,cAAc,QAAQ;AAAA,EACxB,CAAmC;AACnC,SAAO,aAAa,MAAM;AAC5B;AAKA,eAAsBC,SAAQ,SAGT;AACnB,QAAM,SAAS,MAAQ,UAAQ;AAAA,IAC7B,SAAS,QAAQ;AAAA,IACjB,cAAc,QAAQ,gBAAgB;AAAA,EACxC,CAAC;AACD,SAAO,aAAa,MAAM;AAC5B;AAkBA,eAAsBC,aAA8B,SAKnC;AACf,QAAM,SAAS,MAAQ,cAAY;AAAA,IACjC,SAAS,QAAQ;AAAA,IACjB,SAAS,QAAQ;AAAA,IACjB,UAAU,QAAQ;AAAA,IAClB,eAAe,QAAQ;AAAA,EACzB,CAAwC;AACxC,SAAO,aAAa,MAAM;AAC5B;;;AChNA,SAAS,YAAY,mBAAmB;AACxC,SAAS,QAAAC,aAAY;AAiBrB,SAAS,WAAW,aAAqB,SAA0B;AACjE,SAAO,WAAWA,MAAK,aAAa,OAAO,CAAC;AAC9C;AAEO,SAAS,oBACd,aAC+B;AAC/B,QAAM,QAAQ,CAAC,OAAOA,MAAK,OAAO,KAAK,CAAC;AACxC,QAAM,aAAuB,CAAC;AAE9B,MAAI,aAA4B;AAChC,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAWA,MAAK,aAAa,IAAI,CAAC,GAAG;AACvC,mBAAa;AACb;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,WAAY,QAAO;AAGxB,QAAM,kBAAkB;AAAA,IACtBA,MAAK,YAAY,YAAY;AAAA,IAC7BA,MAAK,YAAY,YAAY;AAAA,IAC7BA,MAAK,YAAY,WAAW;AAAA,IAC5BA,MAAK,YAAY,WAAW;AAAA;AAAA,IAE5BA,MAAK,YAAY,UAAU;AAAA,IAC3BA,MAAK,YAAY,UAAU;AAAA,EAC7B;AAEA,aAAW,OAAO,iBAAiB;AACjC,QAAI,WAAW,aAAa,GAAG,EAAG,YAAW,KAAK,GAAG;AAAA,EACvD;AAGA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAYA,MAAK,aAAa,UAAU;AAAA,IACxC;AAAA,EACF;AACF;AAUA,IAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAQM,SAAS,0BACd,SACA,SAC6B;AAC7B,QAAM,WAAW,SAAS,YAAY;AACtC,QAAM,aAAa,SAAS,cAAc;AAC1C,QAAM,UAAuC,CAAC;AAC9C,QAAM,UAAU,oBAAI,IAAY;AAEhC,WAAS,KAAK,KAAa,OAAe;AACxC,QAAI,QAAQ,SAAU;AACtB,QAAI,QAAQ,IAAI,GAAG,EAAG;AACtB,YAAQ,IAAI,GAAG;AAEf,UAAM,YAAY,oBAAoB,GAAG;AACzC,QAAI,WAAW;AACb,cAAQ,KAAK,EAAE,aAAa,KAAK,UAAU,CAAC;AAE5C;AAAA,IACF;AAEA,QAAI,UAAyD,CAAC;AAC9D,QAAI;AACF,gBAAU,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,QAC9D,MAAM,EAAE;AAAA,QACR,aAAa,EAAE,YAAY;AAAA,MAC7B,EAAE;AAAA,IACJ,QAAQ;AACN;AAAA,IACF;AAEA,eAAW,OAAO,SAAS;AACzB,UAAI,CAAC,IAAI,YAAa;AACtB,UAAI,WAAW,IAAI,IAAI,IAAI,EAAG;AAE9B,UAAI,IAAI,KAAK,WAAW,GAAG,KAAK,IAAI,SAAS,IAAK;AAClD,WAAKA,MAAK,KAAK,IAAI,IAAI,GAAG,QAAQ,CAAC;AAAA,IACrC;AAAA,EACF;AAEA,OAAK,SAAS,CAAC;AACf,SAAO;AACT;","names":["intro","outro","cancel","cancel","note","log","select","confirm","multiselect","join"]}
|