@botlr/cli 0.1.1 → 0.2.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,14 +1,19 @@
1
- import { SectionSourceScope, SectionManifest, SectionSourceFileInput, SectionSourceScopeSummary, SectionSourceSummary, BotlrClient } from '@botlr/sdk';
1
+ import { SectionSourceScope, SectionManifest, SectionSourceFileInput, SectionSourceScopeSummary, SectionSourceSummary, SectionFieldSchema, SectionSchemaField, SectionFormBuilderNode, BotlrClient } from '@botlr/sdk';
2
2
 
3
3
  declare const CONFIG_FILE = "botlr.config.json";
4
4
  declare const DEFAULT_FRAMEWORK = "react-next";
5
5
  declare const DEFAULT_SECTIONS_DIR = "components/sections";
6
+ declare const DEFAULT_REGISTRY_FILE = "publisher-section-registry.ts";
6
7
  /** `botlr.config.json` */
7
8
  interface BotlrCliConfigFile {
8
9
  baseUrl?: string;
9
10
  appId?: string;
10
11
  framework?: string;
11
12
  sectionsDir?: string;
13
+ /** Registry file `pull` keeps up to date, relative to `sectionsDir`. Default `publisher-section-registry.ts`. */
14
+ registryFile?: string;
15
+ /** Write `<name>.props.ts` after a pull. Default true; `false` turns it off (`--no-types` does per call). */
16
+ types?: boolean;
12
17
  }
