@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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SectionSourceScope, SectionManifest, SectionSourceFileInput, SectionSourceScopeSummary, SectionSourceSummary, SectionFieldSchema, SectionSchemaField, SectionFormBuilderNode, SectionSchema, SectionSchemaFormat, BotlrClient } from '@botlr/sdk';
1
+ import { SectionSourceScope, SectionManifest, SectionSourceFileInput, SectionSourceScopeSummary, SectionSourceSummary, SectionFieldSchema, SectionSchemaField, SectionFormBuilderNode, SectionSchema, SectionSchemaFormat, BotlrClient, SectionSchemaPushMode } from '@botlr/sdk';
2
2
 
3
3
  declare const CONFIG_FILE = "botlr.config.json";
4
4
  declare const DEFAULT_FRAMEWORK = "react-next";
@@ -348,16 +348,32 @@ interface SchemaPushPreview {
348
348
  next: unknown;
349
349
  nextFormat: SectionSchemaFormat | null;
350
350
  diff: SchemaKeyDiff;
351
+ /** Whether `--force` was given. */
352
+ force: boolean;
353
+ /** What the server is expected to do with this push. */
354
+ expected: ExpectedPushMode;
351
355
  }
356
+ /**
357
+ * - `replaced`: same format, or `--force`
358
+ * - `merged`: field-DSL fields at a FormBuilder schema; the server adds nodes and keeps the rest
359
+ * - `conflict`: a FormBuilder schema at a field-DSL one; the server answers 409 without `--force`
360
+ */
361
+ type ExpectedPushMode = "replaced" | "merged" | "conflict";
362
+ /** True when both formats are known and differ. */
363
+ declare function formatsDiffer(preview: Pick<SchemaPushPreview, "currentFormat" | "nextFormat">): boolean;
364
+ declare function expectedPushMode(preview: Pick<SchemaPushPreview, "currentFormat" | "nextFormat" | "force">): ExpectedPushMode;
352
365
  /** The preview for a push, with formats detected from the schemas unless given. */
