@botlr/cli 0.2.0 → 0.3.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @botlr/cli
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `botlr schema pull` and `botlr schema push`: the explicit way to resolve props schema drift in either direction. A code push still never changes the schema.
8
+
9
+ - `botlr schema pull <name...>` (or `--all` for every manifest in `sectionsDir`) fetches the section definition's props schema from the dashboard and writes it as `schema` into `<name>.botlr.json`, in the CLI's fixed key order and in whichever format the dashboard has (field DSL or FormBuilder). It says "unchanged" when the manifest already holds the same schema, and regenerates `<name>.props.ts` afterwards unless `--no-types` or `"types": false`.
10
+ - `botlr schema push <name> [--force] [--yes]` reads `schema` from the manifest (an error when it is missing), fetches the dashboard's current schema, prints both formats and a diff of the field keys (added, removed, kept; node names for FormBuilder schemas), asks for confirmation unless `--yes`, then replaces the dashboard's schema (needs `BOTLR_PAT`). When the two schemas are in different formats the server answers 409 and the CLI prints both formats and says to rerun with `--force`; a 422 (not an object of fields, or the definition is not project-scoped) prints the server's message. A successful push regenerates the props file.
11
+ - The drift warning of `push` and `sections status` now names both commands: `botlr schema pull <name>` to take the dashboard's schema, `botlr schema push <name>` to send the code's. A manifest `schema` in the FormBuilder format now takes part in the drift comparison too (by node names), since `schema pull` can write one.
12
+ - `@botlr/sdk`: `client.sections.getSchema(name)` (site token, `GET /api/sections/{name}/schema`) and `client.sections.putSchema(name, { schema, force })` (personal token, `PUT /api/sections/{name}/schema`), with the `SectionSchemaFormat`, `SectionSchemaGetResponse`, `SectionSchemaPutBody` and `SectionSchemaPutResponse` types, `schemaFormat(schema)`, `SectionDefinition.format`, `BotlrSchemaFormatConflictError` (the 409 with `currentFormat` and `newFormat`) and `botlrErrorMessage(error)` to read a server's `detail` message. `SectionManifest.schema` is typed as `SectionSchema` (either format).
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies
17
+ - @botlr/sdk@0.5.0
18
+
3
19
  ## 0.2.0
4
20
 
5
21
  ### Minor Changes
package/README.md CHANGED
@@ -28,8 +28,9 @@ component registry `pull` keeps up to date and `sections init` reads.
28
28
  `types` (default true) makes `pull` write `<name>.props.ts` from the
29
29
  definition's props schema; `false` turns it off for the app.
30
30
 
31
- Environment: `BOTLR_API_KEY` (site token, for pull/list/status) and
32
- `BOTLR_PAT` (personal access token with `write:sections`, for push).
31
+ Environment: `BOTLR_API_KEY` (site token, for pull/list/status and `schema
32
+ pull`) and `BOTLR_PAT` (personal access token with `write:sections`, for
33
+ `push` and `schema push`).
33
34
  `BOTLR_BASE_URL`, `BOTLR_APP_ID`, `BOTLR_FRAMEWORK` and `BOTLR_SECTIONS_DIR`
34
35
  override the file; `--framework` and `--dir` override both.
35
36
 
@@ -41,6 +42,8 @@ botlr push <name> [-m msg] [--shared] [--create] [--force]
41
42
  botlr sections list
42
43
  botlr sections status [name...] [--offline]
43
44
  botlr sections init [--force] [--dry-run]