13
18
  interface ResolvedConfig {
14
19
  /** Directory holding `botlr.config.json` (or the cwd when there is none). Paths resolve against it. */
@@ -19,6 +24,10 @@ interface ResolvedConfig {
19
24
  framework: string;
20
25
  /** Absolute path of the sections directory. */
21
26
  sectionsDir: string;
27
+ /** Absolute path of the registry file (`sectionsDir/publisher-section-registry.ts` by default); it may not exist. */
28
+ registryFile: string;
29
+ /** Whether `pull` writes `<name>.props.ts` from the definition's schema. */
30
+ types: boolean;
22
31
  /** Site token, `BOTLR_API_KEY`. */
23
32
  apiKey: string | undefined;
24
33
  /** Personal access token, `BOTLR_PAT`. */
@@ -52,11 +61,25 @@ interface LockEntry {
52
61
  framework: string;
53
62
  version: number;
54
63
  contentHash: string;
55
- /** ISO timestamp of the pull or push that wrote the entry. */
64
+ /**
65
+ * ISO timestamp of the pull or push that first put this version here. A
66
+ * pull that lands the same scope, version and hash again keeps it, so
67
+ * re-pulling does not touch the lock file.
68
+ */
56
69
  pulledAt: string;
57
70
  }
58
- /** `.botlr/sections.lock.json`: one entry per section name. */
71
+ /**
72
+ * `.botlr/sections.lock.json`: one entry per section name, for the whole
73
+ * app. The lock is not keyed by directory: `--dir` points a command at
74
+ * another sections directory but still records into this one lock, so a
75
+ * section name means one thing per app whichever directory holds it.
76
+ */
59
77
  type SectionsLock = Record<string, LockEntry>;
78
+ /**
79
+ * The entry to record after a pull or push: `fresh` with the previous
80
+ * `pulledAt` kept when scope, framework, version and hash are unchanged.
81
+ */
82
+ declare function nextLockEntry(previous: LockEntry | undefined, fresh: LockEntry): LockEntry;
60
83
  declare function lockPath(root: string): string;
61
84
  declare function readLock(root: string): Promise<SectionsLock>;
62
85
  declare function writeLock(root: string, lock: SectionsLock): Promise<void>;
@@ -71,8 +94,32 @@ declare function manifestPath(sectionsDir: string, name: string): string;
71
94
  declare function listManifests(sectionsDir: string): Promise<string[]>;
72
95
  declare function readManifest(sectionsDir: string, name: string): Promise<SectionManifest>;
73
96
  declare function validateManifest(value: unknown, where?: string): SectionManifest;
74
- /** Write `<name>.botlr.json` without any pull state. */
97
+ /** Top-level manifest keys in the order the CLI writes them; anything else follows alphabetically. */
98
+ declare const MANIFEST_KEY_ORDER: readonly ["section", "framework", "entry", "exportName", "files", "title", "description", "category", "icon", "dependencies", "schema"];
99
+ /**
100
+ * The manifest with pull state removed and keys in a fixed order: the known
101
+ * keys first (`MANIFEST_KEY_ORDER`), then unknown keys alphabetically;
102
+ * `dependencies` lists `npm` before `internal`, and every nested object is
103
+ * sorted alphabetically. Arrays keep their order. The server stores
104
+ * manifests as jsonb, which forgets key order, so this is what makes a pull
105
+ * after a push write the same bytes.
106
+ */
107
+ declare function normalizeManifest(manifest: SectionManifest): SectionManifest;
108
+ /** The exact text `writeManifest` writes: normalized, 2-space JSON, trailing newline. */
109
+ declare function formatManifest(manifest: SectionManifest): string;
110
+ /** True when two manifests hold the same data, whatever their key order or formatting. */
111
+ declare function manifestsEqual(a: SectionManifest, b: SectionManifest): boolean;
112
+ /** Write `<name>.botlr.json` without any pull state, in the CLI's fixed key order. */
75
113
  declare function writeManifest(sectionsDir: string, name: string, manifest: SectionManifest): Promise<string>;
114
+ /**
115
+ * Like `writeManifest`, but leaves the file alone when it already holds the
116
+ * same manifest (compared after normalization, so a hand-formatted file that
117
+ * says the same thing is not rewritten). Returns whether the file changed.
118
+ */
119
+ declare function writeManifestIfChanged(sectionsDir: string, name: string, manifest: SectionManifest): Promise<{
120
+ file: string;
121
+ changed: boolean;
122
+ }>;
76
123
  /** The files a manifest lists, read from `sectionsDir`, in manifest order. */
77
124
  declare function readSectionFiles(sectionsDir: string, manifest: SectionManifest): Promise<SectionSourceFileInput[]>;
78
125
  /** Write pulled files under `sectionsDir`, creating sub-directories. */
@@ -99,6 +146,7 @@ declare function computeStatus({ localHash, lock, remote }: StatusInput): Sectio
99
146
  /** The server summary that corresponds to a lock entry's scope. */
100
147
  declare function remoteForLock(summary: SectionSourceSummary | undefined, lock: LockEntry | undefined): SectionSourceScopeSummary | null | undefined;
101
148
 
149
+ /** @deprecated use `DEFAULT_REGISTRY_FILE` from config; the path is configurable as `registryFile`. */
102
150
  declare const REGISTRY_FILE = "publisher-section-registry.ts";
103
151
  interface RegistryEntry {
104
152
  slug: string;
@@ -114,9 +162,24 @@ interface DefinitionMeta {
114
162
  category?: string;
115
163
  description?: string;
116
164
  icon?: string;
165
+ /** The identifier `schema:` refers to, for example `HeroSchema`. */
166
+ schemaName?: string;
117
167
  }
118
168
  /** Scalar fields of an `export const XDefinition = { ... }` block, when the file has one. */
119
169
  declare function parseDefinition(source: string): DefinitionMeta | null;
170
+ /**
171
+ * A best-effort field DSL entry for one zod expression, or null when the
172
+ * expression is not one the CLI understands. `.optional()`, `.nullable()`,
173
+ * `.nullish()` and `.default(...)` make the field optional; anything else is
174
+ * required.
175
+ */
176
+ declare function zodFieldToSchemaField(expr: string): SectionSchemaField | null;
177
+ /**
178
+ * The field DSL for `export const <schemaName> = z.object({ ... })` in a
179
+ * component source, from the fields the CLI can read; null when the schema
180
+ * is not found or no field could be read.
181
+ */
182
+ declare function parseZodSchema(source: string, schemaName: string): SectionFieldSchema | null;
120
183
  /** Module specifiers a source file imports, split into relative and package imports. */
121
184
  declare function parseImports(source: string): {
122
185
  internal: string[];
@@ -132,12 +195,129 @@ interface DeriveOptions {
132
195
  framework: string;
133
196
  /** npm dependency ranges to record, usually from the app's package.json. */
134
197
  npmVersions?: Record<string, string>;
198
+ /** Registry file to read; `sectionsDir/publisher-section-registry.ts` by default. */
199
+ registryFile?: string;
135
200
  }
136
201
  /** One manifest per registry entry whose component file exists. */
137
202
  declare function deriveManifests(sectionsDir: string, options: DeriveOptions): Promise<DerivedManifest[]>;
138
203
  /** `dependencies` + `devDependencies` of the nearest package.json above `dir`, for version ranges. */
139
204
  declare function readNpmVersions(dir: string): Promise<Record<string, string>>;
140
- declare function hasRegistry(sectionsDir: string): Promise<boolean>;
205
+ /** Whether the registry file exists (`sectionsDir/publisher-section-registry.ts`, or `registryFile` when given). */
206
+ declare function hasRegistry(sectionsDir: string, registryFile?: string): Promise<boolean>;
207
+
208
+ interface RegistryAddition {
209
+ slug: string;
210
+ exportName: string;
211
+ /** Module specifier to import from, for example `./markdown`. */
212
+ importPath: string;
213
+ }
214
+ interface RegistryEditResult {
215
+ source: string;
216
+ /** True when an import or a map entry was appended. */
217
+ changed: boolean;
218
+ addedImport: boolean;
219
+ addedEntry: boolean;
220
+ }
221
+ /** `./markdown` for a registry at `sectionsDir` and an entry `markdown.tsx`; `./parts/x` for `parts/x.tsx`. */
222
+ declare function importPathFor(registryFile: string, entryFile: string): string;
223
+ /**
224
+ * Return `source` with `import { exportName } from "importPath";` added after
225
+ * the last import and `slug: exportName,` added as the last entry of the
226
+ * registry object. Each is skipped when it is already there. Quote style,
227
+ * semicolons, indentation and trailing commas follow the surrounding code.
228
+ */
229
+ declare function addRegistryEntry(source: string, addition: RegistryAddition): RegistryEditResult;
230
+ interface RegistryUpdate {
231
+ /** Absolute path of the registry file. */
232
+ file: string;
233
+ /** False when nothing was added or the registry could not be updated. */
234
+ changed: boolean;
235
+ addedImport?: boolean;
236
+ addedEntry?: boolean;
237
+ /** Why nothing was written, for the user. */
238
+ notice?: string;
239
+ }
240
+ /**
241
+ * Add the pulled section to the registry file, when there is one. Returns a
242
+ * notice instead of throwing when the registry is missing or the manifest has
243
+ * no `exportName`, since a pull should still succeed.
244
+ */
245
+ declare function registerSection(sectionsDir: string, registryFile: string, manifest: SectionManifest): Promise<RegistryUpdate>;
246
+
247
+ declare const PROPS_SUFFIX = ".props.ts";
248
+ /** One property of the generated interface. */
249
+ interface PropField {
250
+ name: string;
251
+ /** TypeScript type text. */
252
+ type: string;
253
+ required: boolean;
254
+ /** Doc comment lines (label, help text), without the comment markers. */
255
+ doc: string[];
256
+ }
257
+ /** The union of a select's option values, or null when the options are not plain values or `{ value }` objects. */
258
+ declare function optionUnion(options: unknown): string | null;
259
+ /** The TypeScript type for one field of the field DSL (or a FormBuilder field node, given its field type). */
260
+ declare function tsTypeForField(field: {
261
+ type?: unknown;
262
+ options?: unknown;
263
+ multiple?: unknown;
264
+ fields?: unknown;
265
+ }): string;
266
+ /** Properties for the field DSL, in schema order. Non-object values are skipped. */
267
+ declare function collectDslFields(schema: Record<string, unknown>): PropField[];
268
+ /** Properties for the FormBuilder format: field and repeater nodes, with groups flattened. */
269
+ declare function collectFormBuilderFields(nodes: SectionFormBuilderNode[]): PropField[];
270
+ /** Properties for a definition schema in either format; empty for anything else. */
271
+ declare function collectFields(schema: unknown): PropField[];
272
+ /** The property names a definition schema defines, in either format. */
273
+ declare function schemaFieldNames(schema: unknown): string[];
274
+ /** `hero-banner` or `forum_new_thread` → `HeroBanner` / `ForumNewThread`. */
275
+ declare function pascalCase(slug: string): string;
276
+ /** `<ExportName>Props`, from the manifest's `exportName` or a PascalCase of the slug. */
277
+ declare function propsInterfaceName(manifest: Pick<SectionManifest, "section" | "exportName">): string;
278
+ interface GenerateOptions {
279
+ /** Section slug, named in the header comment. */
280
+ section: string;
281
+ interfaceName: string;
282
+ }
283
+ /** The full text of `<name>.props.ts` for a definition schema. */
284
+ declare function generatePropsSource(schema: unknown, options: GenerateOptions): string;
285
+ declare function propsPath(sectionsDir: string, name: string): string;
286
+ interface PropsTypesUpdate {
287
+ file: string;
288
+ interfaceName: string;
289
+ /** False when the file already held the generated text. */
290
+ changed: boolean;
291
+ }
292
+ /**
293
+ * Write `<name>.props.ts` for a section, leaving the file alone when its
294
+ * text is already what would be generated.
295
+ */
296
+ declare function writePropsTypes(sectionsDir: string, manifest: SectionManifest, schema: unknown): Promise<PropsTypesUpdate>;
297
+
298
+ /**
299
+ * Schema drift: the props a section's code expects (the manifest's optional
300
+ * `schema`, field DSL only) against the props the dashboard defines (the
301
+ * section definition's schema from the server). The CLI only reports the
302
+ * difference; the dashboard owns the definition's schema.
303
+ */
304
+
305
+ interface SchemaDrift {
306
+ /** Props the code lists that the dashboard does not define. */
307
+ missingInDashboard: string[];
308
+ /** Props the dashboard defines that the code does not list. */
309
+ missingInCode: string[];
310
+ }
311
+ /** The manifest's `schema` when it is a field DSL object, else null. */
312
+ declare function manifestSchema(manifest: SectionManifest): Record<string, unknown> | null;
313
+ /**
314
+ * Key difference between the code's schema (field DSL) and the definition's
315
+ * schema (either format). Null when the manifest has no schema object or
316
+ * the keys match.
317
+ */
318
+ declare function compareSchemas(manifest: SectionManifest, definitionSchema: unknown): SchemaDrift | null;
319
+ /** The warning text for a drift, one line per direction, pointing at the dashboard. */
320
+ declare function formatDriftWarning(name: string, drift: SchemaDrift): string;
141
321
 
142
322
  /** A client from the resolved config. `needPersonalToken` makes a missing `BOTLR_PAT` an error up front. */
143
323
  declare function clientFor(config: ResolvedConfig, options?: {
@@ -153,6 +333,10 @@ interface PullOptions {
153
333
  shared?: boolean;
154
334
  /** Overwrite local files that changed since the last pull. */
155
335
  force?: boolean;
336
+ /** Leave the registry file alone. */
337
+ noRegistry?: boolean;
338
+ /** Do not write `<name>.props.ts`; also off when the config says `types: false`. */
339
+ noTypes?: boolean;
156
340
  }
157
341
  interface PullResult {
158
342
  name: string;
@@ -161,6 +345,14 @@ interface PullResult {
161
345
  files: string[];
162
346
  /** Set when the section was skipped instead of written. */
163
347
  skipped?: string;
348
+ /** Whether `<name>.botlr.json` changed; false when the local manifest already said the same. */
349
+ manifestChanged?: boolean;
350
+ /** What happened to the registry file; absent for skipped sections and with `noRegistry`. */
351
+ registry?: RegistryUpdate;
352
+ /** What happened to `<name>.props.ts`; absent when types are off or the section has no definition. */
353
+ types?: PropsTypesUpdate;
354
+ /** Why no prop types were written, when they were expected. */
355
+ typesNotice?: string;
164
356
  }
165
357
  declare function pullSections(config: ResolvedConfig, options: PullOptions): Promise<PullResult[]>;
166
358
 
@@ -178,6 +370,8 @@ interface PushResult {
178
370
  scope: SectionSourceScope;
179
371
  version: number;
180
372
  contentHash: string;
373
+ /** Set when the manifest's `schema` and the definition's schema list different props. */
374
+ drift?: SchemaDrift;
181
375
  }
182
376
  declare function pushSection(config: ResolvedConfig, options: PushOptions): Promise<PushResult>;
183
377
 
@@ -190,6 +384,8 @@ interface SectionStatusRow {
190
384
  remote: number | null;
191
385
  scope: string;
192
386
  detail?: string;
387
+ /** Set when the manifest's `schema` and the dashboard's definition list different props (not offline). */
388
+ drift?: SchemaDrift;
193
389
  }
194
390
  declare function sectionStatuses(config: ResolvedConfig, options?: {
195
391
  names?: string[];
@@ -215,4 +411,4 @@ interface InitResult {
215
411
  */
216
412
  declare function initSections(config: ResolvedConfig, options?: InitOptions): Promise<InitResult[]>;
217
413
 
218
- export { type BotlrCliConfigFile, CONFIG_FILE, ConfigError, type ConfigOverrides, DEFAULT_FRAMEWORK, DEFAULT_SECTIONS_DIR, type DefinitionMeta, type DeriveOptions, type DerivedManifest, type InitOptions, type InitResult, LOCK_DIR, LOCK_FILE, type LockEntry, MANIFEST_SUFFIX, ManifestError, type PullOptions, type PullResult, type PushOptions, type PushResult, REGISTRY_FILE, type RegistryEntry, type ResolvedConfig, type SectionStatus, type SectionStatusRow, type SectionsLock, type StatusInput, clientFor, computeStatus, deriveManifests, findConfigFile, hasRegistry, initSections, listManifests, listSections, loadConfig, lockPath, manifestPath, parseDefinition, parseImports, parseRegistry, pullSections, pushSection, readConfigFile, readLock, readManifest, readNpmVersions, readSectionFiles, remoteForLock, safeJoin, sectionStatuses, updateLock, validateManifest, writeLock, writeManifest, writeSectionFiles };
414
+ 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, type SectionStatus, type SectionStatusRow, type SectionsLock, type StatusInput, addRegistryEntry, clientFor, collectDslFields, collectFields, collectFormBuilderFields, compareSchemas, computeStatus, deriveManifests, findConfigFile, formatDriftWarning, formatManifest, generatePropsSource, hasRegistry, importPathFor, initSections, listManifests, listSections, loadConfig, lockPath, manifestPath, manifestSchema, manifestsEqual, nextLockEntry, normalizeManifest, optionUnion, parseDefinition, parseImports, parseRegistry, parseZodSchema, pascalCase, propsInterfaceName, propsPath, pullSections, pushSection, readConfigFile, readLock, readManifest, readNpmVersions, readSectionFiles, registerSection, remoteForLock, safeJoin, schemaFieldNames, sectionStatuses, tsTypeForField, updateLock, validateManifest, writeLock, writeManifest, writeManifestIfChanged, writePropsTypes, writeSectionFiles, zodFieldToSchemaField };