openuispec 0.1.12 → 0.1.14

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/index.ts CHANGED
@@ -45,7 +45,7 @@ Usage:
45
45
 
46
46
  Validate groups: manifest, tokens, screens, flows, platform, locales, custom_contracts
47
47
 
48
- Docs: node_modules/openuispec/spec/openuispec-v0.1.md
48
+ Docs: https://openuispec.rsteam.uz
49
49
  `);
50
50
  break;
51
51
 
package/cli/init.ts CHANGED
@@ -137,6 +137,12 @@ OpenUISpec is a YAML-based format that describes your app's UI semantically —
137
137
  | \`platform/\` | Platform overrides — per-target (iOS, Android, Web) behaviors |
138
138
  | \`locales/\` | Localization — i18n strings (JSON, ICU MessageFormat) |
139
139
 
140
+ All directory paths are configured in \`openuispec.yaml\` under \`includes:\` and support relative paths. For example, to share locales across projects:
141
+ \`\`\`yaml
142
+ includes:
143
+ locales: "../../shared/locales" # resolved relative to openuispec.yaml
144
+ \`\`\`
145
+
140
146
  ## Getting started
141
147
 
142
148
  **Start here:** read \`openuispec.yaml\` — it's the root manifest that defines the project structure, data model, API endpoints, and generation targets.
@@ -170,16 +176,19 @@ OpenUISpec is a YAML-based format that describes your app's UI semantically —
170
176
  - \`draft\` — actively being specced. Tracked by drift.
171
177
  - \`ready\` — fully specified (default if omitted). Tracked by drift.
172
178
 
173
- ## Learning OpenUISpec — local files and online reference
179
+ ## Learning OpenUISpec — where to find the docs
174
180
 
175
- All documentation is included in the installed package at \`node_modules/openuispec/\`:
176
- 1. **Full specification:** \`node_modules/openuispec/spec/openuispec-v0.1.md\`
177
- 2. **Example app:** \`node_modules/openuispec/examples/taskflow/\`
178
- 3. **JSON Schemas:** \`node_modules/openuispec/schema/\`
181
+ All documentation is included in the installed \`openuispec\` package. Search for it in this order:
182
+ 1. **Local:** \`node_modules/openuispec/\` (if installed as a project dependency)
183
+ 2. **Global:** run \`npm root -g\` to find the global prefix, then look in \`<prefix>/openuispec/\`
184
+ 3. **Online fallback:** if the package is not installed at all, fetch from:
185
+ - \`https://openuispec.rsteam.uz/llms-full.txt\` — complete spec + all JSON schemas in one file
186
+ - \`https://openuispec.rsteam.uz/llms.txt\` — concise summary with links
179
187
 
180
- If the package is not in node_modules, fetch the full reference from:
181
- - \`https://openuispec.rsteam.uz/llms-full.txt\` — complete spec + all JSON schemas in one file
182
- - \`https://openuispec.rsteam.uz/llms.txt\` — concise summary with links
188
+ Inside the package:
189
+ - **Full specification:** \`spec/openuispec-v0.1.md\`
190
+ - **Example app:** \`examples/taskflow/\`
191
+ - **JSON Schemas:** \`schema/\`
183
192
 
184
193
  ## Token file structure — root wrapper key required
185
194
 
@@ -219,9 +228,9 @@ Root keys: \`color\`, \`typography\`, \`spacing\`, \`elevation\`, \`motion\`, \`
219
228
  | \`tokens/themes.yaml\` | \`tokens/themes.schema.json\` | \`themes\` |
220
229
  | \`tokens/icons.yaml\` | \`tokens/icons.schema.json\` | \`icons\` |
221
230
 
222
- All schemas are in \`node_modules/openuispec/schema/\`. Shared type definitions (actions, data-binding, adaptive, validation, common) are in \`schema/defs/\`.
231
+ All schemas are in \`schema/\` inside the installed package. Shared type definitions (actions, data-binding, adaptive, validation, common) are in \`schema/defs/\`.
223
232
 
224
- **Workflow:** read the schema → read an example from \`node_modules/openuispec/examples/taskflow/\` → create the YAML → run \`openuispec validate\`.
233
+ **Workflow:** read the schema → read an example from \`examples/taskflow/\` → create the YAML → run \`openuispec validate\`.
225
234
 
226
235
  ## Spec format quick reference
227
236
 
@@ -247,12 +256,12 @@ This project generates native code for: **${targetList}**
247
256
 
248
257
  ## Learn more
249
258
 
250
- All docs and examples are local in \`node_modules/openuispec/\` read from disk, not from GitHub.
259
+ All docs and examples are in the installed \`openuispec\` package — check \`node_modules/openuispec/\` or run \`npm root -g\` for the global install path.
251
260
 
252
- - Full spec: \`node_modules/openuispec/spec/openuispec-v0.1.md\`
253
- - Example app: \`node_modules/openuispec/examples/taskflow/\`
254
- - JSON Schemas: \`node_modules/openuispec/schema/\`
255
- - Repository: \`node_modules/openuispec/\` (all files included)
261
+ - Full spec: \`spec/openuispec-v0.1.md\`
262
+ - Example app: \`examples/taskflow/\`
263
+ - JSON Schemas: \`schema/\`
264
+ - Online reference: \`https://openuispec.rsteam.uz/llms-full.txt\`
256
265
  `;