353
- declare function previewSchemaPush(name: string, current: unknown, next: unknown, formats?: {
366
+ declare function previewSchemaPush(name: string, current: unknown, next: unknown, options?: {
354
367
  currentFormat?: SectionSchemaFormat | null;
355
368
  nextFormat?: SectionSchemaFormat | null;
369
+ force?: boolean;
356
370
  }): SchemaPushPreview;
357
371
  /**
358
372
  * The diff as the CLI prints it: one header line with both formats and
359
- * field counts, then one line per non-empty direction. The `kept` line is
360
- * always printed, with "none" when nothing survives.
373
+ * field counts (and, for a merge, what the server will do), then one line
374
+ * per non-empty direction. The `kept` line is always printed, with "none"
375
+ * when nothing survives. In a merge the dashboard's extra nodes are kept,
376
+ * so that line says so instead of "removed".
361
377
  */
362
378
  declare function formatSchemaDiff(preview: SchemaPushPreview): string;
363
379
 
@@ -453,6 +469,13 @@ interface InitResult {
453
469
  */
454
470
  declare function initSections(config: ResolvedConfig, options?: InitOptions): Promise<InitResult[]>;
455
471
 
472
+ /**
473
+ * `botlr schema pull` and `botlr schema push`: the explicit way to resolve
474
+ * drift between the manifest's `schema` (what the code expects) and the
475
+ * dashboard's definition schema. A source push never touches the schema;
476
+ * these two commands do, in the direction the user picks.
477
+ */
478
+
456
479
  interface SchemaPullOptions {
457
480
  /** Section names; empty with `all: true` means every manifest in the sections directory. */
458
481
  names: string[];
@@ -476,7 +499,7 @@ interface SchemaPullResult {
476
499
  declare function pullSchemas(config: ResolvedConfig, options: SchemaPullOptions): Promise<SchemaPullResult[]>;
477
500
  interface SchemaPushOptions {
478
501
  name: string;
479
- /** Replace the schema even when the dashboard's is in the other format. */
502
+ /** Replace the schema outright whatever the formats, instead of merging into a FormBuilder schema. */
480
503
  force?: boolean;
481
504
  /**
482
505
  * Asked once with the preview before anything is sent; return false to
@@ -491,18 +514,29 @@ interface SchemaPushResult {
491
514
  preview: SchemaPushPreview;
492
515
  /** False when `confirm` said no; nothing was sent. */
493
516
  pushed: boolean;
494
- /** From the server: false when the stored schema already equalled the manifest's. */
517
+ /** From the server: false when nothing had to change. */
495
518
  changed?: boolean;
496
519
  previousFormat?: string;
497
520
  format?: string;
521
+ /** What the server did: replaced the schema, or merged the code's fields into a FormBuilder schema. */
522
+ mode?: SectionSchemaPushMode;
523
+ /** Field names that became new nodes (`merged`). */
524
+ added?: string[];
525
+ /** Node names only the dashboard defines, kept (`merged`). */
526
+ dashboardOnly?: string[];
498
527
  types?: PropsTypesUpdate;
499
528
  }
500
- /** 409: the dashboard's schema is in the other format and `--force` was not given. */
529
+ /**
530
+ * A FormBuilder schema pushed at a field-DSL definition without `--force`:
531
+ * raised before anything is sent when the formats are known (then `preview`
532
+ * is set), or from the server's 409.
533
+ */
501
534
  declare class SchemaFormatConflict extends Error {
502
535
  readonly sectionName: string;
503
536
  readonly currentFormat: string;
504
537
  readonly newFormat: string;
505
- constructor(sectionName: string, currentFormat: string, newFormat: string, message?: string);
538
+ readonly preview?: SchemaPushPreview | undefined;
539
+ constructor(sectionName: string, currentFormat: string, newFormat: string, message?: string, preview?: SchemaPushPreview | undefined);
506
540
  }
507
541
  /** 422: the server refused the schema (not an object of fields, or the definition is not project-scoped). */
508
542
  declare class SchemaRejected extends Error {
@@ -512,4 +546,4 @@ declare class SchemaRejected extends Error {
512
546
  /** Send the manifest's `schema` to the dashboard, after showing the diff and asking. */
513
547
  declare function pushSchema(config: ResolvedConfig, options: SchemaPushOptions): Promise<SchemaPushResult>;
514
548
 
515
- export { type BotlrCliConfigFile, CONFIG_FILE, ConfigError, type ConfigOverrides, DEFAULT_FRAMEWORK, DEFAULT_REGISTRY_FILE, DEFAULT_SECTIONS_DIR, type DefinitionMeta, type DeriveOptions, type DerivedManifest, type GenerateOptions, type InitOptions, type InitResult, LOCK_DIR, LOCK_FILE, type LockEntry, MANIFEST_KEY_ORDER, MANIFEST_SUFFIX, ManifestError, PROPS_SUFFIX, type PropField, type PropsTypesUpdate, type PullOptions, type PullResult, type PushOptions, type PushResult, REGISTRY_FILE, type RegistryAddition, type RegistryEditResult, type RegistryEntry, type RegistryUpdate, type ResolvedConfig, type SchemaDrift, SchemaFormatConflict, type SchemaKeyDiff, type SchemaPullOptions, type SchemaPullResult, type SchemaPushOptions, type SchemaPushPreview, type SchemaPushResult, SchemaRejected, type SectionStatus, type SectionStatusRow, type SectionsLock, type StatusInput, addRegistryEntry, clientFor, collectDslFields, collectFields, collectFormBuilderFields, compareSchemas, computeStatus, deriveManifests, diffSchemaKeys, findConfigFile, formatDriftWarning, formatManifest, formatSchemaDiff, generatePropsSource, hasRegistry, importPathFor, initSections, listManifests, listSections, loadConfig, lockPath, manifestPath, manifestSchema, manifestsEqual, nextLockEntry, normalizeManifest, optionUnion, parseDefinition, parseImports, parseRegistry, parseZodSchema, pascalCase, previewSchemaPush, propsInterfaceName, propsPath, pullSchemas, pullSections, pushSchema, pushSection, readConfigFile, readLock, readManifest, readNpmVersions, readSectionFiles, registerSection, remoteForLock, safeJoin, schemaFieldNames, schemaKeys, sectionStatuses, tsTypeForField, updateLock, validateManifest, writeLock, writeManifest, writeManifestIfChanged, writePropsTypes, writeSectionFiles, zodFieldToSchemaField };
549
+ export { type BotlrCliConfigFile, CONFIG_FILE, ConfigError, type ConfigOverrides, DEFAULT_FRAMEWORK, DEFAULT_REGISTRY_FILE, DEFAULT_SECTIONS_DIR, type DefinitionMeta, type DeriveOptions, type DerivedManifest, type ExpectedPushMode, type GenerateOptions, type InitOptions, type InitResult, LOCK_DIR, LOCK_FILE, type LockEntry, MANIFEST_KEY_ORDER, MANIFEST_SUFFIX, ManifestError, PROPS_SUFFIX, type PropField, type PropsTypesUpdate, type PullOptions, type PullResult, type PushOptions, type PushResult, REGISTRY_FILE, type RegistryAddition, type RegistryEditResult, type RegistryEntry, type RegistryUpdate, type ResolvedConfig, type SchemaDrift, SchemaFormatConflict, type SchemaKeyDiff, type SchemaPullOptions, type SchemaPullResult, type SchemaPushOptions, type SchemaPushPreview, type SchemaPushResult, SchemaRejected, type SectionStatus, type SectionStatusRow, type SectionsLock, type StatusInput, addRegistryEntry, clientFor, collectDslFields, collectFields, collectFormBuilderFields, compareSchemas, computeStatus, deriveManifests, diffSchemaKeys, expectedPushMode, findConfigFile, formatDriftWarning, formatManifest, formatSchemaDiff, formatsDiffer, generatePropsSource, hasRegistry, importPathFor, initSections, listManifests, listSections, loadConfig, lockPath, manifestPath, manifestSchema, manifestsEqual, nextLockEntry, normalizeManifest, optionUnion, parseDefinition, parseImports, parseRegistry, parseZodSchema, pascalCase, previewSchemaPush, propsInterfaceName, propsPath, pullSchemas, pullSections, pushSchema, pushSection, readConfigFile, readLock, readManifest, readNpmVersions, readSectionFiles, registerSection, remoteForLock, safeJoin, schemaFieldNames, schemaKeys, sectionStatuses, tsTypeForField, updateLock, validateManifest, writeLock, writeManifest, writeManifestIfChanged, writePropsTypes, writeSectionFiles, zodFieldToSchemaField };
package/dist/index.js CHANGED
@@ -848,15 +848,20 @@ function diffSchemaKeys(current, next) {
848
848
  kept: [...nextKeys].filter((k) => currentKeys.has(k)).sort()
849
849
  };
850
850
  }
851
- function previewSchemaPush(name, current, next, formats = {}) {
852
- return {
853
- name,
854
- current,
855
- currentFormat: formats.currentFormat ?? schemaFormat2(current),
856
- next,
857
- nextFormat: formats.nextFormat ?? schemaFormat2(next),
858
- diff: diffSchemaKeys(current, next)
851
+ function formatsDiffer(preview) {
852
+ return !!preview.currentFormat && !!preview.nextFormat && preview.currentFormat !== preview.nextFormat;
853
+ }
854
+ function expectedPushMode(preview) {
855
+ if (preview.force || !formatsDiffer(preview)) return "replaced";
856
+ return preview.currentFormat === "form-builder" && preview.nextFormat === "field-dsl" ? "merged" : "conflict";
857
+ }
858
+ function previewSchemaPush(name, current, next, options = {}) {
859
+ const formats = {
860
+ currentFormat: options.currentFormat ?? schemaFormat2(current),
861
+ nextFormat: options.nextFormat ?? schemaFormat2(next),
862
+ force: options.force ?? false
859
863
  };
864
+ return { name, current, next, diff: diffSchemaKeys(current, next), ...formats, expected: expectedPushMode(formats) };
860
865
  }
861
866
  function plural(n, word) {
862
867
  return `${n} ${word}${n === 1 ? "" : "s"}`;
@@ -865,14 +870,21 @@ function formatName(format) {
865
870
  return format ?? "unknown";
866
871
  }
867
872
  function formatSchemaDiff(preview) {
868
- const { diff } = preview;
869
- 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")})`;
873
+ const { diff, expected } = preview;
874
+ 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")})`;
875
+ if (expected === "merged") {
876
+ header += ": the server will add the missing fields as FormBuilder nodes and keep the dashboard's (format kept; --force would replace it)";
877
+ }
870
878
  const lines = [header];
871
879
  if (diff.added.length) lines.push(` + added ${diff.added.join(", ")}`);
872
- if (diff.removed.length) lines.push(` - removed ${diff.removed.join(", ")}`);
880
+ if (diff.removed.length) {
881
+ lines.push(expected === "merged" ? ` ~ dashboard only, kept ${diff.removed.join(", ")}` : ` - removed ${diff.removed.join(", ")}`);
882
+ }
873
883
  lines.push(` = kept ${diff.kept.length ? diff.kept.join(", ") : "none"}`);
874
- if (preview.currentFormat && preview.nextFormat && preview.currentFormat !== preview.nextFormat) {
875
- lines.push(` the formats differ: the push needs --force to replace ${preview.currentFormat} with ${preview.nextFormat}`);
884
+ if (formatsDiffer(preview) && expected !== "merged") {
885
+ lines.push(
886
+ 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}`
887
+ );
876
888
  }
877
889
  if (!diff.added.length && !diff.removed.length) lines.push(" the field keys match; only field settings may change");
878
890
  return lines.join("\n");
@@ -1126,7 +1138,11 @@ async function initSections(config, options = {}) {
1126
1138
  }
1127
1139
 
1128
1140
  // src/commands/schema.ts
1129
- import { BotlrError, BotlrSchemaFormatConflictError, botlrErrorMessage } from "@botlr/sdk";
1141
+ import {
1142
+ BotlrError,
1143
+ BotlrSchemaFormatConflictError,
1144
+ botlrErrorMessage
1145
+ } from "@botlr/sdk";
1130
1146
  function isNotFound(error) {
1131
1147
  return error instanceof BotlrError && error.status === 404;
1132
1148
  }
@@ -1158,16 +1174,18 @@ async function pullSchemas(config, options) {
1158
1174
  return results;
1159
1175
  }
1160
1176
  var SchemaFormatConflict = class extends Error {
1161
- constructor(sectionName, currentFormat, newFormat, message) {
1177
+ constructor(sectionName, currentFormat, newFormat, message, preview) {
1162
1178
  super(message ?? `the schema of '${sectionName}' is stored as ${currentFormat}, the manifest's is ${newFormat}`);
1163
1179
  this.sectionName = sectionName;
1164
1180
  this.currentFormat = currentFormat;
1165
1181
  this.newFormat = newFormat;
1182
+ this.preview = preview;
1166
1183
  this.name = "SchemaFormatConflict";
1167
1184
  }
1168
1185
  sectionName;
1169
1186
  currentFormat;
1170
1187
  newFormat;
1188
+ preview;
1171
1189
  };
1172
1190
  var SchemaRejected = class extends Error {
1173
1191
  constructor(sectionName, message) {
@@ -1194,13 +1212,17 @@ async function pushSchema(config, options) {
1194
1212
  if (!isNotFound(error)) throw error;
1195
1213
  throw new ManifestError(`the app has no section type '${name}'; push the code first with 'botlr push ${name} --create'`);
1196
1214
  }
1197
- const preview = previewSchemaPush(name, current.schema, next, { currentFormat: current.format });
1215
+ const force = options.force ?? false;
1216
+ const preview = previewSchemaPush(name, current.schema, next, { currentFormat: current.format, force });
1217
+ if (preview.expected === "conflict") {
1218
+ throw new SchemaFormatConflict(name, preview.currentFormat, preview.nextFormat, void 0, preview);
1219
+ }
1198
1220
  if (options.confirm && !await options.confirm(preview)) {
1199
1221
  return { name, preview, pushed: false };
1200
1222
  }
1201
1223
  let response;
1202
1224
  try {
1203
- response = await client.sections.putSchema(name, { schema: next, force: options.force ?? false });
1225
+ response = await client.sections.putSchema(name, { schema: next, force });
1204
1226
  } catch (error) {
1205
1227
  if (error instanceof BotlrSchemaFormatConflictError) {
1206
1228
  throw new SchemaFormatConflict(name, error.currentFormat, error.newFormat, botlrErrorMessage(error));
@@ -1218,6 +1240,9 @@ async function pushSchema(config, options) {
1218
1240
  changed: response.changed,
1219
1241
  previousFormat: response.previousFormat,
1220
1242
  format: response.definition.format,
1243
+ mode: response.mode,
1244
+ added: response.added,
1245
+ dashboardOnly: response.dashboardOnly,
1221
1246
  types
1222
1247
  };
1223
1248
  }
@@ -1245,10 +1270,12 @@ export {
1245
1270
  computeStatus,
1246
1271
  deriveManifests,
1247
1272
  diffSchemaKeys,
1273
+ expectedPushMode,
1248
1274
  findConfigFile,
1249
1275
  formatDriftWarning,
1250
1276
  formatManifest,
1251
1277
  formatSchemaDiff,
1278
+ formatsDiffer,
1252
1279
  generatePropsSource,
1253
1280
  hasRegistry,
1254
1281
  importPathFor,