45
+ botlr schema pull <name...> | --all [--no-types]
46
+ botlr schema push <name> [--force] [-y, --yes] [--no-types]
44
47
  ```
45
48
 
46
49
  - `pull` writes every file of the section under `sectionsDir`, plus
@@ -76,6 +79,10 @@ botlr sections init [--force] [--dry-run]
76
79
  `.optional()`, `.nullable()` or `.default(...)`). Fields it cannot read are
77
80
  left out, and so is `schema` when nothing could be read. Review them, then
78
81
  `botlr push <name> --create` per section.
82
+ - `schema pull` copies the dashboard's props schema into the manifest's
83
+ `schema`; `schema push` sends the manifest's `schema` to the dashboard.
84
+ These are the only commands that change a definition's schema; see
85
+ "Schema drift" below.
79
86
 
80
87
  ## Prop types
81
88
 
@@ -117,22 +124,58 @@ export function Hero(props: HeroProps) { ... }
117
124
 
118
125
  ## Schema drift
119
126
 
120
- A manifest may carry a `schema` object in the field DSL (the same shape the
121
- dashboard stores): the props the code expects. The CLI never writes it to
122
- the server's definition except as the seed for `push --create`. On `push`
123
- and `sections status` it compares that object's keys with the definition's
124
- schema and warns:
127
+ A manifest may carry a `schema` object (normally the field DSL, the same
128
+ shape the dashboard stores): the props the code expects. A code `push`
129
+ never writes it to the server's definition except as the seed for
130
+ `push --create`. On `push` and `sections status` the CLI compares that
131
+ object's keys with the definition's schema and warns:
125
132
 
126
133
  ```
127
134
  warning: props schema of 'hero' differs between the code and the dashboard
128
135
  the code expects props the dashboard does not define: ctaLink
129
136
  the dashboard defines props the code does not list: image
