@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/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,
|
|
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,
|
|
360
|
-
* always printed, with "none"
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
852
|
-
return
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
875
|
-
lines.push(
|
|
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 {
|
|
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
|
|
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
|
|
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,
|