@sitecoreai-labs/sitecoreai-cli 0.1.0 → 0.1.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.
@@ -14,10 +14,11 @@ const addRequiredInputOption = (command, label) => command.addOption(new command
14
14
  const addOutputOption = (command) => command.addOption(new commander_1.Option("-o, --output <path>", "Path to write the output file"));
15
15
  // All flags are optional. Each falls back to the matching field on
16
16
  // envProfiles[<name>] in sitecoreai.cli.json. resolveRecipeRoots() throws
17
- // `INPUT_INVALID` for the two required ones (templatesRoot, renderingsRoot)
18
- // when neither source is set; the Phase 4 composition roots are optional
19
- // and only surface errors when their corresponding recipe kinds are
20
- // being compiled.
17
+ // `INPUT_INVALID` for templatesRoot / renderingsRoot when neither source
18
+ // is set AND the recipe set contains a kind that creates template or
19
+ // rendering items; a workflow- / webhook-authorization-only set needs
20
+ // neither. The Phase 4 composition roots are optional and only surface
21
+ // errors when their corresponding recipe kinds are being compiled.
21
22
  const addRecipeRootOptions = (command) => command
22
23
  .addOption(new commander_1.Option("--templates-root <path>", "Sitecore parent path for template items. Falls back to envProfiles[<name>].templatesRoot."))
23
24
  .addOption(new commander_1.Option("--renderings-root <path>", "Sitecore parent path for rendering items. Falls back to envProfiles[<name>].renderingsRoot."))
@@ -24,10 +24,21 @@ const shared_1 = require("./shared");
24
24
  const runRecipeCompile = async (options) => {
25
25
  const logger = (0, shared_1.toLogger)(options);
26
26
  const root = (0, root_config_1.readRootConfiguration)(options.config ?? process.cwd(), options.environmentName);
27
- // Resolve parent paths from CLI flags or active env profile (when given).
28
27
  const envName = options.environmentName ?? root.defaultEnvironment;
29
28
  const environment = envName ? root.environments[envName] : undefined;
30
- const { templatesRoot, renderingsRoot } = (0, shared_1.resolveRecipeRoots)(options, environment, envName ?? "(no environment)");
29
+ const { files, source } = await (0, shared_1.resolveRecipeInputs)(options, root);
30
+ if (options.output && files.length > 1) {
31
+ throw (0, errors_1.createScaiError)("--output cannot be combined with multi-file compile.", "INPUT_INVALID", {
32
+ hint: "Compile a single recipe with --input <file> --output <ir>, or omit --output to write per-recipe IRs to <dir>/<handle>.ir.json.",
33
+ });
34
+ }
35
+ // Load every recipe up front so the templatesRoot / renderingsRoot
36
+ // requirement can be scoped to what the set actually compiles — a
37
+ // workflow- / webhook-authorization-only set creates its items under
38
+ // hardcoded /sitecore/system roots and needs neither.
39
+ const loaded = await Promise.all(files.map(async (file) => ({ file, recipe: await (0, io_1.loadRecipe)(file) })));
40
+ // Resolve parent paths from CLI flags or active env profile (when given).
41
+ const { templatesRoot, renderingsRoot } = (0, shared_1.resolveRecipeRoots)(options, environment, envName ?? "(no environment)", (0, shared_1.recipeSetNeedsRoots)(loaded.map((entry) => entry.recipe)));
31
42
  // Phase 2 per-site folder layout roots — optional. When unset the
32
43
  // compiler falls back to `templatesRoot` for both, which means
33
44
  // section-aware components nest under templatesRoot (mid-migration
@@ -51,15 +62,8 @@ const runRecipeCompile = async (options) => {
51
62
  const pageTemplatesRoot = environment?.pageTemplatesRoot;
52
63
  const placeholderSettingsRoot = environment?.placeholderSettingsRoot;
53
64
  const pagesRoot = environment?.pagesRoot;
54
- const { files, source } = await (0, shared_1.resolveRecipeInputs)(options, root);
55
- if (options.output && files.length > 1) {
56
- throw (0, errors_1.createScaiError)("--output cannot be combined with multi-file compile.", "INPUT_INVALID", {
57
- hint: "Compile a single recipe with --input <file> --output <ir>, or omit --output to write per-recipe IRs to <dir>/<handle>.ir.json.",
58
- });
59
- }
60
65
  const results = [];
61
- for (const file of files) {
62
- const recipe = await (0, io_1.loadRecipe)(file);
66
+ for (const { file, recipe } of loaded) {
63
67
  const ir = (0, compile_1.compileRecipe)(recipe, {
64
68
  templatesRoot,
65
69
  renderingsRoot,
@@ -97,7 +97,6 @@ const runRecipePush = async (options) => {
97
97
  // logger on first write — successful pushes leave no file behind.
98
98
  const rollbackRunId = (0, node_crypto_1.randomUUID)();
99
99
  const rollbackLog = (0, rollback_log_1.createRollbackLogger)(rollbackRunId);
100
- const { templatesRoot, renderingsRoot } = (0, shared_1.resolveRecipeRoots)(options, tenant.environment, tenant.envName);
101
100
  // Phase 2 per-site folder layout roots — optional at the envProfile
102
101
  // level. When unset the compiler falls back to `templatesRoot` for
103
102
  // both, which means section-aware components nest under templatesRoot
@@ -160,6 +159,12 @@ const runRecipePush = async (options) => {
160
159
  }
161
160
  }
162
161
  const recipes = await (0, cli_tasks_1.mapWithConcurrency)(recipeFiles, (f) => (0, io_1.loadRecipe)(f));
162
+ // Resolve templatesRoot / renderingsRoot now that the recipe kinds are
163
+ // known: a set built only from workflow / webhook-authorization recipes
164
+ // — which create items under hardcoded /sitecore/system roots — needs
165
+ // neither. An IR-only push has an empty `recipes` and skips the
166
+ // requirement too; pre-compiled IRs carry their roots baked in.
167
+ const { templatesRoot, renderingsRoot } = (0, shared_1.resolveRecipeRoots)(options, tenant.environment, tenant.envName, (0, shared_1.recipeSetNeedsRoots)(recipes));
163
168
  const compiled = (0, compile_1.compileRecipeSet)(recipes, {
164
169
  templatesRoot,
165
170
  renderingsRoot,
@@ -126,6 +126,19 @@ export interface ResolvedTenant {
126
126
  export declare const resolveTenant: (options: RecipeTenantOptions, clientOptions?: {
127
127
  pathItemIdCache?: Map<string, string>;
128
128
  }) => ResolvedTenant;
129
+ /**
130
+ * Whether a recipe set needs `templatesRoot` / `renderingsRoot` resolved.
131
+ *
132
+ * True when at least one recipe creates template / rendering items —
133
+ * every kind except `ROOTLESS_RECIPE_KINDS`. An empty set (e.g. a push
134
+ * fed only pre-compiled `.ir.json` inputs, which carry their roots baked
135
+ * in) needs neither. New recipe kinds default to *needing* roots; add a
136
+ * kind to `ROOTLESS_RECIPE_KINDS` only once its compiler is confirmed to
137
+ * ignore both roots.
138
+ */
139
+ export declare const recipeSetNeedsRoots: (recipes: readonly {
140
+ kind: string;
141
+ }[]) => boolean;
129
142
  /**
130
143
  * Resolve the recipe parent paths that the compiler will use for top-level
131
144
  * template + rendering items.
@@ -136,7 +149,13 @@ export declare const resolveTenant: (options: RecipeTenantOptions, clientOptions
136
149
  * sitecoreai.cli.json (env-overrides via
137
150
  * `SITECOREAI_ENV_<NAME>_TEMPLATES_ROOT` / `_RENDERINGS_ROOT` apply
138
151
  * at config-load time before this helper runs)
139
- * 3. Throws `INPUT_INVALID` with a hint pointing at the envProfile shape
152
+ * 3. When `required`, throws `INPUT_INVALID` with a hint pointing at
153
+ * the envProfile shape. When not required (a workflow- /
154
+ * webhook-authorization-only set, or an IR-only push), missing
155
+ * roots resolve to `""` — the compilers in play never read them.
156
+ *
157
+ * Pass `required` from `recipeSetNeedsRoots(recipes)` once the set's
158
+ * recipe kinds are known.
140
159
  *
141
160
  * Tenant-specific because each site has its own
142
161
  * `/sitecore/templates/Project/<site>/Components` location. Putting roots
@@ -146,7 +165,7 @@ export declare const resolveTenant: (options: RecipeTenantOptions, clientOptions
146
165
  export declare const resolveRecipeRoots: (options: {
147
166
  templatesRoot?: string;
148
167
  renderingsRoot?: string;
149
- }, environment: EnvironmentConfiguration | undefined, envName: string) => {
168
+ }, environment: EnvironmentConfiguration | undefined, envName: string, required?: boolean) => {
150
169
  templatesRoot: string;
151
170
  renderingsRoot: string;
152
171
  };
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ensureAllowWrite = exports.resolveRecipeInputs = exports.resolveRecipeRoots = exports.resolveTenant = exports.toLogger = void 0;
6
+ exports.ensureAllowWrite = exports.resolveRecipeInputs = exports.resolveRecipeRoots = exports.recipeSetNeedsRoots = exports.resolveTenant = exports.toLogger = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  const fast_glob_1 = __importDefault(require("fast-glob"));
9
9
  const logger_1 = require("../../shared/logger");
@@ -28,6 +28,29 @@ const resolveTenant = (options, clientOptions) => {
28
28
  return { envName, environment, root, client };
29
29
  };
30
30
  exports.resolveTenant = resolveTenant;
31
+ /**
32
+ * Recipe kinds whose compilers create items under hardcoded
33
+ * `/sitecore/system/*` roots and never read `templatesRoot` /
34
+ * `renderingsRoot`:
35
+ * - `workflow` → `/sitecore/system/Workflows`
36
+ * - `webhook-authorization` → `/sitecore/system/Settings/Webhooks/Authorizations`
37
+ *
38
+ * A recipe set built only from these kinds can compile, plan, and push
39
+ * with neither root configured — see `recipeSetNeedsRoots`.
40
+ */
41
+ const ROOTLESS_RECIPE_KINDS = new Set(["workflow", "webhook-authorization"]);
42
+ /**
43
+ * Whether a recipe set needs `templatesRoot` / `renderingsRoot` resolved.
44
+ *
45
+ * True when at least one recipe creates template / rendering items —
46
+ * every kind except `ROOTLESS_RECIPE_KINDS`. An empty set (e.g. a push
47
+ * fed only pre-compiled `.ir.json` inputs, which carry their roots baked
48
+ * in) needs neither. New recipe kinds default to *needing* roots; add a
49
+ * kind to `ROOTLESS_RECIPE_KINDS` only once its compiler is confirmed to
50
+ * ignore both roots.
51
+ */
52
+ const recipeSetNeedsRoots = (recipes) => recipes.some((recipe) => !ROOTLESS_RECIPE_KINDS.has(recipe.kind));
53
+ exports.recipeSetNeedsRoots = recipeSetNeedsRoots;
31
54
  /**
32
55
  * Resolve the recipe parent paths that the compiler will use for top-level
33
56
  * template + rendering items.
@@ -38,16 +61,28 @@ exports.resolveTenant = resolveTenant;
38
61
  * sitecoreai.cli.json (env-overrides via
39
62
  * `SITECOREAI_ENV_<NAME>_TEMPLATES_ROOT` / `_RENDERINGS_ROOT` apply
40
63
  * at config-load time before this helper runs)
41
- * 3. Throws `INPUT_INVALID` with a hint pointing at the envProfile shape
64
+ * 3. When `required`, throws `INPUT_INVALID` with a hint pointing at
65
+ * the envProfile shape. When not required (a workflow- /
66
+ * webhook-authorization-only set, or an IR-only push), missing
67
+ * roots resolve to `""` — the compilers in play never read them.
68
+ *
69
+ * Pass `required` from `recipeSetNeedsRoots(recipes)` once the set's
70
+ * recipe kinds are known.
42
71
  *
43
72
  * Tenant-specific because each site has its own
44
73
  * `/sitecore/templates/Project/<site>/Components` location. Putting roots
45
74
  * in config keeps the orchestrator's `recipe push` invocation
46
75
  * config-driven (no plan-schema fields, no extra arg plumbing).
47
76
  */
48
- const resolveRecipeRoots = (options, environment, envName) => {
77
+ const resolveRecipeRoots = (options, environment, envName, required = true) => {
49
78
  const templatesRoot = options.templatesRoot ?? environment?.templatesRoot;
50
79
  const renderingsRoot = options.renderingsRoot ?? environment?.renderingsRoot;
80
+ if (!required) {
81
+ // The recipe set in play never reads these roots — pass through
82
+ // whatever's configured, or "" so the requirement doesn't block a
83
+ // set that doesn't need it.
84
+ return { templatesRoot: templatesRoot ?? "", renderingsRoot: renderingsRoot ?? "" };
85
+ }
51
86
  if (!templatesRoot || !renderingsRoot) {
52
87
  const missing = !templatesRoot && !renderingsRoot
53
88
  ? "both roots"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecoreai-labs/sitecoreai-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "SitecoreAI developer toolkit — a native TypeScript CLI, SDK, and MCP server for deploy, serialization, recipes, publishing, and content operations.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",