@ollie-shop/cli 1.1.0 → 1.2.2
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/.env.example +3 -0
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +26 -0
- package/CONTEXT.md +24 -1
- package/README.md +53 -7
- package/dist/index.js +836 -207
- package/package.json +1 -1
- package/src/commands/business-rule-cmd.ts +161 -0
- package/src/commands/deploy-cmd.ts +146 -0
- package/src/commands/function-cmd.ts +146 -0
- package/src/commands/help.tsx +27 -0
- package/src/commands/status-cmd.ts +68 -0
- package/src/core/business-rule.ts +128 -0
- package/src/core/deploy.ts +171 -0
- package/src/core/function.ts +92 -0
- package/src/core/schema.ts +18 -2
- package/src/index.tsx +8 -0
- package/src/utils/supabase.ts +19 -11
- package/src/utils/validate.ts +44 -0
- package/tsup.config.ts +9 -2
package/package.json
CHANGED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getBusinessRule,
|
|
3
|
+
listBusinessRules,
|
|
4
|
+
updateBusinessRule,
|
|
5
|
+
} from "../core/business-rule.js";
|
|
6
|
+
import {
|
|
7
|
+
detectOutputFormat,
|
|
8
|
+
outputDryRun,
|
|
9
|
+
outputResult,
|
|
10
|
+
} from "../utils/output.js";
|
|
11
|
+
import { type ParsedArgs, getFlag } from "../utils/parse-args.js";
|
|
12
|
+
import { getAuthenticatedClient } from "../utils/supabase.js";
|
|
13
|
+
import { validateRequired, validateUuid } from "../utils/validate.js";
|
|
14
|
+
|
|
15
|
+
export async function businessRuleCommand(parsed: ParsedArgs): Promise<void> {
|
|
16
|
+
const sub = parsed.subcommand;
|
|
17
|
+
if (sub === "list" || sub === "ls") return businessRuleListCommand(parsed);
|
|
18
|
+
if (sub === "get") return businessRuleGetCommand(parsed);
|
|
19
|
+
if (sub === "update") return businessRuleUpdateCommand(parsed);
|
|
20
|
+
|
|
21
|
+
console.error(
|
|
22
|
+
`Unknown business-rule subcommand: ${sub}. Use: business-rule list | business-rule get | business-rule update`,
|
|
23
|
+
);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function businessRuleListCommand(parsed: ParsedArgs): Promise<void> {
|
|
28
|
+
const format = detectOutputFormat(parsed.global.output);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const storeId = getFlag(parsed.flags, "store-id");
|
|
32
|
+
const versionId = getFlag(parsed.flags, "version-id");
|
|
33
|
+
const codeUpdatedRaw = parsed.flags["code-updated"];
|
|
34
|
+
|
|
35
|
+
let codeUpdated: boolean | undefined;
|
|
36
|
+
if (codeUpdatedRaw === "true" || codeUpdatedRaw === true) {
|
|
37
|
+
codeUpdated = true;
|
|
38
|
+
} else if (codeUpdatedRaw === "false") {
|
|
39
|
+
codeUpdated = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (storeId !== undefined) validateUuid(storeId, "store-id");
|
|
43
|
+
if (versionId !== undefined) validateUuid(versionId, "version-id");
|
|
44
|
+
|
|
45
|
+
const client = await getAuthenticatedClient();
|
|
46
|
+
const result = await listBusinessRules(client, {
|
|
47
|
+
store_id: storeId,
|
|
48
|
+
version_id: versionId,
|
|
49
|
+
code_updated: codeUpdated,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
outputResult(result, format, parsed.global.fields);
|
|
53
|
+
if (result.error) process.exit(1);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
outputResult(
|
|
56
|
+
{
|
|
57
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
58
|
+
},
|
|
59
|
+
format,
|
|
60
|
+
);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function businessRuleGetCommand(parsed: ParsedArgs): Promise<void> {
|
|
66
|
+
const format = detectOutputFormat(parsed.global.output);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const id = validateUuid(
|
|
70
|
+
validateRequired(getFlag(parsed.flags, "id"), "id"),
|
|
71
|
+
"id",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const client = await getAuthenticatedClient();
|
|
75
|
+
const result = await getBusinessRule(client, id);
|
|
76
|
+
|
|
77
|
+
outputResult(result, format, parsed.global.fields);
|
|
78
|
+
if (result.error) process.exit(1);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
outputResult(
|
|
81
|
+
{
|
|
82
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
83
|
+
},
|
|
84
|
+
format,
|
|
85
|
+
);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function businessRuleUpdateCommand(parsed: ParsedArgs): Promise<void> {
|
|
91
|
+
const format = detectOutputFormat(parsed.global.output);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
let id: string;
|
|
95
|
+
let input: {
|
|
96
|
+
content: string;
|
|
97
|
+
versions_ids?: string[] | null;
|
|
98
|
+
components_ids?: string[] | null;
|
|
99
|
+
functions_ids?: string[] | null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (parsed.global.data) {
|
|
103
|
+
const raw = JSON.parse(parsed.global.data);
|
|
104
|
+
id = validateUuid(validateRequired(raw.id, "id"), "id");
|
|
105
|
+
input = {
|
|
106
|
+
content: validateRequired(raw.content, "content"),
|
|
107
|
+
versions_ids: raw.versionsIds ?? undefined,
|
|
108
|
+
components_ids: raw.componentsIds ?? undefined,
|
|
109
|
+
functions_ids: raw.functionsIds ?? undefined,
|
|
110
|
+
};
|
|
111
|
+
} else {
|
|
112
|
+
id = validateUuid(
|
|
113
|
+
validateRequired(getFlag(parsed.flags, "id"), "id"),
|
|
114
|
+
"id",
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const versionsIdsRaw = getFlag(parsed.flags, "versions-ids");
|
|
118
|
+
const componentsIdsRaw = getFlag(parsed.flags, "components-ids");
|
|
119
|
+
const functionsIdsRaw = getFlag(parsed.flags, "functions-ids");
|
|
120
|
+
|
|
121
|
+
input = {
|
|
122
|
+
content: validateRequired(
|
|
123
|
+
getFlag(parsed.flags, "content", "c"),
|
|
124
|
+
"content",
|
|
125
|
+
),
|
|
126
|
+
versions_ids: versionsIdsRaw
|
|
127
|
+
? (JSON.parse(versionsIdsRaw) as string[])
|
|
128
|
+
: undefined,
|
|
129
|
+
components_ids: componentsIdsRaw
|
|
130
|
+
? (JSON.parse(componentsIdsRaw) as string[])
|
|
131
|
+
: undefined,
|
|
132
|
+
functions_ids: functionsIdsRaw
|
|
133
|
+
? (JSON.parse(functionsIdsRaw) as string[])
|
|
134
|
+
: undefined,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (parsed.global.dryRun) {
|
|
139
|
+
outputDryRun(
|
|
140
|
+
"business-rule.update",
|
|
141
|
+
{ id, ...input } as Record<string, unknown>,
|
|
142
|
+
format,
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const client = await getAuthenticatedClient();
|
|
148
|
+
const result = await updateBusinessRule(client, id, input);
|
|
149
|
+
|
|
150
|
+
outputResult(result, format, parsed.global.fields);
|
|
151
|
+
if (result.error) process.exit(1);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
outputResult(
|
|
154
|
+
{
|
|
155
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
156
|
+
},
|
|
157
|
+
format,
|
|
158
|
+
);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ResourceType,
|
|
3
|
+
isTerminalStatus,
|
|
4
|
+
pollBuildStatus,
|
|
5
|
+
uploadBuild,
|
|
6
|
+
} from "../core/deploy.js";
|
|
7
|
+
import { createComponentBundle } from "../utils/bundle.js";
|
|
8
|
+
import {
|
|
9
|
+
detectOutputFormat,
|
|
10
|
+
outputDryRun,
|
|
11
|
+
outputResult,
|
|
12
|
+
} from "../utils/output.js";
|
|
13
|
+
import { type ParsedArgs, getBoolFlag, getFlag } from "../utils/parse-args.js";
|
|
14
|
+
import { validateRequired, validateUuid } from "../utils/validate.js";
|
|
15
|
+
|
|
16
|
+
export async function deployCommand(parsed: ParsedArgs): Promise<void> {
|
|
17
|
+
const format = detectOutputFormat(parsed.global.output);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
let resourceId: string;
|
|
21
|
+
let componentName: string;
|
|
22
|
+
let resourceType: ResourceType;
|
|
23
|
+
let wait: boolean;
|
|
24
|
+
let timeout: number;
|
|
25
|
+
|
|
26
|
+
if (parsed.global.data) {
|
|
27
|
+
const raw = JSON.parse(parsed.global.data);
|
|
28
|
+
resourceId = validateUuid(
|
|
29
|
+
raw.componentId || raw.functionId || raw.resourceId,
|
|
30
|
+
"componentId or functionId",
|
|
31
|
+
);
|
|
32
|
+
componentName = validateRequired(raw.name, "name");
|
|
33
|
+
resourceType = raw.type === "function" ? "function" : "component";
|
|
34
|
+
wait = raw.wait ?? false;
|
|
35
|
+
timeout = raw.timeout ?? 300;
|
|
36
|
+
} else {
|
|
37
|
+
const compId = getFlag(parsed.flags, "component-id");
|
|
38
|
+
const funcId = getFlag(parsed.flags, "function-id");
|
|
39
|
+
|
|
40
|
+
if (compId && funcId) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Provide either --component-id or --function-id, not both.",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (compId) {
|
|
47
|
+
resourceId = validateUuid(compId, "component-id");
|
|
48
|
+
resourceType = "component";
|
|
49
|
+
} else if (funcId) {
|
|
50
|
+
resourceId = validateUuid(funcId, "function-id");
|
|
51
|
+
resourceType = "function";
|
|
52
|
+
} else {
|
|
53
|
+
throw new Error("Either --component-id or --function-id is required.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
componentName = validateRequired(
|
|
57
|
+
getFlag(parsed.flags, "name", "n"),
|
|
58
|
+
"name",
|
|
59
|
+
);
|
|
60
|
+
wait = getBoolFlag(parsed.flags, "wait");
|
|
61
|
+
timeout = Number(getFlag(parsed.flags, "timeout") ?? "300");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Bundle the component
|
|
65
|
+
const stream = await createComponentBundle({
|
|
66
|
+
componentName,
|
|
67
|
+
cwd: process.cwd(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Collect the zip buffer
|
|
71
|
+
const chunks: Buffer[] = [];
|
|
72
|
+
for await (const chunk of stream) {
|
|
73
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
74
|
+
}
|
|
75
|
+
const zipBuffer = Buffer.concat(chunks);
|
|
76
|
+
|
|
77
|
+
if (parsed.global.dryRun) {
|
|
78
|
+
outputDryRun(
|
|
79
|
+
"deploy",
|
|
80
|
+
{
|
|
81
|
+
resourceId,
|
|
82
|
+
resourceType,
|
|
83
|
+
componentName,
|
|
84
|
+
bundleSizeBytes: zipBuffer.length,
|
|
85
|
+
bundleSizeKB: Math.round(zipBuffer.length / 1024),
|
|
86
|
+
wait,
|
|
87
|
+
timeout,
|
|
88
|
+
},
|
|
89
|
+
format,
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Upload to builder
|
|
95
|
+
const uploadResult = await uploadBuild({
|
|
96
|
+
resourceId,
|
|
97
|
+
type: resourceType,
|
|
98
|
+
zipBuffer,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!uploadResult.success) {
|
|
102
|
+
outputResult(uploadResult, format);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If --wait, poll until terminal
|
|
107
|
+
if (wait) {
|
|
108
|
+
const isJson = format === "json";
|
|
109
|
+
if (!isJson) {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
`Build ${uploadResult.data.id} started. Polling...\n`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const pollResult = await pollBuildStatus(uploadResult.data.id, {
|
|
116
|
+
timeoutMs: timeout * 1000,
|
|
117
|
+
onPoll: (build) => {
|
|
118
|
+
if (!isJson) {
|
|
119
|
+
process.stderr.write(` status: ${build.status}\n`);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
outputResult(pollResult, format, parsed.global.fields);
|
|
125
|
+
if (!pollResult.success) process.exit(1);
|
|
126
|
+
if (
|
|
127
|
+
pollResult.success &&
|
|
128
|
+
isTerminalStatus(pollResult.data.status) &&
|
|
129
|
+
pollResult.data.status !== "SUCCEEDED"
|
|
130
|
+
) {
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
outputResult(uploadResult, format, parsed.global.fields);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
outputResult(
|
|
138
|
+
{
|
|
139
|
+
success: false,
|
|
140
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
141
|
+
},
|
|
142
|
+
format,
|
|
143
|
+
);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { createFunction, listFunctions } from "../core/function.js";
|
|
2
|
+
import { functionCreateSchema } from "../core/schema.js";
|
|
3
|
+
import {
|
|
4
|
+
detectOutputFormat,
|
|
5
|
+
outputDryRun,
|
|
6
|
+
outputResult,
|
|
7
|
+
} from "../utils/output.js";
|
|
8
|
+
import { type ParsedArgs, getBoolFlag, getFlag } from "../utils/parse-args.js";
|
|
9
|
+
import { getAuthenticatedClient } from "../utils/supabase.js";
|
|
10
|
+
import {
|
|
11
|
+
validateEnum,
|
|
12
|
+
validateInteger,
|
|
13
|
+
validateRequired,
|
|
14
|
+
validateTriggerUrl,
|
|
15
|
+
validateUuid,
|
|
16
|
+
} from "../utils/validate.js";
|
|
17
|
+
|
|
18
|
+
const ON_ERROR_VALUES = ["throw", "skip"] as const;
|
|
19
|
+
|
|
20
|
+
export async function functionCommand(parsed: ParsedArgs): Promise<void> {
|
|
21
|
+
const sub = parsed.subcommand;
|
|
22
|
+
if (sub === "create") return functionCreateCommand(parsed);
|
|
23
|
+
if (sub === "list" || sub === "ls") return functionListCommand(parsed);
|
|
24
|
+
|
|
25
|
+
console.error(
|
|
26
|
+
`Unknown function subcommand: ${sub}. Use: function create | function list`,
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function functionCreateCommand(parsed: ParsedArgs): Promise<void> {
|
|
32
|
+
const format = detectOutputFormat(parsed.global.output);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
let input: {
|
|
36
|
+
versionId: string;
|
|
37
|
+
name: string;
|
|
38
|
+
trigger?: string;
|
|
39
|
+
active?: boolean;
|
|
40
|
+
onError?: "throw" | "skip";
|
|
41
|
+
priority?: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (parsed.global.data) {
|
|
45
|
+
const raw = JSON.parse(parsed.global.data);
|
|
46
|
+
input = {
|
|
47
|
+
versionId: validateUuid(raw.versionId, "versionId"),
|
|
48
|
+
name: validateRequired(raw.name, "name"),
|
|
49
|
+
trigger:
|
|
50
|
+
raw.trigger !== undefined && raw.trigger !== null
|
|
51
|
+
? validateTriggerUrl(String(raw.trigger), "trigger")
|
|
52
|
+
: undefined,
|
|
53
|
+
active: raw.active === true,
|
|
54
|
+
onError:
|
|
55
|
+
raw.onError !== undefined && raw.onError !== null
|
|
56
|
+
? validateEnum(String(raw.onError), ON_ERROR_VALUES, "on-error")
|
|
57
|
+
: "throw",
|
|
58
|
+
priority:
|
|
59
|
+
raw.priority !== undefined && raw.priority !== null
|
|
60
|
+
? validateInteger(raw.priority, "priority", { min: 0 })
|
|
61
|
+
: 0,
|
|
62
|
+
};
|
|
63
|
+
} else {
|
|
64
|
+
const triggerFlag = getFlag(parsed.flags, "trigger");
|
|
65
|
+
const onErrorFlag = getFlag(parsed.flags, "on-error");
|
|
66
|
+
const priorityFlag = getFlag(parsed.flags, "priority");
|
|
67
|
+
input = {
|
|
68
|
+
versionId: validateUuid(
|
|
69
|
+
validateRequired(getFlag(parsed.flags, "version-id"), "version-id"),
|
|
70
|
+
"version-id",
|
|
71
|
+
),
|
|
72
|
+
name: validateRequired(getFlag(parsed.flags, "name", "n"), "name"),
|
|
73
|
+
trigger:
|
|
74
|
+
triggerFlag !== undefined
|
|
75
|
+
? validateTriggerUrl(triggerFlag, "trigger")
|
|
76
|
+
: undefined,
|
|
77
|
+
active: getBoolFlag(parsed.flags, "active"),
|
|
78
|
+
onError:
|
|
79
|
+
onErrorFlag !== undefined
|
|
80
|
+
? validateEnum(onErrorFlag, ON_ERROR_VALUES, "on-error")
|
|
81
|
+
: "throw",
|
|
82
|
+
priority:
|
|
83
|
+
priorityFlag !== undefined
|
|
84
|
+
? validateInteger(priorityFlag, "priority", { min: 0 })
|
|
85
|
+
: 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const validated = functionCreateSchema.safeParse(input);
|
|
90
|
+
if (!validated.success) {
|
|
91
|
+
throw new Error(validated.error.issues.map((i) => i.message).join("; "));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (parsed.global.dryRun) {
|
|
95
|
+
outputDryRun(
|
|
96
|
+
"function.create",
|
|
97
|
+
validated.data as Record<string, unknown>,
|
|
98
|
+
format,
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const client = await getAuthenticatedClient();
|
|
104
|
+
const result = await createFunction(client, validated.data);
|
|
105
|
+
|
|
106
|
+
outputResult(result, format, parsed.global.fields);
|
|
107
|
+
if (!result.success) process.exit(1);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
outputResult(
|
|
110
|
+
{
|
|
111
|
+
success: false,
|
|
112
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
113
|
+
},
|
|
114
|
+
format,
|
|
115
|
+
);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function functionListCommand(parsed: ParsedArgs): Promise<void> {
|
|
121
|
+
const format = detectOutputFormat(parsed.global.output);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const storeId = validateUuid(
|
|
125
|
+
validateRequired(getFlag(parsed.flags, "store-id"), "store-id"),
|
|
126
|
+
"store-id",
|
|
127
|
+
);
|
|
128
|
+
const versionId = getFlag(parsed.flags, "version-id");
|
|
129
|
+
if (versionId) validateUuid(versionId, "version-id");
|
|
130
|
+
|
|
131
|
+
const client = await getAuthenticatedClient();
|
|
132
|
+
const result = await listFunctions(client, storeId, versionId);
|
|
133
|
+
|
|
134
|
+
outputResult(result, format, parsed.global.fields);
|
|
135
|
+
if (!result.success) process.exit(1);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
outputResult(
|
|
138
|
+
{
|
|
139
|
+
success: false,
|
|
140
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
141
|
+
},
|
|
142
|
+
format,
|
|
143
|
+
);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/src/commands/help.tsx
CHANGED
|
@@ -61,6 +61,24 @@ export function HelpCommand() {
|
|
|
61
61
|
</Box>
|
|
62
62
|
<Text>Create or list components</Text>
|
|
63
63
|
</Box>
|
|
64
|
+
<Box>
|
|
65
|
+
<Box width={24}>
|
|
66
|
+
<Text color="green">function create|list</Text>
|
|
67
|
+
</Box>
|
|
68
|
+
<Text>Create or list functions</Text>
|
|
69
|
+
</Box>
|
|
70
|
+
<Box>
|
|
71
|
+
<Box width={24}>
|
|
72
|
+
<Text color="green">deploy</Text>
|
|
73
|
+
</Box>
|
|
74
|
+
<Text>Bundle and upload a component/function build</Text>
|
|
75
|
+
</Box>
|
|
76
|
+
<Box>
|
|
77
|
+
<Box width={24}>
|
|
78
|
+
<Text color="green">status</Text>
|
|
79
|
+
</Box>
|
|
80
|
+
<Text>Check or poll a build status</Text>
|
|
81
|
+
</Box>
|
|
64
82
|
<Box>
|
|
65
83
|
<Box width={24}>
|
|
66
84
|
<Text color="green">schema</Text>
|
|
@@ -146,6 +164,15 @@ export function HelpCommand() {
|
|
|
146
164
|
$ ollieshop version create --store-id UUID --name v1 --active
|
|
147
165
|
</Text>
|
|
148
166
|
<Text dimColor>$ ollieshop init --store-id UUID --version-id UUID</Text>
|
|
167
|
+
<Text dimColor>
|
|
168
|
+
$ ollieshop function create --version-id UUID --name myHook
|
|
169
|
+
</Text>
|
|
170
|
+
<Text dimColor>
|
|
171
|
+
$ ollieshop deploy --component-id UUID --name FreeShippingBar --wait
|
|
172
|
+
</Text>
|
|
173
|
+
<Text dimColor>
|
|
174
|
+
$ ollieshop status --build-id BUILD_ID --wait -o json
|
|
175
|
+
</Text>
|
|
149
176
|
</Box>
|
|
150
177
|
</Box>
|
|
151
178
|
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchBuildStatus,
|
|
3
|
+
isTerminalStatus,
|
|
4
|
+
pollBuildStatus,
|
|
5
|
+
} from "../core/deploy.js";
|
|
6
|
+
import { detectOutputFormat, outputResult } from "../utils/output.js";
|
|
7
|
+
import { type ParsedArgs, getBoolFlag, getFlag } from "../utils/parse-args.js";
|
|
8
|
+
import { validateRequired } from "../utils/validate.js";
|
|
9
|
+
|
|
10
|
+
export async function statusCommand(parsed: ParsedArgs): Promise<void> {
|
|
11
|
+
const format = detectOutputFormat(parsed.global.output);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
let buildId: string;
|
|
15
|
+
let wait: boolean;
|
|
16
|
+
let timeout: number;
|
|
17
|
+
|
|
18
|
+
if (parsed.global.data) {
|
|
19
|
+
const raw = JSON.parse(parsed.global.data);
|
|
20
|
+
buildId = validateRequired(raw.buildId, "buildId");
|
|
21
|
+
wait = raw.wait ?? false;
|
|
22
|
+
timeout = raw.timeout ?? 300;
|
|
23
|
+
} else {
|
|
24
|
+
buildId = validateRequired(getFlag(parsed.flags, "build-id"), "build-id");
|
|
25
|
+
wait = getBoolFlag(parsed.flags, "wait");
|
|
26
|
+
timeout = Number(getFlag(parsed.flags, "timeout") ?? "300");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (wait) {
|
|
30
|
+
const isJson = format === "json";
|
|
31
|
+
if (!isJson) {
|
|
32
|
+
process.stderr.write(`Polling build ${buildId}...\n`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = await pollBuildStatus(buildId, {
|
|
36
|
+
timeoutMs: timeout * 1000,
|
|
37
|
+
onPoll: (build) => {
|
|
38
|
+
if (!isJson) {
|
|
39
|
+
process.stderr.write(` status: ${build.status}\n`);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
outputResult(result, format, parsed.global.fields);
|
|
45
|
+
if (!result.success) process.exit(1);
|
|
46
|
+
if (
|
|
47
|
+
result.success &&
|
|
48
|
+
isTerminalStatus(result.data.status) &&
|
|
49
|
+
result.data.status !== "SUCCEEDED"
|
|
50
|
+
) {
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
const result = await fetchBuildStatus(buildId);
|
|
55
|
+
outputResult(result, format, parsed.global.fields);
|
|
56
|
+
if (!result.success) process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
outputResult(
|
|
60
|
+
{
|
|
61
|
+
success: false,
|
|
62
|
+
error: { message: err instanceof Error ? err.message : String(err) },
|
|
63
|
+
},
|
|
64
|
+
format,
|
|
65
|
+
);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
|
|
3
|
+
export interface BusinessRuleRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
store_id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
content: string;
|
|
8
|
+
previous_content: string | null;
|
|
9
|
+
versions_ids: string[] | null;
|
|
10
|
+
components_ids: string[] | null;
|
|
11
|
+
functions_ids: string[] | null;
|
|
12
|
+
status: "draft" | "active" | "deprecated";
|
|
13
|
+
code_updated: boolean;
|
|
14
|
+
created_at: string;
|
|
15
|
+
updated_at: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ListBusinessRulesFilters {
|
|
19
|
+
store_id?: string;
|
|
20
|
+
version_id?: string;
|
|
21
|
+
code_updated?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UpdateBusinessRuleInput {
|
|
25
|
+
content: string;
|
|
26
|
+
versions_ids?: string[] | null;
|
|
27
|
+
components_ids?: string[] | null;
|
|
28
|
+
functions_ids?: string[] | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SELECT_FIELDS =
|
|
32
|
+
"id, store_id, title, content, previous_content, versions_ids, components_ids, functions_ids, status, code_updated, created_at, updated_at";
|
|
33
|
+
|
|
34
|
+
export async function listBusinessRules(
|
|
35
|
+
client: SupabaseClient,
|
|
36
|
+
filters: ListBusinessRulesFilters = {},
|
|
37
|
+
): Promise<{ data?: BusinessRuleRecord[]; error?: { message: string } }> {
|
|
38
|
+
let query = client
|
|
39
|
+
.from("business_rules")
|
|
40
|
+
.select(SELECT_FIELDS)
|
|
41
|
+
.order("created_at", { ascending: false });
|
|
42
|
+
|
|
43
|
+
if (filters.store_id !== undefined) {
|
|
44
|
+
query = query.eq("store_id", filters.store_id);
|
|
45
|
+
}
|
|
46
|
+
if (filters.version_id !== undefined) {
|
|
47
|
+
// versions_ids is a JSON array — use "contains" to find rules linked to this version
|
|
48
|
+
query = query.filter(
|
|
49
|
+
"versions_ids",
|
|
50
|
+
"cs",
|
|
51
|
+
JSON.stringify([filters.version_id]),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (filters.code_updated !== undefined) {
|
|
55
|
+
query = query.eq("code_updated", filters.code_updated);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { data, error } = await query;
|
|
59
|
+
|
|
60
|
+
if (error) {
|
|
61
|
+
return { error: { message: error.message } };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { data: data as BusinessRuleRecord[] };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getBusinessRule(
|
|
68
|
+
client: SupabaseClient,
|
|
69
|
+
id: string,
|
|
70
|
+
): Promise<{ data?: BusinessRuleRecord; error?: { message: string } }> {
|
|
71
|
+
const { data, error } = await client
|
|
72
|
+
.from("business_rules")
|
|
73
|
+
.select(SELECT_FIELDS)
|
|
74
|
+
.eq("id", id)
|
|
75
|
+
.single();
|
|
76
|
+
|
|
77
|
+
if (error) {
|
|
78
|
+
return { error: { message: error.message } };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { data: data as BusinessRuleRecord };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function updateBusinessRule(
|
|
85
|
+
client: SupabaseClient,
|
|
86
|
+
id: string,
|
|
87
|
+
input: UpdateBusinessRuleInput,
|
|
88
|
+
): Promise<{ data?: { id: string }; error?: { message: string } }> {
|
|
89
|
+
// Fetch current content to preserve it as previous_content
|
|
90
|
+
const { data: current, error: fetchError } = await client
|
|
91
|
+
.from("business_rules")
|
|
92
|
+
.select("content")
|
|
93
|
+
.eq("id", id)
|
|
94
|
+
.single();
|
|
95
|
+
|
|
96
|
+
if (fetchError) {
|
|
97
|
+
return { error: { message: fetchError.message } };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const updatePayload: Record<string, unknown> = {
|
|
101
|
+
previous_content: current.content,
|
|
102
|
+
content: input.content,
|
|
103
|
+
code_updated: false,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (input.versions_ids !== undefined) {
|
|
107
|
+
updatePayload.versions_ids = input.versions_ids;
|
|
108
|
+
}
|
|
109
|
+
if (input.components_ids !== undefined) {
|
|
110
|
+
updatePayload.components_ids = input.components_ids;
|
|
111
|
+
}
|
|
112
|
+
if (input.functions_ids !== undefined) {
|
|
113
|
+
updatePayload.functions_ids = input.functions_ids;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { data, error } = await client
|
|
117
|
+
.from("business_rules")
|
|
118
|
+
.update(updatePayload)
|
|
119
|
+
.eq("id", id)
|
|
120
|
+
.select("id")
|
|
121
|
+
.single();
|
|
122
|
+
|
|
123
|
+
if (error) {
|
|
124
|
+
return { error: { message: error.message } };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { data: { id: data.id } };
|
|
128
|
+
}
|