@storewright/cli 0.14.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/README.md +19 -0
- package/VERSION +1 -0
- package/bin/storewright.mjs +62 -0
- package/contracts/action-registry.json +175 -0
- package/contracts/capability-registry.json +63 -0
- package/contracts/workflow-manifest.json +207 -0
- package/lib/cli/storewright-cli.mjs +259 -0
- package/lib/internal/launch-envelope.mjs +223 -0
- package/lib/internal/multi-agent-contracts.mjs +137 -0
- package/lib/internal/operation-ledger.mjs +190 -0
- package/lib/internal/pricing/default-preview-pricing.mjs +181 -0
- package/lib/internal/run-state-helpers.mjs +313 -0
- package/lib/internal/shopify-operation-adapter.mjs +456 -0
- package/package.json +38 -0
- package/schemas/action-registry.schema.json +11 -0
- package/schemas/agent-report.schema.json +14 -0
- package/schemas/approval-grant.schema.json +16 -0
- package/schemas/base-theme-report.schema.json +25 -0
- package/schemas/brand-identity.schema.json +142 -0
- package/schemas/capability-registry.schema.json +11 -0
- package/schemas/competitor-audit.schema.json +38 -0
- package/schemas/design-direction.schema.json +64 -0
- package/schemas/external-operation.schema.json +34 -0
- package/schemas/intake-blocked-report.schema.json +76 -0
- package/schemas/launch-envelope.schema.json +25 -0
- package/schemas/launch-readiness.schema.json +73 -0
- package/schemas/media-file-inspection-report.schema.json +223 -0
- package/schemas/media-manifest.schema.json +84 -0
- package/schemas/merchandising-brief.schema.json +27 -0
- package/schemas/normalized-product-catalog.schema.json +42 -0
- package/schemas/product-content-generation-input.schema.json +40 -0
- package/schemas/product-content-generation-output.schema.json +43 -0
- package/schemas/raw-product-candidates.schema.json +32 -0
- package/schemas/shopify-access-preflight-report.schema.json +213 -0
- package/schemas/shopify-content-sync-report.schema.json +190 -0
- package/schemas/shopify-media-map.schema.json +87 -0
- package/schemas/shopify-media-upload-report.schema.json +96 -0
- package/schemas/shopify-operation-request.schema.json +81 -0
- package/schemas/shopify-preflight-report.schema.json +187 -0
- package/schemas/store-blueprint.schema.json +112 -0
- package/schemas/store-content-generation-output.schema.json +102 -0
- package/schemas/store-intake.schema.json +205 -0
- package/schemas/store-ops-plan.schema.json +82 -0
- package/schemas/storefront-preview-review.schema.json +227 -0
- package/schemas/supplier-access-report.schema.json +36 -0
- package/schemas/supplier-extraction-report.schema.json +185 -0
- package/schemas/theme-build-report.schema.json +43 -0
- package/schemas/theme-code-change-summary.schema.json +65 -0
- package/schemas/theme-plan.schema.json +26 -0
- package/schemas/theme-push-report.schema.json +151 -0
- package/schemas/theme-workspace-validation-report.schema.json +61 -0
- package/schemas/workflow-manifest.schema.json +29 -0
- package/scripts/audit-run-state.mjs +472 -0
- package/scripts/execute-shopify-operation.mjs +190 -0
- package/scripts/generate-image-assets-openai.mjs +342 -0
- package/scripts/generate-media-assets.mjs +121 -0
- package/scripts/init-run-state.mjs +69 -0
- package/scripts/inspect-media-files.mjs +334 -0
- package/scripts/prepare-launch-envelope.mjs +47 -0
- package/scripts/shopify-access-preflight.mjs +432 -0
- package/scripts/upload-shopify-media.mjs +831 -0
- package/scripts/validate-agent-report.mjs +46 -0
- package/scripts/validate-artifact.mjs +196 -0
- package/scripts/validate-launch-envelope.mjs +50 -0
- package/scripts/validate-registries.mjs +50 -0
- package/scripts/validate-workflow-manifest.mjs +38 -0
- package/scripts/version.mjs +192 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
prepareExternalOperation,
|
|
8
|
+
transitionExternalOperation
|
|
9
|
+
} from "./operation-ledger.mjs";
|
|
10
|
+
|
|
11
|
+
const allowedGraphqlOperationsByAction = {
|
|
12
|
+
"shopify.products.upsert": ["productCreate", "productSet", "productUpdate", "productVariantsBulkUpdate"],
|
|
13
|
+
"shopify.products.publishToOnlineStore": ["publishablePublish"],
|
|
14
|
+
"shopify.collections.upsert": ["collectionCreate", "collectionUpdate"],
|
|
15
|
+
"shopify.collections.attachProducts": ["productUpdate"],
|
|
16
|
+
"shopify.collections.publishToOnlineStore": ["publishablePublish"],
|
|
17
|
+
"shopify.content.upsertPages": ["pageCreate", "pageUpdate"],
|
|
18
|
+
"shopify.navigation.upsertPreviewMenu": ["menuCreate", "menuUpdate"]
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const expectedExecutorToolByAction = {
|
|
22
|
+
"shopify.products.upsert": "catalog_upsert",
|
|
23
|
+
"shopify.products.publishToOnlineStore": "publishable_publish",
|
|
24
|
+
"shopify.collections.upsert": "collection_upsert",
|
|
25
|
+
"shopify.collections.attachProducts": "collection_attach_products",
|
|
26
|
+
"shopify.collections.publishToOnlineStore": "publishable_publish",
|
|
27
|
+
"shopify.content.upsertPages": "content_upsert_pages",
|
|
28
|
+
"shopify.navigation.upsertPreviewMenu": "navigation_upsert_preview_menu",
|
|
29
|
+
"shopify.theme.pushUnpublished": "theme_push_unpublished"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const expectedExecutorServer = "storewright-operations";
|
|
33
|
+
|
|
34
|
+
function defaultRunCommand(command, args, options = {}) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const child = spawn(command, args, { cwd: options.cwd });
|
|
37
|
+
let stdout = "";
|
|
38
|
+
let stderr = "";
|
|
39
|
+
let timedOut = false;
|
|
40
|
+
let settled = false;
|
|
41
|
+
|
|
42
|
+
const timeout = setTimeout(() => {
|
|
43
|
+
timedOut = true;
|
|
44
|
+
child.kill("SIGTERM");
|
|
45
|
+
}, options.timeoutMs ?? 300000);
|
|
46
|
+
|
|
47
|
+
const finish = (result) => {
|
|
48
|
+
if (settled) return;
|
|
49
|
+
settled = true;
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
resolve(result);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
child.stdout.on("data", (chunk) => {
|
|
55
|
+
stdout += chunk;
|
|
56
|
+
process.stdout.write(chunk);
|
|
57
|
+
});
|
|
58
|
+
child.stderr.on("data", (chunk) => {
|
|
59
|
+
stderr += chunk;
|
|
60
|
+
process.stderr.write(chunk);
|
|
61
|
+
});
|
|
62
|
+
child.on("error", (error) => {
|
|
63
|
+
finish({
|
|
64
|
+
exitCode: 1,
|
|
65
|
+
stdout,
|
|
66
|
+
stderr: stderr || error.message,
|
|
67
|
+
timedOut
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
child.on("close", (exitCode, signal) => {
|
|
71
|
+
finish({
|
|
72
|
+
exitCode: exitCode ?? 1,
|
|
73
|
+
stdout,
|
|
74
|
+
stderr,
|
|
75
|
+
timedOut: timedOut || signal === "SIGTERM"
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function summarize(text) {
|
|
82
|
+
return String(text ?? "").replace(/\s+/g, " ").trim().slice(0, 500);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseJson(text) {
|
|
86
|
+
const raw = String(text ?? "").trim();
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(raw);
|
|
89
|
+
} catch {
|
|
90
|
+
let lastParsed = null;
|
|
91
|
+
for (let start = 0; start < raw.length; start += 1) {
|
|
92
|
+
if (raw[start] !== "{") continue;
|
|
93
|
+
let depth = 0;
|
|
94
|
+
let inString = false;
|
|
95
|
+
let escaped = false;
|
|
96
|
+
for (let index = start; index < raw.length; index += 1) {
|
|
97
|
+
const char = raw[index];
|
|
98
|
+
if (inString) {
|
|
99
|
+
if (escaped) {
|
|
100
|
+
escaped = false;
|
|
101
|
+
} else if (char === "\\") {
|
|
102
|
+
escaped = true;
|
|
103
|
+
} else if (char === "\"") {
|
|
104
|
+
inString = false;
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (char === "\"") {
|
|
109
|
+
inString = true;
|
|
110
|
+
} else if (char === "{") {
|
|
111
|
+
depth += 1;
|
|
112
|
+
} else if (char === "}") {
|
|
113
|
+
depth -= 1;
|
|
114
|
+
if (depth === 0) {
|
|
115
|
+
try {
|
|
116
|
+
lastParsed = JSON.parse(raw.slice(start, index + 1));
|
|
117
|
+
} catch {
|
|
118
|
+
// Keep scanning for the final valid JSON object in noisy CLI output.
|
|
119
|
+
}
|
|
120
|
+
start = index;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return lastParsed;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parsedFacts({ executorInput, result }) {
|
|
131
|
+
const facts = [];
|
|
132
|
+
const json = parseJson(result.stdout);
|
|
133
|
+
if (executorInput.kind === "admin-graphql") {
|
|
134
|
+
const summary = graphqlPayloadSummary({ json, operationName: executorInput.operationName });
|
|
135
|
+
if (summary.payloadCount === 1) {
|
|
136
|
+
facts.push(`${executorInput.operationName}:payload-present`);
|
|
137
|
+
} else if (summary.payloadCount > 1) {
|
|
138
|
+
facts.push(`${executorInput.operationName}:payloadCount=${summary.payloadCount}`);
|
|
139
|
+
}
|
|
140
|
+
if (summary.payloadCount > 0) facts.push(`${executorInput.operationName}:userErrors=${summary.userErrorCount}`);
|
|
141
|
+
if (summary.topLevelErrorCount > 0) facts.push(`topLevelErrors=${summary.topLevelErrorCount}`);
|
|
142
|
+
}
|
|
143
|
+
if (executorInput.kind === "theme-push-unpublished") {
|
|
144
|
+
const theme = json?.theme ?? json;
|
|
145
|
+
if (theme?.id) facts.push(`themeId=${theme.id}`);
|
|
146
|
+
if (theme?.name) facts.push(`themeName=${theme.name}`);
|
|
147
|
+
if (themeHasWarning(theme)) facts.push(`themeWarning=${summarize(theme.warning)}`);
|
|
148
|
+
const errorCount = countThemeErrors(theme?.errors);
|
|
149
|
+
if (errorCount > 0) facts.push(`themeErrorCount=${errorCount}`);
|
|
150
|
+
}
|
|
151
|
+
return facts;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function themeHasWarning(theme) {
|
|
155
|
+
return typeof theme?.warning === "string" && theme.warning.trim().length > 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function countThemeErrors(errors) {
|
|
159
|
+
if (!errors) return 0;
|
|
160
|
+
if (Array.isArray(errors)) return errors.length;
|
|
161
|
+
if (typeof errors === "string") return errors.trim().length > 0 ? 1 : 0;
|
|
162
|
+
if (typeof errors !== "object") return 0;
|
|
163
|
+
return Object.values(errors).reduce((count, value) => {
|
|
164
|
+
if (Array.isArray(value)) return count + value.length;
|
|
165
|
+
if (typeof value === "string" && value.trim().length > 0) return count + 1;
|
|
166
|
+
if (value && typeof value === "object") return count + countThemeErrors(value);
|
|
167
|
+
return count;
|
|
168
|
+
}, 0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function candidatePayloadEntries(json, operationName) {
|
|
172
|
+
const root = json?.data ?? json;
|
|
173
|
+
if (!root || typeof root !== "object" || Array.isArray(root)) return [];
|
|
174
|
+
const direct = root[operationName];
|
|
175
|
+
if (direct && typeof direct === "object") return [[operationName, direct]];
|
|
176
|
+
return Object.entries(root).filter(([, value]) => (
|
|
177
|
+
value
|
|
178
|
+
&& typeof value === "object"
|
|
179
|
+
&& !Array.isArray(value)
|
|
180
|
+
&& Array.isArray(value.userErrors)
|
|
181
|
+
));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function graphqlPayloadSummary({ json, operationName }) {
|
|
185
|
+
const payloadEntries = candidatePayloadEntries(json, operationName);
|
|
186
|
+
let userErrorCount = 0;
|
|
187
|
+
for (const [, payload] of payloadEntries) {
|
|
188
|
+
if (Array.isArray(payload.userErrors)) userErrorCount += payload.userErrors.length;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
payloadCount: payloadEntries.length,
|
|
192
|
+
userErrorCount,
|
|
193
|
+
topLevelErrorCount: Array.isArray(json?.errors) ? json.errors.length : 0
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function hasGraphqlErrors({ executorInput, result }) {
|
|
198
|
+
if (executorInput.kind !== "admin-graphql") return false;
|
|
199
|
+
const json = parseJson(result.stdout);
|
|
200
|
+
const summary = graphqlPayloadSummary({ json, operationName: executorInput.operationName });
|
|
201
|
+
return summary.userErrorCount > 0 || summary.topLevelErrorCount > 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function hasThemePushIssues({ executorInput, result }) {
|
|
205
|
+
if (executorInput.kind !== "theme-push-unpublished") return false;
|
|
206
|
+
const json = parseJson(result.stdout);
|
|
207
|
+
const theme = json?.theme ?? json;
|
|
208
|
+
return themeHasWarning(theme) || countThemeErrors(theme?.errors) > 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function externalOperationStatus({ executorInput, result }) {
|
|
212
|
+
if (result.exitCode !== 0 || result.timedOut) return "failed";
|
|
213
|
+
if (hasGraphqlErrors({ executorInput, result })) return "failed";
|
|
214
|
+
if (hasThemePushIssues({ executorInput, result })) return "indeterminate";
|
|
215
|
+
return "succeeded";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function commandResultFromStatus(status) {
|
|
219
|
+
if (status === "succeeded") return "ok";
|
|
220
|
+
if (status === "indeterminate") return "indeterminate";
|
|
221
|
+
return "error";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function assertAllowedGraphqlOperation(actionId, executorInput) {
|
|
225
|
+
const allowed = allowedGraphqlOperationsByAction[actionId];
|
|
226
|
+
if (!allowed) {
|
|
227
|
+
throw new Error(`${actionId} does not support admin-graphql execution`);
|
|
228
|
+
}
|
|
229
|
+
if (!allowed.includes(executorInput.operationName)) {
|
|
230
|
+
throw new Error(`${executorInput.operationName} is not allowed for ${actionId}`);
|
|
231
|
+
}
|
|
232
|
+
if (!executorInput.query || typeof executorInput.query !== "string") {
|
|
233
|
+
throw new Error("executorInput.query is required for admin-graphql execution");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function assertValidExecutorInput(actionId, executorInput) {
|
|
238
|
+
if (!executorInput || typeof executorInput !== "object") {
|
|
239
|
+
throw new Error("executorInput is required");
|
|
240
|
+
}
|
|
241
|
+
if (executorInput.kind === "admin-graphql") {
|
|
242
|
+
assertAllowedGraphqlOperation(actionId, executorInput);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (executorInput.kind === "theme-push-unpublished") {
|
|
246
|
+
if (actionId !== "shopify.theme.pushUnpublished") {
|
|
247
|
+
throw new Error(`${executorInput.kind} is not allowed for ${actionId}`);
|
|
248
|
+
}
|
|
249
|
+
if (!executorInput.themeWorkspace) {
|
|
250
|
+
throw new Error("executorInput.themeWorkspace is required for theme push");
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
throw new Error(`Unsupported executorInput.kind: ${executorInput.kind}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function assertExecutorMatchesRegistry({ actionRegistry, actionId }) {
|
|
258
|
+
const action = actionRegistry.actions?.[actionId];
|
|
259
|
+
if (!action) {
|
|
260
|
+
throw new Error(`Unknown action: ${actionId}`);
|
|
261
|
+
}
|
|
262
|
+
const expectedTool = expectedExecutorToolByAction[actionId];
|
|
263
|
+
if (!expectedTool) {
|
|
264
|
+
throw new Error(`${actionId} does not have a supported Shopify operation executor`);
|
|
265
|
+
}
|
|
266
|
+
if (action.executor?.server !== expectedExecutorServer) {
|
|
267
|
+
throw new Error(`${actionId} executor server must be ${expectedExecutorServer}`);
|
|
268
|
+
}
|
|
269
|
+
if (action.executor?.tool !== expectedTool) {
|
|
270
|
+
throw new Error(`${actionId} executor tool must be ${expectedTool}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function operationIdFromKey(operationKey) {
|
|
275
|
+
return createHash("sha256").update(operationKey).digest("hex").slice(0, 20);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function writeGraphqlInputFiles({ runDir, stageId, attemptId, operationKey, executorInput }) {
|
|
279
|
+
const operationId = operationIdFromKey(operationKey);
|
|
280
|
+
const inputDir = join(runDir, "stages", stageId, "attempts", attemptId, "work", "operation-inputs");
|
|
281
|
+
const queryPath = join(inputDir, `${operationId}-query.graphql`);
|
|
282
|
+
const variablesPath = join(inputDir, `${operationId}-variables.json`);
|
|
283
|
+
await mkdir(inputDir, { recursive: true });
|
|
284
|
+
await writeFile(queryPath, `${executorInput.query}\n`);
|
|
285
|
+
await writeFile(variablesPath, `${JSON.stringify(executorInput.variables ?? {}, null, 2)}\n`);
|
|
286
|
+
return { queryPath, variablesPath };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function commandForExecutor({ runDir, stageId, attemptId, operationKey, actionId, targetStore, apiVersion, executorInput }) {
|
|
290
|
+
assertValidExecutorInput(actionId, executorInput);
|
|
291
|
+
if (executorInput.kind === "admin-graphql") {
|
|
292
|
+
const inputFiles = await writeGraphqlInputFiles({ runDir, stageId, attemptId, operationKey, executorInput });
|
|
293
|
+
return {
|
|
294
|
+
command: "shopify",
|
|
295
|
+
args: [
|
|
296
|
+
"store",
|
|
297
|
+
"execute",
|
|
298
|
+
"--store",
|
|
299
|
+
targetStore,
|
|
300
|
+
"--allow-mutations",
|
|
301
|
+
"--json",
|
|
302
|
+
"--version",
|
|
303
|
+
apiVersion,
|
|
304
|
+
"--query-file",
|
|
305
|
+
inputFiles.queryPath,
|
|
306
|
+
"--variable-file",
|
|
307
|
+
inputFiles.variablesPath
|
|
308
|
+
]
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const args = [
|
|
313
|
+
"theme",
|
|
314
|
+
"push",
|
|
315
|
+
"--store",
|
|
316
|
+
targetStore,
|
|
317
|
+
"--unpublished",
|
|
318
|
+
"--json",
|
|
319
|
+
"--path",
|
|
320
|
+
executorInput.themeWorkspace
|
|
321
|
+
];
|
|
322
|
+
if (executorInput.themeId || executorInput.themeName) {
|
|
323
|
+
args.push("--theme", String(executorInput.themeId ?? executorInput.themeName));
|
|
324
|
+
}
|
|
325
|
+
if (executorInput.nodelete) {
|
|
326
|
+
args.push("--nodelete");
|
|
327
|
+
}
|
|
328
|
+
return { command: "shopify", args };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function writeRawOutput({ runDir, stageId, attemptId, operationId, command, args, result, parsedFacts: facts }) {
|
|
332
|
+
const rawOutputPath = `stages/${stageId}/attempts/${attemptId}/work/raw-external-operations/${operationId}-dispatch.json`;
|
|
333
|
+
const absolutePath = join(runDir, rawOutputPath);
|
|
334
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
335
|
+
await writeFile(absolutePath, `${JSON.stringify({
|
|
336
|
+
schemaVersion: "1.0.0",
|
|
337
|
+
command,
|
|
338
|
+
args,
|
|
339
|
+
exitCode: result.exitCode ?? 1,
|
|
340
|
+
timedOut: Boolean(result.timedOut),
|
|
341
|
+
stdout: result.stdout ?? "",
|
|
342
|
+
stderr: result.stderr ?? "",
|
|
343
|
+
parsedFacts: facts
|
|
344
|
+
}, null, 2)}\n`);
|
|
345
|
+
return rawOutputPath;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function executeShopifyOperation({
|
|
349
|
+
runDir,
|
|
350
|
+
stageId,
|
|
351
|
+
attemptId,
|
|
352
|
+
envelope,
|
|
353
|
+
actionRegistry,
|
|
354
|
+
actionId,
|
|
355
|
+
launchEnvelopeSha256,
|
|
356
|
+
grantedScopes = [],
|
|
357
|
+
approvalGrantPath = null,
|
|
358
|
+
operationKey,
|
|
359
|
+
targetStore,
|
|
360
|
+
apiVersion,
|
|
361
|
+
requestFingerprint,
|
|
362
|
+
naturalResourceKey,
|
|
363
|
+
providerIdempotencySupported = false,
|
|
364
|
+
resourceType,
|
|
365
|
+
executorInput,
|
|
366
|
+
runCommand = defaultRunCommand,
|
|
367
|
+
cwd = process.cwd(),
|
|
368
|
+
timeoutMs
|
|
369
|
+
}) {
|
|
370
|
+
assertExecutorMatchesRegistry({ actionRegistry, actionId });
|
|
371
|
+
const commandPlan = await commandForExecutor({
|
|
372
|
+
runDir,
|
|
373
|
+
stageId,
|
|
374
|
+
attemptId,
|
|
375
|
+
operationKey,
|
|
376
|
+
actionId,
|
|
377
|
+
targetStore,
|
|
378
|
+
apiVersion,
|
|
379
|
+
executorInput
|
|
380
|
+
});
|
|
381
|
+
const prepared = await prepareExternalOperation({
|
|
382
|
+
runDir,
|
|
383
|
+
stageId,
|
|
384
|
+
attemptId,
|
|
385
|
+
envelope,
|
|
386
|
+
actionRegistry,
|
|
387
|
+
actionId,
|
|
388
|
+
launchEnvelopeSha256,
|
|
389
|
+
grantedScopes,
|
|
390
|
+
approvalGrantPath,
|
|
391
|
+
operationKey,
|
|
392
|
+
targetStore,
|
|
393
|
+
apiVersion,
|
|
394
|
+
requestFingerprint,
|
|
395
|
+
naturalResourceKey,
|
|
396
|
+
providerIdempotencySupported,
|
|
397
|
+
resourceType
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
await transitionExternalOperation({
|
|
401
|
+
runDir,
|
|
402
|
+
stageId,
|
|
403
|
+
attemptId,
|
|
404
|
+
operationId: prepared.operation.operationId,
|
|
405
|
+
status: "dispatched",
|
|
406
|
+
patch: {
|
|
407
|
+
actionId,
|
|
408
|
+
command: [commandPlan.command, ...commandPlan.args].join(" ")
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const commandResult = await runCommand(commandPlan.command, commandPlan.args, { cwd, timeoutMs });
|
|
413
|
+
const facts = parsedFacts({ executorInput, result: commandResult });
|
|
414
|
+
const rawOutputPath = await writeRawOutput({
|
|
415
|
+
runDir,
|
|
416
|
+
stageId,
|
|
417
|
+
attemptId,
|
|
418
|
+
operationId: prepared.operation.operationId,
|
|
419
|
+
command: commandPlan.command,
|
|
420
|
+
args: commandPlan.args,
|
|
421
|
+
result: commandResult,
|
|
422
|
+
parsedFacts: facts
|
|
423
|
+
});
|
|
424
|
+
const status = externalOperationStatus({ executorInput, result: commandResult });
|
|
425
|
+
|
|
426
|
+
await transitionExternalOperation({
|
|
427
|
+
runDir,
|
|
428
|
+
stageId,
|
|
429
|
+
attemptId,
|
|
430
|
+
operationId: prepared.operation.operationId,
|
|
431
|
+
status,
|
|
432
|
+
patch: {
|
|
433
|
+
actionId,
|
|
434
|
+
rawOutputPath,
|
|
435
|
+
exitCode: commandResult.exitCode ?? 1,
|
|
436
|
+
stdoutSummary: summarize(commandResult.stdout),
|
|
437
|
+
stderrSummary: summarize(commandResult.stderr),
|
|
438
|
+
parsedFacts: facts
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
status,
|
|
444
|
+
operationId: prepared.operation.operationId,
|
|
445
|
+
rawOutputPath,
|
|
446
|
+
commandAttempt: {
|
|
447
|
+
command: [commandPlan.command, ...commandPlan.args].join(" "),
|
|
448
|
+
exitCode: commandResult.exitCode ?? 1,
|
|
449
|
+
commandResult: commandResultFromStatus(status),
|
|
450
|
+
stdoutSummary: summarize(commandResult.stdout),
|
|
451
|
+
stderrSummary: summarize(commandResult.stderr),
|
|
452
|
+
parsedFacts: facts,
|
|
453
|
+
rawOutputPath
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@storewright/cli",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "Storewright CLI bootstrap and deterministic helper commands.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"storewright": "bin/storewright.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"scripts/audit-run-state.mjs",
|
|
13
|
+
"scripts/execute-shopify-operation.mjs",
|
|
14
|
+
"scripts/generate-image-assets-openai.mjs",
|
|
15
|
+
"scripts/generate-media-assets.mjs",
|
|
16
|
+
"scripts/init-run-state.mjs",
|
|
17
|
+
"scripts/inspect-media-files.mjs",
|
|
18
|
+
"scripts/prepare-launch-envelope.mjs",
|
|
19
|
+
"scripts/shopify-access-preflight.mjs",
|
|
20
|
+
"scripts/upload-shopify-media.mjs",
|
|
21
|
+
"scripts/validate-agent-report.mjs",
|
|
22
|
+
"scripts/validate-artifact.mjs",
|
|
23
|
+
"scripts/validate-launch-envelope.mjs",
|
|
24
|
+
"scripts/validate-registries.mjs",
|
|
25
|
+
"scripts/validate-workflow-manifest.mjs",
|
|
26
|
+
"scripts/version.mjs",
|
|
27
|
+
"contracts/",
|
|
28
|
+
"schemas/",
|
|
29
|
+
"VERSION",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"playwright-core": "1.56.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"required": ["schemaVersion", "launchEnvelopeSha256", "inputArtifacts", "effectiveAllowedActions", "outputArtifacts"],
|
|
4
|
+
"properties": {
|
|
5
|
+
"schemaVersion": { "const": "1.0.0" },
|
|
6
|
+
"launchEnvelopeSha256": { "type": "string", "minLength": 1 },
|
|
7
|
+
"inputArtifacts": { "type": "array" },
|
|
8
|
+
"effectiveAllowedActions": { "type": "array" },
|
|
9
|
+
"outputArtifacts": { "type": "array" },
|
|
10
|
+
"observedRuntime": { "type": "object" },
|
|
11
|
+
"blocked_questions": { "type": "array" }
|
|
12
|
+
},
|
|
13
|
+
"additionalProperties": true
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"required": ["schemaVersion", "approvalId", "runId", "actionId", "impactClass", "approvalRequirement", "approvedBy", "source", "grantedAt"],
|
|
4
|
+
"properties": {
|
|
5
|
+
"schemaVersion": { "const": "1.0.0" },
|
|
6
|
+
"approvalId": { "type": "string", "minLength": 1 },
|
|
7
|
+
"runId": { "type": "string", "minLength": 1 },
|
|
8
|
+
"actionId": { "type": "string", "minLength": 1 },
|
|
9
|
+
"impactClass": { "enum": ["local-only", "remote-read", "remote-draft", "remote-public"] },
|
|
10
|
+
"approvalRequirement": { "enum": ["none", "workflow-gate", "explicit-user"] },
|
|
11
|
+
"approvedBy": { "type": "string", "minLength": 1 },
|
|
12
|
+
"source": { "type": "string", "minLength": 1 },
|
|
13
|
+
"grantedAt": { "type": "string", "minLength": 1 }
|
|
14
|
+
},
|
|
15
|
+
"additionalProperties": false
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://example.local/storewright/base-theme-report.schema.json",
|
|
4
|
+
"title": "Storewright Base Theme Report",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schemaVersion", "baseTheme"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schemaVersion": { "type": "string", "const": "1.0.0" },
|
|
10
|
+
"runId": { "type": "string" },
|
|
11
|
+
"baseTheme": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"required": ["source", "baseThemeDir", "created", "notes"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"source": { "type": "string", "enum": ["existing", "shopify-live-theme"] },
|
|
17
|
+
"baseThemeDir": { "type": "string", "minLength": 1 },
|
|
18
|
+
"created": { "type": "boolean" },
|
|
19
|
+
"store": { "type": "string" },
|
|
20
|
+
"command": { "type": "string" },
|
|
21
|
+
"notes": { "type": "array", "items": { "type": "string" } }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|