130
- the dashboard owns the schema: edit it there, or update 'schema' in hero.botlr.json
137
+ the dashboard owns the schema; a code push never changes it. To resolve:
138
+ botlr schema pull hero takes the dashboard's schema into hero.botlr.json
139
+ botlr schema push hero sends the code's schema to the dashboard
131
140
  ```
132
141
 
133
- The warning changes nothing; fix the schema in the dashboard, pull to
134
- refresh `<name>.props.ts`, and update the manifest's `schema` (or drop it)
135
- when the code catches up.
142
+ The warning changes nothing. Resolve it in one of two directions:
143
+
144
+ ```bash
145
+ botlr schema pull hero # the dashboard is right: copy its schema into hero.botlr.json
146
+ botlr schema push hero # the code is right: replace the dashboard's schema with the manifest's
147
+ ```
148
+
149
+ `schema pull <name...>` (or `--all` for every manifest in `sectionsDir`)
150
+ fetches the definition's props schema and writes it as `schema` into
151
+ `<name>.botlr.json`, in the fixed key order and in whichever format the
152
+ dashboard has. It prints `schema unchanged` when the manifest already says
153
+ the same, and regenerates `<name>.props.ts` afterwards unless `--no-types`
154
+ (or `"types": false`). A section the app has no definition for is skipped
155
+ with a notice.
156
+
157
+ `schema push <name>` reads `schema` from the manifest (an error when it is
158
+ missing), fetches the dashboard's current schema and prints the formats and
159
+ a diff of the field keys, then asks before sending (`--yes` skips the
160
+ question, and is required when there is no terminal):
161
+
162
+ ```
163
+ schema of 'hero': dashboard has field-dsl (2 fields), code has field-dsl (3 fields)
164
+ + added ctaLink, subheading
165
+ - removed image
166
+ = kept heading
167
+ replace the dashboard's schema of 'hero' with the code's? [y/N]
168
+ ```
169
+
170
+ For a FormBuilder schema (`{ nodes: [...] }`) the keys are the node names.
171
+ When the dashboard's schema and the manifest's are in different formats the
172
+ server refuses with 409; the CLI prints both formats and says to rerun with
173
+ `--force`, which replaces the schema whatever its format. A 422 (the
174
+ `schema` is not an object of field definitions, or the definition is not
175
+ project-scoped and the template has to be installed into the project first)
176
+ prints the server's message. After a successful push the CLI regenerates
177
+ `<name>.props.ts` from the stored schema. `schema push` needs `BOTLR_PAT`,
178
+ like `push`.
136
179
 
137
180
  ## Manifest format
138
181
 
package/dist/cli.js CHANGED
@@ -446,14 +446,14 @@ function parseZodSchema(source, schemaName) {
446
446
  }
447
447
  }
448
448
  if (end < 0) return null;
449
- const schema = {};
449
+ const schema2 = {};
450
450
  for (const entry of splitTopLevel(source.slice(open, end))) {
451
451
  const m = entry.match(/^(?:"([^"]+)"|'([^']+)'|([A-Za-z_$][A-Za-z0-9_$]*))\s*:\s*([\s\S]+)$/);
452
452
  if (!m) continue;
453
453
  const field = zodFieldToSchemaField(m[4]);
454
- if (field) schema[m[1] ?? m[2] ?? m[3]] = field;
454
+ if (field) schema2[m[1] ?? m[2] ?? m[3]] = field;
455
455
  }
456
- return Object.keys(schema).length ? schema : null;
456
+ return Object.keys(schema2).length ? schema2 : null;
457
457
  }
458
458
  function parseImports(source) {
459
459
  const internal = /* @__PURE__ */ new Set();
@@ -488,7 +488,7 @@ async function deriveManifests(sectionsDir, options) {
488
488
  if (!entryFile) continue;
489
489
  const source = await readFile4(entryFile, "utf8");
490
490
  const meta = parseDefinition(source);
491
- const schema = meta?.schemaName ? parseZodSchema(source, meta.schemaName) : null;
491
+ const schema2 = meta?.schemaName ? parseZodSchema(source, meta.schemaName) : null;
492
492
  const imports = parseImports(source);
493
493
  const relative = path4.relative(sectionsDir, entryFile).split(path4.sep).join("/");
494
494
  const npm = {};
@@ -510,7 +510,7 @@ async function deriveManifests(sectionsDir, options) {
510
510
  ...Object.keys(npm).length ? { npm } : {},
511
511
  ...imports.internal.length ? { internal: imports.internal } : {}
512
512
  },
513
- ...schema ? { schema } : {}
513
+ ...schema2 ? { schema: schema2 } : {}
514
514
  };
515
515
  out.push({ name: entry.slug, manifest, entryFile });
516
516
  }
@@ -734,9 +734,9 @@ function docFor(field) {
734
734
  }
735
735
  return doc;
736
736
  }
737
- function collectDslFields(schema) {
737
+ function collectDslFields(schema2) {
738
738
  const out = [];
739
- for (const [name, value] of Object.entries(schema)) {
739
+ for (const [name, value] of Object.entries(schema2)) {
740
740
  if (!value || typeof value !== "object" || Array.isArray(value)) continue;
741
741
  const field = value;
742
742
  out.push({ name, type: tsTypeForField(field), required: field.required === true, doc: docFor(field) });
@@ -759,13 +759,13 @@ function collectFormBuilderFields(nodes) {
759
759
  }
760
760
  return out;
761
761
  }
762
- function collectFields(schema) {
763
- if (isFormBuilderSchema(schema)) return collectFormBuilderFields(schema.nodes);
764
- if (isFieldSchema(schema)) return collectDslFields(schema);
762
+ function collectFields(schema2) {
763
+ if (isFormBuilderSchema(schema2)) return collectFormBuilderFields(schema2.nodes);
764
+ if (isFieldSchema(schema2)) return collectDslFields(schema2);
765
765
  return [];
766
766
  }
767
- function schemaFieldNames(schema) {
768
- return collectFields(schema).map((f) => f.name);
767
+ function schemaFieldNames(schema2) {
768
+ return collectFields(schema2).map((f) => f.name);
769
769
  }
770
770
  var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
771
771
  function propertyKey(name) {
@@ -783,8 +783,8 @@ function propsInterfaceName(manifest) {
783
783
  const base = manifest.exportName && IDENTIFIER_RE.test(manifest.exportName) ? manifest.exportName : pascalCase(manifest.section);
784
784
  return `${base}Props`;
785
785
  }
786
- function generatePropsSource(schema, options) {
787
- const fields = collectFields(schema);
786
+ function generatePropsSource(schema2, options) {
787
+ const fields = collectFields(schema2);
788
788
  const lines = [
789
789
  `// Generated by @botlr/cli from the Botlr section definition "${options.section}".`,
