Setting the file. One moment. Execution Manifest · Replatform · wix/skills · Skills DocsBundled file Config Env
lib/execution-manifest.js
JavaScript·185 lines·7 KB
'skipped'
]);
7const CROSSWALK_AUTHORITY = new Set(['local']);
8const CMS_MIRROR_MODE = new Set(['none', 'download', 'upload', 'download-and-upload']);
9const URL_PRESERVATION_PATHS = [
10 'basePathsPath',
11 'ledgerPath',
12 'redirectsPath',
13 'unresolvedPath',
14];
15
16function createManifest({ projectId, planVersion, tasks = [], generatedArtifacts = [], llmHandoff = null, timestamp = new Date().toISOString() }) {
17 return {
18 schemaVersion: SCHEMA_VERSION,
19 projectId,
20 planVersion,
21 generatedAt: timestamp,
22 generatedArtifacts,
23 tasks,
24 llmHandoff,
25 };
26}
27
28function validateManifest(manifest) {
29 const errors = [];
30 if (!manifest || typeof manifest !== 'object') {
31 errors.push('manifest must be an object');
32 return { ok: false, errors };
33 }
34 if (manifest.schemaVersion !== SCHEMA_VERSION) {
35 errors.push(`schemaVersion must be ${SCHEMA_VERSION}`);
36 }
37 if (!manifest.projectId || typeof manifest.projectId !== 'string') {
38 errors.push('projectId must be a non-empty string');
39 }
40 if (!manifest.planVersion || typeof manifest.planVersion !== 'string') {
41 errors.push('planVersion must be a non-empty string');
42 }
43 if (!Array.isArray(manifest.generatedArtifacts)) {
44 errors.push('generatedArtifacts must be an array');
45 }
46 if (!Array.isArray(manifest.tasks) || manifest.tasks.length === 0) {
47 errors.push('tasks must be a non-empty array');
48 } else {
49 const ids = new Set();
50 for (const task of manifest.tasks) {
51 if (!task || typeof task !== 'object') {
52 errors.push('task must be an object');
53 continue;
54 }
55 if (!task.id || typeof task.id !== 'string') {
56 errors.push('task.id must be a non-empty string');
57 } else if (ids.has(task.id)) {
58 errors.push(`duplicate task id: ${task.id}`);
59 } else {
60 ids.add(task.id);
61 }
62 if (!TASK_KIND.has(task.kind)) {
63 errors.push(`task.kind invalid for ${task.id || '<unknown>'}`);
64 }
65 if (task.status && !TASK_STATUS.has(task.status)) {
66 errors.push(`task.status invalid for ${task.id || '<unknown>'}`);
67 }
68 if (!Array.isArray(task.dependsOn)) {
69 errors.push(`task.dependsOn must be an array for ${task.id || '<unknown>'}`);
70 }
71 if (!Array.isArray(task.artifactRefs)) {
72 errors.push(`task.artifactRefs must be an array for ${task.id || '<unknown>'}`);
73 }
74 validateUrlPreservation(task, errors);
75 if (task.kind === 'import') {
76 validateImportTaskState(task, errors);
77 }
78 }
79 }
80 return { ok: errors.length === 0, errors };
81}
82
83function validateUrlPreservation(task, errors) {
84 if (task.urlPreservation === undefined) {
85 return;
86 }
87 const label = task.id || '<unknown>';
88 if (task.kind !== 'import') {
89 errors.push(`task.urlPreservation is only allowed for import tasks in ${label}`);
90 }
91 const policy = task.urlPreservation;
92 if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
93 errors.push(`task.urlPreservation must be an object for ${label}`);
94 return;
95 }
96 if (policy.enabled !== undefined && typeof policy.enabled !== 'boolean') {
97 errors.push(`task.urlPreservation.enabled must be a boolean for ${label}`);
98 }
99 if (policy.applyRedirects === true) {
100 errors.push(`task.urlPreservation.applyRedirects must be false until website-builder redirect application is supported for ${label}`);
101 }
102 if (policy.applyRedirects !== undefined && typeof policy.applyRedirects !== 'boolean') {
103 errors.push(`task.urlPreservation.applyRedirects must be a boolean for ${label}`);
104 }
105 if (policy.enabled === true) {
106 for (const field of URL_PRESERVATION_PATHS) {
107 if (!policy[field] || typeof policy[field] !== 'string') {
108 errors.push(`task.urlPreservation.${field} required when enabled for ${label}`);
109 }
110 }
111 if (policy.applyRedirects !== false) {
112 errors.push(`task.urlPreservation.applyRedirects must be false when enabled for ${label}`);
113 }
114 }
115}
116
117function hasServerAssignedNativeTargetIds(task) {
118 return task.targetIdAssignment === 'server' ||
119 task.targetIdAssignment === 'server-assigned' ||
120 task.nativeTargetIds === 'server-assigned' ||
121 task.targetIdsServerAssigned === true;
122}
123
124function validateCmsMirror(task, state, errors) {
125 if (!state.cmsMirror || typeof state.cmsMirror !== 'object') {
126 return;
127 }
128 const mode = state.cmsMirror.mode;
129 if (!CMS_MIRROR_MODE.has(mode)) {
130 errors.push(`task.state.cmsMirror.mode invalid for ${task.id || '<unknown>'}`);
131 return;
132 }
133 if (mode !== 'none') {
134 if (!state.cmsMirror.collectionId || typeof state.cmsMirror.collectionId !== 'string') {
135 errors.push(`task.state.cmsMirror.collectionId required when CMS mirror is enabled for ${task.id || '<unknown>'}`);
136 }
137 if (!state.cmsMirror.setupRequirementId || typeof state.cmsMirror.setupRequirementId !== 'string') {
138 errors.push(`task.state.cmsMirror.setupRequirementId required when CMS mirror is enabled for ${task.id || '<unknown>'}`);
139 }
140 }
141}
142
143function validateImportTaskState(task, errors) {
144 const state = task.state;
145 if (!state || typeof state !== 'object') {
146 if (hasServerAssignedNativeTargetIds(task)) {
147 errors.push(`task.state required for server-assigned native target IDs in ${task.id || '<unknown>'}`);
148 }
149 return;
150 }
151 if (state.crosswalkAuthority === 'cms') {
152 errors.push(`task.state.crosswalkAuthority must not be cms for ${task.id || '<unknown>'}`);
153 }
154 if (state.crosswalkAuthority !== undefined && !CROSSWALK_AUTHORITY.has(state.crosswalkAuthority)) {
155 errors.push(`task.state.crosswalkAuthority invalid for ${task.id || '<unknown>'}`);
156 }
157 if (hasServerAssignedNativeTargetIds(task) && (!state.crosswalkPath || typeof state.crosswalkPath !== 'string')) {
158 errors.push(`task.state.crosswalkPath required for server-assigned native target IDs in ${task.id || '<unknown>'}`);
159 }
160 if (state.attemptJournalPath !== undefined && typeof state.attemptJournalPath !== 'string') {
161 errors.push(`task.state.attemptJournalPath must be a string for ${task.id || '<unknown>'}`);
162 }
163 validateCmsMirror(task, state, errors);
164}
165
166function nextPendingTask(manifest, completedIds = new Set()) {
167 const done = completedIds instanceof Set ? completedIds : new Set(completedIds || []);
168 for (const task of manifest.tasks || []) {
169 if (task.status === 'completed' || done.has(task.id)) {
170 continue;
171 }
172 const deps = Array.isArray(task.dependsOn) ? task.dependsOn : [];
173 if (deps.every((dep) => done.has(dep))) {
174 return task;
175 }
176 }
177 return null;
178}
179
180module.exports = {
181 SCHEMA_VERSION,
182 createManifest,
183 validateManifest,
184 nextPendingTask,
185};