@adobe/spacecat-shared-utils 1.91.0 → 1.92.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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/llmo-strategy.js +8 -4
- package/src/strategy-schema.js +129 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-utils-v1.92.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.91.0...@adobe/spacecat-shared-utils-v1.92.0) (2026-02-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* schema for oppty workspace data ([#1307](https://github.com/adobe/spacecat-shared/issues/1307)) ([49decad](https://github.com/adobe/spacecat-shared/commit/49decada5a0e598628563c8856b77715de549fce))
|
|
7
|
+
|
|
1
8
|
# [@adobe/spacecat-shared-utils-v1.91.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.90.3...@adobe/spacecat-shared-utils-v1.91.0) (2026-02-08)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
package/src/llmo-strategy.js
CHANGED
|
@@ -11,9 +11,13 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
14
|
+
import { strategyWorkspaceData } from './strategy-schema.js';
|
|
15
|
+
|
|
16
|
+
export { strategyWorkspaceData };
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* @import { S3Client } from "@aws-sdk/client-s3"
|
|
20
|
+
* @import { StrategyWorkspaceData } from "./strategy-schema.js"
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
/**
|
|
@@ -34,8 +38,8 @@ export function strategyPath(siteId) {
|
|
|
34
38
|
* @param {string} [options.version] Optional version ID of the strategy to read.
|
|
35
39
|
* Defaults to the latest version.
|
|
36
40
|
* @param {string} [options.s3Bucket] Optional S3 bucket name.
|
|
37
|
-
* @returns {Promise<{data:
|
|
38
|
-
* a flag indicating if it existed, and the version ID if it exists.
|
|
41
|
+
* @returns {Promise<{data: StrategyWorkspaceData | null, exists: boolean, version?: string}>}
|
|
42
|
+
* The strategy data, a flag indicating if it existed, and the version ID if it exists.
|
|
39
43
|
* @throws {Error} If reading the strategy fails for reasons other than it not existing.
|
|
40
44
|
*/
|
|
41
45
|
export async function readStrategy(siteId, s3Client, options) {
|
|
@@ -64,14 +68,14 @@ export async function readStrategy(siteId, s3Client, options) {
|
|
|
64
68
|
throw new Error('Strategy body is empty');
|
|
65
69
|
}
|
|
66
70
|
const text = await body.transformToString();
|
|
67
|
-
const data = JSON.parse(text);
|
|
71
|
+
const data = strategyWorkspaceData.parse(JSON.parse(text));
|
|
68
72
|
return { data, exists: true, version: res.VersionId || undefined };
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/**
|
|
72
76
|
* Writes the strategy JSON for a given site.
|
|
73
77
|
* @param {string} siteId The ID of the site.
|
|
74
|
-
* @param {
|
|
78
|
+
* @param {StrategyWorkspaceData} data The strategy data object to write.
|
|
75
79
|
* @param {S3Client} s3Client The S3 client to use for writing the strategy.
|
|
76
80
|
* @param {object} [options]
|
|
77
81
|
* @param {string} [options.s3Bucket] Optional S3 bucket name.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as z from 'zod';
|
|
14
|
+
|
|
15
|
+
// ===== SCHEMA DEFINITIONS ====================================================
|
|
16
|
+
// Schemas defined here must be forward- and backward-compatible when making changes.
|
|
17
|
+
// See llmoConfig schema for detailed guidelines.
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {z.output<typeof strategyWorkspaceData>} StrategyWorkspaceData
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const nonEmptyString = z.string().min(1);
|
|
25
|
+
|
|
26
|
+
// Shared status union for forward compatibility
|
|
27
|
+
const workflowStatus = z.union([
|
|
28
|
+
z.literal('new'),
|
|
29
|
+
z.literal('planning'),
|
|
30
|
+
z.literal('in_progress'),
|
|
31
|
+
z.literal('completed'),
|
|
32
|
+
z.literal('on_hold'),
|
|
33
|
+
z.string(), // Catchall for future statuses
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const strategyGoalType = z.union([
|
|
37
|
+
z.literal('visibility-score'),
|
|
38
|
+
z.string(), // Catchall for future goal types
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Library opportunity - user-created reusable opportunity template
|
|
43
|
+
*/
|
|
44
|
+
const opportunity = z.object({
|
|
45
|
+
id: nonEmptyString,
|
|
46
|
+
name: nonEmptyString,
|
|
47
|
+
description: z.string(),
|
|
48
|
+
category: nonEmptyString,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reference to an opportunity within a strategy
|
|
53
|
+
* Two types:
|
|
54
|
+
* 1. Library opportunity: opportunityId references Opportunity.id, no link field
|
|
55
|
+
* 2. System opportunity: has link field, opportunityId is system ID
|
|
56
|
+
*/
|
|
57
|
+
const strategyOpportunity = z.object({
|
|
58
|
+
opportunityId: nonEmptyString,
|
|
59
|
+
name: z.string().optional(), // For system opportunities (name stored directly)
|
|
60
|
+
link: z.string().optional(), // URL path to opportunity page (system-generated)
|
|
61
|
+
status: workflowStatus,
|
|
62
|
+
assignee: z.string(),
|
|
63
|
+
completedAt: z.string().optional(), // ISO 8601 date string
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Strategy containing references to opportunities
|
|
68
|
+
*/
|
|
69
|
+
const strategy = z.object({
|
|
70
|
+
id: nonEmptyString,
|
|
71
|
+
name: nonEmptyString,
|
|
72
|
+
status: workflowStatus,
|
|
73
|
+
url: z.string(),
|
|
74
|
+
description: z.string(),
|
|
75
|
+
topic: z.string(),
|
|
76
|
+
platform: nonEmptyString,
|
|
77
|
+
opportunities: z.array(strategyOpportunity),
|
|
78
|
+
createdAt: z.string(), // ISO 8601 date string
|
|
79
|
+
completedAt: z.string().optional(), // ISO 8601 date string
|
|
80
|
+
goalType: strategyGoalType.optional(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Root schema for strategy workspace data
|
|
85
|
+
*/
|
|
86
|
+
export const strategyWorkspaceData = z.object({
|
|
87
|
+
opportunities: z.array(opportunity),
|
|
88
|
+
strategies: z.array(strategy),
|
|
89
|
+
}).superRefine((value, ctx) => {
|
|
90
|
+
const { opportunities, strategies } = value;
|
|
91
|
+
|
|
92
|
+
// Build a set of library opportunity IDs for reference validation
|
|
93
|
+
const libraryOpportunityIds = new Set(opportunities.map((opp) => opp.id));
|
|
94
|
+
|
|
95
|
+
// Validate that library opportunity references exist
|
|
96
|
+
strategies.forEach((strat, strategyIndex) => {
|
|
97
|
+
strat.opportunities.forEach((stratOpp, oppIndex) => {
|
|
98
|
+
// Only validate library opportunities (those without a link)
|
|
99
|
+
if (!stratOpp.link && !libraryOpportunityIds.has(stratOpp.opportunityId)) {
|
|
100
|
+
ctx.addIssue({
|
|
101
|
+
code: 'custom',
|
|
102
|
+
path: ['strategies', strategyIndex, 'opportunities', oppIndex, 'opportunityId'],
|
|
103
|
+
message: `Library opportunity ${stratOpp.opportunityId} does not exist`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Validate completedAt consistency with status
|
|
110
|
+
strategies.forEach((strat, strategyIndex) => {
|
|
111
|
+
if (strat.status === 'completed' && !strat.completedAt) {
|
|
112
|
+
ctx.addIssue({
|
|
113
|
+
code: 'custom',
|
|
114
|
+
path: ['strategies', strategyIndex, 'completedAt'],
|
|
115
|
+
message: 'completedAt is required when status is completed',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
strat.opportunities.forEach((stratOpp, oppIndex) => {
|
|
120
|
+
if (stratOpp.status === 'completed' && !stratOpp.completedAt) {
|
|
121
|
+
ctx.addIssue({
|
|
122
|
+
code: 'custom',
|
|
123
|
+
path: ['strategies', strategyIndex, 'opportunities', oppIndex, 'completedAt'],
|
|
124
|
+
message: 'completedAt is required when status is completed',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|