@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
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
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 {
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|