257
266
  }
258
267
 
@@ -278,6 +287,8 @@ OpenUISpec is a YAML-based spec format that describes an app's UI semantically
278
287
  - Platform: \`${specDir}/platform/\` — per-target overrides (iOS, Android, Web)
279
288
  - Locales: \`${specDir}/locales/\` — i18n strings (JSON, ICU MessageFormat)
280
289
 
290
+ **Note:** These are the default paths. Actual paths are in \`includes:\` in \`openuispec.yaml\` and may use relative paths (e.g. \`../../shared/locales\`). Always read \`openuispec.yaml\` to find the real directories.
291
+
281
292
  ## If spec directories are empty (first-time setup)
282
293
  This means the project has existing UI code but hasn't been specced yet. Your job:
283
294
 
@@ -311,15 +322,18 @@ This means the project has existing UI code but hasn't been specced yet. Your jo
311
322
  2. Run \`openuispec drift --snapshot --target <target>\` for each affected platform.
312
323
  3. Run \`openuispec drift\` to verify no untracked drift remains.
313
324
 
314
- ## Learning OpenUISpec — local files and online reference
315
- All documentation is included in the installed package at \`node_modules/openuispec/\`:
316
- 1. **Full specification:** \`node_modules/openuispec/spec/openuispec-v0.1.md\` — the complete spec (read this to understand the format)
317
- 2. **Example app:** \`node_modules/openuispec/examples/taskflow/\` a complete working app with all file types
318
- 3. **JSON Schemas:** \`node_modules/openuispec/schema/\` validation schemas that define the exact structure of every file type
325
+ ## Learning OpenUISpec — where to find the docs
326
+ All documentation is in the installed \`openuispec\` package. Search in this order:
327
+ 1. **Local:** \`node_modules/openuispec/\` (project dependency)
328
+ 2. **Global:** run \`npm root -g\` to get the global prefix, then look in \`<prefix>/openuispec/\`
329
+ 3. **Online fallback:** if not installed, fetch from:
330
+ - \`https://openuispec.rsteam.uz/llms-full.txt\` — complete spec + all JSON schemas
331
+ - \`https://openuispec.rsteam.uz/llms.txt\` — concise summary with links
319
332
 
320
- If the package is not in node_modules, fetch the full reference from:
321
- - \`https://openuispec.rsteam.uz/llms-full.txt\` — complete spec + all JSON schemas in one file
322
- - \`https://openuispec.rsteam.uz/llms.txt\`concise summary with links
333
+ Inside the package:
334
+ 1. **Full specification:** \`spec/openuispec-v0.1.md\` — the complete spec (read this to understand the format)
335
+ 2. **Example app:** \`examples/taskflow/\`a complete working app with all file types
336
+ 3. **JSON Schemas:** \`schema/\` — validation schemas that define the exact structure of every file type
323
337
 
324
338
  ## Token file structure — root wrapper key required
325
339
  Every token file must have a single root key matching the token type. Do NOT put properties at the top level.
@@ -335,7 +349,7 @@ Every token file must have a single root key matching the token type. Do NOT put
335
349
  ## File formats and schemas — read before creating spec files
336
350
  Before creating or editing any spec file, read the corresponding JSON Schema. Do not guess the file format.
337
351
 
338
- | File | Schema (in \`node_modules/openuispec/schema/\`) | Root key |
352
+ | File | Schema (in \`schema/\` inside the installed package) | Root key |
339
353
  |------|--------|----------|
340
354
  | \`openuispec.yaml\` | \`openuispec.schema.json\` | \`spec_version\` |
341
355
  | \`screens/*.yaml\` | \`screen.schema.json\` | \`<screen_id>\` |
@@ -354,7 +368,7 @@ Before creating or editing any spec file, read the corresponding JSON Schema. Do
354
368
 
355
369
  Shared type definitions (actions, data-binding, adaptive, validation, common) are in \`schema/defs/\`.
356
370
 
357
- Workflow: read the schema → read an example from \`node_modules/openuispec/examples/taskflow/\` → create the YAML → run \`openuispec validate\`.
371
+ Workflow: read the schema → read an example from \`examples/taskflow/\` → create the YAML → run \`openuispec validate\`.
358
372
 
359
373
  ## Spec format reference
360
374
  - 7 contract families: nav_container, surface, action_trigger, input_field, data_display, collection, feedback
@@ -499,7 +513,7 @@ Commands:
499
513
 
500
514
  AI rules have been added to CLAUDE.md and AGENTS.md.
501
515
 
502
- Docs: node_modules/openuispec/spec/openuispec-v0.1.md
516
+ Docs: https://openuispec.rsteam.uz
503
517
  `);
