@botlr/cli 0.3.0 → 0.4.0
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/CHANGELOG.md +15 -0
- package/README.md +20 -3
- package/dist/cli.js +55 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +43 -9
- package/dist/index.js +44 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @botlr/cli
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `botlr schema push` follows the server's merge behaviour for FormBuilder schemas instead of asking for `--force`.
|
|
8
|
+
|
|
9
|
+
- Field-DSL `schema` in the manifest pushed at a definition whose dashboard schema is in the FormBuilder format is merged by the server: a node per field the dashboard lacks, every existing node kept with its id, label and settings, nothing removed. The CLI's diff header says so (`the server will add the missing fields as FormBuilder nodes and keep the dashboard's (format kept; --force would replace it)`), lists the dashboard's extra nodes as "dashboard only, kept", asks as before, and then prints what the server did: `merged schema of <name> (form-builder): added ...; kept ...`, plus `the dashboard also defines: ... (not in the code)` when the dashboard has nodes the code does not declare. Same format: `replaced schema of <name>`. `changed: false`: `nothing to change`.
|
|
10
|
+
- A FormBuilder `schema` pushed at a field-DSL definition still needs `--force`: the CLI stops right after the diff with the hint, without asking or sending anything (the server would answer 409). `--force` replaces the schema outright whatever the formats, and the question says which format replaces which.
|
|
11
|
+
- `@botlr/sdk`: `SectionSchemaPutResponse` gains `mode: "replaced" | "merged"` (the `SectionSchemaPushMode` type), `added` and `dashboardOnly`.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- @botlr/sdk@0.6.0
|
|
17
|
+
|
|
3
18
|
## 0.3.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -168,9 +168,26 @@ replace the dashboard's schema of 'hero' with the code's? [y/N]
|
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
For a FormBuilder schema (`{ nodes: [...] }`) the keys are the node names.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
What the server does depends on the two formats:
|
|
172
|
+
|
|
173
|
+
- Same format: the schema is replaced.
|
|
174
|
+
- Field DSL in the manifest, FormBuilder in the dashboard (the common case
|
|
175
|
+
for sections configured in the dashboard first): the server **merges**. It
|
|
176
|
+
adds a FormBuilder node per field the dashboard lacks, built like the
|
|
177
|
+
dashboard's own "Convert to FormBuilder", keeps every existing node with
|
|
178
|
+
its id, label and settings, only sets `required: true` where the code
|
|
179
|
+
requires it, and removes nothing. The diff header says so, the dashboard's
|
|
180
|
+
extra nodes are listed as "dashboard only, kept", and the result line
|
|
181
|
+
reads `merged schema of hero (form-builder): added ctaLink, subheading;
|
|
182
|
+
kept heading`, followed by `the dashboard also defines: image (not in the
|
|
183
|
+
code)` when the dashboard has nodes the code does not declare.
|
|
184
|
+
- FormBuilder in the manifest, field DSL in the dashboard: the CLI prints
|
|
185
|
+
the diff and both formats and stops with a hint to rerun with `--force`,
|
|
186
|
+
without asking or sending anything (the server would refuse with 409).
|
|
187
|
+
|
|
188
|
+
`--force` replaces the schema outright whatever the formats, and the
|
|
189
|
+
question says which format replaces which. When nothing had to change the
|
|
190
|
+
result line says `nothing to change`. A 422 (the
|
|
174
191
|
`schema` is not an object of field definitions, or the definition is not
|
|
175
192
|
project-scoped and the template has to be installed into the project first)
|
|
176
193
|
prints the server's message. After a successful push the CLI regenerates
|
package/dist/cli.js
CHANGED
|
@@ -1096,7 +1096,11 @@ async function initSections(config, options = {}) {
|
|
|
1096
1096
|
}
|
|
1097
1097
|
|
|
1098
1098
|
// src/commands/schema.ts
|
|
1099
|
-
import {
|
|
1099
|
+
import {
|
|
1100
|
+
BotlrError,
|
|
1101
|
+
BotlrSchemaFormatConflictError,
|
|
1102
|
+
botlrErrorMessage
|
|
1103
|
+
} from "@botlr/sdk";
|
|
1100
1104
|
|
|
1101
1105
|
// src/schema-diff.ts
|
|
1102
1106
|
import { schemaFormat as schemaFormat2 } from "@botlr/sdk";
|
|
@@ -1112,15 +1116,20 @@ function diffSchemaKeys(current, next) {
|
|
|
1112
1116
|
kept: [...nextKeys].filter((k) => currentKeys.has(k)).sort()
|
|
1113
1117
|
};
|
|
1114
1118
|
}
|
|
1115
|
-
function
|
|
1116
|
-
return
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1119
|
+
function formatsDiffer(preview) {
|
|
1120
|
+
return !!preview.currentFormat && !!preview.nextFormat && preview.currentFormat !== preview.nextFormat;
|
|
1121
|
+
}
|
|
1122
|
+
function expectedPushMode(preview) {
|
|
1123
|
+
if (preview.force || !formatsDiffer(preview)) return "replaced";
|
|
1124
|
+
return preview.currentFormat === "form-builder" && preview.nextFormat === "field-dsl" ? "merged" : "conflict";
|
|
1125
|
+
}
|
|
1126
|
+
function previewSchemaPush(name, current, next, options = {}) {
|
|
1127
|
+
const formats = {
|
|
1128
|
+
currentFormat: options.currentFormat ?? schemaFormat2(current),
|
|
1129
|
+
nextFormat: options.nextFormat ?? schemaFormat2(next),
|
|
1130
|
+
force: options.force ?? false
|
|
1123
1131
|
};
|
|
1132
|
+
return { name, current, next, diff: diffSchemaKeys(current, next), ...formats, expected: expectedPushMode(formats) };
|
|
1124
1133
|
}
|
|
1125
1134
|
function plural(n, word) {
|
|
1126
1135
|
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
@@ -1129,14 +1138,21 @@ function formatName(format) {
|
|
|
1129
1138
|
return format ?? "unknown";
|
|
1130
1139
|
}
|
|
1131
1140
|
function formatSchemaDiff(preview) {
|
|
1132
|
-
const { diff } = preview;
|
|
1133
|
-
|
|
1141
|
+
const { diff, expected } = preview;
|
|
1142
|
+
let header = `schema of '${preview.name}': dashboard has ${formatName(preview.currentFormat)} (${plural(schemaKeys(preview.current).length, "field")}), code has ${formatName(preview.nextFormat)} (${plural(schemaKeys(preview.next).length, "field")})`;
|
|
1143
|
+
if (expected === "merged") {
|
|
1144
|
+
header += ": the server will add the missing fields as FormBuilder nodes and keep the dashboard's (format kept; --force would replace it)";
|
|
1145
|
+
}
|
|
1134
1146
|
const lines = [header];
|
|
1135
1147
|
if (diff.added.length) lines.push(` + added ${diff.added.join(", ")}`);
|
|
1136
|
-
if (diff.removed.length)
|
|
1148
|
+
if (diff.removed.length) {
|
|
1149
|
+
lines.push(expected === "merged" ? ` ~ dashboard only, kept ${diff.removed.join(", ")}` : ` - removed ${diff.removed.join(", ")}`);
|
|
1150
|
+
}
|
|
1137
1151
|
lines.push(` = kept ${diff.kept.length ? diff.kept.join(", ") : "none"}`);
|
|
1138
|
-
if (preview
|
|
1139
|
-
lines.push(
|
|
1152
|
+
if (formatsDiffer(preview) && expected !== "merged") {
|
|
1153
|
+
lines.push(
|
|
1154
|
+
preview.force ? ` the formats differ: replacing ${preview.currentFormat} with ${preview.nextFormat} (--force)` : ` the formats differ: the push needs --force to replace ${preview.currentFormat} with ${preview.nextFormat}`
|
|
1155
|
+
);
|
|
1140
1156
|
}
|
|
1141
1157
|
if (!diff.added.length && !diff.removed.length) lines.push(" the field keys match; only field settings may change");
|
|
1142
1158
|
return lines.join("\n");
|
|
@@ -1174,16 +1190,18 @@ async function pullSchemas(config, options) {
|
|
|
1174
1190
|
return results;
|
|
1175
1191
|
}
|
|
1176
1192
|
var SchemaFormatConflict = class extends Error {
|
|
1177
|
-
constructor(sectionName, currentFormat, newFormat, message) {
|
|
1193
|
+
constructor(sectionName, currentFormat, newFormat, message, preview) {
|
|
1178
1194
|
super(message ?? `the schema of '${sectionName}' is stored as ${currentFormat}, the manifest's is ${newFormat}`);
|
|
1179
1195
|
this.sectionName = sectionName;
|
|
1180
1196
|
this.currentFormat = currentFormat;
|
|
1181
1197
|
this.newFormat = newFormat;
|
|
1198
|
+
this.preview = preview;
|
|
1182
1199
|
this.name = "SchemaFormatConflict";
|
|
1183
1200
|
}
|
|
1184
1201
|
sectionName;
|
|
1185
1202
|
currentFormat;
|
|
1186
1203
|
newFormat;
|
|
1204
|
+
preview;
|
|
1187
1205
|
};
|
|
1188
1206
|
var SchemaRejected = class extends Error {
|
|
1189
1207
|
constructor(sectionName, message) {
|
|
@@ -1210,13 +1228,17 @@ async function pushSchema(config, options) {
|
|
|
1210
1228
|
if (!isNotFound(error)) throw error;
|
|
1211
1229
|
throw new ManifestError(`the app has no section type '${name}'; push the code first with 'botlr push ${name} --create'`);
|
|
1212
1230
|
}
|
|
1213
|
-
const
|
|
1231
|
+
const force = options.force ?? false;
|
|
1232
|
+
const preview = previewSchemaPush(name, current.schema, next, { currentFormat: current.format, force });
|
|
1233
|
+
if (preview.expected === "conflict") {
|
|
1234
|
+
throw new SchemaFormatConflict(name, preview.currentFormat, preview.nextFormat, void 0, preview);
|
|
1235
|
+
}
|
|
1214
1236
|
if (options.confirm && !await options.confirm(preview)) {
|
|
1215
1237
|
return { name, preview, pushed: false };
|
|
1216
1238
|
}
|
|
1217
1239
|
let response;
|
|
1218
1240
|
try {
|
|
1219
|
-
response = await client.sections.putSchema(name, { schema: next, force
|
|
1241
|
+
response = await client.sections.putSchema(name, { schema: next, force });
|
|
1220
1242
|
} catch (error) {
|
|
1221
1243
|
if (error instanceof BotlrSchemaFormatConflictError) {
|
|
1222
1244
|
throw new SchemaFormatConflict(name, error.currentFormat, error.newFormat, botlrErrorMessage(error));
|
|
@@ -1234,6 +1256,9 @@ async function pushSchema(config, options) {
|
|
|
1234
1256
|
changed: response.changed,
|
|
1235
1257
|
previousFormat: response.previousFormat,
|
|
1236
1258
|
format: response.definition.format,
|
|
1259
|
+
mode: response.mode,
|
|
1260
|
+
added: response.added,
|
|
1261
|
+
dashboardOnly: response.dashboardOnly,
|
|
1237
1262
|
types
|
|
1238
1263
|
};
|
|
1239
1264
|
}
|
|
@@ -1280,6 +1305,7 @@ async function run(action) {
|
|
|
1280
1305
|
pull first, or push --force to overwrite`);
|
|
1281
1306
|
}
|
|
1282
1307
|
if (error instanceof SchemaFormatConflict) {
|
|
1308
|
+
if (error.preview) console.log(formatSchemaDiff(error.preview));
|
|
1283
1309
|
fail(
|
|
1284
1310
|
`${error.message}
|
|
1285
1311
|
dashboard: ${error.currentFormat}
|
|
@@ -1443,16 +1469,17 @@ async function confirmOnTerminal(preview) {
|
|
|
1443
1469
|
if (!process.stdin.isTTY) {
|
|
1444
1470
|
fail(`not a terminal: rerun with --yes to push the schema of '${preview.name}' without asking`);
|
|
1445
1471
|
}
|
|
1472
|
+
const question = preview.expected === "merged" ? `merge the code's fields into the dashboard's ${preview.currentFormat} schema of '${preview.name}'? [y/N] ` : preview.force && preview.currentFormat !== preview.nextFormat ? `replace the dashboard's ${preview.currentFormat} schema of '${preview.name}' with the code's ${preview.nextFormat} schema? [y/N] ` : `replace the dashboard's schema of '${preview.name}' with the code's? [y/N] `;
|
|
1446
1473
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1447
1474
|
try {
|
|
1448
|
-
const answer = await rl.question(
|
|
1475
|
+
const answer = await rl.question(question);
|
|
1449
1476
|
return /^y(es)?$/i.test(answer.trim());
|
|
1450
1477
|
} finally {
|
|
1451
1478
|
rl.close();
|
|
1452
1479
|
}
|
|
1453
1480
|
}
|
|
1454
1481
|
withCommon(
|
|
1455
|
-
schema.command("push <name>").description("send the manifest's 'schema' to the dashboard as the definition's props schema (needs BOTLR_PAT)").option("--force", "replace the schema
|
|
1482
|
+
schema.command("push <name>").description("send the manifest's 'schema' to the dashboard as the definition's props schema (needs BOTLR_PAT)").option("--force", "replace the schema outright, instead of merging the code's fields into a FormBuilder schema").option("-y, --yes", "push without asking").option("--no-types", "do not write <name>.props.ts afterwards")
|
|
1456
1483
|
).action(
|
|
1457
1484
|
(name, opts) => run(async () => {
|
|
1458
1485
|
const config = await loadConfig(process.cwd(), overrides(opts));
|
|
@@ -1470,8 +1497,15 @@ withCommon(
|
|
|
1470
1497
|
return;
|
|
1471
1498
|
}
|
|
1472
1499
|
const formats = result.previousFormat && result.format && result.previousFormat !== result.format ? ` (${result.previousFormat} -> ${result.format})` : result.format ? ` (${result.format})` : "";
|
|
1473
|
-
if (result.changed) console.log(`
|
|
1474
|
-
else
|
|
1500
|
+
if (!result.changed) console.log(`schema of ${result.name}: nothing to change${formats}`);
|
|
1501
|
+
else if (result.mode === "merged") {
|
|
1502
|
+
const added = result.added?.length ? `added ${result.added.join(", ")}` : "added nothing";
|
|
1503
|
+
const kept = result.preview.diff.kept.length ? `; kept ${result.preview.diff.kept.join(", ")}` : "";
|
|
1504
|
+
console.log(`merged schema of ${result.name}${formats}: ${added}${kept}`);
|
|
1505
|
+
} else console.log(`replaced schema of ${result.name}${formats}`);
|
|
1506
|
+
if (result.dashboardOnly?.length) {
|
|
1507
|
+
console.log(` the dashboard also defines: ${result.dashboardOnly.join(", ")} (not in the code)`);
|
|
1508
|
+
}
|
|
1475
1509
|
if (result.types?.changed) console.log(` types: wrote ${result.types.interfaceName} to ${path7.relative(process.cwd(), result.types.file)}`);
|
|
1476
1510
|
})
|
|
1477
1511
|
);
|