@refrainai/cli 0.4.1
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/dist/ai-model-FM6GWCID.js +37 -0
- package/dist/ai-model-FM6GWCID.js.map +1 -0
- package/dist/chunk-2BVDAJZT.js +236 -0
- package/dist/chunk-2BVDAJZT.js.map +1 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-7UCVPKD4.js +902 -0
- package/dist/chunk-7UCVPKD4.js.map +1 -0
- package/dist/chunk-AG3CFMYU.js +36 -0
- package/dist/chunk-AG3CFMYU.js.map +1 -0
- package/dist/chunk-CLYJHKPY.js +1131 -0
- package/dist/chunk-CLYJHKPY.js.map +1 -0
- package/dist/chunk-D5SI2PHK.js +74 -0
- package/dist/chunk-D5SI2PHK.js.map +1 -0
- package/dist/chunk-DJVUITRB.js +9084 -0
- package/dist/chunk-DJVUITRB.js.map +1 -0
- package/dist/chunk-H47NWH7N.js +4427 -0
- package/dist/chunk-H47NWH7N.js.map +1 -0
- package/dist/chunk-HQDXLWAY.js +109 -0
- package/dist/chunk-HQDXLWAY.js.map +1 -0
- package/dist/chunk-IGFCYKHC.js +1974 -0
- package/dist/chunk-IGFCYKHC.js.map +1 -0
- package/dist/chunk-RT664YIO.js +245 -0
- package/dist/chunk-RT664YIO.js.map +1 -0
- package/dist/chunk-RYIJPYM3.js +164 -0
- package/dist/chunk-RYIJPYM3.js.map +1 -0
- package/dist/chunk-TDSM3UXI.js +40 -0
- package/dist/chunk-TDSM3UXI.js.map +1 -0
- package/dist/chunk-UGPXCQY3.js +778 -0
- package/dist/chunk-UGPXCQY3.js.map +1 -0
- package/dist/chunk-VPK2MQAZ.js +589 -0
- package/dist/chunk-VPK2MQAZ.js.map +1 -0
- package/dist/chunk-WEYR56ZN.js +953 -0
- package/dist/chunk-WEYR56ZN.js.map +1 -0
- package/dist/chunk-XMFCXPYU.js +275 -0
- package/dist/chunk-XMFCXPYU.js.map +1 -0
- package/dist/chunk-Z33FCOTZ.js +251 -0
- package/dist/chunk-Z33FCOTZ.js.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/compose-MTSIJY5D.js +547 -0
- package/dist/compose-MTSIJY5D.js.map +1 -0
- package/dist/config-ZSUNCFXR.js +9 -0
- package/dist/config-ZSUNCFXR.js.map +1 -0
- package/dist/fix-runbook-ZSBOTLC2.js +294 -0
- package/dist/fix-runbook-ZSBOTLC2.js.map +1 -0
- package/dist/google-sheets-DRWIVEVC.js +482 -0
- package/dist/google-sheets-DRWIVEVC.js.map +1 -0
- package/dist/registry-LZLYTNDJ.js +17 -0
- package/dist/registry-LZLYTNDJ.js.map +1 -0
- package/dist/runbook-data-helpers-KRR2SH76.js +16 -0
- package/dist/runbook-data-helpers-KRR2SH76.js.map +1 -0
- package/dist/runbook-executor-K7T6RJWJ.js +1480 -0
- package/dist/runbook-executor-K7T6RJWJ.js.map +1 -0
- package/dist/runbook-generator-MPXJBQ5N.js +800 -0
- package/dist/runbook-generator-MPXJBQ5N.js.map +1 -0
- package/dist/runbook-schema-3T6TP3JJ.js +35 -0
- package/dist/runbook-schema-3T6TP3JJ.js.map +1 -0
- package/dist/runbook-store-G5GUOWRR.js +11 -0
- package/dist/runbook-store-G5GUOWRR.js.map +1 -0
- package/dist/schema-5G6UQSPT.js +91 -0
- package/dist/schema-5G6UQSPT.js.map +1 -0
- package/dist/server-AG3LXQBI.js +8778 -0
- package/dist/server-AG3LXQBI.js.map +1 -0
- package/dist/tenant-ai-config-QPFEJUVJ.js +14 -0
- package/dist/tenant-ai-config-QPFEJUVJ.js.map +1 -0
- package/dist/yaml-patcher-VGUS2JGH.js +15 -0
- package/dist/yaml-patcher-VGUS2JGH.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildFlatRunbook,
|
|
4
|
+
copyVersionData,
|
|
5
|
+
insertSteps,
|
|
6
|
+
isDeletable,
|
|
7
|
+
isEditable,
|
|
8
|
+
loadVersionDetail,
|
|
9
|
+
resolveDisplayStatus
|
|
10
|
+
} from "./chunk-Z33FCOTZ.js";
|
|
11
|
+
import {
|
|
12
|
+
jobs,
|
|
13
|
+
runbookSecrets,
|
|
14
|
+
runbookSettings,
|
|
15
|
+
runbookSteps,
|
|
16
|
+
runbookVariables,
|
|
17
|
+
runbookVersionReviews,
|
|
18
|
+
runbookVersions,
|
|
19
|
+
runbooks
|
|
20
|
+
} from "./chunk-CLYJHKPY.js";
|
|
21
|
+
|
|
22
|
+
// src/server/store/runbook-store.ts
|
|
23
|
+
import { eq as eq2, and as and2, desc as desc2, sql as sql2, count as count2 } from "drizzle-orm";
|
|
24
|
+
|
|
25
|
+
// src/server/store/runbook-version-store.ts
|
|
26
|
+
import { eq, and, desc, max, inArray, isNull, count } from "drizzle-orm";
|
|
27
|
+
import { sql } from "drizzle-orm";
|
|
28
|
+
var VersionStore = class {
|
|
29
|
+
constructor(db) {
|
|
30
|
+
this.db = db;
|
|
31
|
+
}
|
|
32
|
+
// ── 作成 ──
|
|
33
|
+
/**
|
|
34
|
+
* 初期バージョン(v1)を作成する。
|
|
35
|
+
* steps ありなら pending_verification、なしなら draft。
|
|
36
|
+
*/
|
|
37
|
+
async createInitialVersion(tx, runbookId, data) {
|
|
38
|
+
const hasSteps = data.steps && data.steps.length > 0;
|
|
39
|
+
const status = hasSteps ? "pending_verification" : "draft";
|
|
40
|
+
const [verRow] = await tx.insert(runbookVersions).values({
|
|
41
|
+
runbookId,
|
|
42
|
+
versionNumber: 1,
|
|
43
|
+
status,
|
|
44
|
+
goal: data.goal,
|
|
45
|
+
startUrl: data.startUrl ?? null,
|
|
46
|
+
context: data.context ?? null,
|
|
47
|
+
generatedAt: data.generatedAt ?? null,
|
|
48
|
+
createdBy: data.createdBy ?? null,
|
|
49
|
+
changeDescription: data.changeDescription ?? null
|
|
50
|
+
}).returning();
|
|
51
|
+
if (data.settings) {
|
|
52
|
+
await tx.insert(runbookSettings).values({
|
|
53
|
+
versionId: verRow.id,
|
|
54
|
+
...data.settings
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (data.variables) {
|
|
58
|
+
const vars = Object.entries(data.variables).map(([name, def]) => ({
|
|
59
|
+
versionId: verRow.id,
|
|
60
|
+
name,
|
|
61
|
+
source: def.source,
|
|
62
|
+
description: def.description ?? null,
|
|
63
|
+
required: def.required,
|
|
64
|
+
sensitive: def.sensitive,
|
|
65
|
+
value: def.value ?? null
|
|
66
|
+
}));
|
|
67
|
+
if (vars.length > 0) {
|
|
68
|
+
await tx.insert(runbookVariables).values(vars);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (data.secrets) {
|
|
72
|
+
const secretRows = Object.entries(data.secrets).map(([key, val]) => ({
|
|
73
|
+
versionId: verRow.id,
|
|
74
|
+
key,
|
|
75
|
+
encryptedValue: val
|
|
76
|
+
}));
|
|
77
|
+
if (secretRows.length > 0) {
|
|
78
|
+
await tx.insert(runbookSecrets).values(secretRows);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (data.steps && data.steps.length > 0) {
|
|
82
|
+
await insertSteps(tx, verRow.id, data.steps, null);
|
|
83
|
+
}
|
|
84
|
+
return verRow;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* active バージョンからコピーして新 draft 作成。
|
|
88
|
+
* 同時に draft/pending_verification は 1 つまで。
|
|
89
|
+
*/
|
|
90
|
+
async createVersionFromActive(tenantId, runbookId, createdBy, changeDescription) {
|
|
91
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
92
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
|
|
93
|
+
});
|
|
94
|
+
if (!rb) return null;
|
|
95
|
+
const existingInProgress = await this.db.query.runbookVersions.findFirst({
|
|
96
|
+
where: and(
|
|
97
|
+
eq(runbookVersions.runbookId, runbookId),
|
|
98
|
+
inArray(runbookVersions.status, ["draft", "pending_verification"])
|
|
99
|
+
)
|
|
100
|
+
});
|
|
101
|
+
if (existingInProgress) {
|
|
102
|
+
return { error: "DRAFT_EXISTS", draftVersionId: existingInProgress.id };
|
|
103
|
+
}
|
|
104
|
+
return await this.db.transaction(async (tx) => {
|
|
105
|
+
const [maxResult] = await tx.select({ maxVer: max(runbookVersions.versionNumber) }).from(runbookVersions).where(eq(runbookVersions.runbookId, runbookId));
|
|
106
|
+
const nextVersion = (maxResult?.maxVer ?? 0) + 1;
|
|
107
|
+
let sourceVersionId = rb.activeVersionId;
|
|
108
|
+
if (!sourceVersionId) {
|
|
109
|
+
const latestVersion = await tx.query.runbookVersions.findFirst({
|
|
110
|
+
where: eq(runbookVersions.runbookId, runbookId),
|
|
111
|
+
orderBy: [desc(runbookVersions.versionNumber)]
|
|
112
|
+
});
|
|
113
|
+
if (latestVersion) {
|
|
114
|
+
sourceVersionId = latestVersion.id;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
let sourceVersion = null;
|
|
118
|
+
if (sourceVersionId) {
|
|
119
|
+
const [sv] = await tx.select().from(runbookVersions).where(eq(runbookVersions.id, sourceVersionId));
|
|
120
|
+
sourceVersion = sv ?? null;
|
|
121
|
+
}
|
|
122
|
+
const [newVer] = await tx.insert(runbookVersions).values({
|
|
123
|
+
runbookId,
|
|
124
|
+
versionNumber: nextVersion,
|
|
125
|
+
status: "draft",
|
|
126
|
+
goal: sourceVersion?.goal ?? "",
|
|
127
|
+
startUrl: sourceVersion?.startUrl ?? null,
|
|
128
|
+
context: sourceVersion?.context ?? null,
|
|
129
|
+
generatedAt: sourceVersion?.generatedAt ?? null,
|
|
130
|
+
createdBy: createdBy ?? null,
|
|
131
|
+
changeDescription: changeDescription ?? null
|
|
132
|
+
}).returning();
|
|
133
|
+
if (sourceVersionId) {
|
|
134
|
+
await copyVersionData(tx, sourceVersionId, newVer.id, { skipSteps: true });
|
|
135
|
+
}
|
|
136
|
+
return { version: newVer };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// ── 更新 ──
|
|
140
|
+
/**
|
|
141
|
+
* バージョン更新(isEditable ガードつき)
|
|
142
|
+
*/
|
|
143
|
+
async updateVersion(tenantId, runbookId, versionId, data) {
|
|
144
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
145
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
|
|
146
|
+
});
|
|
147
|
+
if (!rb) return null;
|
|
148
|
+
const [ver] = await this.db.select().from(runbookVersions).where(and(
|
|
149
|
+
eq(runbookVersions.id, versionId),
|
|
150
|
+
eq(runbookVersions.runbookId, runbookId)
|
|
151
|
+
));
|
|
152
|
+
if (!ver) return null;
|
|
153
|
+
if (!isEditable(ver.status)) return null;
|
|
154
|
+
return await this.db.transaction(async (tx) => {
|
|
155
|
+
const updates = {};
|
|
156
|
+
if (data.goal !== void 0) updates.goal = data.goal;
|
|
157
|
+
if (data.startUrl !== void 0) updates.startUrl = data.startUrl;
|
|
158
|
+
if (data.context !== void 0) updates.context = data.context;
|
|
159
|
+
if (data.changeDescription !== void 0) updates.changeDescription = data.changeDescription;
|
|
160
|
+
if (ver.status === "pending_verification" && ver.verificationJobId && !data.skipVerificationJobClear) {
|
|
161
|
+
updates.verificationJobId = null;
|
|
162
|
+
}
|
|
163
|
+
if (Object.keys(updates).length > 0) {
|
|
164
|
+
await tx.update(runbookVersions).set(updates).where(eq(runbookVersions.id, versionId));
|
|
165
|
+
}
|
|
166
|
+
if (data.settings !== void 0) {
|
|
167
|
+
await tx.delete(runbookSettings).where(eq(runbookSettings.versionId, versionId));
|
|
168
|
+
if (data.settings) {
|
|
169
|
+
await tx.insert(runbookSettings).values({ versionId, ...data.settings });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (data.secrets !== void 0) {
|
|
173
|
+
await tx.delete(runbookSecrets).where(eq(runbookSecrets.versionId, versionId));
|
|
174
|
+
const secretRows = Object.entries(data.secrets).map(([key, val]) => ({
|
|
175
|
+
versionId,
|
|
176
|
+
key,
|
|
177
|
+
encryptedValue: val
|
|
178
|
+
}));
|
|
179
|
+
if (secretRows.length > 0) {
|
|
180
|
+
await tx.insert(runbookSecrets).values(secretRows);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (data.variables !== void 0) {
|
|
184
|
+
await tx.delete(runbookVariables).where(eq(runbookVariables.versionId, versionId));
|
|
185
|
+
const vars = Object.entries(data.variables).map(([name, def]) => ({
|
|
186
|
+
versionId,
|
|
187
|
+
name,
|
|
188
|
+
source: def.source,
|
|
189
|
+
description: def.description ?? null,
|
|
190
|
+
required: def.required,
|
|
191
|
+
sensitive: def.sensitive,
|
|
192
|
+
value: def.value ?? null
|
|
193
|
+
}));
|
|
194
|
+
if (vars.length > 0) {
|
|
195
|
+
await tx.insert(runbookVariables).values(vars);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (data.steps !== void 0) {
|
|
199
|
+
await tx.delete(runbookSteps).where(eq(runbookSteps.versionId, versionId));
|
|
200
|
+
await insertSteps(tx, versionId, data.steps, null);
|
|
201
|
+
}
|
|
202
|
+
if (data.title !== void 0) {
|
|
203
|
+
await tx.update(runbooks).set({ title: data.title }).where(eq(runbooks.id, runbookId));
|
|
204
|
+
}
|
|
205
|
+
return await this.findById(tenantId, runbookId, versionId);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// ── 状態遷移 ──
|
|
209
|
+
/**
|
|
210
|
+
* draft → pending_verification
|
|
211
|
+
*/
|
|
212
|
+
async completeVersion(tenantId, runbookId, versionId, data) {
|
|
213
|
+
if (data) {
|
|
214
|
+
await this.updateVersion(tenantId, runbookId, versionId, data);
|
|
215
|
+
}
|
|
216
|
+
const [ver] = await this.db.select().from(runbookVersions).where(and(
|
|
217
|
+
eq(runbookVersions.id, versionId),
|
|
218
|
+
eq(runbookVersions.runbookId, runbookId)
|
|
219
|
+
));
|
|
220
|
+
if (!ver || ver.status !== "draft") return null;
|
|
221
|
+
await this.db.update(runbookVersions).set({ status: "pending_verification" }).where(eq(runbookVersions.id, versionId));
|
|
222
|
+
return await this.findById(tenantId, runbookId, versionId);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* pending_verification|pending_approval → active(旧 active を archived)
|
|
226
|
+
*/
|
|
227
|
+
async publishVersion(tenantId, runbookId, versionId, publishedBy, maxVersions) {
|
|
228
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
229
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
|
|
230
|
+
});
|
|
231
|
+
if (!rb) return null;
|
|
232
|
+
const [ver] = await this.db.select().from(runbookVersions).where(and(
|
|
233
|
+
eq(runbookVersions.id, versionId),
|
|
234
|
+
eq(runbookVersions.runbookId, runbookId)
|
|
235
|
+
));
|
|
236
|
+
if (!ver) return null;
|
|
237
|
+
if (ver.status !== "pending_verification" && ver.status !== "pending_approval") {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return await this.db.transaction(async (tx) => {
|
|
241
|
+
if (rb.activeVersionId) {
|
|
242
|
+
if (maxVersions <= 1) {
|
|
243
|
+
await tx.delete(runbookVersions).where(eq(runbookVersions.id, rb.activeVersionId));
|
|
244
|
+
} else {
|
|
245
|
+
await tx.update(runbookVersions).set({ status: "archived" }).where(eq(runbookVersions.id, rb.activeVersionId));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await tx.update(runbookVersions).set({
|
|
249
|
+
status: "active",
|
|
250
|
+
publishedAt: /* @__PURE__ */ new Date(),
|
|
251
|
+
publishedBy: publishedBy ?? null
|
|
252
|
+
}).where(eq(runbookVersions.id, versionId));
|
|
253
|
+
await tx.update(runbooks).set({ activeVersionId: versionId }).where(eq(runbooks.id, runbookId));
|
|
254
|
+
if (maxVersions > 1) {
|
|
255
|
+
const archivedVersions = await tx.select({ id: runbookVersions.id }).from(runbookVersions).where(and(
|
|
256
|
+
eq(runbookVersions.runbookId, runbookId),
|
|
257
|
+
eq(runbookVersions.status, "archived")
|
|
258
|
+
)).orderBy(desc(runbookVersions.versionNumber));
|
|
259
|
+
const maxArchived = maxVersions - 1;
|
|
260
|
+
if (archivedVersions.length > maxArchived) {
|
|
261
|
+
const toDelete = archivedVersions.slice(maxArchived);
|
|
262
|
+
for (const v of toDelete) {
|
|
263
|
+
await tx.delete(runbookVersions).where(eq(runbookVersions.id, v.id));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { status: "active" };
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* archived → active(現 active を archived)
|
|
272
|
+
*/
|
|
273
|
+
async rollbackVersion(tenantId, runbookId, versionId, publishedBy, _maxVersions) {
|
|
274
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
275
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
|
|
276
|
+
});
|
|
277
|
+
if (!rb) return null;
|
|
278
|
+
const [ver] = await this.db.select().from(runbookVersions).where(and(
|
|
279
|
+
eq(runbookVersions.id, versionId),
|
|
280
|
+
eq(runbookVersions.runbookId, runbookId)
|
|
281
|
+
));
|
|
282
|
+
if (!ver || ver.status !== "archived") return null;
|
|
283
|
+
return await this.db.transaction(async (tx) => {
|
|
284
|
+
if (rb.activeVersionId) {
|
|
285
|
+
await tx.update(runbookVersions).set({ status: "archived" }).where(eq(runbookVersions.id, rb.activeVersionId));
|
|
286
|
+
}
|
|
287
|
+
await tx.update(runbookVersions).set({
|
|
288
|
+
status: "active",
|
|
289
|
+
publishedAt: /* @__PURE__ */ new Date(),
|
|
290
|
+
publishedBy: publishedBy ?? null
|
|
291
|
+
}).where(eq(runbookVersions.id, versionId));
|
|
292
|
+
await tx.update(runbooks).set({ activeVersionId: versionId }).where(eq(runbooks.id, runbookId));
|
|
293
|
+
return { status: "active" };
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
// ── 検証ジョブ ──
|
|
297
|
+
async setVerificationJob(versionId, jobId) {
|
|
298
|
+
const result = await this.db.update(runbookVersions).set({ verificationJobId: jobId }).where(eq(runbookVersions.id, versionId)).returning({ id: runbookVersions.id });
|
|
299
|
+
return result.length > 0;
|
|
300
|
+
}
|
|
301
|
+
async clearVerificationJob(versionId) {
|
|
302
|
+
const result = await this.db.update(runbookVersions).set({ verificationJobId: null }).where(eq(runbookVersions.id, versionId)).returning({ id: runbookVersions.id });
|
|
303
|
+
return result.length > 0;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* バージョンのステータスを直接更新(internal use)
|
|
307
|
+
*/
|
|
308
|
+
async updateStatus(versionId, status) {
|
|
309
|
+
const result = await this.db.update(runbookVersions).set({ status }).where(eq(runbookVersions.id, versionId)).returning({ id: runbookVersions.id });
|
|
310
|
+
return result.length > 0;
|
|
311
|
+
}
|
|
312
|
+
// ── レビュー ──
|
|
313
|
+
async requestReview(versionId, requestedBy) {
|
|
314
|
+
const [row] = await this.db.insert(runbookVersionReviews).values({
|
|
315
|
+
versionId,
|
|
316
|
+
requestedBy
|
|
317
|
+
}).returning();
|
|
318
|
+
return row;
|
|
319
|
+
}
|
|
320
|
+
async submitReview(reviewId, reviewerId, status, comment) {
|
|
321
|
+
const result = await this.db.update(runbookVersionReviews).set({
|
|
322
|
+
reviewerId,
|
|
323
|
+
reviewedAt: /* @__PURE__ */ new Date(),
|
|
324
|
+
status,
|
|
325
|
+
comment: comment ?? null
|
|
326
|
+
}).where(eq(runbookVersionReviews.id, reviewId)).returning();
|
|
327
|
+
return result.length > 0;
|
|
328
|
+
}
|
|
329
|
+
async getReviews(versionId) {
|
|
330
|
+
return await this.db.query.runbookVersionReviews.findMany({
|
|
331
|
+
where: eq(runbookVersionReviews.versionId, versionId)
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// ── クエリ ──
|
|
335
|
+
/**
|
|
336
|
+
* バージョン詳細取得
|
|
337
|
+
*/
|
|
338
|
+
async findById(tenantId, runbookId, versionId) {
|
|
339
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
340
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId)),
|
|
341
|
+
columns: { id: true, activeVersionId: true }
|
|
342
|
+
});
|
|
343
|
+
if (!rb) return null;
|
|
344
|
+
const [ver] = await this.db.select().from(runbookVersions).where(and(
|
|
345
|
+
eq(runbookVersions.id, versionId),
|
|
346
|
+
eq(runbookVersions.runbookId, runbookId)
|
|
347
|
+
));
|
|
348
|
+
if (!ver) return null;
|
|
349
|
+
const detail = await loadVersionDetail(this.db, versionId);
|
|
350
|
+
let verificationJobStatus = null;
|
|
351
|
+
if (ver.verificationJobId) {
|
|
352
|
+
const [job] = await this.db.select({ status: jobs.status }).from(jobs).where(eq(jobs.id, ver.verificationJobId));
|
|
353
|
+
verificationJobStatus = job?.status ?? null;
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
...ver,
|
|
357
|
+
isActive: rb.activeVersionId === versionId,
|
|
358
|
+
stepsCount: detail.steps.filter((s) => !s.parentStepId).length,
|
|
359
|
+
verificationJobStatus,
|
|
360
|
+
displayStatus: resolveDisplayStatus(ver.status, ver.verificationJobId, verificationJobStatus),
|
|
361
|
+
...detail
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* バージョン一覧
|
|
366
|
+
*/
|
|
367
|
+
async list(tenantId, runbookId) {
|
|
368
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
369
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId)),
|
|
370
|
+
columns: { id: true, activeVersionId: true }
|
|
371
|
+
});
|
|
372
|
+
if (!rb) return null;
|
|
373
|
+
const versions = await this.db.select({
|
|
374
|
+
id: runbookVersions.id,
|
|
375
|
+
versionNumber: runbookVersions.versionNumber,
|
|
376
|
+
status: runbookVersions.status,
|
|
377
|
+
goal: runbookVersions.goal,
|
|
378
|
+
startUrl: runbookVersions.startUrl,
|
|
379
|
+
generatedAt: runbookVersions.generatedAt,
|
|
380
|
+
verificationJobId: runbookVersions.verificationJobId,
|
|
381
|
+
verificationJobStatus: sql`(
|
|
382
|
+
SELECT j.status FROM jobs j
|
|
383
|
+
WHERE j.id = ${runbookVersions.verificationJobId}
|
|
384
|
+
)`.as("verificationJobStatus"),
|
|
385
|
+
createdBy: runbookVersions.createdBy,
|
|
386
|
+
createdAt: runbookVersions.createdAt,
|
|
387
|
+
publishedAt: runbookVersions.publishedAt,
|
|
388
|
+
changeDescription: runbookVersions.changeDescription
|
|
389
|
+
}).from(runbookVersions).where(eq(runbookVersions.runbookId, runbookId)).orderBy(desc(runbookVersions.versionNumber));
|
|
390
|
+
if (versions.length === 0) return [];
|
|
391
|
+
const stepCounts = await this.db.select({
|
|
392
|
+
versionId: runbookSteps.versionId,
|
|
393
|
+
count: count()
|
|
394
|
+
}).from(runbookSteps).where(
|
|
395
|
+
and(
|
|
396
|
+
inArray(runbookSteps.versionId, versions.map((v) => v.id)),
|
|
397
|
+
isNull(runbookSteps.parentStepId)
|
|
398
|
+
)
|
|
399
|
+
).groupBy(runbookSteps.versionId);
|
|
400
|
+
const countMap = new Map(stepCounts.map((sc) => [sc.versionId, sc.count]));
|
|
401
|
+
return versions.map((v) => ({
|
|
402
|
+
...v,
|
|
403
|
+
stepsCount: countMap.get(v.id) ?? 0,
|
|
404
|
+
isActive: rb.activeVersionId === v.id,
|
|
405
|
+
displayStatus: resolveDisplayStatus(v.status, v.verificationJobId, v.verificationJobStatus)
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* バージョン削除(isDeletable ガードつき)
|
|
410
|
+
*/
|
|
411
|
+
async deleteVersion(tenantId, runbookId, versionId) {
|
|
412
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
413
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
|
|
414
|
+
});
|
|
415
|
+
if (!rb) return false;
|
|
416
|
+
const [ver] = await this.db.select().from(runbookVersions).where(and(
|
|
417
|
+
eq(runbookVersions.id, versionId),
|
|
418
|
+
eq(runbookVersions.runbookId, runbookId)
|
|
419
|
+
));
|
|
420
|
+
if (!ver || !isDeletable(ver.status)) return false;
|
|
421
|
+
await this.db.delete(runbookVersions).where(eq(runbookVersions.id, versionId));
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* ステップの部分更新
|
|
426
|
+
*/
|
|
427
|
+
async patchSteps(tenantId, runbookId, versionId, patches) {
|
|
428
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
429
|
+
where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
|
|
430
|
+
});
|
|
431
|
+
if (!rb) return false;
|
|
432
|
+
await this.db.transaction(async (tx) => {
|
|
433
|
+
for (const patch of patches) {
|
|
434
|
+
const updates = {};
|
|
435
|
+
if (patch.description !== void 0) updates.description = patch.description;
|
|
436
|
+
if (patch.riskLevel !== void 0) updates.riskLevel = patch.riskLevel;
|
|
437
|
+
if (patch.requiresConfirmation !== void 0) updates.requiresConfirmation = patch.requiresConfirmation;
|
|
438
|
+
if (Object.keys(updates).length > 0) {
|
|
439
|
+
await tx.update(runbookSteps).set(updates).where(and(eq(runbookSteps.id, patch.id), eq(runbookSteps.versionId, versionId)));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// src/server/store/runbook-store.ts
|
|
448
|
+
var RunbookStore = class {
|
|
449
|
+
constructor(db) {
|
|
450
|
+
this.db = db;
|
|
451
|
+
this.versions = new VersionStore(db);
|
|
452
|
+
}
|
|
453
|
+
// ── 作成 ──
|
|
454
|
+
/**
|
|
455
|
+
* Runbook + 初期バージョン(v1)を作成する。
|
|
456
|
+
* ParsedRunbook を含むフル作成。ステータスは pending_verification。
|
|
457
|
+
*/
|
|
458
|
+
async insert(data) {
|
|
459
|
+
const { tenantId, createdBy, runbook, secrets, context, settings } = data;
|
|
460
|
+
return await this.db.transaction(async (tx) => {
|
|
461
|
+
const [rbRow] = await tx.insert(runbooks).values({
|
|
462
|
+
tenantId,
|
|
463
|
+
title: runbook.title,
|
|
464
|
+
createdBy: createdBy ?? null
|
|
465
|
+
}).returning();
|
|
466
|
+
const verRow = await this.versions.createInitialVersion(tx, rbRow.id, {
|
|
467
|
+
goal: runbook.metadata.goal,
|
|
468
|
+
startUrl: runbook.metadata.startUrl,
|
|
469
|
+
context: context ?? null,
|
|
470
|
+
generatedAt: runbook.metadata.generatedAt,
|
|
471
|
+
createdBy: createdBy ?? null,
|
|
472
|
+
settings,
|
|
473
|
+
secrets,
|
|
474
|
+
variables: runbook.variables,
|
|
475
|
+
steps: runbook.steps
|
|
476
|
+
});
|
|
477
|
+
await tx.update(runbooks).set({ activeVersionId: verRow.id }).where(eq2(runbooks.id, rbRow.id));
|
|
478
|
+
return { ...rbRow, activeVersionId: verRow.id };
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Draft の Runbook + 初期 draft バージョンを作成する。
|
|
483
|
+
*/
|
|
484
|
+
async insertDraft(data) {
|
|
485
|
+
return await this.db.transaction(async (tx) => {
|
|
486
|
+
const [rbRow] = await tx.insert(runbooks).values({
|
|
487
|
+
tenantId: data.tenantId,
|
|
488
|
+
title: data.title,
|
|
489
|
+
createdBy: data.createdBy ?? null
|
|
490
|
+
}).returning();
|
|
491
|
+
const verRow = await this.versions.createInitialVersion(tx, rbRow.id, {
|
|
492
|
+
goal: data.goal ?? "",
|
|
493
|
+
startUrl: data.startUrl ?? null,
|
|
494
|
+
context: data.context ?? null,
|
|
495
|
+
createdBy: data.createdBy ?? null,
|
|
496
|
+
secrets: data.secrets,
|
|
497
|
+
variables: data.variables
|
|
498
|
+
});
|
|
499
|
+
return { ...rbRow, versionId: verRow.id };
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
// ── クエリ ──
|
|
503
|
+
/**
|
|
504
|
+
* Runbook 詳細取得(active バージョンの情報を含む)
|
|
505
|
+
* 旧 API との互換性を維持するためにフラット構造で返す。
|
|
506
|
+
*/
|
|
507
|
+
async findById(tenantId, id) {
|
|
508
|
+
const row = await this.db.query.runbooks.findFirst({
|
|
509
|
+
where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))
|
|
510
|
+
});
|
|
511
|
+
if (!row) return null;
|
|
512
|
+
const versionId = row.activeVersionId;
|
|
513
|
+
if (!versionId) {
|
|
514
|
+
const latestDraft = await this.db.query.runbookVersions.findFirst({
|
|
515
|
+
where: eq2(runbookVersions.runbookId, id),
|
|
516
|
+
orderBy: [desc2(runbookVersions.versionNumber)]
|
|
517
|
+
});
|
|
518
|
+
if (!latestDraft) {
|
|
519
|
+
const status = row.disabled ? "disabled" : "draft";
|
|
520
|
+
return {
|
|
521
|
+
...row,
|
|
522
|
+
goal: "",
|
|
523
|
+
startUrl: null,
|
|
524
|
+
context: null,
|
|
525
|
+
generatedAt: null,
|
|
526
|
+
status,
|
|
527
|
+
verificationJobId: null,
|
|
528
|
+
verificationJobStatus: null,
|
|
529
|
+
displayStatus: resolveDisplayStatus(status, null, null),
|
|
530
|
+
settings: null,
|
|
531
|
+
variables: [],
|
|
532
|
+
secrets: [],
|
|
533
|
+
steps: [],
|
|
534
|
+
activeVersionId: null,
|
|
535
|
+
currentVersionId: null
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return await buildFlatRunbook(this.db, row, latestDraft);
|
|
539
|
+
}
|
|
540
|
+
const [ver] = await this.db.select().from(runbookVersions).where(eq2(runbookVersions.id, versionId));
|
|
541
|
+
if (!ver) return null;
|
|
542
|
+
return await buildFlatRunbook(this.db, row, ver);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Runbook 一覧
|
|
546
|
+
*/
|
|
547
|
+
async list(tenantId, opts = {}) {
|
|
548
|
+
const { limit = 50, offset = 0, status, includeDisabled = false } = opts;
|
|
549
|
+
const conditions = [eq2(runbooks.tenantId, tenantId)];
|
|
550
|
+
if (status === "disabled") {
|
|
551
|
+
conditions.push(eq2(runbooks.disabled, true));
|
|
552
|
+
} else if (status) {
|
|
553
|
+
conditions.push(eq2(runbooks.disabled, false));
|
|
554
|
+
} else if (!includeDisabled) {
|
|
555
|
+
conditions.push(eq2(runbooks.disabled, false));
|
|
556
|
+
}
|
|
557
|
+
const where = and2(...conditions);
|
|
558
|
+
const rows = await this.db.select({
|
|
559
|
+
id: runbooks.id,
|
|
560
|
+
title: runbooks.title,
|
|
561
|
+
disabled: runbooks.disabled,
|
|
562
|
+
activeVersionId: runbooks.activeVersionId,
|
|
563
|
+
createdAt: runbooks.createdAt,
|
|
564
|
+
updatedAt: runbooks.updatedAt,
|
|
565
|
+
goal: sql2`COALESCE((
|
|
566
|
+
SELECT rv.goal FROM runbook_versions rv
|
|
567
|
+
WHERE rv.id = ${runbooks.activeVersionId}
|
|
568
|
+
), (
|
|
569
|
+
SELECT rv.goal FROM runbook_versions rv
|
|
570
|
+
WHERE rv.runbook_id = ${runbooks.id}
|
|
571
|
+
ORDER BY rv.version_number DESC LIMIT 1
|
|
572
|
+
), '')`.as("goal"),
|
|
573
|
+
startUrl: sql2`COALESCE((
|
|
574
|
+
SELECT rv.start_url FROM runbook_versions rv
|
|
575
|
+
WHERE rv.id = ${runbooks.activeVersionId}
|
|
576
|
+
), (
|
|
577
|
+
SELECT rv.start_url FROM runbook_versions rv
|
|
578
|
+
WHERE rv.runbook_id = ${runbooks.id}
|
|
579
|
+
ORDER BY rv.version_number DESC LIMIT 1
|
|
580
|
+
))`.as("startUrl"),
|
|
581
|
+
generatedAt: sql2`COALESCE((
|
|
582
|
+
SELECT rv.generated_at FROM runbook_versions rv
|
|
583
|
+
WHERE rv.id = ${runbooks.activeVersionId}
|
|
584
|
+
), (
|
|
585
|
+
SELECT rv.generated_at FROM runbook_versions rv
|
|
586
|
+
WHERE rv.runbook_id = ${runbooks.id}
|
|
587
|
+
ORDER BY rv.version_number DESC LIMIT 1
|
|
588
|
+
))`.as("generatedAt"),
|
|
589
|
+
versionStatus: sql2`COALESCE((
|
|
590
|
+
SELECT rv.status FROM runbook_versions rv
|
|
591
|
+
WHERE rv.id = ${runbooks.activeVersionId}
|
|
592
|
+
), (
|
|
593
|
+
SELECT rv.status FROM runbook_versions rv
|
|
594
|
+
WHERE rv.runbook_id = ${runbooks.id}
|
|
595
|
+
ORDER BY rv.version_number DESC LIMIT 1
|
|
596
|
+
))`.as("versionStatus"),
|
|
597
|
+
verificationJobId: sql2`COALESCE((
|
|
598
|
+
SELECT rv.verification_job_id FROM runbook_versions rv
|
|
599
|
+
WHERE rv.id = ${runbooks.activeVersionId}
|
|
600
|
+
), (
|
|
601
|
+
SELECT rv.verification_job_id FROM runbook_versions rv
|
|
602
|
+
WHERE rv.runbook_id = ${runbooks.id}
|
|
603
|
+
ORDER BY rv.version_number DESC LIMIT 1
|
|
604
|
+
))`.as("verificationJobId"),
|
|
605
|
+
stepsCount: sql2`COALESCE((
|
|
606
|
+
SELECT count(*)::int FROM runbook_steps rs
|
|
607
|
+
JOIN runbook_versions rv ON rv.id = rs.version_id
|
|
608
|
+
WHERE rv.id = COALESCE(${runbooks.activeVersionId}, (
|
|
609
|
+
SELECT rv2.id FROM runbook_versions rv2
|
|
610
|
+
WHERE rv2.runbook_id = ${runbooks.id}
|
|
611
|
+
ORDER BY rv2.version_number DESC LIMIT 1
|
|
612
|
+
))
|
|
613
|
+
AND rs.parent_step_id IS NULL
|
|
614
|
+
), 0)`.as("stepsCount"),
|
|
615
|
+
lastJobStatus: sql2`(
|
|
616
|
+
SELECT ${jobs.status} FROM ${jobs}
|
|
617
|
+
WHERE ${jobs.runbookId} = ${runbooks.id}
|
|
618
|
+
AND ${jobs.mode} IN ('execute', 'self_heal')
|
|
619
|
+
ORDER BY ${jobs.createdAt} DESC
|
|
620
|
+
LIMIT 1
|
|
621
|
+
)`.as("lastJobStatus"),
|
|
622
|
+
lastJobCompletedAt: sql2`(
|
|
623
|
+
SELECT ${jobs.completedAt} FROM ${jobs}
|
|
624
|
+
WHERE ${jobs.runbookId} = ${runbooks.id}
|
|
625
|
+
AND ${jobs.mode} IN ('execute', 'self_heal')
|
|
626
|
+
ORDER BY ${jobs.createdAt} DESC
|
|
627
|
+
LIMIT 1
|
|
628
|
+
)`.as("lastJobCompletedAt"),
|
|
629
|
+
verificationJobStatus: sql2`(
|
|
630
|
+
SELECT j.status FROM jobs j
|
|
631
|
+
WHERE j.id = COALESCE((
|
|
632
|
+
SELECT rv.verification_job_id FROM runbook_versions rv
|
|
633
|
+
WHERE rv.id = ${runbooks.activeVersionId}
|
|
634
|
+
), (
|
|
635
|
+
SELECT rv.verification_job_id FROM runbook_versions rv
|
|
636
|
+
WHERE rv.runbook_id = ${runbooks.id}
|
|
637
|
+
ORDER BY rv.version_number DESC LIMIT 1
|
|
638
|
+
))
|
|
639
|
+
)`.as("verificationJobStatus"),
|
|
640
|
+
versionCount: sql2`(
|
|
641
|
+
SELECT count(*)::int FROM runbook_versions rv
|
|
642
|
+
WHERE rv.runbook_id = ${runbooks.id}
|
|
643
|
+
)`.as("versionCount"),
|
|
644
|
+
hasDraft: sql2`EXISTS(
|
|
645
|
+
SELECT 1 FROM runbook_versions rv
|
|
646
|
+
WHERE rv.runbook_id = ${runbooks.id} AND rv.status = 'draft'
|
|
647
|
+
)`.as("hasDraft")
|
|
648
|
+
}).from(runbooks).where(where).orderBy(desc2(runbooks.createdAt)).limit(limit).offset(offset);
|
|
649
|
+
let filteredRows = rows;
|
|
650
|
+
if (status && status !== "disabled") {
|
|
651
|
+
const mappedStatus = status === "active" ? "active" : status === "pending_verification" ? "pending_verification" : status;
|
|
652
|
+
filteredRows = rows.filter((r) => r.versionStatus === mappedStatus);
|
|
653
|
+
}
|
|
654
|
+
const items = filteredRows.map((r) => {
|
|
655
|
+
const itemStatus = r.disabled ? "disabled" : r.versionStatus ?? "draft";
|
|
656
|
+
return {
|
|
657
|
+
id: r.id,
|
|
658
|
+
title: r.title,
|
|
659
|
+
goal: r.goal ?? "",
|
|
660
|
+
startUrl: r.startUrl,
|
|
661
|
+
generatedAt: r.generatedAt ?? "",
|
|
662
|
+
status: itemStatus,
|
|
663
|
+
verificationJobId: r.verificationJobId,
|
|
664
|
+
verificationJobStatus: r.verificationJobStatus,
|
|
665
|
+
displayStatus: resolveDisplayStatus(itemStatus, r.verificationJobId, r.verificationJobStatus),
|
|
666
|
+
createdAt: r.createdAt,
|
|
667
|
+
updatedAt: r.updatedAt,
|
|
668
|
+
stepsCount: r.stepsCount,
|
|
669
|
+
lastJobStatus: r.lastJobStatus,
|
|
670
|
+
lastJobCompletedAt: r.lastJobCompletedAt,
|
|
671
|
+
versionCount: r.versionCount,
|
|
672
|
+
hasDraft: r.hasDraft,
|
|
673
|
+
activeVersionId: r.activeVersionId
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
const [totalResult] = await this.db.select({ count: count2() }).from(runbooks).where(where);
|
|
677
|
+
return { items, total: totalResult.count };
|
|
678
|
+
}
|
|
679
|
+
// ── 更新 ──
|
|
680
|
+
/**
|
|
681
|
+
* Runbook の active バージョンを更新する(旧 API 互換)。
|
|
682
|
+
*/
|
|
683
|
+
async update(tenantId, id, data) {
|
|
684
|
+
const existing = await this.findById(tenantId, id);
|
|
685
|
+
if (!existing) return null;
|
|
686
|
+
const versionId = existing.currentVersionId;
|
|
687
|
+
if (!versionId) return null;
|
|
688
|
+
return await this.db.transaction(async (tx) => {
|
|
689
|
+
if (data.runbook) {
|
|
690
|
+
const rb = data.runbook;
|
|
691
|
+
const versionStatus = existing.status;
|
|
692
|
+
const statusReset = versionStatus === "active" && !data.skipStatusReset ? { status: "pending_verification", verificationJobId: null } : versionStatus === "draft" ? { status: "pending_verification" } : {};
|
|
693
|
+
await tx.update(runbookVersions).set({
|
|
694
|
+
goal: rb.metadata.goal,
|
|
695
|
+
startUrl: rb.metadata.startUrl,
|
|
696
|
+
context: data.context ?? existing.context,
|
|
697
|
+
generatedAt: rb.metadata.generatedAt,
|
|
698
|
+
...statusReset
|
|
699
|
+
}).where(eq2(runbookVersions.id, versionId));
|
|
700
|
+
await tx.update(runbooks).set({ title: rb.title }).where(eq2(runbooks.id, id));
|
|
701
|
+
const nextVariables = data.variables ?? rb.variables;
|
|
702
|
+
if (nextVariables !== void 0) {
|
|
703
|
+
await tx.delete(runbookVariables).where(eq2(runbookVariables.versionId, versionId));
|
|
704
|
+
const vars = Object.entries(nextVariables).map(([name, def]) => ({
|
|
705
|
+
versionId,
|
|
706
|
+
name,
|
|
707
|
+
source: def.source,
|
|
708
|
+
description: def.description ?? null,
|
|
709
|
+
required: def.required,
|
|
710
|
+
sensitive: def.sensitive,
|
|
711
|
+
value: def.value ?? null
|
|
712
|
+
}));
|
|
713
|
+
if (vars.length > 0) {
|
|
714
|
+
await tx.insert(runbookVariables).values(vars);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
await tx.delete(runbookSteps).where(eq2(runbookSteps.versionId, versionId));
|
|
718
|
+
await insertSteps(tx, versionId, rb.steps, null);
|
|
719
|
+
} else if (data.context !== void 0) {
|
|
720
|
+
await tx.update(runbookVersions).set({ context: data.context }).where(eq2(runbookVersions.id, versionId));
|
|
721
|
+
}
|
|
722
|
+
if (!data.runbook && data.variables !== void 0) {
|
|
723
|
+
await tx.delete(runbookVariables).where(eq2(runbookVariables.versionId, versionId));
|
|
724
|
+
const vars = Object.entries(data.variables).map(([name, def]) => ({
|
|
725
|
+
versionId,
|
|
726
|
+
name,
|
|
727
|
+
source: def.source,
|
|
728
|
+
description: def.description ?? null,
|
|
729
|
+
required: def.required,
|
|
730
|
+
sensitive: def.sensitive,
|
|
731
|
+
value: def.value ?? null
|
|
732
|
+
}));
|
|
733
|
+
if (vars.length > 0) {
|
|
734
|
+
await tx.insert(runbookVariables).values(vars);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (data.settings !== void 0) {
|
|
738
|
+
await tx.delete(runbookSettings).where(eq2(runbookSettings.versionId, versionId));
|
|
739
|
+
if (data.settings) {
|
|
740
|
+
await tx.insert(runbookSettings).values({ versionId, ...data.settings });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (data.secrets !== void 0) {
|
|
744
|
+
await tx.delete(runbookSecrets).where(eq2(runbookSecrets.versionId, versionId));
|
|
745
|
+
const secretRows = Object.entries(data.secrets).map(([key, val]) => ({
|
|
746
|
+
versionId,
|
|
747
|
+
key,
|
|
748
|
+
encryptedValue: val
|
|
749
|
+
}));
|
|
750
|
+
if (secretRows.length > 0) {
|
|
751
|
+
await tx.insert(runbookSecrets).values(secretRows);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return await this.findById(tenantId, id);
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* updateDraft: draft/pending_verification ステータスのバージョンを更新
|
|
759
|
+
*/
|
|
760
|
+
async updateDraft(tenantId, id, data) {
|
|
761
|
+
const existing = await this.findById(tenantId, id);
|
|
762
|
+
if (!existing) return null;
|
|
763
|
+
const versionId = existing.currentVersionId;
|
|
764
|
+
if (!versionId) return null;
|
|
765
|
+
if (!isEditable(existing.versionStatus)) return null;
|
|
766
|
+
const result = await this.versions.updateVersion(tenantId, id, versionId, {
|
|
767
|
+
...data
|
|
768
|
+
});
|
|
769
|
+
if (!result) return null;
|
|
770
|
+
return await this.findById(tenantId, id);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* completeDraft: draft → pending_verification
|
|
774
|
+
*/
|
|
775
|
+
async completeDraft(tenantId, id, data) {
|
|
776
|
+
const existing = await this.findById(tenantId, id);
|
|
777
|
+
if (!existing) return null;
|
|
778
|
+
const versionId = existing.currentVersionId;
|
|
779
|
+
if (!versionId) return null;
|
|
780
|
+
const result = await this.versions.completeVersion(tenantId, id, versionId, data);
|
|
781
|
+
if (!result) return null;
|
|
782
|
+
return await this.findById(tenantId, id);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* updateStatus: 旧 API 互換。disabled フラグとバージョンステータスを操作。
|
|
786
|
+
*/
|
|
787
|
+
async updateStatus(tenantId, id, status) {
|
|
788
|
+
if (status === "disabled") {
|
|
789
|
+
const result = await this.db.update(runbooks).set({ disabled: true }).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
|
|
790
|
+
return result.length > 0;
|
|
791
|
+
}
|
|
792
|
+
if (status === "active") {
|
|
793
|
+
const rb2 = await this.db.query.runbooks.findFirst({
|
|
794
|
+
where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))
|
|
795
|
+
});
|
|
796
|
+
if (!rb2) return false;
|
|
797
|
+
if (rb2.disabled) {
|
|
798
|
+
await this.db.update(runbooks).set({ disabled: false }).where(eq2(runbooks.id, id));
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
if (rb2.activeVersionId) {
|
|
802
|
+
const [ver] = await this.db.select().from(runbookVersions).where(eq2(runbookVersions.id, rb2.activeVersionId));
|
|
803
|
+
if (ver && ver.status === "pending_verification") {
|
|
804
|
+
await this.db.update(runbookVersions).set({ status: "active", publishedAt: /* @__PURE__ */ new Date() }).where(eq2(runbookVersions.id, rb2.activeVersionId));
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const latestVer = await this.db.query.runbookVersions.findFirst({
|
|
809
|
+
where: and2(
|
|
810
|
+
eq2(runbookVersions.runbookId, id),
|
|
811
|
+
eq2(runbookVersions.status, "pending_verification")
|
|
812
|
+
),
|
|
813
|
+
orderBy: [desc2(runbookVersions.versionNumber)]
|
|
814
|
+
});
|
|
815
|
+
if (latestVer) {
|
|
816
|
+
await this.db.update(runbookVersions).set({ status: "active", publishedAt: /* @__PURE__ */ new Date() }).where(eq2(runbookVersions.id, latestVer.id));
|
|
817
|
+
await this.db.update(runbooks).set({ activeVersionId: latestVer.id }).where(eq2(runbooks.id, id));
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
823
|
+
where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))
|
|
824
|
+
});
|
|
825
|
+
if (!rb) return false;
|
|
826
|
+
const targetVersionId = rb.activeVersionId;
|
|
827
|
+
if (!targetVersionId) {
|
|
828
|
+
const latestVer = await this.db.query.runbookVersions.findFirst({
|
|
829
|
+
where: eq2(runbookVersions.runbookId, id),
|
|
830
|
+
orderBy: [desc2(runbookVersions.versionNumber)]
|
|
831
|
+
});
|
|
832
|
+
if (latestVer) {
|
|
833
|
+
await this.db.update(runbookVersions).set({ status }).where(eq2(runbookVersions.id, latestVer.id));
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
await this.db.update(runbookVersions).set({ status }).where(eq2(runbookVersions.id, targetVersionId));
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* getStatus: 旧 API 互換
|
|
843
|
+
*/
|
|
844
|
+
async getStatus(tenantId, id) {
|
|
845
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
846
|
+
where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId)),
|
|
847
|
+
columns: { id: true, disabled: true, activeVersionId: true }
|
|
848
|
+
});
|
|
849
|
+
if (!rb) return null;
|
|
850
|
+
if (rb.disabled) return "disabled";
|
|
851
|
+
const verId = rb.activeVersionId;
|
|
852
|
+
if (verId) {
|
|
853
|
+
const [ver] = await this.db.select({ status: runbookVersions.status }).from(runbookVersions).where(eq2(runbookVersions.id, verId));
|
|
854
|
+
if (ver) return ver.status;
|
|
855
|
+
}
|
|
856
|
+
const latestVer = await this.db.query.runbookVersions.findFirst({
|
|
857
|
+
where: eq2(runbookVersions.runbookId, id),
|
|
858
|
+
orderBy: [desc2(runbookVersions.versionNumber)],
|
|
859
|
+
columns: { status: true }
|
|
860
|
+
});
|
|
861
|
+
return latestVer?.status ?? "draft";
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* setVerificationJob: 旧 API 互換
|
|
865
|
+
*/
|
|
866
|
+
async setVerificationJob(tenantId, runbookId, jobId) {
|
|
867
|
+
const rb = await this.db.query.runbooks.findFirst({
|
|
868
|
+
where: and2(eq2(runbooks.id, runbookId), eq2(runbooks.tenantId, tenantId))
|
|
869
|
+
});
|
|
870
|
+
if (!rb) return false;
|
|
871
|
+
const targetVer = await this.db.query.runbookVersions.findFirst({
|
|
872
|
+
where: eq2(runbookVersions.runbookId, runbookId),
|
|
873
|
+
orderBy: [desc2(runbookVersions.versionNumber)]
|
|
874
|
+
});
|
|
875
|
+
if (!targetVer) return false;
|
|
876
|
+
return await this.versions.setVerificationJob(targetVer.id, jobId);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* clearVerificationJob: 旧 API 互換
|
|
880
|
+
*/
|
|
881
|
+
async clearVerificationJob(tenantId, runbookId) {
|
|
882
|
+
const targetVer = await this.db.query.runbookVersions.findFirst({
|
|
883
|
+
where: eq2(runbookVersions.runbookId, runbookId),
|
|
884
|
+
orderBy: [desc2(runbookVersions.versionNumber)]
|
|
885
|
+
});
|
|
886
|
+
if (!targetVer) return false;
|
|
887
|
+
return await this.versions.clearVerificationJob(targetVer.id);
|
|
888
|
+
}
|
|
889
|
+
// ── ライフサイクル ──
|
|
890
|
+
/**
|
|
891
|
+
* duplicate: Runbook をコピーする(active バージョンから draft 作成)
|
|
892
|
+
*/
|
|
893
|
+
async duplicate(tenantId, sourceId, createdBy) {
|
|
894
|
+
const source = await this.findById(tenantId, sourceId);
|
|
895
|
+
if (!source) return null;
|
|
896
|
+
return await this.db.transaction(async (tx) => {
|
|
897
|
+
const [newRb] = await tx.insert(runbooks).values({
|
|
898
|
+
tenantId,
|
|
899
|
+
title: `${source.title} (Copy)`,
|
|
900
|
+
createdBy: createdBy ?? null
|
|
901
|
+
}).returning();
|
|
902
|
+
const verRow = await this.versions.createInitialVersion(tx, newRb.id, {
|
|
903
|
+
goal: source.goal,
|
|
904
|
+
startUrl: source.startUrl,
|
|
905
|
+
context: source.context,
|
|
906
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
907
|
+
createdBy: createdBy ?? null
|
|
908
|
+
});
|
|
909
|
+
if (source.currentVersionId) {
|
|
910
|
+
const { copyVersionData: copyVersionData2 } = await import("./runbook-data-helpers-KRR2SH76.js");
|
|
911
|
+
await copyVersionData2(tx, source.currentVersionId, verRow.id);
|
|
912
|
+
}
|
|
913
|
+
return { id: newRb.id };
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* patchSteps: ステップの部分更新(最新バージョン対象)
|
|
918
|
+
*/
|
|
919
|
+
async patchSteps(tenantId, runbookId, patches) {
|
|
920
|
+
const targetVer = await this.db.query.runbookVersions.findFirst({
|
|
921
|
+
where: eq2(runbookVersions.runbookId, runbookId),
|
|
922
|
+
orderBy: [desc2(runbookVersions.versionNumber)]
|
|
923
|
+
});
|
|
924
|
+
if (!targetVer) return false;
|
|
925
|
+
return await this.versions.patchSteps(tenantId, runbookId, targetVer.id, patches);
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* delete: Runbook を削除(CASCADE で全バージョン・子テーブルも削除)
|
|
929
|
+
*/
|
|
930
|
+
async delete(tenantId, id) {
|
|
931
|
+
const result = await this.db.delete(runbooks).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
|
|
932
|
+
return result.length > 0;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* disable: Runbook を無効化
|
|
936
|
+
*/
|
|
937
|
+
async disable(tenantId, id) {
|
|
938
|
+
const result = await this.db.update(runbooks).set({ disabled: true }).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
|
|
939
|
+
return result.length > 0;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* enable: Runbook を有効化
|
|
943
|
+
*/
|
|
944
|
+
async enable(tenantId, id) {
|
|
945
|
+
const result = await this.db.update(runbooks).set({ disabled: false }).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
|
|
946
|
+
return result.length > 0;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
export {
|
|
951
|
+
RunbookStore
|
|
952
|
+
};
|
|
953
|
+
//# sourceMappingURL=chunk-WEYR56ZN.js.map
|