@mawaru/sdk 0.40.0 → 0.41.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/_schemas/graph.ts +0 -5
- package/_schemas/human-manifest.test.ts +39 -101
- package/_schemas/human-manifest.ts +25 -152
- package/_schemas/port-spec.test.ts +33 -92
- package/_schemas/port-spec.ts +35 -44
- package/dist/_cli/login.js +15 -1
- package/dist/_cli/previewApi.d.ts +1 -1
- package/dist/_cli/previewCommand.d.ts +0 -8
- package/dist/_cli/previewCommand.js +37 -95
- package/dist/_cli/previewFiles.d.ts +1 -2
- package/dist/_cli/previewFiles.js +18 -42
- package/dist/_schemas/graph.d.ts +0 -15
- package/dist/_schemas/graph.js +0 -5
- package/dist/_schemas/human-manifest.d.ts +6 -24
- package/dist/_schemas/human-manifest.js +24 -116
- package/dist/_schemas/port-spec.d.ts +1 -4
- package/dist/_schemas/port-spec.js +25 -34
- package/dist/cli.js +1 -2
- package/dist/typegen.d.ts +2 -2
- package/dist/typegen.js +21 -21
- package/dist/validate.js +9 -7
- package/dist/views/_humanProps.d.ts +10 -0
- package/dist/views/_humanProps.js +11 -0
- package/dist/views/{index.d.ts → components.d.ts} +0 -1
- package/dist/views/components.js +12 -0
- package/dist/views/host.d.ts +8 -3
- package/dist/views/host.js +5 -5
- package/dist/views/human.d.ts +49 -68
- package/dist/views/human.js +5 -6
- package/dist/views/protocol.d.ts +13 -3
- package/dist/views/protocol.js +2 -2
- package/dist/views/runtime.d.ts +2 -3
- package/dist/views/runtime.js +24 -24
- package/dist/views/sample.d.ts +22 -0
- package/dist/views/sample.js +38 -0
- package/docs/development.md +30 -31
- package/package.json +5 -1
- package/skills/create-loop/SKILL.md +3 -2
- package/templates/CLAUDE.md +2 -0
- package/views/_humanProps.ts +27 -0
- package/views/components.ts +16 -0
- package/views/host.ts +9 -5
- package/views/human.ts +71 -79
- package/views/protocol.ts +29 -6
- package/views/runtime.tsx +36 -45
- package/views/sample.tsx +94 -0
- package/dist/_cli/openBrowser.d.ts +0 -1
- package/dist/_cli/openBrowser.js +0 -15
- package/dist/views/_approval/ApprovalBar.d.ts +0 -20
- package/dist/views/_approval/ApprovalBar.js +0 -74
- package/dist/views/_approvalActions.d.ts +0 -18
- package/dist/views/_approvalActions.js +0 -10
- package/dist/views/index.js +0 -12
- package/views/_approval/ApprovalBar.tsx +0 -239
- package/views/_approvalActions.tsx +0 -31
- package/views/index.ts +0 -16
package/_schemas/port-spec.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
|
-
import type { HumanDeclaration } from "./human-manifest.js"
|
|
3
2
|
import type { GraphPort, JsonSchema, PortIo } from "./node.js"
|
|
4
3
|
|
|
5
4
|
// ポートの kind 別型システム(docs/tasks/done/ポートのkind別仕様.md)。
|
|
6
|
-
// 型の決まり方は2分類:宣言(program / ai の in・out
|
|
5
|
+
// 型の決まり方は2分類:宣言(program / ai と、実装(nodes/human/<dir>/)を指す human の in・out)/
|
|
7
6
|
// 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
|
|
8
7
|
// mawaru がコードで型を固定するポートは無い
|
|
9
8
|
// (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
@@ -266,9 +265,10 @@ export const kindPortsViolation = (
|
|
|
266
265
|
// ---- 型伝播(導出ポートの解決) ----
|
|
267
266
|
// 接続の両端は同じスキーマになる(値入力フォームのSchemaForm統一.md)。
|
|
268
267
|
// wait は素通し(パラメトリック)ノード:in は接続元の out、out は in と同型。
|
|
269
|
-
// human は実装(nodes/human
|
|
270
|
-
//
|
|
271
|
-
// program / ai
|
|
268
|
+
// human は実装(nodes/human/<dir>/)を指していれば program / ai と同じ宣言ノード、指していなければ
|
|
269
|
+
// (標準の承認画面)wait と同じ素通し(approve は in と同型、reject は標準の差し戻し instructionSchema)。
|
|
270
|
+
// 宣言 out(program / ai / 実装を指す human)は触らない。
|
|
271
|
+
// program / ai(と実装を指す human)の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
|
|
272
272
|
// 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
|
|
273
273
|
// config.json へも書き戻される。docs/tasks/done/ポート編集のrepo書き戻し同期.md)
|
|
274
274
|
|
|
@@ -329,19 +329,11 @@ type PropagationNode = {
|
|
|
329
329
|
id: string
|
|
330
330
|
kind: string
|
|
331
331
|
ports: GraphPort[]
|
|
332
|
-
// human のときだけ意味を持つ:実装(nodes/human/<dir
|
|
333
|
-
|
|
334
|
-
human?: { declaration?: HumanDeclaration } | null
|
|
332
|
+
// human のときだけ意味を持つ:実装(nodes/human/<dir>/)を指しているか
|
|
333
|
+
human?: { human_dir?: string | null } | null
|
|
335
334
|
}
|
|
336
335
|
type PortLink = { from_port_id: string; to_port_id: string }
|
|
337
336
|
|
|
338
|
-
// 「上流の out をそのままコピーする」(in)/「in と同型」(approve)を表す指示。
|
|
339
|
-
// 承認ビューのカタログ(@mawaru/common)の COPY_UPSTREAM と同じ値
|
|
340
|
-
export const COPY_UPSTREAM_SCHEMA = "T"
|
|
341
|
-
|
|
342
|
-
// ポートの型宣言:具体型か、上流からのコピー指示か
|
|
343
|
-
export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA
|
|
344
|
-
|
|
345
337
|
// end も導出ノード(各 in は上流から、out main は未宣言)。個別処理は
|
|
346
338
|
// resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
|
|
347
339
|
// start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
|
|
@@ -352,10 +344,22 @@ const DERIVED_KINDS = new Set(["wait", "human", "end", "start", "join"])
|
|
|
352
344
|
// in を接続鏡映(sticky)するノード:接続元 out が具体型のときだけ上書き
|
|
353
345
|
const MIRROR_KINDS = new Set(["program", "ai"])
|
|
354
346
|
|
|
355
|
-
|
|
356
|
-
|
|
347
|
+
// 実装(nodes/human/<dir>/)を指す human は program / ai と同じ(in は鏡映・out は宣言。
|
|
348
|
+
// 型は config.json へ書き戻される)。指していない human は導出(標準の承認画面の素通し)
|
|
349
|
+
const isMirrorNode = (node: PropagationNode): boolean =>
|
|
350
|
+
MIRROR_KINDS.has(node.kind) ||
|
|
351
|
+
(node.kind === "human" && Boolean(node.human?.human_dir))
|
|
352
|
+
|
|
353
|
+
const isDerivedNode = (node: PropagationNode): boolean =>
|
|
354
|
+
DERIVED_KINDS.has(node.kind) && !isMirrorNode(node)
|
|
355
|
+
|
|
356
|
+
const isDerivedPort = (
|
|
357
|
+
node: PropagationNode,
|
|
358
|
+
port: Pick<GraphPort, "io" | "key">,
|
|
359
|
+
) =>
|
|
360
|
+
isDerivedNode(node) &&
|
|
357
361
|
(port.io === "in" ||
|
|
358
|
-
(kind !== "human" && port.key === "main") ||
|
|
362
|
+
(node.kind !== "human" && port.key === "main") ||
|
|
359
363
|
port.key === "approve" ||
|
|
360
364
|
port.key === "reject")
|
|
361
365
|
|
|
@@ -375,7 +379,7 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
375
379
|
kindByPortId.set(port.id, node.kind)
|
|
376
380
|
resolved.set(
|
|
377
381
|
port.id,
|
|
378
|
-
isDerivedPort(node
|
|
382
|
+
isDerivedPort(node, port) ? UNDECLARED_SCHEMA : port.schema,
|
|
379
383
|
)
|
|
380
384
|
}
|
|
381
385
|
}
|
|
@@ -466,9 +470,9 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
466
470
|
}
|
|
467
471
|
continue
|
|
468
472
|
}
|
|
469
|
-
const isMirror =
|
|
470
|
-
if (!
|
|
471
|
-
// 鏡映(program / ai):全ての in が対象。具体型が流れてきたときだけ上書きする
|
|
473
|
+
const isMirror = isMirrorNode(node)
|
|
474
|
+
if (!isDerivedNode(node) && !isMirror) continue
|
|
475
|
+
// 鏡映(program / ai / 実装を指す human):全ての in が対象。具体型が流れてきたときだけ上書きする
|
|
472
476
|
// (宣言を未宣言で潰さない)。ai の in はどの口も対等(instruction の特別扱いは無い)
|
|
473
477
|
if (isMirror) {
|
|
474
478
|
for (const port of node.ports) {
|
|
@@ -483,37 +487,24 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
483
487
|
}
|
|
484
488
|
continue
|
|
485
489
|
}
|
|
486
|
-
// 導出(wait / human):in
|
|
490
|
+
// 導出(wait / 実装を指さない human):in は上流から、out(main・approve)は in と同型。
|
|
491
|
+
// human の reject は標準の承認画面の差し戻し(指示+参考ファイル)。
|
|
492
|
+
// 上流の out は書き換えない(逆伝播すると fan-out 先の型割れが config.json へ書き戻され、
|
|
493
|
+
// 元の出力宣言を黙って壊す。ズレは接続の型エラーで見せる)
|
|
487
494
|
const inPort = node.ports.find((p) => p.io === "in")
|
|
488
495
|
if (!inPort) continue
|
|
489
496
|
const sources = inbound.get(inPort.id) ?? []
|
|
490
497
|
const inSchema = mergeInboundSchemas(
|
|
491
498
|
sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA),
|
|
492
499
|
)
|
|
493
|
-
|
|
494
|
-
// "T" は「上流の out をコピー」(in)/「in と同型」(approve)。reject は具体型の宣言
|
|
495
|
-
// のみで、宣言が無ければ未宣言のまま(mawaru が型を焼き込むことはしない)。
|
|
496
|
-
// 上流の out は書き換えない:逆伝播すると fan-out 先のビューが食い違ったとき
|
|
497
|
-
// 型割れが config.json へ write-through され、元の出力宣言を黙って壊す。
|
|
498
|
-
// ズレは接続の型エラーで見せてユーザーの [修正する] で直す
|
|
499
|
-
const declared =
|
|
500
|
-
node.kind === "human" ? node.human?.declaration : undefined
|
|
501
|
-
const effectiveIn =
|
|
502
|
-
declared && declared.in !== COPY_UPSTREAM_SCHEMA
|
|
503
|
-
? declared.in
|
|
504
|
-
: inSchema
|
|
505
|
-
assign(inPort.id, effectiveIn)
|
|
500
|
+
assign(inPort.id, inSchema)
|
|
506
501
|
for (const port of node.ports) {
|
|
507
|
-
if (port.io !== "out" || !isDerivedPort(node
|
|
508
|
-
if (node.kind === "human" && port.key === "reject") {
|
|
509
|
-
if (declared) assign(port.id, declared.reject)
|
|
510
|
-
continue
|
|
511
|
-
}
|
|
502
|
+
if (port.io !== "out" || !isDerivedPort(node, port)) continue
|
|
512
503
|
assign(
|
|
513
504
|
port.id,
|
|
514
|
-
|
|
515
|
-
?
|
|
516
|
-
:
|
|
505
|
+
node.kind === "human" && port.key === "reject"
|
|
506
|
+
? instructionSchema()
|
|
507
|
+
: inSchema,
|
|
517
508
|
)
|
|
518
509
|
}
|
|
519
510
|
}
|
package/dist/_cli/login.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { createServer } from "node:http";
|
|
3
4
|
import { saveCredentials } from "./credentials.js";
|
|
4
|
-
import { openBrowser } from "./openBrowser.js";
|
|
5
5
|
// `gh auth login` と同じループバック方式(RFC 8252)。
|
|
6
6
|
// CLI が 127.0.0.1 の一時ポートで待ち受け、ブラウザで mawaru の /cli-auth を開く。
|
|
7
7
|
// 承認するとブラウザがループバック URL へ**遷移**してトークンを渡す
|
|
@@ -11,6 +11,20 @@ const page = (title, message) => `<!doctype html><meta charset="utf-8"><title>${
|
|
|
11
11
|
`<body style="font-family:system-ui;display:grid;place-items:center;height:100vh;margin:0">` +
|
|
12
12
|
`<div style="text-align:center"><h1 style="font-size:1.25rem">${title}</h1>` +
|
|
13
13
|
`<p style="color:#666">${message}</p></div></body>`;
|
|
14
|
+
// ブラウザを開く(開けなければ URL を出して手で開いてもらう。依存は足さない)
|
|
15
|
+
const openBrowser = (url) => {
|
|
16
|
+
const [command, args] = process.platform === "darwin"
|
|
17
|
+
? ["open", [url]]
|
|
18
|
+
: process.platform === "win32"
|
|
19
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
20
|
+
: ["xdg-open", [url]];
|
|
21
|
+
try {
|
|
22
|
+
spawn(command, args, { stdio: "ignore", detached: true }).unref();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// 何もしない(URL は既に印字済み)
|
|
26
|
+
}
|
|
27
|
+
};
|
|
14
28
|
export const runLogin = async (timeoutMs = 5 * 60 * 1000) => {
|
|
15
29
|
// 別のローカルプロセスが投げ込んだ応答を受け付けないための照合値
|
|
16
30
|
const state = randomUUID();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Credentials } from "./credentials.js";
|
|
2
|
-
export type MawaruApi = (method: "GET" | "POST"
|
|
2
|
+
export type MawaruApi = (method: "GET" | "POST", path: string, body?: unknown) => Promise<{
|
|
3
3
|
status: number;
|
|
4
4
|
body: unknown;
|
|
5
5
|
}>;
|
|
@@ -5,22 +5,14 @@ export type PreviewOptions = {
|
|
|
5
5
|
case?: string;
|
|
6
6
|
surface?: SlackSurface;
|
|
7
7
|
channel?: string;
|
|
8
|
-
once?: boolean;
|
|
9
8
|
};
|
|
10
9
|
export declare const parsePreviewArgs: (args: string[]) => PreviewOptions;
|
|
11
10
|
type Log = (line: string) => void;
|
|
12
|
-
export type PreviewEnv = {
|
|
13
|
-
tenantId: string;
|
|
14
|
-
appUrl: string;
|
|
15
|
-
openUrl: (url: string) => void;
|
|
16
|
-
watch: (dir: string, onChange: () => Promise<void>) => Promise<void>;
|
|
17
|
-
};
|
|
18
11
|
export declare const runPreviewWith: (params: {
|
|
19
12
|
root: string;
|
|
20
13
|
options: PreviewOptions;
|
|
21
14
|
api: MawaruApi;
|
|
22
15
|
log: Log;
|
|
23
|
-
env?: Partial<PreviewEnv>;
|
|
24
16
|
}) => Promise<number>;
|
|
25
17
|
export declare const runPreview: (args: string[]) => Promise<number>;
|
|
26
18
|
export {};
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { requireCredentials } from "./credentials.js";
|
|
5
|
-
import { openBrowser } from "./openBrowser.js";
|
|
6
5
|
import { expectOk, mawaruApiFor } from "./previewApi.js";
|
|
7
|
-
import {
|
|
6
|
+
import { readViewFiles, resolveView } from "./previewFiles.js";
|
|
8
7
|
import { previewSlack, SLACK_SURFACES, } from "./previewSlack.js";
|
|
9
|
-
// `mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>]
|
|
8
|
+
// `mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>]`
|
|
10
9
|
// (実行 repo のルートで実行)
|
|
11
10
|
//
|
|
12
11
|
// views/<種類>/<名前>/ のビューを、push せずに手元のファイルのまま確かめる。描画は mawaru API がやるので、
|
|
13
12
|
// `mawaru login` のセッション(web と同じ membership)と、どのテナントのつもりで描くかの
|
|
14
13
|
// MAWARU_TENANT_ID(repo の .env。create-loop スキルと同じ)が要る。
|
|
15
|
-
//
|
|
16
|
-
// (Slack
|
|
17
|
-
// (docs/tasks/done/Slackビューの顧客repo配置.md
|
|
18
|
-
const USAGE = "使い方: npx mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|チャンネル ID>]
|
|
19
|
-
" 例: npx mawaru preview slack/pr-review(views/slack/pr-review/ の cases.ts の全ケースを Slack
|
|
20
|
-
" npx mawaru preview human/invoice-review(承認画面で開き、保存するたびに送り直す。--once は1回送って終わる)";
|
|
14
|
+
// ファイルの集め方・cases.ts の評価・ケースの絞り込みは種類をまたいで共通で、種類ごとに「確かめ方」
|
|
15
|
+
// (Slack は送る)だけを差し替える
|
|
16
|
+
// (docs/tasks/done/Slackビューの顧客repo配置.md の「手元プレビュー」)
|
|
17
|
+
const USAGE = "使い方: npx mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|チャンネル ID>]\n" +
|
|
18
|
+
" 例: npx mawaru preview slack/pr-review(views/slack/pr-review/ の cases.ts の全ケースを Slack に送る)";
|
|
21
19
|
export const parsePreviewArgs = (args) => {
|
|
22
20
|
const { values, positionals } = parseArgs({
|
|
23
21
|
args,
|
|
@@ -26,7 +24,6 @@ export const parsePreviewArgs = (args) => {
|
|
|
26
24
|
case: { type: "string" },
|
|
27
25
|
surface: { type: "string" },
|
|
28
26
|
channel: { type: "string" },
|
|
29
|
-
once: { type: "boolean" },
|
|
30
27
|
},
|
|
31
28
|
});
|
|
32
29
|
const [target, ...extra] = positionals;
|
|
@@ -43,12 +40,35 @@ export const parsePreviewArgs = (args) => {
|
|
|
43
40
|
? { surface: values.surface }
|
|
44
41
|
: {}),
|
|
45
42
|
...(values.channel !== undefined ? { channel: values.channel } : {}),
|
|
46
|
-
...(values.once ? { once: true } : {}),
|
|
47
43
|
};
|
|
48
44
|
};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
const PREVIEWERS = {
|
|
46
|
+
slack: {
|
|
47
|
+
casesPath: "/views/slack/cases",
|
|
48
|
+
run: ({ name, files, cases, options, api, log }) => {
|
|
49
|
+
const channel = options.channel ?? process.env.MAWARU_PREVIEW_CHANNEL;
|
|
50
|
+
if (!channel) {
|
|
51
|
+
throw new Error("送り先のチャンネルを --channel か repo の .env の MAWARU_PREVIEW_CHANNEL で指定してください(#名前 かチャンネル ID)");
|
|
52
|
+
}
|
|
53
|
+
return previewSlack({
|
|
54
|
+
name,
|
|
55
|
+
files,
|
|
56
|
+
cases: cases,
|
|
57
|
+
surfaces: options.surface ? [options.surface] : SLACK_SURFACES,
|
|
58
|
+
channel,
|
|
59
|
+
api,
|
|
60
|
+
log,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
// 終了コードを返す(描画に失敗したものがあれば 1)。テストはここを呼ぶ
|
|
66
|
+
export const runPreviewWith = async (params) => {
|
|
67
|
+
const { root, options, api, log } = params;
|
|
68
|
+
const view = resolveView(root, options.target);
|
|
69
|
+
const files = readViewFiles(root, view);
|
|
70
|
+
const previewer = PREVIEWERS[view.kind];
|
|
71
|
+
const { cases } = expectOk("ケースの読み込み", await api("POST", previewer.casesPath, { files }));
|
|
52
72
|
const picked = options.case === undefined
|
|
53
73
|
? cases
|
|
54
74
|
: cases.filter((c) => c.name === options.case);
|
|
@@ -57,92 +77,15 @@ const previewSlackView = async ({ view, files, options, api, log, }) => {
|
|
|
57
77
|
? "cases.ts にケースがありません"
|
|
58
78
|
: `ケース「${options.case}」がありません(ある: ${cases.map((c) => c.name).join(", ")})`);
|
|
59
79
|
}
|
|
60
|
-
const
|
|
61
|
-
if (!channel) {
|
|
62
|
-
throw new Error("送り先のチャンネルを --channel か repo の .env の MAWARU_PREVIEW_CHANNEL で指定してください(#名前 かチャンネル ID)");
|
|
63
|
-
}
|
|
64
|
-
return previewSlack({
|
|
80
|
+
const failed = await previewer.run({
|
|
65
81
|
name: view.name,
|
|
66
82
|
files,
|
|
67
83
|
cases: picked,
|
|
68
|
-
surfaces: options.surface ? [options.surface] : SLACK_SURFACES,
|
|
69
|
-
channel,
|
|
70
|
-
api,
|
|
71
|
-
log,
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
// human ノードの実装(承認画面):ファイルを mawaru に置いて、本番と同じ承認画面(sandbox iframe)で開く。
|
|
75
|
-
// cases.ts の評価は画面を開いたときに mawaru がやる。保存するたびに置き直し、開いている画面が読み直す
|
|
76
|
-
const previewHuman = async ({ root, view, files, options, api, log, env, }) => {
|
|
77
|
-
const created = expectOk("プレビューの用意", await api("POST", "/previews", {
|
|
78
|
-
kind: "human",
|
|
79
|
-
name: view.name,
|
|
80
|
-
files,
|
|
81
|
-
}));
|
|
82
|
-
if (created.error) {
|
|
83
|
-
log(`✘ human/${view.name} をまとめられません\n${created.error}`);
|
|
84
|
-
return 1;
|
|
85
|
-
}
|
|
86
|
-
const url = `${env.appUrl.replace(/\/$/, "")}/previews/${created.id}?tenant=${env.tenantId}`;
|
|
87
|
-
log(`✔ human/${view.name} を開きます: ${url}`);
|
|
88
|
-
env.openUrl(url);
|
|
89
|
-
if (options.once)
|
|
90
|
-
return 0;
|
|
91
|
-
log("保存するたびに送り直します(Ctrl-C で終わる)");
|
|
92
|
-
await env.watch(kindRoot(root, "human"), async () => {
|
|
93
|
-
const res = await api("PUT", `/previews/${created.id}`, {
|
|
94
|
-
files: readViewFiles(root, view),
|
|
95
|
-
});
|
|
96
|
-
if (res.status >= 300) {
|
|
97
|
-
log(`✘ 送り直せませんでした(${res.status})`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const { error } = res.body;
|
|
101
|
-
log(error ? `✘ まとめられません\n${error}` : "✔ 送り直しました");
|
|
102
|
-
});
|
|
103
|
-
return 0;
|
|
104
|
-
};
|
|
105
|
-
const PREVIEWERS = {
|
|
106
|
-
slack: previewSlackView,
|
|
107
|
-
human: previewHuman,
|
|
108
|
-
};
|
|
109
|
-
// 保存の監視:まとめて保存されることがあるので少し待ってから1回送る
|
|
110
|
-
const watchFiles = (dir, onChange) => new Promise((resolve) => {
|
|
111
|
-
let timer;
|
|
112
|
-
let running = Promise.resolve();
|
|
113
|
-
const watcher = watch(dir, { recursive: true }, () => {
|
|
114
|
-
clearTimeout(timer);
|
|
115
|
-
timer = setTimeout(() => {
|
|
116
|
-
running = running.then(onChange);
|
|
117
|
-
}, 300);
|
|
118
|
-
});
|
|
119
|
-
process.once("SIGINT", () => {
|
|
120
|
-
watcher.close();
|
|
121
|
-
resolve();
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
// 終了コードを返す(描画・まとめに失敗したものがあれば 1)。テストはここを呼ぶ
|
|
125
|
-
export const runPreviewWith = async (params) => {
|
|
126
|
-
const { root, options, api, log } = params;
|
|
127
|
-
const view = resolveView(root, options.target);
|
|
128
|
-
const files = readViewFiles(root, view);
|
|
129
|
-
const failed = await PREVIEWERS[view.kind]({
|
|
130
|
-
root,
|
|
131
|
-
view,
|
|
132
|
-
files,
|
|
133
84
|
options,
|
|
134
85
|
api,
|
|
135
86
|
log,
|
|
136
|
-
env: {
|
|
137
|
-
tenantId: params.env?.tenantId ?? "",
|
|
138
|
-
appUrl: params.env?.appUrl ??
|
|
139
|
-
process.env.MAWARU_APP_URL ??
|
|
140
|
-
"https://app.mawaru.ai",
|
|
141
|
-
openUrl: params.env?.openUrl ?? openBrowser,
|
|
142
|
-
watch: params.env?.watch ?? watchFiles,
|
|
143
|
-
},
|
|
144
87
|
});
|
|
145
|
-
if (
|
|
88
|
+
if (failed > 0)
|
|
146
89
|
log(`失敗 ${failed} 件`);
|
|
147
90
|
return failed > 0 ? 1 : 0;
|
|
148
91
|
};
|
|
@@ -162,6 +105,5 @@ export const runPreview = async (args) => {
|
|
|
162
105
|
options,
|
|
163
106
|
api: mawaruApiFor(credentials, tenantId),
|
|
164
107
|
log: (line) => console.log(line),
|
|
165
|
-
env: { tenantId },
|
|
166
108
|
});
|
|
167
109
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export declare const VIEW_KINDS: readonly ["slack"
|
|
1
|
+
export declare const VIEW_KINDS: readonly ["slack"];
|
|
2
2
|
export type ViewKind = (typeof VIEW_KINDS)[number];
|
|
3
|
-
export declare const kindRoot: (root: string, kind: ViewKind) => string;
|
|
4
3
|
export type ViewTarget = {
|
|
5
4
|
kind: ViewKind;
|
|
6
5
|
name: string;
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
// 手元プレビュー(`mawaru preview`)が読む、実行 repo
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
export const VIEW_KINDS = ["slack", "human"];
|
|
9
|
-
const KIND_ROOTS = {
|
|
10
|
-
slack: ["views", "slack"],
|
|
11
|
-
human: ["nodes", "human"],
|
|
12
|
-
};
|
|
13
|
-
export const kindRoot = (root, kind) => join(root, ...KIND_ROOTS[kind]);
|
|
3
|
+
// 手元プレビュー(`mawaru preview`)が読む、実行 repo の views/<種類>/<名前>/ のファイル。
|
|
4
|
+
// 描画は mawaru API がやるので、ここは「対象を決めてファイルを集める」だけで、種類をまたいで共通
|
|
5
|
+
// (docs/tasks/done/Slackビューの顧客repo配置.md の「手元プレビュー」)
|
|
6
|
+
// 手元プレビューできるビューの種類(views/ 直下のディレクトリ名。previewCommand.ts の振り分けと揃える)
|
|
7
|
+
export const VIEW_KINDS = ["slack"];
|
|
14
8
|
const dirsIn = (dir) => {
|
|
15
9
|
try {
|
|
16
10
|
return readdirSync(dir, { withFileTypes: true })
|
|
@@ -22,17 +16,15 @@ const dirsIn = (dir) => {
|
|
|
22
16
|
return [];
|
|
23
17
|
}
|
|
24
18
|
};
|
|
25
|
-
//
|
|
26
|
-
export const listViews = (root) => VIEW_KINDS.flatMap((kind) => dirsIn(
|
|
27
|
-
.filter((name) => !name.startsWith("_"))
|
|
28
|
-
.map((name) => ({ kind, name })));
|
|
19
|
+
// views/<種類>/ 直下のディレクトリ(=ビュー)。無ければ空
|
|
20
|
+
export const listViews = (root) => VIEW_KINDS.flatMap((kind) => dirsIn(join(root, "views", kind)).map((name) => ({ kind, name })));
|
|
29
21
|
const labelOf = (view) => `${view.kind}/${view.name}`;
|
|
30
|
-
// 指定(views/slack/pr・slack/pr・
|
|
31
|
-
// (../
|
|
22
|
+
// 指定(views/slack/pr・slack/pr・pr)をビューに決める。一覧にあるものだけを受ける
|
|
23
|
+
// (../ でディレクトリの外を読ませない)。決まらなければ、あるビューを添えて失敗する
|
|
32
24
|
export const resolveView = (root, spec) => {
|
|
33
25
|
const normalized = spec
|
|
34
26
|
.replace(/^\.\//, "")
|
|
35
|
-
.replace(/^
|
|
27
|
+
.replace(/^views\//, "")
|
|
36
28
|
.replace(/\/+$/, "");
|
|
37
29
|
const views = listViews(root);
|
|
38
30
|
const found = normalized.includes("/")
|
|
@@ -41,14 +33,17 @@ export const resolveView = (root, spec) => {
|
|
|
41
33
|
if (found.length === 1 && found[0])
|
|
42
34
|
return found[0];
|
|
43
35
|
const known = views.length === 0
|
|
44
|
-
? "views/
|
|
36
|
+
? "views/ にビューがありません"
|
|
45
37
|
: `ある対象: ${views.map(labelOf).join(", ")}`;
|
|
46
38
|
throw new Error(found.length > 1
|
|
47
39
|
? `${spec} は複数の種類にあります。<種類>/<名前> で指定してください(${found.map(labelOf).join(", ")})`
|
|
48
40
|
: `${spec} が見つかりません(${known})`);
|
|
49
41
|
};
|
|
50
|
-
//
|
|
51
|
-
|
|
42
|
+
// mawaru API へ送る files({ ビューのディレクトリからの相対パス(/ 区切り): 中身 })。
|
|
43
|
+
// .ts だけをサブディレクトリまで集める(上限の検証は API 側)
|
|
44
|
+
export const readViewFiles = (root, view) => {
|
|
45
|
+
const dir = join(root, "views", view.kind, view.name);
|
|
46
|
+
const files = {};
|
|
52
47
|
const walk = (relative) => {
|
|
53
48
|
for (const entry of readdirSync(join(dir, relative), {
|
|
54
49
|
withFileTypes: true,
|
|
@@ -58,30 +53,11 @@ const collect = (dir, prefix, extensions, files) => {
|
|
|
58
53
|
const path = relative ? `${relative}/${entry.name}` : entry.name;
|
|
59
54
|
if (entry.isDirectory())
|
|
60
55
|
walk(path);
|
|
61
|
-
else if (entry.isFile() &&
|
|
62
|
-
|
|
63
|
-
files[`${prefix}${path}`] = readFileSync(join(dir, path), "utf8");
|
|
56
|
+
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
57
|
+
files[path] = readFileSync(join(dir, path), "utf8");
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
60
|
};
|
|
67
61
|
walk("");
|
|
68
|
-
};
|
|
69
|
-
// mawaru API へ送る files(上限の検証は API 側)。
|
|
70
|
-
// - Slack ビュー:ビューのディレクトリの .ts(キーはビューのディレクトリからの相対パス)
|
|
71
|
-
// - human ノードの実装:nodes/human/<名前>/ と nodes/human/_<部品>/ の .ts / .tsx(キーは nodes/human/ からの相対パス。
|
|
72
|
-
// 本番と同じく、部品は複数の human ノードで共有できる)
|
|
73
|
-
export const readViewFiles = (root, view) => {
|
|
74
|
-
const kindDir = kindRoot(root, view.kind);
|
|
75
|
-
const files = {};
|
|
76
|
-
if (view.kind === "slack") {
|
|
77
|
-
collect(join(kindDir, view.name), "", [".ts"], files);
|
|
78
|
-
return files;
|
|
79
|
-
}
|
|
80
|
-
for (const dir of [
|
|
81
|
-
view.name,
|
|
82
|
-
...dirsIn(kindDir).filter((name) => name.startsWith("_")),
|
|
83
|
-
]) {
|
|
84
|
-
collect(join(kindDir, dir), `${dir}/`, [".ts", ".tsx"], files);
|
|
85
|
-
}
|
|
86
62
|
return files;
|
|
87
63
|
};
|
package/dist/_schemas/graph.d.ts
CHANGED
|
@@ -53,11 +53,6 @@ export type AssignMember = z.infer<typeof assignMemberSchema>;
|
|
|
53
53
|
export declare const humanConfigSchema: z.ZodObject<{
|
|
54
54
|
human_dir: z.ZodOptional<z.ZodString>;
|
|
55
55
|
ref: z.ZodOptional<z.ZodString>;
|
|
56
|
-
declaration: z.ZodOptional<z.ZodObject<{
|
|
57
|
-
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
58
|
-
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
59
|
-
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
60
|
-
}, z.core.$strip>>;
|
|
61
56
|
assignment: z.ZodObject<{
|
|
62
57
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
63
58
|
fixed: "fixed";
|
|
@@ -202,11 +197,6 @@ export declare const graphNodeSchema: z.ZodObject<{
|
|
|
202
197
|
human: z.ZodOptional<z.ZodObject<{
|
|
203
198
|
human_dir: z.ZodOptional<z.ZodString>;
|
|
204
199
|
ref: z.ZodOptional<z.ZodString>;
|
|
205
|
-
declaration: z.ZodOptional<z.ZodObject<{
|
|
206
|
-
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
207
|
-
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
208
|
-
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
209
|
-
}, z.core.$strip>>;
|
|
210
200
|
assignment: z.ZodObject<{
|
|
211
201
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
212
202
|
fixed: "fixed";
|
|
@@ -309,11 +299,6 @@ export declare const saveGraphBodySchema: z.ZodObject<{
|
|
|
309
299
|
human: z.ZodOptional<z.ZodObject<{
|
|
310
300
|
human_dir: z.ZodOptional<z.ZodString>;
|
|
311
301
|
ref: z.ZodOptional<z.ZodString>;
|
|
312
|
-
declaration: z.ZodOptional<z.ZodObject<{
|
|
313
|
-
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
314
|
-
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
315
|
-
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
316
|
-
}, z.core.$strip>>;
|
|
317
302
|
assignment: z.ZodObject<{
|
|
318
303
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
319
304
|
fixed: "fixed";
|
package/dist/_schemas/graph.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { humanDeclarationSchema } from "./human-manifest.js";
|
|
3
2
|
import { graphPortSchema, jsonSchemaSchema, nodeKindSchema, portKeySchema, } from "./node.js";
|
|
4
3
|
import { connectionAccepted, hasInboundConflict, kindPortsViolation, resolvePortSchemas, UNDECLARED_SCHEMA, } from "./port-spec.js";
|
|
5
4
|
// refs=ループ変数の袋(key→任意の JSON 値。上流出力の参照(refs).md)。
|
|
@@ -127,10 +126,6 @@ export const humanConfigSchema = z.object({
|
|
|
127
126
|
.optional(),
|
|
128
127
|
// 読むブランチ(省略時はデフォルトブランチ)。publish でその先頭の commit に固定する
|
|
129
128
|
ref: z.string().min(1).optional(),
|
|
130
|
-
// 宣言の写し(in / approve / reject の型)。伝播はこれを読む。human_dir を選んだときに
|
|
131
|
-
// 一覧 API(GET /tenants/:id/github/humans)の宣言を入れる。省略すると保存時に mawaru が repo の
|
|
132
|
-
// config.json から読む(下書きに同じ human_dir の同じノードがあればその写しを保つ)
|
|
133
|
-
declaration: humanDeclarationSchema.optional(),
|
|
134
129
|
assignment: z
|
|
135
130
|
.object({
|
|
136
131
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { type JsonSchema } from "./node.js";
|
|
3
|
-
import { type DeclaredPortSchema } from "./port-spec.js";
|
|
4
|
-
export type HumanDeclaration = {
|
|
5
|
-
in: DeclaredPortSchema;
|
|
6
|
-
approve: DeclaredPortSchema;
|
|
7
|
-
reject: JsonSchema;
|
|
8
|
-
};
|
|
9
|
-
export declare const humanDeclarationSchema: z.ZodObject<{
|
|
10
|
-
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
11
|
-
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
12
|
-
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
13
|
-
}, z.core.$strip>;
|
|
14
|
-
export declare const STANDARD_HUMAN_DECLARATION: HumanDeclaration;
|
|
15
2
|
export declare const humanManifestSchema: z.ZodObject<{
|
|
16
|
-
inputs: z.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
approve: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
23
|
-
reject: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
24
|
-
}, z.core.$strict>>;
|
|
25
|
-
}, z.core.$strict>;
|
|
3
|
+
inputs: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
4
|
+
schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
5
|
+
}, z.core.$strip>>;
|
|
6
|
+
outputs: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
7
|
+
refs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
8
|
+
}, z.core.$strip>;
|
|
26
9
|
export type HumanManifest = z.infer<typeof humanManifestSchema>;
|
|
27
|
-
export declare const humanDeclarationOfManifest: (config: HumanManifest) => HumanDeclaration;
|