504
518
  } catch (err) {
505
519
  rl.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",
@@ -18,9 +18,25 @@
18
18
  },
19
19
  "patternProperties": {
20
20
  "^[a-zA-Z_][a-zA-Z0-9_.]*$": {
21
- "type": "string",
22
- "description": "Translation string (ICU MessageFormat)"
21
+ "$ref": "#/$defs/message_value"
23
22
  }
24
23
  },
25
- "additionalProperties": false
24
+ "additionalProperties": false,
25
+ "$defs": {
26
+ "message_value": {
27
+ "description": "Translation string (ICU MessageFormat) or nested group of translations",
28
+ "oneOf": [
29
+ { "type": "string" },
30
+ {
31
+ "type": "object",
32
+ "patternProperties": {
33
+ "^[a-zA-Z_][a-zA-Z0-9_.]*$": {
34
+ "$ref": "#/$defs/message_value"
35
+ }
36
+ },
37
+ "additionalProperties": false
38
+ }
39
+ ]
40
+ }
41
+ }
26
42
  }
@@ -97,10 +97,8 @@ function validateFile(
97
97
  }
98
98
 
99
99
  // Convert schema URL to a local path for display
100
- const schemaLocalPath = schemaId.replace(
101
- BASE,
102
- "node_modules/openuispec/schema/",
103
- );
100
+ const schemaRelPath = schemaId.replace(BASE, "");
101
+ const schemaLocalPath = resolve(SCHEMA_DIR, schemaRelPath);
104
102
 
105
103
  const errors: ErrorObject[] = validate.errors ?? [];
106
104
  console.log(` FAIL ${name} (${errors.length} error(s))`);
