openuispec 0.1.3 → 0.1.4
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/cli/init.ts +39 -141
- package/package.json +1 -1
package/cli/init.ts
CHANGED
|
@@ -35,13 +35,9 @@ async function askList(
|
|
|
35
35
|
options: string[],
|
|
36
36
|
defaults: string[]
|
|
37
37
|
): Promise<string[]> {
|
|
38
|
-
|
|
39
|
-
for (const opt of options) {
|
|
40
|
-
const mark = defaults.includes(opt) ? "[x]" : "[ ]";
|
|
41
|
-
console.log(` ${mark} ${opt}`);
|
|
42
|
-
}
|
|
38
|
+
const defaultStr = defaults.join(", ");
|
|
43
39
|
const raw = (
|
|
44
|
-
await rl.question(
|
|
40
|
+
await rl.question(`${question} [${options.join(", ")}] (${defaultStr}): `)
|
|
45
41
|
).trim();
|
|
46
42
|
if (!raw) return defaults;
|
|
47
43
|
return raw
|
|
@@ -122,84 +118,6 @@ api:
|
|
|
122
118
|
`;
|
|
123
119
|
}
|
|
124
120
|
|
|
125
|
-
function starterColorTokens(): string {
|
|
126
|
-
return `# Design tokens: Color palette
|
|
127
|
-
brand:
|
|
128
|
-
primary:
|
|
129
|
-
"50": "#EEF2FF"
|
|
130
|
-
"100": "#E0E7FF"
|
|
131
|
-
"500": "#6366F1"
|
|
132
|
-
"600": "#4F46E5"
|
|
133
|
-
"900": "#312E81"
|
|
134
|
-
|
|
135
|
-
surface:
|
|
136
|
-
background: "#FFFFFF"
|
|
137
|
-
card: "#F9FAFB"
|
|
138
|
-
elevated: "#FFFFFF"
|
|
139
|
-
|
|
140
|
-
text:
|
|
141
|
-
primary: "#111827"
|
|
142
|
-
secondary: "#6B7280"
|
|
143
|
-
disabled: "#D1D5DB"
|
|
144
|
-
|
|
145
|
-
semantic:
|
|
146
|
-
success: "#10B981"
|
|
147
|
-
warning: "#F59E0B"
|
|
148
|
-
error: "#EF4444"
|
|
149
|
-
info: "#3B82F6"
|
|
150
|
-
`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function starterTypographyTokens(): string {
|
|
154
|
-
return `# Design tokens: Typography
|
|
155
|
-
font_families:
|
|
156
|
-
sans: { default: "System" }
|
|
157
|
-
|
|
158
|
-
type_scale:
|
|
159
|
-
title_lg: { size: 28, weight: bold, tracking: 0, line_height: 1.2 }
|
|
160
|
-
title_md: { size: 22, weight: semibold, tracking: 0, line_height: 1.3 }
|
|
161
|
-
title_sm: { size: 17, weight: semibold, tracking: 0, line_height: 1.3 }
|
|
162
|
-
body_lg: { size: 17, weight: regular, tracking: 0, line_height: 1.5 }
|
|
163
|
-
body_md: { size: 15, weight: regular, tracking: 0, line_height: 1.5 }
|
|
164
|
-
body_sm: { size: 13, weight: regular, tracking: 0, line_height: 1.4 }
|
|
165
|
-
label_lg: { size: 15, weight: medium, tracking: 0.02, line_height: 1.3 }
|
|
166
|
-
label_md: { size: 13, weight: medium, tracking: 0.02, line_height: 1.3 }
|
|
167
|
-
caption: { size: 11, weight: regular, tracking: 0.02, line_height: 1.3 }
|
|
168
|
-
`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function starterSpacingTokens(): string {
|
|
172
|
-
return `# Design tokens: Spacing
|
|
173
|
-
base_unit: 4
|
|
174
|
-
platform_flex: "10%"
|
|
175
|
-
|
|
176
|
-
scale:
|
|
177
|
-
"0": 0
|
|
178
|
-
"1": 4
|
|
179
|
-
"2": 8
|
|
180
|
-
"3": 12
|
|
181
|
-
"4": 16
|
|
182
|
-
"5": 20
|
|
183
|
-
"6": 24
|
|
184
|
-
"8": 32
|
|
185
|
-
"10": 40
|
|
186
|
-
"12": 48
|
|
187
|
-
"16": 64
|
|
188
|
-
`;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function starterLocale(): string {
|
|
192
|
-
return JSON.stringify(
|
|
193
|
-
{
|
|
194
|
-
app: {
|
|
195
|
-
name: "My App",
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
null,
|
|
199
|
-
2
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
121
|
function aiRulesBlock(specDir: string, targets: string[]): string {
|
|
204
122
|
const targetList = targets.map((t) => `"${t}"`).join(", ");
|
|
205
123
|
return `
|
|
@@ -261,10 +179,9 @@ export async function init(): Promise<void> {
|
|
|
261
179
|
console.log("\nOpenUISpec — Project Setup\n");
|
|
262
180
|
|
|
263
181
|
try {
|
|
264
|
-
// 1. Project name
|
|
182
|
+
// 1. Project name (display name in manifest, derived from current folder)
|
|
265
183
|
const cwd = process.cwd();
|
|
266
|
-
const defaultName =
|
|
267
|
-
cwd.split("/").pop()?.replace(/[^a-zA-Z0-9]/g, "") || "MyApp";
|
|
184
|
+
const defaultName = cwd.split("/").pop() || "MyApp";
|
|
268
185
|
const name = await ask(rl, "Project name", defaultName);
|
|
269
186
|
|
|
270
187
|
// 2. Spec directory
|
|
@@ -276,7 +193,7 @@ export async function init(): Promise<void> {
|
|
|
276
193
|
rl,
|
|
277
194
|
"\nWhich platforms?",
|
|
278
195
|
allTargets,
|
|
279
|
-
|
|
196
|
+
allTargets
|
|
280
197
|
);
|
|
281
198
|
|
|
282
199
|
if (targets.length === 0) {
|
|
@@ -284,16 +201,6 @@ export async function init(): Promise<void> {
|
|
|
284
201
|
process.exit(1);
|
|
285
202
|
}
|
|
286
203
|
|
|
287
|
-
// 4. Starter tokens?
|
|
288
|
-
const wantTokens = await ask(rl, "Include starter tokens? (y/n)", "y");
|
|
289
|
-
|
|
290
|
-
// 5. AI rules?
|
|
291
|
-
const wantRules = await ask(
|
|
292
|
-
rl,
|
|
293
|
-
"Add rules to CLAUDE.md and AGENTS.md? (y/n)",
|
|
294
|
-
"y"
|
|
295
|
-
);
|
|
296
|
-
|
|
297
204
|
rl.close();
|
|
298
205
|
|
|
299
206
|
// ── create folders ─────────────────────────────────────────────
|
|
@@ -322,27 +229,6 @@ export async function init(): Promise<void> {
|
|
|
322
229
|
manifestTemplate(name, targets, specDir)
|
|
323
230
|
);
|
|
324
231
|
|
|
325
|
-
// ── starter tokens ─────────────────────────────────────────────
|
|
326
|
-
|
|
327
|
-
if (wantTokens.toLowerCase().startsWith("y")) {
|
|
328
|
-
writeIfMissing(join(root, "tokens", "color.yaml"), starterColorTokens());
|
|
329
|
-
writeIfMissing(
|
|
330
|
-
join(root, "tokens", "typography.yaml"),
|
|
331
|
-
starterTypographyTokens()
|
|
332
|
-
);
|
|
333
|
-
writeIfMissing(
|
|
334
|
-
join(root, "tokens", "spacing.yaml"),
|
|
335
|
-
starterSpacingTokens()
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// ── starter locale ─────────────────────────────────────────────
|
|
340
|
-
|
|
341
|
-
writeIfMissing(
|
|
342
|
-
join(root, "locales", "en.json"),
|
|
343
|
-
starterLocale() + "\n"
|
|
344
|
-
);
|
|
345
|
-
|
|
346
232
|
// ── .gitkeep for empty dirs ────────────────────────────────────
|
|
347
233
|
|
|
348
234
|
for (const d of dirs) {
|
|
@@ -361,23 +247,21 @@ export async function init(): Promise<void> {
|
|
|
361
247
|
|
|
362
248
|
// ── AI assistant rules ─────────────────────────────────────────
|
|
363
249
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
appendFileSync(filePath, "\n" + rules);
|
|
376
|
-
console.log(` update ${file} (appended rules)`);
|
|
377
|
-
} else {
|
|
378
|
-
writeFileSync(filePath, rules.trimStart());
|
|
379
|
-
console.log(` create ${file}`);
|
|
250
|
+
const rules = aiRulesBlock(specDir, targets);
|
|
251
|
+
|
|
252
|
+
for (const file of ["CLAUDE.md", "AGENTS.md"]) {
|
|
253
|
+
const filePath = join(cwd, file);
|
|
254
|
+
if (existsSync(filePath)) {
|
|
255
|
+
const existing = readFileSync(filePath, "utf-8");
|
|
256
|
+
if (existing.includes("OpenUISpec")) {
|
|
257
|
+
console.log(` skip ${file} (already has OpenUISpec rules)`);
|
|
258
|
+
continue;
|
|
380
259
|
}
|
|
260
|
+
appendFileSync(filePath, "\n" + rules);
|
|
261
|
+
console.log(` update ${file} (appended rules)`);
|
|
262
|
+
} else {
|
|
263
|
+
writeFileSync(filePath, rules.trimStart());
|
|
264
|
+
console.log(` create ${file}`);
|
|
381
265
|
}
|
|
382
266
|
}
|
|
383
267
|
|
|
@@ -386,14 +270,28 @@ export async function init(): Promise<void> {
|
|
|
386
270
|
console.log(`
|
|
387
271
|
Done! Your spec project is ready at ./${specDir}/
|
|
388
272
|
|
|
389
|
-
|
|
390
|
-
1. Edit ${specDir}/openuispec.yaml
|
|
391
|
-
2.
|
|
392
|
-
3.
|
|
393
|
-
4.
|
|
273
|
+
Getting started (new project):
|
|
274
|
+
1. Edit ${specDir}/openuispec.yaml — define your data model and API
|
|
275
|
+
2. Create screens in ${specDir}/screens/ (one YAML per screen)
|
|
276
|
+
3. Create flows in ${specDir}/flows/ (multi-step navigation)
|
|
277
|
+
4. Ask AI to generate native code from the spec
|
|
394
278
|
5. Run \`openuispec drift --snapshot --target ${targets[0]}\` to baseline
|
|
395
279
|
|
|
396
|
-
|
|
280
|
+
Getting started (existing project):
|
|
281
|
+
1. Ask AI to read your existing UI code and generate spec files:
|
|
282
|
+
"Read src/screens/HomeScreen.swift and create ${specDir}/screens/home.yaml as status: stub"
|
|
283
|
+
2. Spec screens incrementally: stub → draft → ready
|
|
284
|
+
3. Only ready/draft screens are tracked by drift detection
|
|
285
|
+
4. Run \`openuispec validate\` to check specs against the schema
|
|
286
|
+
|
|
287
|
+
Commands:
|
|
288
|
+
openuispec validate Validate spec files
|
|
289
|
+
openuispec drift --target ios Check for spec changes
|
|
290
|
+
openuispec drift --snapshot --target ios Save current state
|
|
291
|
+
|
|
292
|
+
AI rules have been added to CLAUDE.md and AGENTS.md.
|
|
293
|
+
|
|
294
|
+
Learn more: https://github.com/rsktash/openuispec
|
|
397
295
|
`);
|
|
398
296
|
} catch (err) {
|
|
399
297
|
rl.close();
|