openuispec 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +52 -55
  2. package/cli/configure-target.ts +416 -0
  3. package/cli/index.ts +14 -3
  4. package/cli/init.ts +241 -55
  5. package/cli/target-presets.json +746 -0
  6. package/docs/implementation-notes.md +47 -10
  7. package/docs/release-notes-v0.1.26.md +1 -1
  8. package/docs/release-notes-v0.1.28.md +25 -0
  9. package/docs/stress-test-maturity-report.md +1 -1
  10. package/drift/index.ts +31 -11
  11. package/examples/taskflow/AGENTS.md +113 -0
  12. package/examples/taskflow/CLAUDE.md +113 -0
  13. package/examples/taskflow/backend/.gitkeep +1 -0
  14. package/examples/taskflow/generated/android/TaskFlow/README.md +43 -0
  15. package/examples/taskflow/generated/android/TaskFlow/app/build.gradle.kts +76 -0
  16. package/examples/taskflow/generated/android/TaskFlow/app/proguard-rules.pro +1 -0
  17. package/examples/taskflow/generated/android/TaskFlow/app/src/main/AndroidManifest.xml +21 -0
  18. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/MainActivity.kt +19 -0
  19. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/TaskFlowApp.kt +283 -0
  20. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/DomainModels.kt +106 -0
  21. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/model/SampleData.kt +57 -0
  22. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/components/Common.kt +109 -0
  23. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/HomeScreen.kt +112 -0
  24. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/ProjectsScreen.kt +61 -0
  25. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/SettingsScreen.kt +82 -0
  26. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/screens/TaskDetailScreen.kt +111 -0
  27. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/sheets/Sheets.kt +77 -0
  28. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Color.kt +30 -0
  29. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Theme.kt +86 -0
  30. package/examples/taskflow/generated/android/TaskFlow/app/src/main/java/uz/rsteam/taskflow/ui/theme/Type.kt +57 -0
  31. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/strings.xml +155 -0
  32. package/examples/taskflow/generated/android/TaskFlow/app/src/main/res/values/themes.xml +4 -0
  33. package/examples/taskflow/generated/android/TaskFlow/build.gradle.kts +5 -0
  34. package/examples/taskflow/generated/android/TaskFlow/gradle/gradle-daemon-jvm.properties +12 -0
  35. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.jar +0 -0
  36. package/examples/taskflow/generated/android/TaskFlow/gradle/wrapper/gradle-wrapper.properties +7 -0
  37. package/examples/taskflow/generated/android/TaskFlow/gradle.properties +4 -0
  38. package/examples/taskflow/generated/android/TaskFlow/gradlew +18 -0
  39. package/examples/taskflow/generated/android/TaskFlow/gradlew.bat +12 -0
  40. package/examples/taskflow/generated/android/TaskFlow/settings.gradle.kts +18 -0
  41. package/examples/taskflow/generated/ios/TaskFlow/README.md +21 -0
  42. package/examples/taskflow/generated/ios/TaskFlow/Resources/en.lproj/Localizable.strings +115 -0
  43. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/App/TaskFlowApp.swift +24 -0
  44. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Components/AppChrome.swift +150 -0
  45. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Flows/TaskEditorSheet.swift +220 -0
  46. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Models/DomainModels.swift +122 -0
  47. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/CalendarView.swift +21 -0
  48. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/HomeView.swift +201 -0
  49. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProfileEditView.swift +48 -0
  50. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectDetailView.swift +59 -0
  51. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/ProjectsView.swift +63 -0
  52. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/SettingsView.swift +85 -0
  53. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Screens/TaskDetailView.swift +219 -0
  54. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppModel.swift +320 -0
  55. package/examples/taskflow/generated/ios/TaskFlow/Sources/TaskFlow/Support/AppSupport.swift +41 -0
  56. package/examples/taskflow/generated/ios/TaskFlow/project.yml +26 -0
  57. package/examples/taskflow/generated/web/TaskFlow/README.md +19 -0
  58. package/examples/taskflow/generated/web/TaskFlow/index.html +12 -0
  59. package/examples/taskflow/generated/web/TaskFlow/package-lock.json +1908 -0
  60. package/examples/taskflow/generated/web/TaskFlow/package.json +24 -0
  61. package/examples/taskflow/generated/web/TaskFlow/src/App.tsx +58 -0
  62. package/examples/taskflow/generated/web/TaskFlow/src/AppShell.tsx +55 -0
  63. package/examples/taskflow/generated/web/TaskFlow/src/components/Common.tsx +82 -0
  64. package/examples/taskflow/generated/web/TaskFlow/src/components/Modals.tsx +191 -0
  65. package/examples/taskflow/generated/web/TaskFlow/src/components/Nav.tsx +41 -0
  66. package/examples/taskflow/generated/web/TaskFlow/src/generated/messages.ts +131 -0
  67. package/examples/taskflow/generated/web/TaskFlow/src/hooks.ts +25 -0
  68. package/examples/taskflow/generated/web/TaskFlow/src/i18n.ts +39 -0
  69. package/examples/taskflow/generated/web/TaskFlow/src/locales.en.json +111 -0
  70. package/examples/taskflow/generated/web/TaskFlow/src/main.tsx +13 -0
  71. package/examples/taskflow/generated/web/TaskFlow/src/screens/HomeScreen.tsx +111 -0
  72. package/examples/taskflow/generated/web/TaskFlow/src/screens/ProjectsScreen.tsx +82 -0
  73. package/examples/taskflow/generated/web/TaskFlow/src/screens/SettingsScreens.tsx +132 -0
  74. package/examples/taskflow/generated/web/TaskFlow/src/screens/TaskDetail.tsx +105 -0
  75. package/examples/taskflow/generated/web/TaskFlow/src/store.ts +216 -0
  76. package/examples/taskflow/generated/web/TaskFlow/src/styles.css +617 -0
  77. package/examples/taskflow/generated/web/TaskFlow/src/types.ts +64 -0
  78. package/examples/taskflow/generated/web/TaskFlow/src/utils.ts +78 -0
  79. package/examples/taskflow/generated/web/TaskFlow/tsconfig.json +21 -0
  80. package/examples/taskflow/generated/web/TaskFlow/vite.config.ts +6 -0
  81. package/examples/taskflow/openuispec/README.md +54 -0
  82. package/examples/taskflow/{openuispec.yaml → openuispec/openuispec.yaml} +2 -0
  83. package/examples/todo-orbit/AGENTS.md +48 -22
  84. package/examples/todo-orbit/CLAUDE.md +48 -22
  85. package/examples/todo-orbit/backend/.gitkeep +1 -0
  86. package/examples/todo-orbit/openuispec/README.md +9 -4
  87. package/examples/todo-orbit/openuispec/openuispec.yaml +2 -0
  88. package/package.json +1 -1
  89. package/prepare/index.ts +811 -25
  90. package/schema/openuispec.schema.json +10 -0
  91. package/schema/semantic-lint.ts +36 -12
  92. package/schema/validate.ts +9 -4
  93. package/status/index.ts +16 -3
  94. /package/examples/taskflow/{contracts → openuispec/contracts}/README.md +0 -0
  95. /package/examples/taskflow/{contracts → openuispec/contracts}/action_trigger.yaml +0 -0
  96. /package/examples/taskflow/{contracts → openuispec/contracts}/collection.yaml +0 -0
  97. /package/examples/taskflow/{contracts → openuispec/contracts}/data_display.yaml +0 -0
  98. /package/examples/taskflow/{contracts → openuispec/contracts}/feedback.yaml +0 -0
  99. /package/examples/taskflow/{contracts → openuispec/contracts}/input_field.yaml +0 -0
  100. /package/examples/taskflow/{contracts → openuispec/contracts}/nav_container.yaml +0 -0
  101. /package/examples/taskflow/{contracts → openuispec/contracts}/surface.yaml +0 -0
  102. /package/examples/taskflow/{contracts → openuispec/contracts}/x_media_player.yaml +0 -0
  103. /package/examples/taskflow/{flows → openuispec/flows}/create_task.yaml +0 -0
  104. /package/examples/taskflow/{flows → openuispec/flows}/edit_task.yaml +0 -0
  105. /package/examples/taskflow/{locales → openuispec/locales}/en.json +0 -0
  106. /package/examples/taskflow/{platform → openuispec/platform}/android.yaml +0 -0
  107. /package/examples/taskflow/{platform → openuispec/platform}/ios.yaml +0 -0
  108. /package/examples/taskflow/{platform → openuispec/platform}/web.yaml +0 -0
  109. /package/examples/taskflow/{screens → openuispec/screens}/calendar.yaml +0 -0
  110. /package/examples/taskflow/{screens → openuispec/screens}/home.yaml +0 -0
  111. /package/examples/taskflow/{screens → openuispec/screens}/profile_edit.yaml +0 -0
  112. /package/examples/taskflow/{screens → openuispec/screens}/project_detail.yaml +0 -0
  113. /package/examples/taskflow/{screens → openuispec/screens}/projects.yaml +0 -0
  114. /package/examples/taskflow/{screens → openuispec/screens}/settings.yaml +0 -0
  115. /package/examples/taskflow/{screens → openuispec/screens}/task_detail.yaml +0 -0
  116. /package/examples/taskflow/{tokens → openuispec/tokens}/color.yaml +0 -0
  117. /package/examples/taskflow/{tokens → openuispec/tokens}/elevation.yaml +0 -0
  118. /package/examples/taskflow/{tokens → openuispec/tokens}/icons.yaml +0 -0
  119. /package/examples/taskflow/{tokens → openuispec/tokens}/layout.yaml +0 -0
  120. /package/examples/taskflow/{tokens → openuispec/tokens}/motion.yaml +0 -0
  121. /package/examples/taskflow/{tokens → openuispec/tokens}/spacing.yaml +0 -0
  122. /package/examples/taskflow/{tokens → openuispec/tokens}/themes.yaml +0 -0
  123. /package/examples/taskflow/{tokens → openuispec/tokens}/typography.yaml +0 -0
