openuispec 0.1.25 → 0.1.27
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/README.md +44 -2
- package/cli/index.ts +21 -3
- package/cli/init.ts +24 -8
- package/docs/implementation-notes.md +115 -0
- package/docs/release-notes-v0.1.26.md +64 -0
- package/docs/release-notes-v0.1.27.md +28 -0
- package/drift/index.ts +375 -18
- package/examples/todo-orbit/AGENTS.md +11 -4
- package/examples/todo-orbit/CLAUDE.md +11 -4
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/components/CommonComponents.kt +69 -18
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/screens/TasksScreen.kt +5 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/java/uz/rsteam/todoorbit/ui/sheets/EditorSheets.kt +5 -2
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values/strings.xml +1 -0
- package/examples/todo-orbit/generated/android/Todo Orbit/app/src/main/res/values-ru/strings.xml +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/en.lproj/Localizable.strings +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Resources/ru.lproj/Localizable.strings +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/RecurringRuleSheet.swift +3 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Flows/TaskEditorSheet.swift +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/SettingsView.swift +2 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Screens/TasksHomeView.swift +1 -0
- package/examples/todo-orbit/generated/ios/Todo Orbit/Sources/TodoOrbit/Support/AppModel.swift +1 -1
- package/examples/todo-orbit/generated/web/Todo Orbit/src/App.tsx +59 -6
- package/examples/todo-orbit/generated/web/Todo Orbit/src/styles.css +40 -0
- package/examples/todo-orbit/openuispec/README.md +24 -131
- package/examples/todo-orbit/openuispec/flows/create_recurring_rule.yaml +3 -0
- package/examples/todo-orbit/openuispec/flows/create_task.yaml +1 -0
- package/examples/todo-orbit/openuispec/flows/edit_task.yaml +1 -0
- package/examples/todo-orbit/openuispec/locales/en.json +1 -0
- package/examples/todo-orbit/openuispec/locales/ru.json +1 -0
- package/examples/todo-orbit/openuispec/screens/task_detail.yaml +1 -0
- package/examples/todo-orbit/openuispec/tokens/icons.yaml +6 -0
- package/package.json +6 -1
- package/prepare/index.ts +391 -0
- package/schema/semantic-lint.ts +592 -0
- package/schema/validate.ts +8 -9
- package/status/index.ts +187 -0
package/status/index.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Cross-target status summary for OpenUISpec projects.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* openuispec status # summarize every target
|
|
7
|
+
* openuispec status --json # machine-readable output
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import {
|
|
12
|
+
computeDrift,
|
|
13
|
+
discoverTargets,
|
|
14
|
+
explainDrift,
|
|
15
|
+
findProjectDir,
|
|
16
|
+
formatBaseline,
|
|
17
|
+
readOutputDirs,
|
|
18
|
+
readProjectName,
|
|
19
|
+
resolveOutputDir,
|
|
20
|
+
stateFilePath,
|
|
21
|
+
type StateFile,
|
|
22
|
+
} from "../drift/index.js";
|
|
23
|
+
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
|
|
26
|
+
interface TargetStatus {
|
|
27
|
+
target: string;
|
|
28
|
+
output_dir: string;
|
|
29
|
+
snapshot: boolean;
|
|
30
|
+
snapshot_at: string | null;
|
|
31
|
+
baseline: {
|
|
32
|
+
kind: string | null;
|
|
33
|
+
commit: string | null;
|
|
34
|
+
branch: string | null;
|
|
35
|
+
label: string | null;
|
|
36
|
+
};
|
|
37
|
+
changed: number;
|
|
38
|
+
added: number;
|
|
39
|
+
removed: number;
|
|
40
|
+
behind: boolean;
|
|
41
|
+
explain_available: boolean;
|
|
42
|
+
note?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface StatusResult {
|
|
46
|
+
project: string;
|
|
47
|
+
targets: TargetStatus[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function configuredTargets(projectDir: string): string[] {
|
|
51
|
+
try {
|
|
52
|
+
const manifest = readOutputDirs(projectDir);
|
|
53
|
+
const keys = Object.keys(manifest);
|
|
54
|
+
if (keys.length > 0) return keys.sort();
|
|
55
|
+
} catch {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Fallback to common targets when manifest output_dir is absent.
|
|
60
|
+
return ["android", "ios", "web"];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function allTargets(projectDir: string, projectName: string): string[] {
|
|
64
|
+
const seen = new Set<string>(configuredTargets(projectDir));
|
|
65
|
+
for (const target of discoverTargets(projectDir, projectName)) {
|
|
66
|
+
seen.add(target);
|
|
67
|
+
}
|
|
68
|
+
return Array.from(seen).sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readState(statePath: string): StateFile {
|
|
72
|
+
return JSON.parse(readFileSync(statePath, "utf-8")) as StateFile;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildTargetStatus(cwd: string, projectDir: string, projectName: string, target: string): TargetStatus {
|
|
76
|
+
const outputDir = resolveOutputDir(projectDir, projectName, target);
|
|
77
|
+
const path = stateFilePath(projectDir, projectName, target);
|
|
78
|
+
|
|
79
|
+
if (!existsSync(path)) {
|
|
80
|
+
return {
|
|
81
|
+
target,
|
|
82
|
+
output_dir: outputDir,
|
|
83
|
+
snapshot: false,
|
|
84
|
+
snapshot_at: null,
|
|
85
|
+
baseline: {
|
|
86
|
+
kind: null,
|
|
87
|
+
commit: null,
|
|
88
|
+
branch: null,
|
|
89
|
+
label: null,
|
|
90
|
+
},
|
|
91
|
+
changed: 0,
|
|
92
|
+
added: 0,
|
|
93
|
+
removed: 0,
|
|
94
|
+
behind: false,
|
|
95
|
+
explain_available: false,
|
|
96
|
+
note: "No snapshot found for this target.",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const state = readState(path);
|
|
101
|
+
const result = computeDrift(projectDir, state, false);
|
|
102
|
+
const explanation = explainDrift(projectDir, result);
|
|
103
|
+
const changed = result.drift.changed.length;
|
|
104
|
+
const added = result.drift.added.length;
|
|
105
|
+
const removed = result.drift.removed.length;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
target,
|
|
109
|
+
output_dir: outputDir,
|
|
110
|
+
snapshot: true,
|
|
111
|
+
snapshot_at: state.snapshot_at,
|
|
112
|
+
baseline: {
|
|
113
|
+
kind: state.baseline?.kind ?? null,
|
|
114
|
+
commit: state.baseline?.commit ?? null,
|
|
115
|
+
branch: state.baseline?.branch ?? null,
|
|
116
|
+
label: formatBaseline(state.baseline),
|
|
117
|
+
},
|
|
118
|
+
changed,
|
|
119
|
+
added,
|
|
120
|
+
removed,
|
|
121
|
+
behind: changed + added + removed > 0,
|
|
122
|
+
explain_available: explanation.available,
|
|
123
|
+
note: explanation.available ? undefined : explanation.note,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildStatusResult(): StatusResult {
|
|
128
|
+
const cwd = process.cwd();
|
|
129
|
+
const projectDir = findProjectDir(cwd);
|
|
130
|
+
const projectName = readProjectName(projectDir);
|
|
131
|
+
const targets = allTargets(projectDir, projectName).map((target) =>
|
|
132
|
+
buildTargetStatus(cwd, projectDir, projectName, target)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
project: projectName,
|
|
137
|
+
targets,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function printReport(result: StatusResult): void {
|
|
142
|
+
console.log("OpenUISpec Status");
|
|
143
|
+
console.log("=================");
|
|
144
|
+
console.log(`Project: ${result.project}`);
|
|
145
|
+
console.log("");
|
|
146
|
+
|
|
147
|
+
for (const target of result.targets) {
|
|
148
|
+
const summary = target.snapshot
|
|
149
|
+
? `${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";
|
|
152
|
+
|
|
153
|
+
console.log(`${target.target}`);
|
|
154
|
+
console.log(` output: ${target.output_dir}`);
|
|
155
|
+
console.log(` snapshot: ${target.snapshot ? target.snapshot_at : "missing"}`);
|
|
156
|
+
if (target.baseline.label) {
|
|
157
|
+
console.log(` baseline: ${target.baseline.label}`);
|
|
158
|
+
}
|
|
159
|
+
console.log(` drift: ${summary}`);
|
|
160
|
+
console.log(` status: ${status}`);
|
|
161
|
+
console.log(` explain: ${target.explain_available ? "available" : "unavailable"}`);
|
|
162
|
+
if (target.note) {
|
|
163
|
+
console.log(` note: ${target.note}`);
|
|
164
|
+
}
|
|
165
|
+
console.log("");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function runStatus(argv: string[]): void {
|
|
170
|
+
const isJson = argv.includes("--json");
|
|
171
|
+
const result = buildStatusResult();
|
|
172
|
+
|
|
173
|
+
if (isJson) {
|
|
174
|
+
console.log(JSON.stringify(result, null, 2));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
printReport(result);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const isDirectRun =
|
|
182
|
+
process.argv[1]?.endsWith("status/index.ts") ||
|
|
183
|
+
process.argv[1]?.endsWith("status/index.js");
|
|
184
|
+
|
|
185
|
+
if (isDirectRun) {
|
|
186
|
+
runStatus(process.argv.slice(2));
|
|
187
|
+
}
|