Setting the file. One moment. Stores Verification · Rp Target Wix · wix/skills · Skills DocsPost
This file
- Number
- 44.110
- Position
- 110 of 115
- Type
- JavaScript
- Size
- 8 KB
- Lines
- 238
lib/stores-verification.js
JavaScript·238 lines·8 KB
,
10 'product.subscriptionDetails.subscriptions[]',
11 'product.subscriptionDetails.subscriptions[].id',
12 'product.subscriptionDetails.subscriptions[].title',
13 'product.subscriptionDetails.subscriptions[].description',
14 'product.subscriptionDetails.subscriptions[].frequency',
15 'product.subscriptionDetails.subscriptions[].interval',
16 'product.subscriptionDetails.subscriptions[].autoRenewal',
17];
18
19function timestamp() {
20 return new Date().toISOString();
21}
22
23function defaultProbeProduct({ marker, name } = {}) {
24 const suffix = marker || `rp-probe-${Date.now()}`;
25 return {
26 name: name || `RePlatform subscription probe ${suffix}`,
27 productType: 'PHYSICAL',
28 visible: false,
29 sku: suffix,
30 price: { actualPrice: { amount: '1.00' } },
31 subscriptionDetails: {
32 allowOneTimePurchases: true,
33 subscriptions: [{
34 title: 'Monthly delivery',
35 description: 'Ships every month',
36 frequency: 'MONTH',
37 interval: 1,
38 autoRenewal: true,
39 }],
40 },
41 };
42}
43
44function artifactBase({ command, siteId, endpoint, method }) {
45 return {
46 schemaVersion: 1,
47 command,
48 targetSiteIdentifier: siteId || null,
49 endpoint,
50 method,
51 status: 'unknown',
52 verifiedPaths: [],
53 constraintsDiscovered: [],
54 probeRecordId: null,
55 cleanup: { attempted: false, status: 'not_applicable' },
56 warnings: [],
57 recoveryInstructions: [],
58 timestamp: timestamp(),
59 };
60}
61
62function valueAtPath(root, pathExpr) {
63 const parts = String(pathExpr).split('.');
64 let values = [root];
65 for (const part of parts) {
66 const arrayPart = part.endsWith('[]') ? part.slice(0, -2) : null;
67 const key = arrayPart || part;
68 const next = [];
69 for (const value of values) {
70 if (!value || typeof value !== 'object') continue;
71 const child = value[key];
72 if (arrayPart) {
73 if (Array.isArray(child)) next.push(...child);
74 } else {
75 next.push(child);
76 }
77 }
78 values = next;
79 }
80 return values.filter((value) => value !== undefined && value !== null);
81}
82
83function verifyPaths(root, paths) {
84 return paths.map((pathExpr) => ({
85 path: pathExpr,
86 present: valueAtPath(root, pathExpr).length > 0,
87 }));
88}
89
90function queryFilterByMarker({ markerPath, markerValue }) {
91 if (!markerPath || markerValue == null) {
92 throw new Error('product-by-source-marker requires --marker-path and --marker-value');
93 }
94 return {
95 filter: { [markerPath]: { $eq: markerValue } },
96 paging: { limit: 100, offset: 0 },
97 };
98}
99
100async function writeArtifact(file, result) {
101 if (!file) return result;
102 fs.mkdirSync(path.dirname(file), { recursive: true });
103 fs.writeFileSync(file, `${JSON.stringify(result, null, 2)}\n`);
104 return result;
105}
106
107async function verifyStoresSubscriptionCreate({ wix, siteId, artifactPath, probeProduct, marker, cleanup = true } = {}) {
108 const createRequest = w.buildCreateStoresProductRequest(probeProduct || defaultProbeProduct({ marker }));
109 const artifact = artifactBase({
110 command: 'stores subscription-create',
111 siteId,
112 endpoint: createRequest.url,
113 method: createRequest.method,
114 });
115
116 let createdProduct;
117 try {
118 createdProduct = (await wix.send(createRequest)).product;
119 artifact.probeRecordId = createdProduct && createdProduct.id;
120 if (!artifact.probeRecordId) throw new Error('create response did not include product.id');
121
122 const getRequest = w.buildGetStoresProductRequest(artifact.probeRecordId);
123 artifact.readback = { endpoint: getRequest.url, method: getRequest.method };
124 const readProduct = (await wix.send(getRequest)).product;
125 artifact.verifiedPaths = verifyPaths({ product: readProduct }, DEFAULT_VERIFIED_SUBSCRIPTION_PATHS);
126 artifact.constraintsDiscovered = w.STORES_SUBSCRIPTION_CONTRACT.constraints.map((constraint) => ({ ...constraint }));
127 artifact.status = artifact.verifiedPaths.every((entry) => entry.present) ? 'passed' : 'failed';
128 if (artifact.status === 'failed') {
129 artifact.warnings.push('Subscription readback did not include every expected nested path.');
130 }
131 } catch (error) {
132 artifact.status = 'failed';
133 artifact.error = error && error.message ? error.message : String(error);
134 } finally {
135 if (cleanup && artifact.probeRecordId) {
136 artifact.cleanup.attempted = true;
137 artifact.cleanup.endpoint = w.buildDeleteStoresProductRequest(artifact.probeRecordId).url;
138 artifact.cleanup.method = 'DELETE';
139 try {
140 await wix.send(w.buildDeleteStoresProductRequest(artifact.probeRecordId));
141 artifact.cleanup.status = 'deleted';
142 } catch (error) {
143 artifact.cleanup.status = 'failed';
144 artifact.cleanup.error = error && error.message ? error.message : String(error);
145 artifact.warnings.push(`Probe product cleanup failed for ${artifact.probeRecordId}.`);
146 artifact.recoveryInstructions.push(
147 `Delete probe product ${artifact.probeRecordId} from Wix Stores or run: verify stores delete-probe --product-id ${artifact.probeRecordId}`,
148 );
149 }
150 }
151 }
152
153 return writeArtifact(artifactPath, artifact);
154}
155
156async function verifyStoresProductCount({ wix, siteId, artifactPath, query = { paging: { limit: 1, offset: 0 } } } = {}) {
157 const request = w.buildQueryStoresProductsRequest(query);
158 const artifact = artifactBase({
159 command: 'stores product-count',
160 siteId,
161 endpoint: request.url,
162 method: request.method,
163 });
164 try {
165 const response = await wix.send(request);
166 const products = response.products || [];
167 artifact.count = Number.isInteger(response.totalCount) ? response.totalCount : products.length;
168 artifact.status = 'passed';
169 } catch (error) {
170 artifact.status = 'failed';
171 artifact.error = error && error.message ? error.message : String(error);
172 }
173 return writeArtifact(artifactPath, artifact);
174}
175
176async function verifyStoresProductBySourceMarker({ wix, siteId, artifactPath, markerPath, markerValue } = {}) {
177 const query = queryFilterByMarker({ markerPath, markerValue });
178 const request = w.buildQueryStoresProductsRequest(query);
179 const artifact = artifactBase({
180 command: 'stores product-by-source-marker',
181 siteId,
182 endpoint: request.url,
183 method: request.method,
184 });
185 artifact.marker = { path: markerPath, value: markerValue };
186 try {
187 const response = await wix.send(request);
188 artifact.products = (response.products || []).map((product) => ({
189 id: product.id,
190 name: product.name,
191 slug: product.slug,
192 revision: product.revision,
193 }));
194 artifact.count = artifact.products.length;
195 artifact.status = 'passed';
196 } catch (error) {
197 artifact.status = 'failed';
198 artifact.error = error && error.message ? error.message : String(error);
199 }
200 return writeArtifact(artifactPath, artifact);
201}
202
203async function verifyStoresDeleteProbe({ wix, siteId, artifactPath, productId } = {}) {
204 if (!productId) throw new Error('delete-probe requires --product-id');
205 const request = w.buildDeleteStoresProductRequest(productId);
206 const artifact = artifactBase({
207 command: 'stores delete-probe',
208 siteId,
209 endpoint: request.url,
210 method: request.method,
211 });
212 artifact.probeRecordId = productId;
213 artifact.cleanup = { attempted: true, endpoint: request.url, method: request.method, status: 'unknown' };
214 try {
215 await wix.send(request);
216 artifact.cleanup.status = 'deleted';
217 artifact.status = 'passed';
218 } catch (error) {
219 artifact.cleanup.status = 'failed';
220 artifact.status = 'failed';
221 artifact.error = error && error.message ? error.message : String(error);
222 artifact.warnings.push(`Probe product cleanup failed for ${productId}.`);
223 artifact.recoveryInstructions.push(`Delete probe product ${productId} manually in Wix Stores and keep this artifact with the cleanup evidence.`);
224 }
225 return writeArtifact(artifactPath, artifact);
226}
227
228module.exports = {
229 DEFAULT_VERIFIED_SUBSCRIPTION_PATHS,
230 defaultProbeProduct,
231 queryFilterByMarker,
232 valueAtPath,
233 verifyPaths,
234 verifyStoresSubscriptionCreate,
235 verifyStoresProductCount,
236 verifyStoresProductBySourceMarker,
237 verifyStoresDeleteProbe,
238};