@@ -111,6 +111,16 @@
111
111
  "type": "string"
112
112
  }
113
113
  },
114
+ "code_roots": {
115
+ "type": "object",
116
+ "description": "Additional project code roots used as generation context.",
117
+ "properties": {
118
+ "backend": {
119
+ "type": "string"
120
+ }
121
+ },
122
+ "additionalProperties": false
123
+ },
114
124
  "output_format": {
115
125
  "type": "object",
116
126
  "description": "Per-target generation config",
@@ -1,6 +1,7 @@
1
- import { existsSync, readFileSync, readdirSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import YAML from "yaml";
4
+ import { listFiles } from "../drift/index.js";
4
5
 
5
6
  type UnknownRecord = Record<string, unknown>;
6
7
 
@@ -19,6 +20,7 @@ export interface UsageLint {
19
20
  }
20
21
 
21
22
  interface SemanticContext {
23
+ manifest: unknown;
22
24
  localeFiles: Map<string, Set<string>>;
23
25
  formatterNames: Set<string>;
24
26
  mapperNames: Set<string>;
@@ -66,17 +68,6 @@ function loadData(filePath: string): unknown {
66
68
  return filePath.endsWith(".json") ? loadJson(filePath) : loadYaml(filePath);
67
69
  }
68
70
 
69
- function listFiles(dir: string, ext: string): string[] {
70
- try {
71
- return readdirSync(dir)
72
- .filter((file) => file.endsWith(ext))
73
- .sort()
74
- .map((file) => join(dir, file));
75
- } catch {
76
- return [];
77
- }
78
- }
79
-
80
71
  function isRecord(value: unknown): value is UnknownRecord {
81
72
  return typeof value === "object" && value !== null && !Array.isArray(value);
82
73
  }
@@ -281,6 +272,7 @@ function buildContext(projectDir: string, includes: Includes): SemanticContext {
281
272
  : localeFiles.keys().next().value ?? null;
282
273
 
283
274
  return {
275
+ manifest,
284
276
  localeFiles,
285
277
  formatterNames,
286
278
  mapperNames,
@@ -552,6 +544,37 @@ function lintLocaleCoverage(context: SemanticContext): UsageLint[] {
552
544
  return errors;
553
545
  }
554
546
 
547
+ function lintManifestGenerationContext(projectDir: string, manifest: unknown): UsageLint[] {
548
+ if (!isRecord(manifest)) return [];
549
+
550
+ const hasApiEndpoints =
551
+ isRecord(manifest.api) &&
552
+ isRecord(manifest.api.endpoints) &&
553
+ Object.keys(manifest.api.endpoints).length > 0;
554
+ if (!hasApiEndpoints) return [];
555
+
556
+ const generation = isRecord(manifest.generation) ? manifest.generation : {};
557
+ const codeRoots = isRecord(generation.code_roots) ? generation.code_roots : null;
558
+ const backendRoot = codeRoots && typeof codeRoots.backend === "string" ? codeRoots.backend.trim() : "";
559
+
560
+ if (!backendRoot) {
561
+ return [{
562
+ path: "openuispec.yaml",
563
+ message: 'api endpoints require generation.code_roots.backend to point at the backend folder',
564
+ }];
565
+ }
566
+
567
+ const resolvedBackendRoot = resolve(projectDir, backendRoot);
568
+ if (!existsSync(resolvedBackendRoot)) {
569
+ return [{
570
+ path: "openuispec.yaml",
571
+ message: `generation.code_roots.backend points to a missing folder: ${backendRoot}`,
572
+ }];
573
+ }
574
+
575
+ return [];
576
+ }
577
+
555
578
  function printSemanticErrors(label: string, errors: UsageLint[]): number {
556
579
  if (errors.length === 0) return 0;
557
580
  const previewLimit = 10;
@@ -572,6 +595,7 @@ export function runSemanticLint(projectDir: string, includes: Includes): number
572
595
  const contractsDir = resolve(projectDir, includes.contracts);
573
596
 
574
597
  total += printSemanticErrors("locales", lintLocaleCoverage(context));
598
+ total += printSemanticErrors("openuispec.yaml", lintManifestGenerationContext(projectDir, context.manifest));
575
599
 
576
600
  const files = [
577
601
  join(projectDir, "openuispec.yaml"),
@@ -5,7 +5,7 @@
5
5
  * Usage:
6
6
  * openuispec validate # validate all spec files
7
7
  * openuispec validate tokens screens # validate specific groups
8
- * npm run validate # from repo (uses examples/taskflow)
8
+ * npm run validate # from repo (uses examples/taskflow/openuispec)
9
9
  */
10
10
 
11
11
  import { readFileSync, readdirSync, existsSync } from "node:fs";
@@ -572,9 +572,14 @@ function findProjectDir(cwd: string): string {
572
572
  }
573
573
  }
574
574
  // Fallback for running from repo root with examples/
575
- const examplesDir = join(cwd, "examples", "taskflow");
576
- if (existsSync(join(examplesDir, "openuispec.yaml"))) {
577
- return examplesDir;
575
+ const exampleCandidates = [
576
+ join(cwd, "examples", "taskflow", "openuispec"),
577
+ join(cwd, "examples", "taskflow"),
578
+ ];
579
+ for (const dir of exampleCandidates) {
580
+ if (existsSync(join(dir, "openuispec.yaml"))) {
581
+ return dir;
582
+ }
578
583
  }
579
584
  console.error(
580
585
  "Error: No openuispec.yaml found.\n" +
package/status/index.ts CHANGED
@@ -26,6 +26,7 @@ import { readFileSync } from "node:fs";
26
26
  interface TargetStatus {
27
27
  target: string;
28
28
  output_dir: string;
29
+ output_exists: boolean;
29
30
  snapshot: boolean;
30
31
  snapshot_at: string | null;
31
32
  baseline: {
@@ -74,12 +75,14 @@ function readState(statePath: string): StateFile {
74
75
 
75
76
  function buildTargetStatus(cwd: string, projectDir: string, projectName: string, target: string): TargetStatus {
76
77
  const outputDir = resolveOutputDir(projectDir, projectName, target);
78
+ const outputExists = existsSync(outputDir);
77
79
  const path = stateFilePath(projectDir, projectName, target);
78
80
 
79
81
  if (!existsSync(path)) {
80
82
  return {
81
83
  target,
82
84
  output_dir: outputDir,
85
+ output_exists: outputExists,
83
86
  snapshot: false,
84
87
  snapshot_at: null,
85
88
  baseline: {
@@ -93,7 +96,9 @@ function buildTargetStatus(cwd: string, projectDir: string, projectName: string,
93
96
  removed: 0,
94
97
  behind: false,
95
98
  explain_available: false,
96
- note: "No snapshot found for this target.",
99
+ note: outputExists
100
+ ? "No snapshot found for this target."
101
+ : `Output directory not found. Run code generation for "${target}" first.`,
97
102
  };
98
103
  }
99
104
 
@@ -107,6 +112,7 @@ function buildTargetStatus(cwd: string, projectDir: string, projectName: string,
107
112
  return {
108
113
  target,
109
114
  output_dir: outputDir,
115
+ output_exists: outputExists,
110
116
  snapshot: true,
111
117
  snapshot_at: state.snapshot_at,
112
118
  baseline: {
@@ -147,11 +153,18 @@ function printReport(result: StatusResult): void {
147
153
  for (const target of result.targets) {
148
154
  const summary = target.snapshot
149
155
  ? `${target.changed} changed, ${target.added} added, ${target.removed} removed`
150
- : "no snapshot";
151
- const status = target.snapshot ? (target.behind ? "behind" : "up to date") : "needs baseline";
156
+ : target.output_exists
157
+ ? "no snapshot"
158
+ : "output missing";
159
+ const status = target.snapshot
160
+ ? (target.behind ? "behind" : "up to date")
161
+ : target.output_exists
162
+ ? "needs baseline"
163
+ : "needs generation";
152
164
 
153
165
  console.log(`${target.target}`);
154
166
  console.log(` output: ${target.output_dir}`);
167
+ console.log(` output exists: ${target.output_exists ? "yes" : "no"}`);
155
168
  console.log(` snapshot: ${target.snapshot ? target.snapshot_at : "missing"}`);
156
169
  if (target.baseline.label) {
157
170
  console.log(` baseline: ${target.baseline.label}`);