@@ -150,9 +148,46 @@ function validateFile(
150
148
 
151
149
  // ── validation groups ────────────────────────────────────────────────
152
150
 
151
+ // ── includes resolution ──────────────────────────────────────────────
152
+
153
+ interface Includes {
154
+ tokens: string;
155
+ contracts: string;
156
+ screens: string;
157
+ flows: string;
158
+ platform: string;
159
+ locales: string;
160
+ }
161
+
162
+ const DEFAULT_INCLUDES: Includes = {
163
+ tokens: "./tokens/",
164
+ contracts: "./contracts/",
165
+ screens: "./screens/",
166
+ flows: "./flows/",
167
+ platform: "./platform/",
168
+ locales: "./locales/",
169
+ };
170
+
171
+ function readIncludes(projectDir: string): Includes {
172
+ const manifestPath = join(projectDir, "openuispec.yaml");
173
+ try {
174
+ const manifest = loadYaml(manifestPath) as Record<string, unknown>;
175
+ const inc = manifest?.includes as Partial<Includes> | undefined;
176
+ return { ...DEFAULT_INCLUDES, ...inc };
177
+ } catch {
178
+ return DEFAULT_INCLUDES;
179
+ }
180
+ }
181
+
182
+ function resolveInclude(projectDir: string, includePath: string): string {
183
+ return resolve(projectDir, includePath);
184
+ }
185
+
186
+ // ── validation groups ────────────────────────────────────────────────
187
+
153
188
  interface ValidationGroup {
154
189
  label: string;
155
- run(ajv: AjvInstance, projectDir: string): number;
190
+ run(ajv: AjvInstance, projectDir: string, includes: Includes): number;
156
191
  }
157
192
 
158
193
  const GROUPS: Record<string, ValidationGroup> = {
@@ -169,8 +204,9 @@ const GROUPS: Record<string, ValidationGroup> = {
169
204
 
170
205
  tokens: {
171
206
  label: "Tokens",
172
- run(ajv, projectDir) {
207
+ run(ajv, projectDir, includes) {
173
208
  let errors = 0;
209
+ const tokensDir = resolveInclude(projectDir, includes.tokens);
174
210
  const tokenMap: Record<string, string> = {
175
211
  "color.yaml": "color.schema.json",
176
212
  "typography.yaml": "typography.schema.json",
@@ -182,7 +218,7 @@ const GROUPS: Record<string, ValidationGroup> = {
182
218
  "icons.yaml": "icons.schema.json",
183
219
  };
184
220
  for (const [data, schema] of Object.entries(tokenMap)) {
185
- const filePath = join(projectDir, "tokens", data);
221
+ const filePath = join(tokensDir, data);
186
222
  if (existsSync(filePath)) {
187
223
  errors += validateFile(ajv, filePath, `${BASE}tokens/${schema}`);
188
224
  }
@@ -193,9 +229,10 @@ const GROUPS: Record<string, ValidationGroup> = {
193
229
 
194
230
  screens: {
195
231
  label: "Screens",
196
- run(ajv, projectDir) {
232
+ run(ajv, projectDir, includes) {
197
233
  let errors = 0;
198
- for (const f of listFiles(join(projectDir, "screens"), ".yaml")) {
234
+ const dir = resolveInclude(projectDir, includes.screens);
235
+ for (const f of listFiles(dir, ".yaml")) {
199
236
  errors += validateFile(ajv, f, `${BASE}screen.schema.json`);
200
237
  }
201
238
  return errors;
@@ -204,9 +241,10 @@ const GROUPS: Record<string, ValidationGroup> = {
204
241
 
205
242
  flows: {
206
243
  label: "Flows",
207
- run(ajv, projectDir) {
244
+ run(ajv, projectDir, includes) {
208
245
  let errors = 0;
209
- for (const f of listFiles(join(projectDir, "flows"), ".yaml")) {
246
+ const dir = resolveInclude(projectDir, includes.flows);
247
+ for (const f of listFiles(dir, ".yaml")) {
210
248
  errors += validateFile(ajv, f, `${BASE}flow.schema.json`);
211
249
  }
212
250
  return errors;
@@ -215,9 +253,10 @@ const GROUPS: Record<string, ValidationGroup> = {
215
253
 
216
254
  platform: {
217
255
  label: "Platform",
218
- run(ajv, projectDir) {
256
+ run(ajv, projectDir, includes) {
219
257
  let errors = 0;
220
- for (const f of listFiles(join(projectDir, "platform"), ".yaml")) {
258
+ const dir = resolveInclude(projectDir, includes.platform);
259
+ for (const f of listFiles(dir, ".yaml")) {
221
260
  errors += validateFile(ajv, f, `${BASE}platform.schema.json`);
222
261
  }
223
262
  return errors;
@@ -226,9 +265,10 @@ const GROUPS: Record<string, ValidationGroup> = {
226
265
 
227
266
  locales: {
228
267
  label: "Locales",
229
- run(ajv, projectDir) {
268
+ run(ajv, projectDir, includes) {
230
269
  let errors = 0;
231
- for (const f of listFiles(join(projectDir, "locales"), ".json")) {
270
+ const dir = resolveInclude(projectDir, includes.locales);
271
+ for (const f of listFiles(dir, ".json")) {
232
272
  errors += validateFile(ajv, f, `${BASE}locale.schema.json`);
233
273
  }
234
274
  return errors;
@@ -237,9 +277,10 @@ const GROUPS: Record<string, ValidationGroup> = {
237
277
 
238
278
  custom_contracts: {
239
279
  label: "Custom contracts",
240
- run(ajv, projectDir) {
280
+ run(ajv, projectDir, includes) {
241
281
  let errors = 0;
242
- for (const f of listFiles(join(projectDir, "contracts"), ".yaml")) {
282
+ const dir = resolveInclude(projectDir, includes.contracts);
283
+ for (const f of listFiles(dir, ".yaml")) {
243
284
  if (basename(f).startsWith("x_")) {
244
285
  errors += validateFile(
245
286
  ajv,
@@ -293,13 +334,14 @@ export function runValidate(argv: string[]): void {
293
334
  }
294
335
 
295
336
  const projectDir = findProjectDir(process.cwd());
337
+ const includes = readIncludes(projectDir);
296
338
  const ajv = buildAjv();
297
339
  let totalErrors = 0;
298
340
 
299
341
  for (const key of selected) {
300
342
  const group = GROUPS[key];
301
343
  console.log(`\n${group.label}:`);
302
- totalErrors += group.run(ajv, projectDir);
344
+ totalErrors += group.run(ajv, projectDir, includes);
303
345
  }
304
346
 
305
347
  console.log(`\n${"=".repeat(50)}`);