openuispec 0.1.14 → 0.1.15
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 +15 -0
- package/cli/init.ts +30 -1
- package/drift/index.ts +53 -20
- package/package.json +1 -1
- package/schema/openuispec.schema.json +7 -0
package/README.md
CHANGED
|
@@ -155,6 +155,21 @@ brand:
|
|
|
155
155
|
|
|
156
156
|
Validate with: `openuispec validate`
|
|
157
157
|
|
|
158
|
+
## Output directories
|
|
159
|
+
|
|
160
|
+
By default, drift stores state in `generated/<target>/<project>/`. To point targets to your actual code directories, add `output_dir` to `openuispec.yaml`:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
generation:
|
|
164
|
+
targets: [ios, android, web]
|
|
165
|
+
output_dir:
|
|
166
|
+
web: "../web-ui/"
|
|
167
|
+
android: "../kmp-ui/"
|
|
168
|
+
ios: "../kmp-ui/iosApp/"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Paths are relative to `openuispec.yaml`. The `.openuispec-state.json` file is stored inside each output directory.
|
|
172
|
+
|
|
158
173
|
## Spec at a glance
|
|
159
174
|
|
|
160
175
|
| Section | What it defines |
|
package/cli/init.ts
CHANGED
|
@@ -106,6 +106,10 @@ i18n:
|
|
|
106
106
|
|
|
107
107
|
generation:
|
|
108
108
|
targets: [${targetList}]
|
|
109
|
+
# output_dir: # Optional: map targets to code directories
|
|
110
|
+
# ios: "../ios-app/" # relative to this file
|
|
111
|
+
# android: "../android-app/"
|
|
112
|
+
# web: "../web-ui/"
|
|
109
113
|
output_format:
|
|
110
114
|
${outputLines}
|
|
111
115
|
|
|
@@ -250,10 +254,23 @@ openuispec drift --snapshot --target ${targets[0]} # Snapshot current state
|
|
|
250
254
|
openuispec drift --all # Include stubs in drift check
|
|
251
255
|
\`\`\`
|
|
252
256
|
|
|
253
|
-
## Targets
|
|
257
|
+
## Targets and output directories
|
|
254
258
|
|
|
255
259
|
This project generates native code for: **${targetList}**
|
|
256
260
|
|
|
261
|
+
By default, drift stores state in \`generated/<target>/<project>/\`. To point targets to your actual code directories, add \`output_dir\` to \`openuispec.yaml\`:
|
|
262
|
+
|
|
263
|
+
\`\`\`yaml
|
|
264
|
+
generation:
|
|
265
|
+
targets: [ios, android, web]
|
|
266
|
+
output_dir:
|
|
267
|
+
web: "../web-ui/"
|
|
268
|
+
android: "../kmp-ui/"
|
|
269
|
+
ios: "../kmp-ui/iosApp/"
|
|
270
|
+
\`\`\`
|
|
271
|
+
|
|
272
|
+
Paths are relative to \`openuispec.yaml\`.
|
|
273
|
+
|
|
257
274
|
## Learn more
|
|
258
275
|
|
|
259
276
|
All docs and examples are in the installed \`openuispec\` package — check \`node_modules/openuispec/\` or run \`npm root -g\` for the global install path.
|
|
@@ -377,6 +394,18 @@ Workflow: read the schema → read an example from \`examples/taskflow/\` → cr
|
|
|
377
394
|
- Actions: typed objects (navigate, api_call, set_state, confirm, sequence, feedback, etc.)
|
|
378
395
|
- Adaptive layout: size classes (compact, regular, expanded) with per-section overrides
|
|
379
396
|
|
|
397
|
+
## Output directories
|
|
398
|
+
Drift tracks spec changes per target. By default state is stored in \`generated/<target>/<project>/\`.
|
|
399
|
+
To map targets to actual code directories, set \`generation.output_dir\` in \`openuispec.yaml\`:
|
|
400
|
+
\`\`\`yaml
|
|
401
|
+
generation:
|
|
402
|
+
output_dir:
|
|
403
|
+
web: "../web-ui/"
|
|
404
|
+
android: "../kmp-ui/"
|
|
405
|
+
ios: "../kmp-ui/iosApp/"
|
|
406
|
+
\`\`\`
|
|
407
|
+
Paths are relative to \`openuispec.yaml\`. The \`.openuispec-state.json\` file is stored inside each output directory.
|
|
408
|
+
|
|
380
409
|
## CLI commands
|
|
381
410
|
- \`openuispec init\` — scaffold a new spec project
|
|
382
411
|
- \`openuispec validate [group...]\` — validate spec files against schemas
|
package/drift/index.ts
CHANGED
|
@@ -159,27 +159,60 @@ function readProjectName(projectDir: string): string {
|
|
|
159
159
|
return doc.project?.name ?? basename(projectDir);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
/** Read per-target output_dir map from the manifest. */
|
|
163
|
+
function readOutputDirs(projectDir: string): Record<string, string> {
|
|
164
|
+
try {
|
|
165
|
+
const doc = YAML.parse(readFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
|
|
166
|
+
return doc.generation?.output_dir ?? {};
|
|
167
|
+
} catch {
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
162
172
|
/** Resolve the generated output directory for a target. */
|
|
163
|
-
function resolveOutputDir(
|
|
164
|
-
|
|
173
|
+
function resolveOutputDir(projectDir: string, projectName: string, target: string): string {
|
|
174
|
+
const outputDirs = readOutputDirs(projectDir);
|
|
175
|
+
if (outputDirs[target]) {
|
|
176
|
+
return resolve(projectDir, outputDirs[target]);
|
|
177
|
+
}
|
|
178
|
+
// Default: generated/<target>/<project_name> relative to cwd
|
|
179
|
+
return resolve(projectDir, "..", "generated", target, projectName);
|
|
165
180
|
}
|
|
166
181
|
|
|
167
|
-
function stateFilePath(
|
|
168
|
-
return join(resolveOutputDir(
|
|
182
|
+
function stateFilePath(projectDir: string, projectName: string, target: string): string {
|
|
183
|
+
return join(resolveOutputDir(projectDir, projectName, target), STATE_FILE);
|
|
169
184
|
}
|
|
170
185
|
|
|
171
|
-
function discoverTargets(
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.
|
|
180
|
-
|
|
181
|
-
|
|
186
|
+
function discoverTargets(projectDir: string, projectName: string): string[] {
|
|
187
|
+
const outputDirs = readOutputDirs(projectDir);
|
|
188
|
+
const targets: string[] = [];
|
|
189
|
+
|
|
190
|
+
// Check configured output_dir entries
|
|
191
|
+
for (const [target, dir] of Object.entries(outputDirs)) {
|
|
192
|
+
const resolved = resolve(projectDir, dir);
|
|
193
|
+
if (existsSync(join(resolved, STATE_FILE))) {
|
|
194
|
+
targets.push(target);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Also check default generated/ directory
|
|
199
|
+
const generatedDir = resolve(projectDir, "..", "generated");
|
|
200
|
+
if (existsSync(generatedDir)) {
|
|
201
|
+
try {
|
|
202
|
+
for (const entry of readdirSync(generatedDir)) {
|
|
203
|
+
if (!targets.includes(entry)) {
|
|
204
|
+
const defaultPath = join(generatedDir, entry, projectName, STATE_FILE);
|
|
205
|
+
if (existsSync(defaultPath)) {
|
|
206
|
+
targets.push(entry);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// ignore
|
|
212
|
+
}
|
|
182
213
|
}
|
|
214
|
+
|
|
215
|
+
return targets.sort();
|
|
183
216
|
}
|
|
184
217
|
|
|
185
218
|
/**
|
|
@@ -197,7 +230,7 @@ function normalizeEntry(value: string | FileEntry): FileEntry {
|
|
|
197
230
|
|
|
198
231
|
function snapshot(cwd: string, projectDir: string, target: string): void {
|
|
199
232
|
const projectName = readProjectName(projectDir);
|
|
200
|
-
const outDir = resolveOutputDir(
|
|
233
|
+
const outDir = resolveOutputDir(projectDir, projectName, target);
|
|
201
234
|
if (!existsSync(outDir)) {
|
|
202
235
|
console.error(
|
|
203
236
|
`Error: Output directory not found: ${relative(cwd, outDir)}\n` +
|
|
@@ -226,7 +259,7 @@ function snapshot(cwd: string, projectDir: string, target: string): void {
|
|
|
226
259
|
files: entries,
|
|
227
260
|
};
|
|
228
261
|
|
|
229
|
-
const outPath = stateFilePath(
|
|
262
|
+
const outPath = stateFilePath(projectDir, projectName, target);
|
|
230
263
|
writeFileSync(outPath, JSON.stringify(state, null, 2) + "\n");
|
|
231
264
|
console.log(`Snapshot saved: ${relative(cwd, outPath)}`);
|
|
232
265
|
console.log(` ${Object.keys(entries).length} files hashed`);
|
|
@@ -301,7 +334,7 @@ function check(
|
|
|
301
334
|
includeAll: boolean
|
|
302
335
|
): void {
|
|
303
336
|
const projectName = readProjectName(projectDir);
|
|
304
|
-
const statePath = stateFilePath(
|
|
337
|
+
const statePath = stateFilePath(projectDir, projectName, target);
|
|
305
338
|
if (!existsSync(statePath)) {
|
|
306
339
|
console.error(
|
|
307
340
|
`No snapshot found for target "${target}".\n` +
|
|
@@ -331,7 +364,7 @@ function checkAll(
|
|
|
331
364
|
includeAll: boolean
|
|
332
365
|
): void {
|
|
333
366
|
const projectName = readProjectName(projectDir);
|
|
334
|
-
const targets = discoverTargets(
|
|
367
|
+
const targets = discoverTargets(projectDir, projectName);
|
|
335
368
|
if (targets.length === 0) {
|
|
336
369
|
console.error(
|
|
337
370
|
"No snapshots found. Run: openuispec drift --snapshot --target <target>"
|
|
@@ -342,7 +375,7 @@ function checkAll(
|
|
|
342
375
|
let anyDrift = false;
|
|
343
376
|
|
|
344
377
|
for (const target of targets) {
|
|
345
|
-
const statePath = stateFilePath(
|
|
378
|
+
const statePath = stateFilePath(projectDir, projectName, target);
|
|
346
379
|
const state: StateFile = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
347
380
|
const result = computeDrift(projectDir, state, includeAll);
|
|
348
381
|
|
package/package.json
CHANGED
|
@@ -104,6 +104,13 @@
|
|
|
104
104
|
"type": "string"
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
|
+
"output_dir": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"description": "Per-target output directory (relative to openuispec.yaml). Defaults to generated/<target>/<project_name> if not set.",
|
|
110
|
+
"additionalProperties": {
|
|
111
|
+
"type": "string"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
107
114
|
"output_format": {
|
|
108
115
|
"type": "object",
|
|
109
116
|
"description": "Per-target generation config",
|