@myna-sh/cli 0.1.4 → 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/dsl.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * order is significant and preserved as an array; object *keys* are sorted only
6
6
  * during canonical stringification for a stable hash.
7
7
  */
8
- type FieldType = "text" | "number" | "boolean" | "date" | "datetime" | "markdown" | "json" | "asset" | "reference" | "list" | "object" | "slug";
8
+ type FieldType = "text" | "number" | "boolean" | "date" | "datetime" | "markdown" | "richText" | "json" | "asset" | "reference" | "list" | "object" | "blocks" | "slug";
9
9
  type JsonValue = string | number | boolean | null | JsonValue[] | {
10
10
  [key: string]: JsonValue;
11
11
  };
@@ -23,8 +23,16 @@ interface FieldBase {
23
23
  label: string;
24
24
  description?: string;
25
25
  required: boolean;
26
+ /** Localized fields store `{ [locale]: value }` keyed by project locale.
27
+ * Only top-level, non-slug fields may be localized. */
28
+ localized?: boolean;
26
29
  ui?: FieldUi;
27
30
  }
31
+ /** Locale context for entry validation and locale resolution. */
32
+ interface EntryLocaleOptions {
33
+ locales: string[];
34
+ defaultLocale: string;
35
+ }
28
36
  interface TextField extends FieldBase {
29
37
  type: "text";
30
38
  multiline: boolean;
@@ -122,7 +130,27 @@ interface ObjectField extends FieldBase {
122
130
  type: "object";
123
131
  fields: FieldDef[];
124
132
  }
125
- type FieldDef = TextField | NumberField | BooleanField | DateField | MarkdownField | JsonField | AssetField | ReferenceField | SlugField | ListField | ObjectField;
133
+ /** Semantic rich text stored as a portable JSON document tree never HTML.
134
+ * The root is `{ "type": "doc", "content": [...] }`; nodes may embed entry
135
+ * references (`ent_` ids) and assets (`ast_` ids). */
136
+ interface RichTextField extends FieldBase {
137
+ type: "richText";
138
+ }
139
+ /** One schema-defined component usable inside a `blocks` field. */
140
+ interface BlockDef {
141
+ key: string;
142
+ label: string;
143
+ fields: FieldDef[];
144
+ }
145
+ /** Heterogeneous, schema-defined component list. Stored as portable JSON:
146
+ * `[{ "type": "<blockKey>", "fields": { ... } }, ...]`. */
147
+ interface BlocksField extends FieldBase {
148
+ type: "blocks";
149
+ blocks: BlockDef[];
150
+ minItems?: number;
151
+ maxItems?: number;
152
+ }
153
+ type FieldDef = TextField | NumberField | BooleanField | DateField | MarkdownField | RichTextField | JsonField | AssetField | ReferenceField | SlugField | ListField | ObjectField | BlocksField;
126
154
  type CollectionKind = "collection" | "singleton";
127
155
  type Visibility = "public" | "private";
128
156
  interface SlugConfig {
@@ -150,6 +178,7 @@ interface CommonOptions {
150
178
  label?: string;
151
179
  description?: string;
152
180
  required?: boolean;
181
+ localized?: boolean;
153
182
  ui?: FieldUi;
154
183
  }
155
184
  interface TextOptions extends CommonOptions {
@@ -201,6 +230,18 @@ interface ListOptions extends CommonOptions {
201
230
  interface ObjectOptions extends CommonOptions {
202
231
  fields: Record<string, FieldInput>;
203
232
  }
233
+ type RichTextOptions = CommonOptions;
234
+ interface BlocksOptions extends CommonOptions {
235
+ /** The block components entries may compose, built with `block()`. */
236
+ allowed: BlockDef[];
237
+ minItems?: number;
238
+ maxItems?: number;
239
+ }
240
+ /** Define a reusable block component for `field.blocks({ allowed: [...] })`. */
241
+ declare function block(key: string, o: {
242
+ label?: string;
243
+ fields: Record<string, FieldInput>;
244
+ }): BlockDef;
204
245
  declare const field: {
205
246
  readonly text: (o?: TextOptions) => Omit<TextField, "key">;
206
247
  readonly number: (o?: NumberOptions) => Omit<NumberField, "key">;
@@ -214,6 +255,8 @@ declare const field: {
214
255
  readonly slug: (o?: SlugOptions) => Omit<SlugField, "key">;
215
256
  readonly list: (o: ListOptions) => Omit<ListField, "key">;
216
257
  readonly object: (o: ObjectOptions) => Omit<ObjectField, "key">;
258
+ readonly richText: (o?: RichTextOptions) => Omit<RichTextField, "key">;
259
+ readonly blocks: (o: BlocksOptions) => Omit<BlocksField, "key">;
217
260
  };
218
261
  /** Item builders for `field.list({ of: item.<type>() })`. */
219
262
  declare const item: {
@@ -273,4 +316,4 @@ interface CollectionInput {
273
316
  */
274
317
  declare function collection(input: CollectionInput): CollectionSchema;
275
318
 
276
- export { type AssetField, type AssetOptions, type BooleanField, type BooleanOptions, type CollectionInput, type CollectionKind, type CollectionSchema, type DateField, type DateOptions, type FieldDef, type FieldInput, type FieldType, type FieldUi, type JsonField, type JsonOptions, type JsonValue, type ListField, type ListItem, type ListOptions, MAX_NESTING_DEPTH, type MarkdownField, type MarkdownOptions, type NumberField, type NumberOptions, type ObjectField, type ObjectOptions, type PrimitiveItem, type ReferenceField, type ReferenceOptions, type SlugConfig, type SlugField, type SlugOptions, type TextField, type TextOptions, type Visibility, assignKeys, collection, field, item };
319
+ export { type AssetField, type AssetOptions, type BlockDef, type BlocksField, type BlocksOptions, type BooleanField, type BooleanOptions, type CollectionInput, type CollectionKind, type CollectionSchema, type DateField, type DateOptions, type EntryLocaleOptions, type FieldDef, type FieldInput, type FieldType, type FieldUi, type JsonField, type JsonOptions, type JsonValue, type ListField, type ListItem, type ListOptions, MAX_NESTING_DEPTH, type MarkdownField, type MarkdownOptions, type NumberField, type NumberOptions, type ObjectField, type ObjectOptions, type PrimitiveItem, type ReferenceField, type ReferenceOptions, type RichTextField, type RichTextOptions, type SlugConfig, type SlugField, type SlugOptions, type TextField, type TextOptions, type Visibility, assignKeys, block, collection, field, item };
package/dist/dsl.js CHANGED
@@ -8,9 +8,13 @@ function common(o) {
8
8
  };
9
9
  if (o.label !== void 0) base.label = o.label;
10
10
  if (o.description !== void 0) base.description = o.description;
11
+ if (o.localized !== void 0) base.localized = o.localized;
11
12
  if (o.ui !== void 0) base.ui = o.ui;
12
13
  return base;
13
14
  }
15
+ function block(key, o) {
16
+ return { key, label: o.label ?? key, fields: assignKeys(o.fields) };
17
+ }
14
18
  function defined(obj) {
15
19
  for (const key of Object.keys(obj)) {
16
20
  if (obj[key] === void 0) delete obj[key];
@@ -119,6 +123,21 @@ var field = {
119
123
  ...common(o),
120
124
  fields: assignKeys(o.fields)
121
125
  });
126
+ },
127
+ richText(o = {}) {
128
+ return defined({
129
+ type: "richText",
130
+ ...common(o)
131
+ });
132
+ },
133
+ blocks(o) {
134
+ return defined({
135
+ type: "blocks",
136
+ ...common(o),
137
+ blocks: o.allowed,
138
+ minItems: o.minItems,
139
+ maxItems: o.maxItems
140
+ });
122
141
  }
123
142
  };
124
143
  var item = {
@@ -182,6 +201,7 @@ function collection(input) {
182
201
  export {
183
202
  MAX_NESTING_DEPTH,
184
203
  assignKeys,
204
+ block,
185
205
  collection,
186
206
  field,
187
207
  item
package/dist/dsl.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../schema/src/types.ts","../../schema/src/fields.ts","../../schema/src/collection.ts"],"sourcesContent":["/**\n * Canonical schema representation (SPEC 6.2 / 7).\n *\n * These are the normalized, serializable shapes stored as `schema_json`. Field\n * order is significant and preserved as an array; object *keys* are sorted only\n * during canonical stringification for a stable hash.\n */\n\nexport type FieldType =\n | \"text\"\n | \"number\"\n | \"boolean\"\n | \"date\"\n | \"datetime\"\n | \"markdown\"\n | \"json\"\n | \"asset\"\n | \"reference\"\n | \"list\"\n | \"object\"\n | \"slug\";\n\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\n/** UI metadata carried on every field (non-semantic, does not affect validation). */\nexport interface FieldUi {\n widget?: string;\n placeholder?: string;\n helpText?: string;\n group?: string;\n hidden?: boolean;\n}\n\ninterface FieldBase {\n key: string;\n type: FieldType;\n label: string;\n description?: string;\n required: boolean;\n ui?: FieldUi;\n}\n\nexport interface TextField extends FieldBase {\n type: \"text\";\n multiline: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n enum?: string[];\n default?: string;\n}\n\nexport interface NumberField extends FieldBase {\n type: \"number\";\n integer: boolean;\n min?: number;\n max?: number;\n default?: number;\n}\n\nexport interface BooleanField extends FieldBase {\n type: \"boolean\";\n default?: boolean;\n}\n\nexport interface DateField extends FieldBase {\n type: \"date\" | \"datetime\";\n min?: string;\n max?: string;\n default?: string;\n}\n\nexport interface MarkdownField extends FieldBase {\n type: \"markdown\";\n minLength?: number;\n maxLength?: number;\n default?: string;\n}\n\nexport interface JsonField extends FieldBase {\n type: \"json\";\n default?: JsonValue;\n}\n\nexport interface AssetField extends FieldBase {\n type: \"asset\";\n /** Allowed MIME families, e.g. `image/*`, `application/pdf`. */\n allowed: string[];\n multiple: boolean;\n}\n\nexport interface ReferenceField extends FieldBase {\n type: \"reference\";\n /** Target collection key. */\n target: string;\n multiple: boolean;\n}\n\nexport interface SlugField extends FieldBase {\n type: \"slug\";\n /** Source field key to derive the slug from. */\n from?: string;\n}\n\nexport type PrimitiveItem =\n | { kind: \"text\"; minLength?: number; maxLength?: number; pattern?: string; enum?: string[] }\n | { kind: \"number\"; integer?: boolean; min?: number; max?: number }\n | { kind: \"boolean\" }\n | { kind: \"date\" | \"datetime\"; min?: string; max?: string }\n | { kind: \"markdown\"; minLength?: number; maxLength?: number }\n | { kind: \"json\" };\n\nexport type ListItem =\n | PrimitiveItem\n | { kind: \"reference\"; target: string }\n | { kind: \"asset\"; allowed: string[] }\n | { kind: \"object\"; fields: FieldDef[] };\n\nexport interface ListField extends FieldBase {\n type: \"list\";\n item: ListItem;\n minItems?: number;\n maxItems?: number;\n}\n\nexport interface ObjectField extends FieldBase {\n type: \"object\";\n fields: FieldDef[];\n}\n\nexport type FieldDef =\n | TextField\n | NumberField\n | BooleanField\n | DateField\n | MarkdownField\n | JsonField\n | AssetField\n | ReferenceField\n | SlugField\n | ListField\n | ObjectField;\n\nexport type CollectionKind = \"collection\" | \"singleton\";\nexport type Visibility = \"public\" | \"private\";\n\nexport interface SlugConfig {\n from: string;\n required: boolean;\n}\n\n/** Canonical collection schema (one `collection_versions.schema_json`). */\nexport interface CollectionSchema {\n name: string;\n label: string;\n kind: CollectionKind;\n visibility: Visibility;\n titleField: string | null;\n slug: SlugConfig | null;\n path: string | null;\n fields: FieldDef[];\n}\n\n/** Maximum object/list nesting depth (SPEC 6.2). */\nexport const MAX_NESTING_DEPTH = 4;\n","import type {\n AssetField,\n BooleanField,\n DateField,\n FieldDef,\n FieldUi,\n JsonField,\n JsonValue,\n ListField,\n ListItem,\n MarkdownField,\n NumberField,\n ObjectField,\n ReferenceField,\n SlugField,\n TextField,\n} from \"./types.js\";\n\n/** A field definition before its `key` is assigned by `collection()`. */\nexport type FieldInput = DistributiveOmit<FieldDef, \"key\">;\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\ninterface CommonOptions {\n label?: string;\n description?: string;\n required?: boolean;\n ui?: FieldUi;\n}\n\nfunction common(o: CommonOptions): { label?: string; description?: string; required: boolean; ui?: FieldUi } {\n const base: { label?: string; description?: string; required: boolean; ui?: FieldUi } = {\n required: o.required ?? false,\n };\n if (o.label !== undefined) base.label = o.label;\n if (o.description !== undefined) base.description = o.description;\n if (o.ui !== undefined) base.ui = o.ui;\n return base;\n}\n\nexport interface TextOptions extends CommonOptions {\n multiline?: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n enum?: string[];\n default?: string;\n}\nexport interface NumberOptions extends CommonOptions {\n integer?: boolean;\n min?: number;\n max?: number;\n default?: number;\n}\nexport interface BooleanOptions extends CommonOptions {\n default?: boolean;\n}\nexport interface DateOptions extends CommonOptions {\n min?: string;\n max?: string;\n default?: string;\n}\nexport interface MarkdownOptions extends CommonOptions {\n minLength?: number;\n maxLength?: number;\n default?: string;\n}\nexport interface JsonOptions extends CommonOptions {\n default?: JsonValue;\n}\nexport interface AssetOptions extends CommonOptions {\n allowed?: string[];\n multiple?: boolean;\n}\nexport interface ReferenceOptions extends CommonOptions {\n to: string;\n multiple?: boolean;\n}\nexport interface SlugOptions extends CommonOptions {\n from?: string;\n}\nexport interface ListOptions extends CommonOptions {\n of: ListItem;\n minItems?: number;\n maxItems?: number;\n}\nexport interface ObjectOptions extends CommonOptions {\n fields: Record<string, FieldInput>;\n}\n\nfunction defined<T extends Record<string, unknown>>(obj: T): T {\n for (const key of Object.keys(obj)) {\n if (obj[key] === undefined) delete obj[key];\n }\n return obj;\n}\n\nexport const field = {\n text(o: TextOptions = {}): Omit<TextField, \"key\"> {\n return defined({\n type: \"text\",\n ...common(o),\n multiline: o.multiline ?? false,\n minLength: o.minLength,\n maxLength: o.maxLength,\n pattern: o.pattern,\n enum: o.enum,\n default: o.default,\n }) as Omit<TextField, \"key\">;\n },\n\n number(o: NumberOptions = {}): Omit<NumberField, \"key\"> {\n return defined({\n type: \"number\",\n ...common(o),\n integer: o.integer ?? false,\n min: o.min,\n max: o.max,\n default: o.default,\n }) as Omit<NumberField, \"key\">;\n },\n\n boolean(o: BooleanOptions = {}): Omit<BooleanField, \"key\"> {\n return defined({\n type: \"boolean\",\n ...common(o),\n default: o.default,\n }) as Omit<BooleanField, \"key\">;\n },\n\n date(o: DateOptions = {}): Omit<DateField, \"key\"> {\n return defined({\n type: \"date\",\n ...common(o),\n min: o.min,\n max: o.max,\n default: o.default,\n }) as Omit<DateField, \"key\">;\n },\n\n datetime(o: DateOptions = {}): Omit<DateField, \"key\"> {\n return defined({\n type: \"datetime\",\n ...common(o),\n min: o.min,\n max: o.max,\n default: o.default,\n }) as Omit<DateField, \"key\">;\n },\n\n markdown(o: MarkdownOptions = {}): Omit<MarkdownField, \"key\"> {\n return defined({\n type: \"markdown\",\n ...common(o),\n minLength: o.minLength,\n maxLength: o.maxLength,\n default: o.default,\n }) as Omit<MarkdownField, \"key\">;\n },\n\n json(o: JsonOptions = {}): Omit<JsonField, \"key\"> {\n return defined({\n type: \"json\",\n ...common(o),\n default: o.default,\n }) as Omit<JsonField, \"key\">;\n },\n\n asset(o: AssetOptions = {}): Omit<AssetField, \"key\"> {\n return defined({\n type: \"asset\",\n ...common(o),\n allowed: o.allowed ?? [\"*/*\"],\n multiple: o.multiple ?? false,\n }) as Omit<AssetField, \"key\">;\n },\n\n reference(o: ReferenceOptions): Omit<ReferenceField, \"key\"> {\n return defined({\n type: \"reference\",\n ...common(o),\n target: o.to,\n multiple: o.multiple ?? false,\n }) as Omit<ReferenceField, \"key\">;\n },\n\n slug(o: SlugOptions = {}): Omit<SlugField, \"key\"> {\n return defined({\n type: \"slug\",\n ...common(o),\n from: o.from,\n }) as Omit<SlugField, \"key\">;\n },\n\n list(o: ListOptions): Omit<ListField, \"key\"> {\n return defined({\n type: \"list\",\n ...common(o),\n item: o.of,\n minItems: o.minItems,\n maxItems: o.maxItems,\n }) as Omit<ListField, \"key\">;\n },\n\n object(o: ObjectOptions): Omit<ObjectField, \"key\"> {\n return defined({\n type: \"object\",\n ...common(o),\n fields: assignKeys(o.fields),\n }) as Omit<ObjectField, \"key\">;\n },\n} as const;\n\n/** Item builders for `field.list({ of: item.<type>() })`. */\nexport const item = {\n text(o: { minLength?: number; maxLength?: number; pattern?: string; enum?: string[] } = {}): ListItem {\n return defined({ kind: \"text\", ...o }) as ListItem;\n },\n number(o: { integer?: boolean; min?: number; max?: number } = {}): ListItem {\n return defined({ kind: \"number\", ...o }) as ListItem;\n },\n boolean(): ListItem {\n return { kind: \"boolean\" };\n },\n date(o: { min?: string; max?: string } = {}): ListItem {\n return defined({ kind: \"date\", ...o }) as ListItem;\n },\n datetime(o: { min?: string; max?: string } = {}): ListItem {\n return defined({ kind: \"datetime\", ...o }) as ListItem;\n },\n markdown(o: { minLength?: number; maxLength?: number } = {}): ListItem {\n return defined({ kind: \"markdown\", ...o }) as ListItem;\n },\n json(): ListItem {\n return { kind: \"json\" };\n },\n reference(o: { to: string }): ListItem {\n return { kind: \"reference\", target: o.to };\n },\n asset(o: { allowed?: string[] } = {}): ListItem {\n return { kind: \"asset\", allowed: o.allowed ?? [\"*/*\"] };\n },\n object(o: { fields: Record<string, FieldInput> }): ListItem {\n return { kind: \"object\", fields: assignKeys(o.fields) };\n },\n} as const;\n\n/** Turn a `{ key: FieldInput }` map into an ordered array of `FieldDef`. */\nexport function assignKeys(fields: Record<string, FieldInput>): FieldDef[] {\n return Object.entries(fields).map(([key, def]) => ({ key, ...def }) as FieldDef);\n}\n","import { assignKeys, type FieldInput } from \"./fields.js\";\nimport type { CollectionKind, CollectionSchema, SlugConfig, Visibility } from \"./types.js\";\n\nexport interface CollectionInput {\n /** Immutable collection key, e.g. `posts`. */\n name: string;\n label?: string;\n kind?: CollectionKind;\n /** Visibility defaults to `private` and must be explicitly declared `public`. */\n visibility?: Visibility;\n titleField?: string;\n /** Path template such as `/blog/{slug}`. */\n path?: string;\n fields: Record<string, FieldInput>;\n}\n\n/**\n * Author a collection schema. Returns the canonical `CollectionSchema` with an\n * ordered fields array. Defaults: `kind=collection`, `visibility=private`.\n */\nexport function collection(input: CollectionInput): CollectionSchema {\n const fields = assignKeys(input.fields);\n const slugField = fields.find((f) => f.type === \"slug\");\n\n let slug: SlugConfig | null = null;\n if (slugField && slugField.type === \"slug\") {\n slug = {\n from: slugField.from ?? input.titleField ?? slugField.key,\n required: slugField.required,\n };\n }\n\n return {\n name: input.name,\n label: input.label ?? input.name,\n kind: input.kind ?? \"collection\",\n visibility: input.visibility ?? \"private\",\n titleField: input.titleField ?? null,\n slug,\n path: input.path ?? null,\n fields,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../schema/src/types.ts","../../schema/src/fields.ts","../../schema/src/collection.ts"],"sourcesContent":["/**\n * Canonical schema representation (SPEC 6.2 / 7).\n *\n * These are the normalized, serializable shapes stored as `schema_json`. Field\n * order is significant and preserved as an array; object *keys* are sorted only\n * during canonical stringification for a stable hash.\n */\n\nexport type FieldType =\n | \"text\"\n | \"number\"\n | \"boolean\"\n | \"date\"\n | \"datetime\"\n | \"markdown\"\n | \"richText\"\n | \"json\"\n | \"asset\"\n | \"reference\"\n | \"list\"\n | \"object\"\n | \"blocks\"\n | \"slug\";\n\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\n/** UI metadata carried on every field (non-semantic, does not affect validation). */\nexport interface FieldUi {\n widget?: string;\n placeholder?: string;\n helpText?: string;\n group?: string;\n hidden?: boolean;\n}\n\ninterface FieldBase {\n key: string;\n type: FieldType;\n label: string;\n description?: string;\n required: boolean;\n /** Localized fields store `{ [locale]: value }` keyed by project locale.\n * Only top-level, non-slug fields may be localized. */\n localized?: boolean;\n ui?: FieldUi;\n}\n\n/** Locale context for entry validation and locale resolution. */\nexport interface EntryLocaleOptions {\n locales: string[];\n defaultLocale: string;\n}\n\nexport interface TextField extends FieldBase {\n type: \"text\";\n multiline: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n enum?: string[];\n default?: string;\n}\n\nexport interface NumberField extends FieldBase {\n type: \"number\";\n integer: boolean;\n min?: number;\n max?: number;\n default?: number;\n}\n\nexport interface BooleanField extends FieldBase {\n type: \"boolean\";\n default?: boolean;\n}\n\nexport interface DateField extends FieldBase {\n type: \"date\" | \"datetime\";\n min?: string;\n max?: string;\n default?: string;\n}\n\nexport interface MarkdownField extends FieldBase {\n type: \"markdown\";\n minLength?: number;\n maxLength?: number;\n default?: string;\n}\n\nexport interface JsonField extends FieldBase {\n type: \"json\";\n default?: JsonValue;\n}\n\nexport interface AssetField extends FieldBase {\n type: \"asset\";\n /** Allowed MIME families, e.g. `image/*`, `application/pdf`. */\n allowed: string[];\n multiple: boolean;\n}\n\nexport interface ReferenceField extends FieldBase {\n type: \"reference\";\n /** Target collection key. */\n target: string;\n multiple: boolean;\n}\n\nexport interface SlugField extends FieldBase {\n type: \"slug\";\n /** Source field key to derive the slug from. */\n from?: string;\n}\n\nexport type PrimitiveItem =\n | { kind: \"text\"; minLength?: number; maxLength?: number; pattern?: string; enum?: string[] }\n | { kind: \"number\"; integer?: boolean; min?: number; max?: number }\n | { kind: \"boolean\" }\n | { kind: \"date\" | \"datetime\"; min?: string; max?: string }\n | { kind: \"markdown\"; minLength?: number; maxLength?: number }\n | { kind: \"json\" };\n\nexport type ListItem =\n | PrimitiveItem\n | { kind: \"reference\"; target: string }\n | { kind: \"asset\"; allowed: string[] }\n | { kind: \"object\"; fields: FieldDef[] };\n\nexport interface ListField extends FieldBase {\n type: \"list\";\n item: ListItem;\n minItems?: number;\n maxItems?: number;\n}\n\nexport interface ObjectField extends FieldBase {\n type: \"object\";\n fields: FieldDef[];\n}\n\n/** Semantic rich text stored as a portable JSON document tree — never HTML.\n * The root is `{ \"type\": \"doc\", \"content\": [...] }`; nodes may embed entry\n * references (`ent_` ids) and assets (`ast_` ids). */\nexport interface RichTextField extends FieldBase {\n type: \"richText\";\n}\n\n/** One schema-defined component usable inside a `blocks` field. */\nexport interface BlockDef {\n key: string;\n label: string;\n fields: FieldDef[];\n}\n\n/** Heterogeneous, schema-defined component list. Stored as portable JSON:\n * `[{ \"type\": \"<blockKey>\", \"fields\": { ... } }, ...]`. */\nexport interface BlocksField extends FieldBase {\n type: \"blocks\";\n blocks: BlockDef[];\n minItems?: number;\n maxItems?: number;\n}\n\nexport type FieldDef =\n | TextField\n | NumberField\n | BooleanField\n | DateField\n | MarkdownField\n | RichTextField\n | JsonField\n | AssetField\n | ReferenceField\n | SlugField\n | ListField\n | ObjectField\n | BlocksField;\n\nexport type CollectionKind = \"collection\" | \"singleton\";\nexport type Visibility = \"public\" | \"private\";\n\nexport interface SlugConfig {\n from: string;\n required: boolean;\n}\n\n/** Canonical collection schema (one `collection_versions.schema_json`). */\nexport interface CollectionSchema {\n name: string;\n label: string;\n kind: CollectionKind;\n visibility: Visibility;\n titleField: string | null;\n slug: SlugConfig | null;\n path: string | null;\n fields: FieldDef[];\n}\n\n/** Maximum object/list nesting depth (SPEC 6.2). */\nexport const MAX_NESTING_DEPTH = 4;\n","import type {\n AssetField,\n BlockDef,\n BlocksField,\n BooleanField,\n DateField,\n FieldDef,\n FieldUi,\n JsonField,\n JsonValue,\n ListField,\n ListItem,\n MarkdownField,\n NumberField,\n ObjectField,\n ReferenceField,\n RichTextField,\n SlugField,\n TextField,\n} from \"./types.js\";\n\n/** A field definition before its `key` is assigned by `collection()`. */\nexport type FieldInput = DistributiveOmit<FieldDef, \"key\">;\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\ninterface CommonOptions {\n label?: string;\n description?: string;\n required?: boolean;\n localized?: boolean;\n ui?: FieldUi;\n}\n\nfunction common(o: CommonOptions): {\n label?: string;\n description?: string;\n required: boolean;\n localized?: boolean;\n ui?: FieldUi;\n} {\n const base: { label?: string; description?: string; required: boolean; localized?: boolean; ui?: FieldUi } = {\n required: o.required ?? false,\n };\n if (o.label !== undefined) base.label = o.label;\n if (o.description !== undefined) base.description = o.description;\n if (o.localized !== undefined) base.localized = o.localized;\n if (o.ui !== undefined) base.ui = o.ui;\n return base;\n}\n\nexport interface TextOptions extends CommonOptions {\n multiline?: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n enum?: string[];\n default?: string;\n}\nexport interface NumberOptions extends CommonOptions {\n integer?: boolean;\n min?: number;\n max?: number;\n default?: number;\n}\nexport interface BooleanOptions extends CommonOptions {\n default?: boolean;\n}\nexport interface DateOptions extends CommonOptions {\n min?: string;\n max?: string;\n default?: string;\n}\nexport interface MarkdownOptions extends CommonOptions {\n minLength?: number;\n maxLength?: number;\n default?: string;\n}\nexport interface JsonOptions extends CommonOptions {\n default?: JsonValue;\n}\nexport interface AssetOptions extends CommonOptions {\n allowed?: string[];\n multiple?: boolean;\n}\nexport interface ReferenceOptions extends CommonOptions {\n to: string;\n multiple?: boolean;\n}\nexport interface SlugOptions extends CommonOptions {\n from?: string;\n}\nexport interface ListOptions extends CommonOptions {\n of: ListItem;\n minItems?: number;\n maxItems?: number;\n}\nexport interface ObjectOptions extends CommonOptions {\n fields: Record<string, FieldInput>;\n}\nexport type RichTextOptions = CommonOptions;\nexport interface BlocksOptions extends CommonOptions {\n /** The block components entries may compose, built with `block()`. */\n allowed: BlockDef[];\n minItems?: number;\n maxItems?: number;\n}\n\n/** Define a reusable block component for `field.blocks({ allowed: [...] })`. */\nexport function block(key: string, o: { label?: string; fields: Record<string, FieldInput> }): BlockDef {\n return { key, label: o.label ?? key, fields: assignKeys(o.fields) };\n}\n\nfunction defined<T extends Record<string, unknown>>(obj: T): T {\n for (const key of Object.keys(obj)) {\n if (obj[key] === undefined) delete obj[key];\n }\n return obj;\n}\n\nexport const field = {\n text(o: TextOptions = {}): Omit<TextField, \"key\"> {\n return defined({\n type: \"text\",\n ...common(o),\n multiline: o.multiline ?? false,\n minLength: o.minLength,\n maxLength: o.maxLength,\n pattern: o.pattern,\n enum: o.enum,\n default: o.default,\n }) as Omit<TextField, \"key\">;\n },\n\n number(o: NumberOptions = {}): Omit<NumberField, \"key\"> {\n return defined({\n type: \"number\",\n ...common(o),\n integer: o.integer ?? false,\n min: o.min,\n max: o.max,\n default: o.default,\n }) as Omit<NumberField, \"key\">;\n },\n\n boolean(o: BooleanOptions = {}): Omit<BooleanField, \"key\"> {\n return defined({\n type: \"boolean\",\n ...common(o),\n default: o.default,\n }) as Omit<BooleanField, \"key\">;\n },\n\n date(o: DateOptions = {}): Omit<DateField, \"key\"> {\n return defined({\n type: \"date\",\n ...common(o),\n min: o.min,\n max: o.max,\n default: o.default,\n }) as Omit<DateField, \"key\">;\n },\n\n datetime(o: DateOptions = {}): Omit<DateField, \"key\"> {\n return defined({\n type: \"datetime\",\n ...common(o),\n min: o.min,\n max: o.max,\n default: o.default,\n }) as Omit<DateField, \"key\">;\n },\n\n markdown(o: MarkdownOptions = {}): Omit<MarkdownField, \"key\"> {\n return defined({\n type: \"markdown\",\n ...common(o),\n minLength: o.minLength,\n maxLength: o.maxLength,\n default: o.default,\n }) as Omit<MarkdownField, \"key\">;\n },\n\n json(o: JsonOptions = {}): Omit<JsonField, \"key\"> {\n return defined({\n type: \"json\",\n ...common(o),\n default: o.default,\n }) as Omit<JsonField, \"key\">;\n },\n\n asset(o: AssetOptions = {}): Omit<AssetField, \"key\"> {\n return defined({\n type: \"asset\",\n ...common(o),\n allowed: o.allowed ?? [\"*/*\"],\n multiple: o.multiple ?? false,\n }) as Omit<AssetField, \"key\">;\n },\n\n reference(o: ReferenceOptions): Omit<ReferenceField, \"key\"> {\n return defined({\n type: \"reference\",\n ...common(o),\n target: o.to,\n multiple: o.multiple ?? false,\n }) as Omit<ReferenceField, \"key\">;\n },\n\n slug(o: SlugOptions = {}): Omit<SlugField, \"key\"> {\n return defined({\n type: \"slug\",\n ...common(o),\n from: o.from,\n }) as Omit<SlugField, \"key\">;\n },\n\n list(o: ListOptions): Omit<ListField, \"key\"> {\n return defined({\n type: \"list\",\n ...common(o),\n item: o.of,\n minItems: o.minItems,\n maxItems: o.maxItems,\n }) as Omit<ListField, \"key\">;\n },\n\n object(o: ObjectOptions): Omit<ObjectField, \"key\"> {\n return defined({\n type: \"object\",\n ...common(o),\n fields: assignKeys(o.fields),\n }) as Omit<ObjectField, \"key\">;\n },\n\n richText(o: RichTextOptions = {}): Omit<RichTextField, \"key\"> {\n return defined({\n type: \"richText\",\n ...common(o),\n }) as Omit<RichTextField, \"key\">;\n },\n\n blocks(o: BlocksOptions): Omit<BlocksField, \"key\"> {\n return defined({\n type: \"blocks\",\n ...common(o),\n blocks: o.allowed,\n minItems: o.minItems,\n maxItems: o.maxItems,\n }) as Omit<BlocksField, \"key\">;\n },\n} as const;\n\n/** Item builders for `field.list({ of: item.<type>() })`. */\nexport const item = {\n text(o: { minLength?: number; maxLength?: number; pattern?: string; enum?: string[] } = {}): ListItem {\n return defined({ kind: \"text\", ...o }) as ListItem;\n },\n number(o: { integer?: boolean; min?: number; max?: number } = {}): ListItem {\n return defined({ kind: \"number\", ...o }) as ListItem;\n },\n boolean(): ListItem {\n return { kind: \"boolean\" };\n },\n date(o: { min?: string; max?: string } = {}): ListItem {\n return defined({ kind: \"date\", ...o }) as ListItem;\n },\n datetime(o: { min?: string; max?: string } = {}): ListItem {\n return defined({ kind: \"datetime\", ...o }) as ListItem;\n },\n markdown(o: { minLength?: number; maxLength?: number } = {}): ListItem {\n return defined({ kind: \"markdown\", ...o }) as ListItem;\n },\n json(): ListItem {\n return { kind: \"json\" };\n },\n reference(o: { to: string }): ListItem {\n return { kind: \"reference\", target: o.to };\n },\n asset(o: { allowed?: string[] } = {}): ListItem {\n return { kind: \"asset\", allowed: o.allowed ?? [\"*/*\"] };\n },\n object(o: { fields: Record<string, FieldInput> }): ListItem {\n return { kind: \"object\", fields: assignKeys(o.fields) };\n },\n} as const;\n\n/** Turn a `{ key: FieldInput }` map into an ordered array of `FieldDef`. */\nexport function assignKeys(fields: Record<string, FieldInput>): FieldDef[] {\n return Object.entries(fields).map(([key, def]) => ({ key, ...def }) as FieldDef);\n}\n","import { assignKeys, type FieldInput } from \"./fields.js\";\nimport type { CollectionKind, CollectionSchema, SlugConfig, Visibility } from \"./types.js\";\n\nexport interface CollectionInput {\n /** Immutable collection key, e.g. `posts`. */\n name: string;\n label?: string;\n kind?: CollectionKind;\n /** Visibility defaults to `private` and must be explicitly declared `public`. */\n visibility?: Visibility;\n titleField?: string;\n /** Path template such as `/blog/{slug}`. */\n path?: string;\n fields: Record<string, FieldInput>;\n}\n\n/**\n * Author a collection schema. Returns the canonical `CollectionSchema` with an\n * ordered fields array. Defaults: `kind=collection`, `visibility=private`.\n */\nexport function collection(input: CollectionInput): CollectionSchema {\n const fields = assignKeys(input.fields);\n const slugField = fields.find((f) => f.type === \"slug\");\n\n let slug: SlugConfig | null = null;\n if (slugField && slugField.type === \"slug\") {\n slug = {\n from: slugField.from ?? input.titleField ?? slugField.key,\n required: slugField.required,\n };\n }\n\n return {\n name: input.name,\n label: input.label ?? input.name,\n kind: input.kind ?? \"collection\",\n visibility: input.visibility ?? \"private\",\n titleField: input.titleField ?? null,\n slug,\n path: input.path ?? null,\n fields,\n };\n}\n"],"mappings":"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","names":[]}
package/dist/main.js CHANGED
@@ -223,7 +223,9 @@ function renderField(field, depth) {
223
223
  const optional = field.required ? "" : "?";
224
224
  const doc = field.description ? `${indent}/** ${escapeComment(field.description)} */
225
225
  ` : "";
226
- return `${doc}${indent}${safeKey(field.key)}${optional}: ${fieldType(field, depth)};`;
226
+ const base = fieldType(field, depth);
227
+ const type = field.localized ? `{ [locale: string]: ${base} }` : base;
228
+ return `${doc}${indent}${safeKey(field.key)}${optional}: ${type};`;
227
229
  }
228
230
  function fieldType(field, depth) {
229
231
  switch (field.type) {
@@ -250,6 +252,21 @@ function fieldType(field, depth) {
250
252
  return renderObject(field, depth);
251
253
  case "list":
252
254
  return `${listItemType(field.item, depth)}[]`;
255
+ case "richText":
256
+ return "RichTextDoc";
257
+ case "blocks": {
258
+ const inner = " ".repeat(depth + 1);
259
+ const options = field.blocks.map((b) => {
260
+ const lines = b.fields.map((f) => renderField(f, depth + 2));
261
+ return `{ type: ${JSON.stringify(b.key)}; fields: {
262
+ ${lines.join("\n")}
263
+ ${inner}} }`;
264
+ });
265
+ return `Array<
266
+ ${inner}${options.join(`
267
+ ${inner}| `)}
268
+ ${" ".repeat(depth)}>`;
269
+ }
253
270
  default:
254
271
  return "unknown";
255
272
  }
@@ -291,7 +308,12 @@ ${closingIndent}}`;
291
308
  return "unknown";
292
309
  }
293
310
  }
294
- var JSON_VALUE = "export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };";
311
+ var JSON_VALUE = [
312
+ "export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };",
313
+ "",
314
+ "/** Portable rich-text document tree (never rendered HTML). */",
315
+ 'export interface RichTextDoc { type: "doc"; content: Array<{ type: string; [key: string]: unknown }>; }'
316
+ ].join("\n");
295
317
  function generateTypesModule(collections) {
296
318
  return `${JSON_VALUE}
297
319
 
@@ -368,7 +390,12 @@ var ManagementClient = class {
368
390
  create: (organization, body) => this.mutate("POST", `/organizations/${enc(organization)}/projects`, body),
369
391
  get: (project, signal) => this.get(`/projects/${enc(project)}`, void 0, signal),
370
392
  update: (project, body) => this.mutate("PATCH", `/projects/${enc(project)}`, body),
371
- archive: (project) => this.mutate("POST", `/projects/${enc(project)}/archive`)
393
+ archive: (project) => this.mutate("POST", `/projects/${enc(project)}/archive`),
394
+ /** Full project export: schemas, entries (draft + published), asset metadata. */
395
+ export: (project, signal) => this.get(`/projects/${enc(project)}/export`, void 0, signal),
396
+ views: (project, signal) => this.get(`/projects/${enc(project)}/views`, void 0, signal),
397
+ createView: (project, body) => this.mutate("POST", `/projects/${enc(project)}/views`, body),
398
+ deleteView: (project, view) => this.mutate("DELETE", `/projects/${enc(project)}/views/${enc(view)}`)
372
399
  };
373
400
  // --- Schema ---------------------------------------------------------------
374
401
  schema = {
@@ -380,6 +407,13 @@ var ManagementClient = class {
380
407
  collections,
381
408
  allowDestructive: opts.allowDestructive ?? false,
382
409
  changeSummary: opts.changeSummary
410
+ }),
411
+ // Environments as projects
412
+ drift: (project, against, signal) => this.get(`/projects/${enc(project)}/schema/drift`, { against }, signal),
413
+ promote: (project, fromProject, opts = {}) => this.mutate("POST", `/projects/${enc(project)}/schema/promote`, {
414
+ fromProject,
415
+ allowDestructive: opts.allowDestructive ?? false,
416
+ confirm: true
383
417
  })
384
418
  };
385
419
  // --- Entries & revisions --------------------------------------------------
@@ -394,6 +428,12 @@ var ManagementClient = class {
394
428
  delete: (project, entry, changeSetId) => this.mutate("DELETE", `/projects/${enc(project)}/entries/${enc(entry)}`, void 0, { changeSetId }),
395
429
  unpublish: (project, entry, changeSetId) => this.mutate("POST", `/projects/${enc(project)}/entries/${enc(entry)}/unpublish`, void 0, { changeSetId }),
396
430
  restore: (project, entry) => this.mutate("POST", `/projects/${enc(project)}/entries/${enc(entry)}/restore`),
431
+ duplicate: (project, entry, changeSetId) => this.mutate("POST", `/projects/${enc(project)}/entries/${enc(entry)}/duplicate`, {
432
+ changeSetId
433
+ }),
434
+ bulk: (project, body) => this.mutate("POST", `/projects/${enc(project)}/entries/bulk`, body),
435
+ import: (project, body) => this.mutate("POST", `/projects/${enc(project)}/entries/import`, body),
436
+ references: (project, entry, signal) => this.get(`/projects/${enc(project)}/entries/${enc(entry)}/references`, void 0, signal),
397
437
  revisions: (project, entry, signal) => this.get(`/projects/${enc(project)}/entries/${enc(entry)}/revisions`, void 0, signal),
398
438
  revision: (project, entry, revision, signal) => this.get(`/projects/${enc(project)}/entries/${enc(entry)}/revisions/${enc(revision)}`, void 0, signal),
399
439
  restoreRevision: (project, entry, revision) => this.mutate(
@@ -409,7 +449,48 @@ var ManagementClient = class {
409
449
  update: (project, changeSet, body) => this.mutate("PATCH", `/projects/${enc(project)}/change-sets/${enc(changeSet)}`, body),
410
450
  validate: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/validate`),
411
451
  publish: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/publish`, { confirm: true }),
412
- close: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/close`)
452
+ close: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/close`),
453
+ // Scheduled publishing
454
+ schedule: (project, changeSet, publishAt) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/schedule`, {
455
+ publishAt
456
+ }),
457
+ cancelSchedule: (project, changeSet) => this.mutate("DELETE", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/schedule`),
458
+ // Reviews & approvals
459
+ reviews: (project, changeSet, signal) => this.get(
460
+ `/projects/${enc(project)}/change-sets/${enc(changeSet)}/reviews`,
461
+ void 0,
462
+ signal
463
+ ),
464
+ requestReview: (project, changeSet, body) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/reviews`, body),
465
+ removeReviewer: (project, changeSet, reviewer) => this.mutate(
466
+ "DELETE",
467
+ `/projects/${enc(project)}/change-sets/${enc(changeSet)}/reviews/${enc(reviewer)}`
468
+ ),
469
+ approve: (project, changeSet, body = {}) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/approve`, body),
470
+ requestChanges: (project, changeSet, body = {}) => this.mutate(
471
+ "POST",
472
+ `/projects/${enc(project)}/change-sets/${enc(changeSet)}/request-changes`,
473
+ body
474
+ ),
475
+ // Comments
476
+ comments: (project, changeSet, signal) => this.get(
477
+ `/projects/${enc(project)}/change-sets/${enc(changeSet)}/comments`,
478
+ void 0,
479
+ signal
480
+ ),
481
+ comment: (project, changeSet, body) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/comments`, body),
482
+ resolveComment: (project, changeSet, comment, resolved = true) => this.mutate(
483
+ "PATCH",
484
+ `/projects/${enc(project)}/change-sets/${enc(changeSet)}/comments/${enc(comment)}`,
485
+ { resolved }
486
+ ),
487
+ // Automated checks
488
+ checks: (project, changeSet, signal) => this.get(
489
+ `/projects/${enc(project)}/change-sets/${enc(changeSet)}/checks`,
490
+ void 0,
491
+ signal
492
+ ),
493
+ runChecks: (project, changeSet) => this.mutate("POST", `/projects/${enc(project)}/change-sets/${enc(changeSet)}/checks/run`)
413
494
  };
414
495
  // --- Previews -------------------------------------------------------------
415
496
  previews = {
@@ -427,6 +508,7 @@ var ManagementClient = class {
427
508
  get: (project, asset, signal) => this.get(`/projects/${enc(project)}/assets/${enc(asset)}`, void 0, signal),
428
509
  usage: (project, asset, signal) => this.get(`/projects/${enc(project)}/assets/${enc(asset)}`, { usage: "true" }, signal),
429
510
  update: (project, asset, body) => this.mutate("PATCH", `/projects/${enc(project)}/assets/${enc(asset)}`, body),
511
+ replace: (project, asset, body) => this.mutate("POST", `/projects/${enc(project)}/assets/${enc(asset)}/replace`, body),
430
512
  delete: (project, asset) => this.mutate("DELETE", `/projects/${enc(project)}/assets/${enc(asset)}`),
431
513
  /** Full presigned upload flow: create → PUT bytes → complete. */
432
514
  upload: (project, input, meta = {}) => this.uploadAsset(project, input, meta)
@@ -1407,6 +1489,35 @@ function registerSchema(program) {
1407
1489
  emit(result, () => renderDiff(result));
1408
1490
  })
1409
1491
  );
1492
+ schema.command("drift").description("Show schema drift between this project and another (e.g. staging)").requiredOption("--against <project>", "project id or slug to compare with").action(
1493
+ handle(async (ctx, _args, opts) => {
1494
+ const project = ctx.requireProject();
1495
+ const result = await ctx.management().schema.drift(project, opts.against);
1496
+ emit(result, () => {
1497
+ if (result.inSync) diag("Schemas are in sync.");
1498
+ else {
1499
+ diag(`${result.ops.length} op(s) of drift (${result.classification}):`);
1500
+ for (const op of result.ops) {
1501
+ process.stderr.write(` ${op.kind} ${op.collection}${op.field ? `.${op.field}` : ""} \u2014 ${op.detail}
1502
+ `);
1503
+ }
1504
+ }
1505
+ });
1506
+ if (!result.inSync) process.exitCode = 1;
1507
+ })
1508
+ );
1509
+ schema.command("promote").description("Promote another project's deployed schemas into this project").requiredOption("--from <project>", "source project id or slug").option("--allow-destructive", "permit destructive schema changes", false).action(
1510
+ handle(async (ctx, _args, opts) => {
1511
+ const project = ctx.requireProject();
1512
+ const result = await ctx.management().schema.promote(project, opts.from, {
1513
+ allowDestructive: Boolean(opts.allowDestructive)
1514
+ });
1515
+ emit(result, () => {
1516
+ renderDiff(result.diff);
1517
+ diag(result.applied ? `Promoted ${result.versions.length} version(s).` : "Nothing to promote.");
1518
+ });
1519
+ })
1520
+ );
1410
1521
  schema.command("push").description("Apply local schemas, creating immutable versions").option("--schema-dir <dir>", "schema directory").option("--allow-destructive", "permit destructive schema changes", false).option("--summary <text>", "change summary").action(
1411
1522
  handle(async (ctx, _args, opts) => {
1412
1523
  const project = ctx.requireProject();
@@ -1480,6 +1591,7 @@ function renderDiff(diff) {
1480
1591
  }
1481
1592
 
1482
1593
  // src/commands/entries.ts
1594
+ import { readFile as readFile2 } from "fs/promises";
1483
1595
  function registerEntries(program) {
1484
1596
  const entries = program.command("entries").description("Create, read, update, and manage entries");
1485
1597
  entries.command("list").description("List entries in a collection").argument("<collection>", "collection key").option("--status <status>", "filter by status").option("--limit <n>", "page size", "25").option("--cursor <cursor>", "pagination cursor").action(
@@ -1578,6 +1690,81 @@ function registerEntries(program) {
1578
1690
  );
1579
1691
  })
1580
1692
  );
1693
+ entries.command("duplicate").description("Duplicate an entry into a new draft").argument("<ref>", "collection/slug or entry id").option("--change-set <id>", "stage on an explicit change set").action(
1694
+ handle(async (ctx, args, opts) => {
1695
+ const project = ctx.requireProject();
1696
+ const id = await resolveEntryId(ctx.management(), project, args[0]);
1697
+ const entry = await ctx.management().entries.duplicate(project, id, opts.changeSet);
1698
+ emit(entry, () => diag(`Duplicated into ${entry.id} (${entry.slug ?? "no slug"}).`));
1699
+ })
1700
+ );
1701
+ entries.command("bulk").description("Stage a delete or unpublish for many entries at once").requiredOption("--action <action>", "delete or unpublish").requiredOption("--ids <ids>", "comma-separated entry ids").option("--change-set <id>", "stage on an explicit change set").action(
1702
+ handle(async (ctx, _args, opts) => {
1703
+ const action = opts.action;
1704
+ if (action !== "delete" && action !== "unpublish") {
1705
+ throw new UsageError("--action must be delete or unpublish.");
1706
+ }
1707
+ const project = ctx.requireProject();
1708
+ const result = await ctx.management().entries.bulk(project, {
1709
+ action,
1710
+ entryIds: opts.ids.split(",").map((s) => s.trim()).filter(Boolean),
1711
+ changeSetId: opts.changeSet
1712
+ });
1713
+ emit(
1714
+ result,
1715
+ () => table(result.results, [
1716
+ { header: "ENTRY", value: (r) => r.entryId },
1717
+ { header: "OK", value: (r) => r.ok ? "yes" : "no" },
1718
+ { header: "CHANGE SET", value: (r) => r.changeSetId ?? "\u2014" },
1719
+ { header: "ERROR", value: (r) => r.error ?? "" }
1720
+ ])
1721
+ );
1722
+ if (result.results.some((r) => !r.ok)) process.exitCode = 1;
1723
+ })
1724
+ );
1725
+ entries.command("import").description("Import entries from a JSON file, with dry-run validation").argument("<file>", 'JSON file: { "collection": "posts", "entries": [{ "slug"?, "data" }] }').option("--dry-run", "validate without creating anything", false).action(
1726
+ handle(async (ctx, args, opts) => {
1727
+ const project = ctx.requireProject();
1728
+ const raw = JSON.parse(await readFile2(args[0], "utf8"));
1729
+ const result = await ctx.management().entries.import(project, {
1730
+ collection: raw.collection,
1731
+ entries: raw.entries,
1732
+ dryRun: Boolean(opts.dryRun)
1733
+ });
1734
+ emit(result, () => {
1735
+ diag(
1736
+ result.dryRun ? `Dry run: ${result.valid ? "all rows valid" : "validation failed"}.` : `Imported ${result.results.filter((r) => r.ok).length}/${result.results.length} into change set ${result.changeSetId}.`
1737
+ );
1738
+ for (const row of result.results.filter((r) => !r.ok)) {
1739
+ for (const e of row.errors ?? []) {
1740
+ process.stderr.write(` row ${row.index}${row.slug ? ` (${row.slug})` : ""}: ${e.path} ${e.message}
1741
+ `);
1742
+ }
1743
+ }
1744
+ });
1745
+ if (!result.valid) process.exitCode = 1;
1746
+ })
1747
+ );
1748
+ entries.command("references").description("Show which entries reference this one and what it references").argument("<ref>", "collection/slug or entry id").action(
1749
+ handle(async (ctx, args) => {
1750
+ const project = ctx.requireProject();
1751
+ const id = await resolveEntryId(ctx.management(), project, args[0]);
1752
+ const refs = await ctx.management().entries.references(project, id);
1753
+ emit(refs, () => {
1754
+ diag(`Referenced by ${refs.referencedBy.length} entr(y/ies):`);
1755
+ table(refs.referencedBy, [
1756
+ { header: "ENTRY", value: (r) => `${r.collectionKey}/${r.slug ?? r.id}` },
1757
+ { header: "IN DRAFT", value: (r) => r.inDraft ? "yes" : "no" },
1758
+ { header: "IN PUBLISHED", value: (r) => r.inPublished ? "yes" : "no" }
1759
+ ]);
1760
+ diag(`References ${refs.references.length} entr(y/ies):`);
1761
+ table(refs.references, [
1762
+ { header: "ENTRY", value: (r) => r.collectionKey ? `${r.collectionKey}/${r.slug ?? r.id}` : r.id },
1763
+ { header: "STATE", value: (r) => !r.exists ? "missing" : r.published ? "published" : "unpublished" }
1764
+ ]);
1765
+ });
1766
+ })
1767
+ );
1581
1768
  entries.command("restore").description("Restore a historical revision into a new draft").argument("<ref>", "collection/slug or entry id").requiredOption("--revision <id>", "revision id to restore").action(
1582
1769
  handle(async (ctx, args, opts) => {
1583
1770
  const project = ctx.requireProject();
@@ -1682,6 +1869,107 @@ function registerChanges(program) {
1682
1869
  emit(cs, () => diag(`Closed ${cs.id}.`));
1683
1870
  })
1684
1871
  );
1872
+ changes.command("schedule").description("Schedule, reschedule, or cancel a change set's publish").argument("<id>", "change set id").option("--at <datetime>", "ISO 8601 datetime to publish at").option("--cancel", "cancel the scheduled publish", false).action(
1873
+ handle(async (ctx, args, opts) => {
1874
+ const project = ctx.requireProject();
1875
+ if (Boolean(opts.at) === Boolean(opts.cancel)) {
1876
+ throw new UsageError("Provide exactly one of --at <datetime> or --cancel.");
1877
+ }
1878
+ const client = ctx.management();
1879
+ const cs = opts.cancel ? await client.changeSets.cancelSchedule(project, args[0]) : await client.changeSets.schedule(project, args[0], new Date(opts.at).toISOString());
1880
+ emit(
1881
+ cs,
1882
+ () => diag(cs.scheduledAt ? `Scheduled to publish at ${cs.scheduledAt}.` : "Schedule cancelled.")
1883
+ );
1884
+ })
1885
+ );
1886
+ changes.command("reviews").description("List reviews and approvals for a change set").argument("<id>", "change set id").action(
1887
+ handle(async (ctx, args) => {
1888
+ const project = ctx.requireProject();
1889
+ const reviews = await ctx.management().changeSets.reviews(project, args[0]);
1890
+ emit(
1891
+ reviews,
1892
+ () => table(reviews, [
1893
+ { header: "REVIEWER", value: (r) => `${r.reviewerType}:${r.reviewerId}` },
1894
+ { header: "STATUS", value: (r) => r.status },
1895
+ { header: "STALE", value: (r) => r.stale ? "yes" : "no" },
1896
+ { header: "DECIDED", value: (r) => r.decidedAt ?? "\u2014" }
1897
+ ])
1898
+ );
1899
+ })
1900
+ );
1901
+ changes.command("request-review").description("Assign a reviewer to a change set").argument("<id>", "change set id").requiredOption("--reviewer <id>", "reviewer id (user, api key, or agent id)").option("--reviewer-type <type>", "reviewer actor type", "user").action(
1902
+ handle(async (ctx, args, opts) => {
1903
+ const project = ctx.requireProject();
1904
+ const reviews = await ctx.management().changeSets.requestReview(project, args[0], {
1905
+ reviewerType: opts.reviewerType,
1906
+ reviewerId: opts.reviewer
1907
+ });
1908
+ emit(reviews, () => diag(`Requested review from ${opts.reviewer}.`));
1909
+ })
1910
+ );
1911
+ changes.command("approve").description("Approve a change set").argument("<id>", "change set id").option("--comment <text>", "optional review comment").action(
1912
+ handle(async (ctx, args, opts) => {
1913
+ const project = ctx.requireProject();
1914
+ const reviews = await ctx.management().changeSets.approve(project, args[0], {
1915
+ comment: opts.comment
1916
+ });
1917
+ emit(reviews, () => diag("Approved."));
1918
+ })
1919
+ );
1920
+ changes.command("request-changes").description("Request changes on a change set").argument("<id>", "change set id").option("--comment <text>", "optional review comment").action(
1921
+ handle(async (ctx, args, opts) => {
1922
+ const project = ctx.requireProject();
1923
+ const reviews = await ctx.management().changeSets.requestChanges(project, args[0], {
1924
+ comment: opts.comment
1925
+ });
1926
+ emit(reviews, () => diag("Requested changes."));
1927
+ })
1928
+ );
1929
+ changes.command("comments").description("List comments on a change set").argument("<id>", "change set id").action(
1930
+ handle(async (ctx, args) => {
1931
+ const project = ctx.requireProject();
1932
+ const comments = await ctx.management().changeSets.comments(project, args[0]);
1933
+ emit(
1934
+ comments,
1935
+ () => table(comments, [
1936
+ { header: "AUTHOR", value: (c) => `${c.authorType}:${c.authorId ?? "\u2014"}` },
1937
+ { header: "ANCHOR", value: (c) => c.resourceId ? `${c.resourceId}${c.fieldPath ? `#${c.fieldPath}` : ""}` : "\u2014" },
1938
+ { header: "RESOLVED", value: (c) => c.resolvedAt ? "yes" : "no" },
1939
+ { header: "BODY", value: (c) => c.body.length > 60 ? `${c.body.slice(0, 57)}...` : c.body }
1940
+ ])
1941
+ );
1942
+ })
1943
+ );
1944
+ changes.command("comment").description("Comment on a change set (optionally anchored to a field)").argument("<id>", "change set id").argument("<body>", "comment body").option("--entry <id>", "anchor to an entry in the change set").option("--field <path>", "anchor to a field path, e.g. fields.title").action(
1945
+ handle(async (ctx, args, opts) => {
1946
+ const project = ctx.requireProject();
1947
+ const comment = await ctx.management().changeSets.comment(project, args[0], {
1948
+ body: args[1],
1949
+ resourceType: opts.entry ? "entry" : void 0,
1950
+ resourceId: opts.entry,
1951
+ fieldPath: opts.field
1952
+ });
1953
+ emit(comment, () => diag(`Commented ${comment.id}.`));
1954
+ })
1955
+ );
1956
+ changes.command("checks").description("Show the latest check run for a change set").argument("<id>", "change set id").option("--run", "run checks before showing results", false).action(
1957
+ handle(async (ctx, args, opts) => {
1958
+ const project = ctx.requireProject();
1959
+ const client = ctx.management();
1960
+ const checks = opts.run ? (await client.changeSets.runChecks(project, args[0])).checks : await client.changeSets.checks(project, args[0]);
1961
+ emit(
1962
+ checks,
1963
+ () => table(checks, [
1964
+ { header: "CHECK", value: (c) => c.name },
1965
+ { header: "STATUS", value: (c) => c.status },
1966
+ { header: "STALE", value: (c) => c.stale ? "yes" : "no" },
1967
+ { header: "ISSUES", value: (c) => String(c.details?.length ?? 0) }
1968
+ ])
1969
+ );
1970
+ if (checks.some((c) => c.status === "failed")) process.exitCode = 1;
1971
+ })
1972
+ );
1685
1973
  }
1686
1974
 
1687
1975
  // src/commands/previews.ts
@@ -1893,6 +2181,14 @@ function registerProjects(program) {
1893
2181
  );
1894
2182
  })
1895
2183
  );
2184
+ projects.command("export").description("Export the full project (schemas, entries, asset metadata) as JSON").argument("[project]", "project id or slug").action(
2185
+ handle(async (ctx, args) => {
2186
+ const ref = args[0] ?? ctx.requireProject();
2187
+ const data = await ctx.management().projects.export(ref);
2188
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
2189
+ `);
2190
+ })
2191
+ );
1896
2192
  projects.command("update").description("Update project settings").argument("[project]", "project id or slug").option("--name <name>", "project name").option("--timezone <timezone>", "IANA timezone").option("--public-api <state>", "public API state (on|off)").option("--default-preview-template <template>", "default preview URL template").option("--clear-default-preview-template", "remove the default preview URL template").option("--origins <urls>", "comma-separated allowed origins").option("--clear-origins", "remove all allowed origins").action(
1897
2193
  handle(async (ctx, args, opts) => {
1898
2194
  const ref = args[0] ?? ctx.requireProject();
@@ -2167,7 +2463,7 @@ function registerBilling(program) {
2167
2463
  }
2168
2464
 
2169
2465
  // src/main.ts
2170
- var VERSION = "0.1.4";
2466
+ var VERSION = "0.2.0";
2171
2467
  var GLOBAL_VALUE_FLAGS = /* @__PURE__ */ new Set(["--project", "--organization", "--token", "--api-url"]);
2172
2468
  var GLOBAL_BOOL_FLAGS = /* @__PURE__ */ new Set(["--json", "--no-interactive", "--interactive"]);
2173
2469
  function normalizeGlobals(argv) {