@sitecoreai-labs/sitecoreai-cli 0.1.2 → 0.2.1
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/agents/tasks/agent.js +2 -2
- package/dist/agents/tasks/resources.js +2 -2
- package/dist/agents/tasks/space.js +1 -1
- package/dist/brand/api/auth.d.ts +16 -9
- package/dist/brand/api/auth.js +29 -19
- package/dist/brand/credential.d.ts +71 -1
- package/dist/brand/credential.js +119 -2
- package/dist/brand/recipe/diff.js +7 -2
- package/dist/brand/recipe/kind.js +0 -0
- package/dist/brand/recipe/schema.d.ts +113 -7
- package/dist/brand/recipe/schema.js +137 -8
- package/dist/brand/seed.d.ts +9 -5
- package/dist/brand/seed.js +30 -5
- package/dist/brief/api/briefs.d.ts +8 -0
- package/dist/brief/api/briefs.js +49 -11
- package/dist/brief/index.d.ts +1 -1
- package/dist/brief/index.js +2 -1
- package/dist/brief/recipe/index.d.ts +11 -2
- package/dist/brief/recipe/index.js +17 -3
- package/dist/brief/recipe/instance-diff.d.ts +4 -0
- package/dist/brief/recipe/instance-diff.js +77 -0
- package/dist/brief/recipe/instance-kind.d.ts +4 -0
- package/dist/brief/recipe/instance-kind.js +190 -0
- package/dist/brief/recipe/instance-schema.d.ts +61 -0
- package/dist/brief/recipe/instance-schema.js +68 -0
- package/dist/brief/recipe/schema.js +4 -1
- package/dist/brief/tasks/index.d.ts +35 -0
- package/dist/brief/tasks/index.js +62 -1
- package/dist/campaigns/recipe/schema.d.ts +39 -8
- package/dist/campaigns/recipe/schema.js +40 -10
- package/dist/commands/agents/sync.js +2 -2
- package/dist/commands/brand/seed.js +1 -1
- package/dist/commands/brand/sync.js +2 -2
- package/dist/commands/brief/create.d.ts +2 -0
- package/dist/commands/brief/create.js +56 -0
- package/dist/commands/brief/index.d.ts +3 -1
- package/dist/commands/brief/index.js +11 -1
- package/dist/commands/brief/sync.d.ts +9 -6
- package/dist/commands/brief/sync.js +54 -22
- package/dist/commands/brief/update.d.ts +2 -0
- package/dist/commands/brief/update.js +84 -0
- package/dist/commands/campaign/sync.js +2 -2
- package/dist/mcp/descriptions.js +3 -3
- package/dist/mcp/tools/brief-recipe.js +67 -23
- package/dist/mcp/tools/brief.js +83 -6
- package/dist/recipe/compile/design-parameters-template.js +5 -3
- package/dist/recipe/compile/enumeration.js +6 -11
- package/dist/recipe/compile/shared.js +12 -12
- package/dist/recipe/compile.js +4 -4
- package/dist/recipe/io.d.ts +8 -3
- package/dist/recipe/io.js +11 -81
- package/dist/recipe/items/read-current.js +31 -24
- package/dist/recipe/schema/recipe.d.ts +167 -84
- package/dist/recipe/schema/recipe.js +130 -46
- package/dist/recipe/schema/source-fields.d.ts +20 -0
- package/dist/recipe/schema/source-fields.js +25 -1
- package/dist/recipe/validate.d.ts +3 -3
- package/dist/recipe/validate.js +20 -10
- package/dist/sync/aggregate-kinds.js +1 -0
- package/dist/sync/aggregate.js +2 -2
- package/dist/sync/io.d.ts +13 -3
- package/dist/sync/io.js +43 -17
- package/dist/sync/typescript-recipe.d.ts +30 -0
- package/dist/sync/typescript-recipe.js +112 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BrandKitRecipeSchema = exports.BrandDocumentSchema = exports.BrandFieldValueSchema = exports.BrandRichEntrySchema = void 0;
|
|
3
|
+
exports.BrandKitRecipeSchema = exports.BrandDocumentSchema = exports.BrandRegistryFileDocumentSchema = exports.BrandUrlDocumentSchema = exports.BRAND_KIT_CANONICAL_SECTIONS = exports.BrandFieldValueSchema = exports.BrandRichEntrySchema = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* `BrandKitRecipe` — the declarative definition of a Sitecore Brand
|
|
6
6
|
* brand kit.
|
|
@@ -8,9 +8,26 @@ exports.BrandKitRecipeSchema = exports.BrandDocumentSchema = exports.BrandFieldV
|
|
|
8
8
|
* This schema is the single source of truth for the `brand-kit` recipe
|
|
9
9
|
* kind: it validates recipe files, drives the `sync` CLI, and becomes
|
|
10
10
|
* the MCP tool input schema. Keep the `.describe()` text accurate — the
|
|
11
|
-
* model reads it.
|
|
11
|
+
* model reads it.
|
|
12
|
+
*
|
|
13
|
+
* Two authoring shapes share this schema:
|
|
14
|
+
*
|
|
15
|
+
* - **scai-native** — flat object with `name`, `documents`, `sections`.
|
|
16
|
+
* No `kind` / `schemaVersion` / `handle`. Used by hand-authored
|
|
17
|
+
* `.brandkit.yaml` files and the `scai brand sync pull` capture
|
|
18
|
+
* path. Stays valid: the discriminator fields are optional here.
|
|
19
|
+
* - **registry-superset** — the richer shape the `@registry`
|
|
20
|
+
* `sitecore-recipes.ts` exports use: adds `kind: "brandkit"`,
|
|
21
|
+
* `schemaVersion: "1"`, `handle`, `displayName`, and a
|
|
22
|
+
* discriminated `documents[]` shape (`url` | `registry-file`)
|
|
23
|
+
* with `tags` / `sections` hints per document. The orchestrator
|
|
24
|
+
* passes recipes through unchanged from the registry to scai;
|
|
25
|
+
* scai's parser accepts the extra fields without stripping them.
|
|
26
|
+
*
|
|
27
|
+
* See docs/recipe-sync-architecture.md.
|
|
12
28
|
*/
|
|
13
29
|
const zod_1 = require("zod");
|
|
30
|
+
const HANDLE_PATTERN = /^[a-z][a-z0-9-]*@\d+$/;
|
|
14
31
|
/** A `richArray`-field entry: text plus optional tags and a constraint. */
|
|
15
32
|
exports.BrandRichEntrySchema = zod_1.z.object({
|
|
16
33
|
name: zod_1.z.string().min(1).describe("The entry's text."),
|
|
@@ -31,29 +48,141 @@ exports.BrandRichEntrySchema = zod_1.z.object({
|
|
|
31
48
|
exports.BrandFieldValueSchema = zod_1.z
|
|
32
49
|
.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string()), zod_1.z.array(exports.BrandRichEntrySchema)])
|
|
33
50
|
.describe("A text value, a list of names, or a list of rich entries.");
|
|
34
|
-
/**
|
|
35
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Canonical Sitecore AI brand-kit section names — the seven buckets
|
|
53
|
+
* the EnrichSections pipeline produces. Mirrors
|
|
54
|
+
* `BRAND_KIT_CANONICAL_SECTIONS` in the registry's recipe definitions.
|
|
55
|
+
* Exported so callers building recipes have a stable list to bias
|
|
56
|
+
* `documents[].sections` against.
|
|
57
|
+
*/
|
|
58
|
+
exports.BRAND_KIT_CANONICAL_SECTIONS = [
|
|
59
|
+
"Brand Context",
|
|
60
|
+
"Global Goals",
|
|
61
|
+
"Tone of Voice",
|
|
62
|
+
"Glossary and Localization",
|
|
63
|
+
"Do's and Don'ts",
|
|
64
|
+
"Grammar Checklists",
|
|
65
|
+
"Visual Guidelines",
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* Optional ingestion hints — `tags` and `sections` — shared by both
|
|
69
|
+
* document variants. Hoisted into a shape so the URL and registry-file
|
|
70
|
+
* shapes stay in lockstep without duplicating field-by-field.
|
|
71
|
+
*/
|
|
72
|
+
const DocumentHints = {
|
|
73
|
+
title: zod_1.z.string().optional().describe('Document title. Defaults to "brand guidelines".'),
|
|
74
|
+
summary: zod_1.z.string().optional().describe("Short document summary."),
|
|
75
|
+
tags: zod_1.z
|
|
76
|
+
.array(zod_1.z.string())
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('Free-form tags surfaced to the EnrichSections pipeline (e.g. "voice").'),
|
|
79
|
+
sections: zod_1.z
|
|
80
|
+
.array(zod_1.z.string())
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Canonical section names to bias ingestion toward (e.g. ["Tone of Voice"]). Prefer values from BRAND_KIT_CANONICAL_SECTIONS.'),
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* A brand document referenced by URL. Sitecore's Documents API fetches
|
|
86
|
+
* the URL server-side and copies the bytes into MMS. The default
|
|
87
|
+
* variant — what `scai brand sync pull` emits when capturing a live
|
|
88
|
+
* kit.
|
|
89
|
+
*/
|
|
90
|
+
exports.BrandUrlDocumentSchema = zod_1.z.object({
|
|
91
|
+
kind: zod_1.z.literal("url"),
|
|
36
92
|
url: zod_1.z
|
|
37
93
|
.string()
|
|
38
94
|
.min(1)
|
|
39
95
|
.describe("Publicly reachable URL of a brand document (PDF). Sitecore fetches it server-side."),
|
|
40
|
-
|
|
41
|
-
summary: zod_1.z.string().optional().describe("Short document summary."),
|
|
96
|
+
...DocumentHints,
|
|
42
97
|
});
|
|
98
|
+
/**
|
|
99
|
+
* A brand document stored alongside the recipe in the registry repo.
|
|
100
|
+
* The `path` is relative to the recipe file's directory. scai itself
|
|
101
|
+
* does **not** upload these — the Sitecore Documents API has no
|
|
102
|
+
* working bytes-upload path (see
|
|
103
|
+
* `src/brand/documents/upload.ts::LOCAL_UPLOAD_UNSUPPORTED_MESSAGE`),
|
|
104
|
+
* so the orchestrator (or whoever owns the recipe before scai sees
|
|
105
|
+
* it) MUST translate `registry-file` entries to `url` entries
|
|
106
|
+
* pointing at an HTTP-reachable host before invoking `scai brand
|
|
107
|
+
* sync push`. The seed runner rejects unresolved `registry-file`
|
|
108
|
+
* documents with a clear hint; this stays in the schema as an
|
|
109
|
+
* authoring-time shape so registry-side recipes round-trip cleanly.
|
|
110
|
+
*/
|
|
111
|
+
exports.BrandRegistryFileDocumentSchema = zod_1.z.object({
|
|
112
|
+
kind: zod_1.z.literal("registry-file"),
|
|
113
|
+
path: zod_1.z.string().min(1).describe("Path to the document, relative to the recipe file."),
|
|
114
|
+
...DocumentHints,
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* A brand document to ingest. Two variants — `{ kind: "url", url }` and
|
|
118
|
+
* `{ kind: "registry-file", path }` — sharing optional `title` /
|
|
119
|
+
* `summary` / `tags` / `sections` ingestion hints.
|
|
120
|
+
*
|
|
121
|
+
* Back-compat: scai-native recipes that pre-date the discriminator wrote
|
|
122
|
+
* documents as the flat `{ url, title?, summary? }` shape. The
|
|
123
|
+
* `z.preprocess` step defaults a missing `kind` to `"url"` whenever
|
|
124
|
+
* `url` is present, so legacy recipes keep parsing without a migration
|
|
125
|
+
* step. Zod 4's discriminated unions require the discriminator to be
|
|
126
|
+
* present BEFORE routing, so `.default("url")` inline on the literal
|
|
127
|
+
* doesn't work — preprocess is the correct seam.
|
|
128
|
+
*/
|
|
129
|
+
exports.BrandDocumentSchema = zod_1.z.preprocess((raw) => {
|
|
130
|
+
if (raw !== null &&
|
|
131
|
+
typeof raw === "object" &&
|
|
132
|
+
!Array.isArray(raw) &&
|
|
133
|
+
!("kind" in raw) &&
|
|
134
|
+
"url" in raw) {
|
|
135
|
+
return { kind: "url", ...raw };
|
|
136
|
+
}
|
|
137
|
+
return raw;
|
|
138
|
+
}, zod_1.z.discriminatedUnion("kind", [exports.BrandUrlDocumentSchema, exports.BrandRegistryFileDocumentSchema]));
|
|
43
139
|
/** The full brand-kit recipe. */
|
|
44
140
|
exports.BrandKitRecipeSchema = zod_1.z.object({
|
|
141
|
+
/**
|
|
142
|
+
* Registry-superset discriminator — `"brandkit"`. Optional: scai-
|
|
143
|
+
* native YAML/JSON recipes pre-date this field and don't carry it.
|
|
144
|
+
* Defaulting in (rather than requiring) keeps the legacy parse
|
|
145
|
+
* path intact. Registry-authored recipes set this explicitly.
|
|
146
|
+
*/
|
|
147
|
+
kind: zod_1.z.literal("brandkit").optional(),
|
|
148
|
+
/**
|
|
149
|
+
* Registry-superset schema version pin — `"1"`. Optional for the
|
|
150
|
+
* same back-compat reason as `kind`. When set, the registry's
|
|
151
|
+
* discriminated-union loader picks this recipe up.
|
|
152
|
+
*/
|
|
153
|
+
schemaVersion: zod_1.z.literal("1").optional(),
|
|
154
|
+
/**
|
|
155
|
+
* Stable handle of the form `<kebab-name>@<major>` (e.g.
|
|
156
|
+
* `acme-brand@1`). Registry-side identifier used to wire brand
|
|
157
|
+
* kits to themes. Optional in scai because scai-native recipes
|
|
158
|
+
* identify kits by `name`, not handle.
|
|
159
|
+
*/
|
|
160
|
+
handle: zod_1.z
|
|
161
|
+
.string()
|
|
162
|
+
.regex(HANDLE_PATTERN, {
|
|
163
|
+
message: "handle must match `<kebab-name>@<major>` (e.g. acme-brand@1)",
|
|
164
|
+
})
|
|
165
|
+
.optional()
|
|
166
|
+
.describe("Stable handle `<kebab-name>@<major>`. Required for registry-authored recipes."),
|
|
45
167
|
name: zod_1.z
|
|
46
168
|
.string()
|
|
47
169
|
.min(1)
|
|
48
170
|
.describe("Display name of the brand kit. Identifies the kit when pushing."),
|
|
171
|
+
/**
|
|
172
|
+
* Author-facing label, mirrors the registry's superset. When absent
|
|
173
|
+
* the registry's loader treats it as identical to `name`; scai's
|
|
174
|
+
* `apply()` likewise ignores it (the API has no separate display
|
|
175
|
+
* name slot beyond `name`).
|
|
176
|
+
*/
|
|
177
|
+
displayName: zod_1.z.string().min(1).optional().describe("Author-facing label; defaults to `name`."),
|
|
49
178
|
description: zod_1.z.string().optional().describe("Human description of the kit."),
|
|
50
179
|
industry: zod_1.z.string().optional().describe('Industry label, e.g. "retail" or "developer-tools".'),
|
|
51
180
|
documents: zod_1.z
|
|
52
181
|
.array(exports.BrandDocumentSchema)
|
|
53
182
|
.default([])
|
|
54
|
-
.describe("Brand documents to ingest. On push, when the kit has no populated sections, each is uploaded and run through the ingestion + enrichment pipelines."),
|
|
183
|
+
.describe("Brand documents to ingest. On push, when the kit has no populated sections, each is uploaded and run through the ingestion + enrichment pipelines. `registry-file` variants must be translated to `url` variants before scai sees the recipe — the Sitecore Documents API has no working bytes-upload path."),
|
|
55
184
|
sections: zod_1.z
|
|
56
185
|
.record(zod_1.z.string(), zod_1.z.record(zod_1.z.string(), exports.BrandFieldValueSchema))
|
|
57
186
|
.default({})
|
|
58
|
-
.describe("Desired field values, keyed by section name then field name. Sections and fields are created by enrichment; push converges their values."),
|
|
187
|
+
.describe("Desired field values, keyed by section name then field name. Prefer canonical names from BRAND_KIT_CANONICAL_SECTIONS for the outer key. Sections and fields are created by enrichment; push converges their values."),
|
|
59
188
|
});
|
package/dist/brand/seed.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { BrandApiClientOptions } from "./api/client";
|
|
|
2
2
|
import { type BrandKitSectionSummary } from "./kits/sections";
|
|
3
3
|
import { type UploadDocumentSource, type UploadedDocument } from "./documents/upload";
|
|
4
4
|
import type { BrandKitSummary } from "./kits/list";
|
|
5
|
+
import type { BrandDocument } from "./recipe/schema";
|
|
5
6
|
/**
|
|
6
7
|
* Stage labels emitted via `onProgress`. Useful for callers that
|
|
7
8
|
* surface progress (CLI streaming output, MCP progress events).
|
|
@@ -30,12 +31,15 @@ export interface SeedBrandKitOptions {
|
|
|
30
31
|
/**
|
|
31
32
|
* Multiple source documents — uploaded before a single ingestion +
|
|
32
33
|
* enrichment pass. Takes precedence over `source` when non-empty.
|
|
34
|
+
* Accepts the recipe-shaped `BrandDocument` union (URL or
|
|
35
|
+
* registry-file). The seed runner rejects `registry-file` entries
|
|
36
|
+
* with `INPUT_INVALID` and a clear hint — see the
|
|
37
|
+
* `LOCAL_UPLOAD_UNSUPPORTED_MESSAGE` rationale in
|
|
38
|
+
* `documents/upload.ts` for why bytes uploads don't work end-to-end.
|
|
39
|
+
* Callers (orchestrator's `brandkit_deploy` handler) must translate
|
|
40
|
+
* registry-file paths to public URLs before invoking scai.
|
|
33
41
|
*/
|
|
34
|
-
documents?:
|
|
35
|
-
url: string;
|
|
36
|
-
title?: string;
|
|
37
|
-
summary?: string;
|
|
38
|
-
}[];
|
|
42
|
+
documents?: BrandDocument[];
|
|
39
43
|
/** Optional kit metadata. */
|
|
40
44
|
description?: string;
|
|
41
45
|
industry?: string;
|
package/dist/brand/seed.js
CHANGED
|
@@ -49,12 +49,37 @@ const seedBrandKit = async (options) => {
|
|
|
49
49
|
const kit = await (0, create_1.createBrandKit)(createInput);
|
|
50
50
|
emit({ stage: "createKit", message: `Created kit ${kit.id}` });
|
|
51
51
|
// 2. UPLOAD doc(s) — one or many, before a single ingestion pass.
|
|
52
|
+
//
|
|
53
|
+
// The `documents` list is the recipe-shaped discriminated union.
|
|
54
|
+
// `registry-file` entries arrive unresolved (path relative to the
|
|
55
|
+
// recipe file) and CANNOT be uploaded as bytes — the Sitecore
|
|
56
|
+
// Documents API has no working local-upload path (verified
|
|
57
|
+
// 2026-05-15; see documents/upload.ts). The caller (orchestrator's
|
|
58
|
+
// `brandkit_deploy` handler) must translate them to URL entries
|
|
59
|
+
// first. Failing fast here — with the path in the hint — beats
|
|
60
|
+
// a confusing 400 from `uploadDocument` minutes later.
|
|
61
|
+
if (options.documents) {
|
|
62
|
+
for (const doc of options.documents) {
|
|
63
|
+
if (doc.kind === "registry-file") {
|
|
64
|
+
throw (0, errors_1.createScaiError)(`Brand document "${doc.path}" is a registry-file path; scai cannot upload local bytes.`, "INPUT_INVALID", {
|
|
65
|
+
hint: 'Host the file at an HTTPS URL Sitecore\'s edge can reach (S3, GitHub raw, a CDN) and pass it as a `{ kind: "url", url }` document. The Sitecore Documents API rejects local-file uploads — translation has to happen upstream of scai.',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
52
70
|
const uploadList = options.documents && options.documents.length > 0
|
|
53
|
-
? options.documents.map((doc) =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
? options.documents.map((doc) => {
|
|
72
|
+
// After the registry-file guard above, every doc is a URL
|
|
73
|
+
// variant; the narrow keeps the TS surface clean.
|
|
74
|
+
if (doc.kind !== "url") {
|
|
75
|
+
throw (0, errors_1.createScaiError)("Unreachable: non-URL document survived the registry-file guard.", "INPUT_INVALID");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
source: { url: doc.url },
|
|
79
|
+
title: doc.title,
|
|
80
|
+
summary: doc.summary,
|
|
81
|
+
};
|
|
82
|
+
})
|
|
58
83
|
: options.source
|
|
59
84
|
? [
|
|
60
85
|
{
|
|
@@ -43,6 +43,14 @@ export type CreateBriefInput = {
|
|
|
43
43
|
fields?: Record<string, unknown>;
|
|
44
44
|
isTemplate?: boolean;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Validate an unknown value as a `CreateBriefInput`. Throws a typed
|
|
48
|
+
* `ScaiError` (`INPUT_INVALID`) on failure; returns the narrowed input
|
|
49
|
+
* on success. `createBrief` calls this itself, so direct SDK consumers
|
|
50
|
+
* are guarded without invoking it explicitly. Mirrors
|
|
51
|
+
* `assertCreateBriefTypeInput` in `./brief-types.ts`.
|
|
52
|
+
*/
|
|
53
|
+
export declare const assertCreateBriefInput: (value: unknown) => CreateBriefInput;
|
|
46
54
|
/** Create a brief. Returns the persisted record (201). */
|
|
47
55
|
export declare const createBrief: (options: BriefApiClientOptions, input: CreateBriefInput) => Promise<Brief>;
|
|
48
56
|
/**
|
package/dist/brief/api/briefs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.getBrief = exports.listBriefs = void 0;
|
|
3
|
+
exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.assertCreateBriefInput = exports.getBrief = exports.listBriefs = void 0;
|
|
4
|
+
const errors_1 = require("../../shared/errors");
|
|
4
5
|
const request_1 = require("./request");
|
|
5
6
|
/** List all briefs in the tenant (paged). */
|
|
6
7
|
const listBriefs = (options, query) => {
|
|
@@ -19,17 +20,54 @@ exports.listBriefs = listBriefs;
|
|
|
19
20
|
/** Read a single brief by id. 404 surfaces as `BRIEF_API_FAILED` with `"Brief not found"`. */
|
|
20
21
|
const getBrief = (options, briefId) => (0, request_1.briefRequest)(options, `/api/brief/v1/briefs/${encodeURIComponent(briefId)}`);
|
|
21
22
|
exports.getBrief = getBrief;
|
|
23
|
+
/** Required keys on a `CreateBriefInput` body. */
|
|
24
|
+
const BRIEF_REQUIRED_KEYS = ["name", "briefTypeId"];
|
|
25
|
+
/**
|
|
26
|
+
* Validate an unknown value as a `CreateBriefInput`. Throws a typed
|
|
27
|
+
* `ScaiError` (`INPUT_INVALID`) on failure; returns the narrowed input
|
|
28
|
+
* on success. `createBrief` calls this itself, so direct SDK consumers
|
|
29
|
+
* are guarded without invoking it explicitly. Mirrors
|
|
30
|
+
* `assertCreateBriefTypeInput` in `./brief-types.ts`.
|
|
31
|
+
*/
|
|
32
|
+
const assertCreateBriefInput = (value) => {
|
|
33
|
+
if (!value || typeof value !== "object") {
|
|
34
|
+
throw (0, errors_1.createScaiError)("Brief body must be a JSON object.", "INPUT_INVALID");
|
|
35
|
+
}
|
|
36
|
+
const obj = value;
|
|
37
|
+
const missing = BRIEF_REQUIRED_KEYS.filter((key) => obj[key] === undefined);
|
|
38
|
+
if (missing.length > 0) {
|
|
39
|
+
throw (0, errors_1.createScaiError)(`Brief body is missing required fields: ${missing.join(", ")}.`, "INPUT_INVALID", { hint: "Required: name (string), briefTypeId (UUID of the brief type)." });
|
|
40
|
+
}
|
|
41
|
+
if (typeof obj.name !== "string" || obj.name.length === 0) {
|
|
42
|
+
throw (0, errors_1.createScaiError)(`Invalid 'name': ${JSON.stringify(obj.name)}.`, "INPUT_INVALID", {
|
|
43
|
+
hint: "Brief name must be a non-empty string.",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (typeof obj.briefTypeId !== "string" || obj.briefTypeId.length === 0) {
|
|
47
|
+
throw (0, errors_1.createScaiError)(`Invalid 'briefTypeId': ${JSON.stringify(obj.briefTypeId)}.`, "INPUT_INVALID", {
|
|
48
|
+
hint: "Pass the brief type's UUID. List types with `scai ops brief types list`.",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (obj.fields !== undefined && (typeof obj.fields !== "object" || Array.isArray(obj.fields))) {
|
|
52
|
+
throw (0, errors_1.createScaiError)("'fields' must be an object keyed by field name.", "INPUT_INVALID");
|
|
53
|
+
}
|
|
54
|
+
return obj;
|
|
55
|
+
};
|
|
56
|
+
exports.assertCreateBriefInput = assertCreateBriefInput;
|
|
22
57
|
/** Create a brief. Returns the persisted record (201). */
|
|
23
|
-
const createBrief = (options, input) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
const createBrief = (options, input) => {
|
|
59
|
+
(0, exports.assertCreateBriefInput)(input);
|
|
60
|
+
return (0, request_1.briefRequest)(options, "/api/brief/v1/briefs", {
|
|
61
|
+
method: "POST",
|
|
62
|
+
body: {
|
|
63
|
+
name: input.name,
|
|
64
|
+
briefTypeId: input.briefTypeId,
|
|
65
|
+
locale: input.locale,
|
|
66
|
+
fields: input.fields,
|
|
67
|
+
isTemplate: input.isTemplate,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
};
|
|
33
71
|
exports.createBrief = createBrief;
|
|
34
72
|
/**
|
|
35
73
|
* Partial update of a brief (`PUT /api/brief/v1/briefs/{id}` — 204 No
|
package/dist/brief/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type { BriefApiClientOptions, BriefRequestInit, BriefQueryValue, BriefQue
|
|
|
23
23
|
export { BRIEF_API_HOST_TEMPLATE, DEFAULT_BRIEF_API_BASE } from "./api/types";
|
|
24
24
|
export { briefRequest } from "./api/request";
|
|
25
25
|
export type { PagedResult, LocalizedString, Link, ExternalLink, Reference, BriefField, BriefFieldBase, RichTextField, DateTimeField, TimelineField, BudgetField, BriefType, Brief, BriefStatus, BriefTask, BriefComment, ExternalMapping, } from "./api/schema";
|
|
26
|
-
export { listBriefs, getBrief, createBrief, updateBrief, setBriefStatus, deleteBrief, type CreateBriefInput, type ListBriefsQuery, } from "./api/briefs";
|
|
26
|
+
export { listBriefs, getBrief, createBrief, updateBrief, setBriefStatus, deleteBrief, assertCreateBriefInput, type CreateBriefInput, type ListBriefsQuery, } from "./api/briefs";
|
|
27
27
|
export { listBriefTypes, getBriefType, createBriefType, updateBriefType, deleteBriefType, type CreateBriefTypeInput, } from "./api/brief-types";
|
|
28
28
|
export { listBriefTasks, getBriefTask, type BriefTaskMetadata, type ListBriefTasksQuery, } from "./api/tasks";
|
|
29
29
|
export { listBriefComments, createBriefComment, type ListBriefCommentsQuery, type CreateBriefCommentInput, } from "./api/comments";
|
package/dist/brief/index.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* (read+write). The Agents env client carries both by default.
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.resolveBriefClient = exports.BRIEF_SCOPES_REQUESTED = exports.acquireBriefToken = exports.createBriefComment = exports.listBriefComments = exports.getBriefTask = exports.listBriefTasks = exports.deleteBriefType = exports.updateBriefType = exports.createBriefType = exports.getBriefType = exports.listBriefTypes = exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.getBrief = exports.listBriefs = exports.briefRequest = exports.DEFAULT_BRIEF_API_BASE = exports.BRIEF_API_HOST_TEMPLATE = void 0;
|
|
24
|
+
exports.resolveBriefClient = exports.BRIEF_SCOPES_REQUESTED = exports.acquireBriefToken = exports.createBriefComment = exports.listBriefComments = exports.getBriefTask = exports.listBriefTasks = exports.deleteBriefType = exports.updateBriefType = exports.createBriefType = exports.getBriefType = exports.listBriefTypes = exports.assertCreateBriefInput = exports.deleteBrief = exports.setBriefStatus = exports.updateBrief = exports.createBrief = exports.getBrief = exports.listBriefs = exports.briefRequest = exports.DEFAULT_BRIEF_API_BASE = exports.BRIEF_API_HOST_TEMPLATE = void 0;
|
|
25
25
|
var types_1 = require("./api/types");
|
|
26
26
|
Object.defineProperty(exports, "BRIEF_API_HOST_TEMPLATE", { enumerable: true, get: function () { return types_1.BRIEF_API_HOST_TEMPLATE; } });
|
|
27
27
|
Object.defineProperty(exports, "DEFAULT_BRIEF_API_BASE", { enumerable: true, get: function () { return types_1.DEFAULT_BRIEF_API_BASE; } });
|
|
@@ -34,6 +34,7 @@ Object.defineProperty(exports, "createBrief", { enumerable: true, get: function
|
|
|
34
34
|
Object.defineProperty(exports, "updateBrief", { enumerable: true, get: function () { return briefs_1.updateBrief; } });
|
|
35
35
|
Object.defineProperty(exports, "setBriefStatus", { enumerable: true, get: function () { return briefs_1.setBriefStatus; } });
|
|
36
36
|
Object.defineProperty(exports, "deleteBrief", { enumerable: true, get: function () { return briefs_1.deleteBrief; } });
|
|
37
|
+
Object.defineProperty(exports, "assertCreateBriefInput", { enumerable: true, get: function () { return briefs_1.assertCreateBriefInput; } });
|
|
37
38
|
var brief_types_1 = require("./api/brief-types");
|
|
38
39
|
Object.defineProperty(exports, "listBriefTypes", { enumerable: true, get: function () { return brief_types_1.listBriefTypes; } });
|
|
39
40
|
Object.defineProperty(exports, "getBriefType", { enumerable: true, get: function () { return brief_types_1.getBriefType; } });
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* for Sitecore Content Operations brief types.
|
|
2
|
+
* The brief recipe surface — declarative definitions + `sync` support
|
|
3
|
+
* for Sitecore Content Operations brief types AND brief instances.
|
|
4
|
+
*
|
|
5
|
+
* Two kinds:
|
|
6
|
+
* - `briefTypeKind` ("brief-type") — the schema template a brief is
|
|
7
|
+
* built against. Identified by stable codename.
|
|
8
|
+
* - `briefInstanceKind` ("brief") — a populated brief instance.
|
|
9
|
+
* Identified by display name; references its type by codename.
|
|
4
10
|
*
|
|
5
11
|
* See docs/recipe-sync-architecture.md.
|
|
6
12
|
*/
|
|
@@ -8,3 +14,6 @@ export { BriefTypeRecipeSchema, BriefFieldSchema, RichTextFieldSchema, DateTimeF
|
|
|
8
14
|
export { diffBriefType } from "./diff";
|
|
9
15
|
export { resolveBriefClient } from "./client";
|
|
10
16
|
export { briefTypeKind } from "./kind";
|
|
17
|
+
export { BriefInstanceRecipeSchema, BriefInstanceStatusSchema, BriefInstanceFieldsSchema, type BriefInstanceRecipe, } from "./instance-schema";
|
|
18
|
+
export { diffBriefInstance } from "./instance-diff";
|
|
19
|
+
export { briefInstanceKind } from "./instance-kind";
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.briefTypeKind = exports.resolveBriefClient = exports.diffBriefType = exports.LocalizedStringSchema = exports.BudgetFieldSchema = exports.TimelineFieldSchema = exports.DateTimeFieldSchema = exports.RichTextFieldSchema = exports.BriefFieldSchema = exports.BriefTypeRecipeSchema = void 0;
|
|
3
|
+
exports.briefInstanceKind = exports.diffBriefInstance = exports.BriefInstanceFieldsSchema = exports.BriefInstanceStatusSchema = exports.BriefInstanceRecipeSchema = exports.briefTypeKind = exports.resolveBriefClient = exports.diffBriefType = exports.LocalizedStringSchema = exports.BudgetFieldSchema = exports.TimelineFieldSchema = exports.DateTimeFieldSchema = exports.RichTextFieldSchema = exports.BriefFieldSchema = exports.BriefTypeRecipeSchema = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
6
|
-
* for Sitecore Content Operations brief types.
|
|
5
|
+
* The brief recipe surface — declarative definitions + `sync` support
|
|
6
|
+
* for Sitecore Content Operations brief types AND brief instances.
|
|
7
|
+
*
|
|
8
|
+
* Two kinds:
|
|
9
|
+
* - `briefTypeKind` ("brief-type") — the schema template a brief is
|
|
10
|
+
* built against. Identified by stable codename.
|
|
11
|
+
* - `briefInstanceKind` ("brief") — a populated brief instance.
|
|
12
|
+
* Identified by display name; references its type by codename.
|
|
7
13
|
*
|
|
8
14
|
* See docs/recipe-sync-architecture.md.
|
|
9
15
|
*/
|
|
@@ -21,3 +27,11 @@ var client_1 = require("./client");
|
|
|
21
27
|
Object.defineProperty(exports, "resolveBriefClient", { enumerable: true, get: function () { return client_1.resolveBriefClient; } });
|
|
22
28
|
var kind_1 = require("./kind");
|
|
23
29
|
Object.defineProperty(exports, "briefTypeKind", { enumerable: true, get: function () { return kind_1.briefTypeKind; } });
|
|
30
|
+
var instance_schema_1 = require("./instance-schema");
|
|
31
|
+
Object.defineProperty(exports, "BriefInstanceRecipeSchema", { enumerable: true, get: function () { return instance_schema_1.BriefInstanceRecipeSchema; } });
|
|
32
|
+
Object.defineProperty(exports, "BriefInstanceStatusSchema", { enumerable: true, get: function () { return instance_schema_1.BriefInstanceStatusSchema; } });
|
|
33
|
+
Object.defineProperty(exports, "BriefInstanceFieldsSchema", { enumerable: true, get: function () { return instance_schema_1.BriefInstanceFieldsSchema; } });
|
|
34
|
+
var instance_diff_1 = require("./instance-diff");
|
|
35
|
+
Object.defineProperty(exports, "diffBriefInstance", { enumerable: true, get: function () { return instance_diff_1.diffBriefInstance; } });
|
|
36
|
+
var instance_kind_1 = require("./instance-kind");
|
|
37
|
+
Object.defineProperty(exports, "briefInstanceKind", { enumerable: true, get: function () { return instance_kind_1.briefInstanceKind; } });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RecipePlan } from "../../sync";
|
|
2
|
+
import type { BriefInstanceRecipe } from "./instance-schema";
|
|
3
|
+
/** Diff a desired brief-instance recipe against captured current state. */
|
|
4
|
+
export declare const diffBriefInstance: (desired: BriefInstanceRecipe, current: BriefInstanceRecipe | null) => RecipePlan;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffBriefInstance = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The pure diff for the `brief` recipe kind — desired recipe vs.
|
|
6
|
+
* captured current state → a `RecipePlan`. No I/O, so it is cheap to
|
|
7
|
+
* unit-test and is the testable core of the kind.
|
|
8
|
+
*
|
|
9
|
+
* Unlike `brief-type` (which is a single full-replacement PUT), brief
|
|
10
|
+
* instances support a partial PUT: the diff therefore emits two stages
|
|
11
|
+
* of changes:
|
|
12
|
+
*
|
|
13
|
+
* - One `stage: "instance"` change carries the whole desired recipe.
|
|
14
|
+
* `apply` consumes it to drive `createBrief` or `updateBrief`.
|
|
15
|
+
* - Per-element `stage: "field"` changes describe the convergence
|
|
16
|
+
* element-by-element so the rendered plan reads cleanly. They are
|
|
17
|
+
* descriptive only — the single `stage: "instance"` change is what
|
|
18
|
+
* `apply` writes.
|
|
19
|
+
*
|
|
20
|
+
* Identification is by `name` (the brief's display name). Brief
|
|
21
|
+
* instances are not enforced unique by name on the server; the diff and
|
|
22
|
+
* apply both treat "first match wins", matching the campaign-instance
|
|
23
|
+
* precedent. See docs/recipe-sync-architecture.md.
|
|
24
|
+
*/
|
|
25
|
+
const node_util_1 = require("node:util");
|
|
26
|
+
/**
|
|
27
|
+
* The top-level recipe elements compared one-by-one when the brief
|
|
28
|
+
* exists. `briefTypeName` is included because the diff surfaces a
|
|
29
|
+
* type-change attempt — `apply` then refuses it (the Brief API has no
|
|
30
|
+
* verified path to repoint an existing brief at a different type).
|
|
31
|
+
*/
|
|
32
|
+
const COMPARED_ELEMENTS = ["briefTypeName", "locale", "status", "isTemplate", "fields"];
|
|
33
|
+
/** Diff a desired brief-instance recipe against captured current state. */
|
|
34
|
+
const diffBriefInstance = (desired, current) => {
|
|
35
|
+
const changes = [];
|
|
36
|
+
if (current === null) {
|
|
37
|
+
changes.push({
|
|
38
|
+
kind: "create",
|
|
39
|
+
path: "brief",
|
|
40
|
+
summary: `Create brief "${desired.name}"`,
|
|
41
|
+
after: desired.name,
|
|
42
|
+
meta: { stage: "instance", recipe: desired },
|
|
43
|
+
});
|
|
44
|
+
return { changes };
|
|
45
|
+
}
|
|
46
|
+
// The brief exists — compare each element so the plan reads
|
|
47
|
+
// element-by-element. `apply` acts on the lead `stage: "instance"`
|
|
48
|
+
// change, not on these per-element entries.
|
|
49
|
+
let anyElementChanged = false;
|
|
50
|
+
for (const element of COMPARED_ELEMENTS) {
|
|
51
|
+
const before = current[element];
|
|
52
|
+
const after = desired[element];
|
|
53
|
+
const path = `brief.${element}`;
|
|
54
|
+
const meta = { stage: "field", element };
|
|
55
|
+
if ((0, node_util_1.isDeepStrictEqual)(before, after)) {
|
|
56
|
+
changes.push({ kind: "noop", path, summary: `${element} unchanged`, meta });
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
anyElementChanged = true;
|
|
60
|
+
changes.push({ kind: "update", path, summary: `${element}`, before, after, meta });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// A single PUT converges every changed element at once. Emitted as
|
|
64
|
+
// the lead change so `apply` finds it without scanning.
|
|
65
|
+
if (anyElementChanged) {
|
|
66
|
+
changes.unshift({
|
|
67
|
+
kind: "update",
|
|
68
|
+
path: "brief",
|
|
69
|
+
summary: `Update brief "${desired.name}"`,
|
|
70
|
+
before: current.name,
|
|
71
|
+
after: desired.name,
|
|
72
|
+
meta: { stage: "instance", recipe: desired },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return { changes };
|
|
76
|
+
};
|
|
77
|
+
exports.diffBriefInstance = diffBriefInstance;
|