790
790
  "// Do not edit: the props schema lives in the dashboard, and `botlr pull` rewrites this file.",
@@ -810,10 +810,10 @@ function generatePropsSource(schema, options) {
810
810
  function propsPath(sectionsDir, name) {
811
811
  return path6.join(sectionsDir, `${name}${PROPS_SUFFIX}`);
812
812
  }
813
- async function writePropsTypes(sectionsDir, manifest, schema) {
813
+ async function writePropsTypes(sectionsDir, manifest, schema2) {
814
814
  const interfaceName = propsInterfaceName(manifest);
815
815
  const file = propsPath(sectionsDir, manifest.section);
816
- const text = generatePropsSource(schema, { section: manifest.section, interfaceName });
816
+ const text = generatePropsSource(schema2, { section: manifest.section, interfaceName });
817
817
  try {
818
818
  if (await readFile6(file, "utf8") === text) return { file, interfaceName, changed: false };
819
819
  } catch {
@@ -900,15 +900,15 @@ import {
900
900
  } from "@botlr/sdk";
901
901
 
902
902
  // src/drift.ts
903
- import { isFieldSchema as isFieldSchema2 } from "@botlr/sdk";
903
+ import { schemaFormat } from "@botlr/sdk";
904
904
  function manifestSchema(manifest) {
905
- const schema = manifest.schema;
906
- return isFieldSchema2(schema) ? schema : null;
905
+ const schema2 = manifest.schema;
906
+ return schemaFormat(schema2) ? schema2 : null;
907
907
  }
908
908
  function compareSchemas(manifest, definitionSchema) {
909
909
  const code = manifestSchema(manifest);
910
910
  if (!code) return null;
911
- const codeKeys = new Set(Object.keys(code).filter((k) => code[k] && typeof code[k] === "object"));
911
+ const codeKeys = new Set(schemaFieldNames(code));
912
912
  const dashboardKeys = new Set(schemaFieldNames(definitionSchema));
913
913
  const missingInDashboard = [...codeKeys].filter((k) => !dashboardKeys.has(k)).sort();
914
914
  const missingInCode = [...dashboardKeys].filter((k) => !codeKeys.has(k)).sort();
@@ -923,7 +923,9 @@ function formatDriftWarning(name, drift) {
923
923
  if (drift.missingInCode.length) {
924
924
  lines.push(` the dashboard defines props the code does not list: ${drift.missingInCode.join(", ")}`);
925
925
  }
926
- lines.push(` the dashboard owns the schema: edit it there, or update 'schema' in ${name}.botlr.json`);
926
+ lines.push(` the dashboard owns the schema; a code push never changes it. To resolve:`);
927
+ lines.push(` botlr schema pull ${name} takes the dashboard's schema into ${name}.botlr.json`);
928
+ lines.push(` botlr schema push ${name} sends the code's schema to the dashboard`);
927
929
  return lines.join("\n");
928
930
  }
929
931
 
@@ -1093,6 +1095,149 @@ async function initSections(config, options = {}) {
1093
1095
  return results;
1094
1096
  }
1095
1097
 
1098
+ // src/commands/schema.ts
1099
+ import { BotlrError, BotlrSchemaFormatConflictError, botlrErrorMessage } from "@botlr/sdk";
1100
+
1101
+ // src/schema-diff.ts
1102
+ import { schemaFormat as schemaFormat2 } from "@botlr/sdk";
1103
+ function schemaKeys(schema2) {
1104
+ return [...new Set(schemaFieldNames(schema2))];
1105
+ }
1106
+ function diffSchemaKeys(current, next) {
1107
+ const currentKeys = new Set(schemaKeys(current));
1108
+ const nextKeys = new Set(schemaKeys(next));
1109
+ return {
1110
+ added: [...nextKeys].filter((k) => !currentKeys.has(k)).sort(),
1111
+ removed: [...currentKeys].filter((k) => !nextKeys.has(k)).sort(),
1112
+ kept: [...nextKeys].filter((k) => currentKeys.has(k)).sort()
1113
+ };
1114
+ }
1115
+ function previewSchemaPush(name, current, next, formats = {}) {
1116
+ return {
1117
+ name,
1118
+ current,
1119
+ currentFormat: formats.currentFormat ?? schemaFormat2(current),
1120
+ next,
1121
+ nextFormat: formats.nextFormat ?? schemaFormat2(next),
1122
+ diff: diffSchemaKeys(current, next)
1123
+ };
1124
+ }
1125
+ function plural(n, word) {
1126
+ return `${n} ${word}${n === 1 ? "" : "s"}`;
1127
+ }
1128
+ function formatName(format) {
1129
+ return format ?? "unknown";
1130
+ }
1131
+ function formatSchemaDiff(preview) {
1132
+ const { diff } = preview;
1133
+ const 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")})`;
1134
+ const lines = [header];
1135
+ if (diff.added.length) lines.push(` + added ${diff.added.join(", ")}`);
1136
+ if (diff.removed.length) lines.push(` - removed ${diff.removed.join(", ")}`);
1137
+ lines.push(` = kept ${diff.kept.length ? diff.kept.join(", ") : "none"}`);
1138
+ if (preview.currentFormat && preview.nextFormat && preview.currentFormat !== preview.nextFormat) {
1139
+ lines.push(` the formats differ: the push needs --force to replace ${preview.currentFormat} with ${preview.nextFormat}`);
1140
+ }
1141
+ if (!diff.added.length && !diff.removed.length) lines.push(" the field keys match; only field settings may change");
1142
+ return lines.join("\n");
1143
+ }
1144
+
1145
+ // src/commands/schema.ts
1146
+ function isNotFound(error) {
1147
+ return error instanceof BotlrError && error.status === 404;
1148
+ }
1149
+ async function pullSchemas(config, options) {
1150
+ const client = clientFor(config);
1151
+ const names = options.all ? await listManifests(config.sectionsDir) : options.names;
1152
+ const results = [];
1153
+ for (const name of names) {
1154
+ let manifest;
1155
+ try {
1156
+ manifest = await readManifest(config.sectionsDir, name);
1157
+ } catch (error) {
1158
+ results.push({ name, changed: false, file: "", error: error.message });
1159
+ continue;
1160
+ }
1161
+ let definition;
1162
+ try {
1163
+ definition = (await client.sections.getSchema(name)).definition;
1164
+ } catch (error) {
1165
+ if (!isNotFound(error)) throw error;
1166
+ results.push({ name, changed: false, file: "", error: `the app has no section type '${name}' (botlr push ${name} --create adds one)` });
1167
+ continue;
1168
+ }
1169
+ const updated = { ...manifest, schema: definition.schema };
1170
+ const written = await writeManifestIfChanged(config.sectionsDir, name, updated);
1171
+ const types = config.types && !options.noTypes ? await writePropsTypes(config.sectionsDir, manifest, definition.schema) : void 0;
1172
+ results.push({ name, changed: written.changed, file: written.file, format: definition.format, types });
1173
+ }
1174
+ return results;
1175
+ }
1176
+ var SchemaFormatConflict = class extends Error {
1177
+ constructor(sectionName, currentFormat, newFormat, message) {
1178
+ super(message ?? `the schema of '${sectionName}' is stored as ${currentFormat}, the manifest's is ${newFormat}`);
1179
+ this.sectionName = sectionName;
1180
+ this.currentFormat = currentFormat;
1181
+ this.newFormat = newFormat;
1182
+ this.name = "SchemaFormatConflict";
1183
+ }
1184
+ sectionName;
1185
+ currentFormat;
1186
+ newFormat;
1187
+ };
1188
+ var SchemaRejected = class extends Error {
1189
+ constructor(sectionName, message) {
1190
+ super(message);
1191
+ this.sectionName = sectionName;
1192
+ this.name = "SchemaRejected";
1193
+ }
1194
+ sectionName;
1195
+ };
1196
+ async function pushSchema(config, options) {
1197
+ const client = clientFor(config, { needPersonalToken: true });
1198
+ const { name } = options;
1199
+ const manifest = await readManifest(config.sectionsDir, name);
1200
+ const next = manifest.schema;
1201
+ if (!next || typeof next !== "object" || Array.isArray(next)) {
1202
+ throw new ManifestError(
1203
+ `${name}.botlr.json has no 'schema' object to push; add one, or run 'botlr schema pull ${name}' to take the dashboard's`
1204
+ );
1205
+ }
1206
+ let current;
1207
+ try {
1208
+ current = (await client.sections.getSchema(name)).definition;
1209
+ } catch (error) {
1210
+ if (!isNotFound(error)) throw error;
1211
+ throw new ManifestError(`the app has no section type '${name}'; push the code first with 'botlr push ${name} --create'`);
1212
+ }
1213
+ const preview = previewSchemaPush(name, current.schema, next, { currentFormat: current.format });
1214
+ if (options.confirm && !await options.confirm(preview)) {
1215
+ return { name, preview, pushed: false };
1216
+ }
1217
+ let response;
1218
+ try {
1219
+ response = await client.sections.putSchema(name, { schema: next, force: options.force ?? false });
1220
+ } catch (error) {
1221
+ if (error instanceof BotlrSchemaFormatConflictError) {
1222
+ throw new SchemaFormatConflict(name, error.currentFormat, error.newFormat, botlrErrorMessage(error));
1223
+ }
1224
+ if (error instanceof BotlrError && error.status === 422) {
1225
+ throw new SchemaRejected(name, botlrErrorMessage(error) ?? "the server rejected the schema (422)");
1226
+ }
1227
+ throw error;
1228
+ }
1229
+ const types = config.types && !options.noTypes ? await writePropsTypes(config.sectionsDir, manifest, response.definition.schema) : void 0;
1230
+ return {
1231
+ name,
1232
+ preview,
1233
+ pushed: true,
1234
+ changed: response.changed,
1235
+ previousFormat: response.previousFormat,
1236
+ format: response.definition.format,
1237
+ types
1238
+ };
1239
+ }
1240
+
1096
1241
  // src/output.ts
1097
1242
  function table(rows, header) {
1098
1243
  const all = header ? [header, ...rows] : rows;
@@ -1110,6 +1255,7 @@ function fail(message) {
1110
1255
  }
1111
1256
 
1112
1257
  // src/cli.ts
1258
+ import { createInterface } from "readline/promises";
1113
1259
  var require2 = createRequire(import.meta.url);
1114
1260
  var { version } = require2("../package.json");
1115
1261
  function parseVersion(value) {
@@ -1133,6 +1279,17 @@ async function run(action) {
1133
1279
  latest on server: ${latest}
1134
1280
  pull first, or push --force to overwrite`);
1135
1281
  }
1282
+ if (error instanceof SchemaFormatConflict) {
1283
+ fail(
1284
+ `${error.message}
1285
+ dashboard: ${error.currentFormat}
1286
+ code: ${error.newFormat}
1287
+ rerun with --force to replace the ${error.currentFormat} schema: botlr schema push ${error.sectionName} --force`
1288
+ );
1289
+ }
1290
+ if (error instanceof SchemaRejected) {
1291
+ fail(`the server rejected the schema of '${error.sectionName}': ${error.message}`);
1292
+ }
1136
1293
  fail(error.message);
1137
1294
  }
1138
1295
  }
@@ -1256,5 +1413,67 @@ withCommon(
1256
1413
  review the manifests, then push each section: botlr push <name> --create`);
1257
1414
  })
1258
1415
  );
1416
+ var schema = program.command("schema").description("copy a section's props schema between the dashboard and <name>.botlr.json");
1417
+ withCommon(
1418
+ schema.command("pull [names...]").description("write the dashboard's props schema into <name>.botlr.json as 'schema', then regenerate <name>.props.ts").option("--all", "every section with a manifest in the sections directory").option("--no-types", "do not write <name>.props.ts afterwards")
1419
+ ).action(
1420
+ (names, opts) => run(async () => {
1421
+ if (names.length === 0 && !opts.all) fail("name a section or pass --all");
1422
+ const config = await loadConfig(process.cwd(), overrides(opts));
1423
+ const results = await pullSchemas(config, { names, all: opts.all, noTypes: opts.types === false });
1424
+ if (results.length === 0) {
1425
+ console.log(`no *.botlr.json manifests in ${config.sectionsDir}`);
1426
+ return;
1427
+ }
1428
+ for (const r of results) {
1429
+ if (r.error) {
1430
+ console.log(`skip ${r.name}: ${r.error}`);
1431
+ process.exitCode = 1;
1432
+ continue;
1433
+ }
1434
+ const format = r.format ? ` (${r.format})` : "";
1435
+ if (r.changed) console.log(`pull ${r.name}: wrote schema${format} to ${path7.relative(process.cwd(), r.file)}`);
1436
+ else console.log(`pull ${r.name}: schema unchanged${format}`);
1437
+ if (r.types?.changed) console.log(` types: wrote ${r.types.interfaceName} to ${path7.relative(process.cwd(), r.types.file)}`);
1438
+ }
1439
+ })
1440
+ );
1441
+ async function confirmOnTerminal(preview) {
1442
+ console.log(formatSchemaDiff(preview));
1443
+ if (!process.stdin.isTTY) {
1444
+ fail(`not a terminal: rerun with --yes to push the schema of '${preview.name}' without asking`);
1445
+ }
1446
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
1447
+ try {
1448
+ const answer = await rl.question(`replace the dashboard's schema of '${preview.name}' with the code's? [y/N] `);
1449
+ return /^y(es)?$/i.test(answer.trim());
1450
+ } finally {
1451
+ rl.close();
1452
+ }
1453
+ }
1454
+ 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 even when the dashboard's is in the other format").option("-y, --yes", "push without asking").option("--no-types", "do not write <name>.props.ts afterwards")
1456
+ ).action(
1457
+ (name, opts) => run(async () => {
1458
+ const config = await loadConfig(process.cwd(), overrides(opts));
1459
+ const result = await pushSchema(config, {
1460
+ name,
1461
+ force: opts.force,
1462
+ noTypes: opts.types === false,
1463
+ confirm: opts.yes ? (preview) => {
1464
+ console.log(formatSchemaDiff(preview));
1465
+ return true;
1466
+ } : confirmOnTerminal
1467
+ });
1468
+ if (!result.pushed) {
1469
+ console.log(`nothing pushed`);
1470
+ return;
1471
+ }
1472
+ const formats = result.previousFormat && result.format && result.previousFormat !== result.format ? ` (${result.previousFormat} -> ${result.format})` : result.format ? ` (${result.format})` : "";
1473
+ if (result.changed) console.log(`pushed schema of ${result.name}${formats}`);
1474
+ else console.log(`schema of ${result.name} already matches, nothing changed${formats}`);
1475
+ if (result.types?.changed) console.log(` types: wrote ${result.types.interfaceName} to ${path7.relative(process.cwd(), result.types.file)}`);
1476
+ })
1477
+ );
1259
1478
  program.parseAsync(process.argv).catch((error) => fail(error.message));
1260
1479
  //# sourceMappingURL=cli.js.map