fix(cli): address Gemini test report — local mode, validate bug, offline flag
A. acr init --local: new local mode, no Cloudflare account required;
config defaults to mode:local when ~/.arcrun/config.yaml missing
B. validate node-count bug: removed faulty input/output node heuristic
that dropped start/end nodes from config check; now all nodes except
reserved 'input' keyword must have config entries
C. acr validate --offline: skip remote component-existence and credentials
checks; local mode also auto-skips these checks
D. parts.ts: replace require('node:fs') with static import (ES module fix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import { loadConfig, getCypherExecutorUrl } from '../lib/config.js';
|
||||
import { loadWorkflowYaml, parseTriplets, validateRelations, getNodeNames } from '../lib/yaml-parser.js';
|
||||
import { CfKvClient } from '../lib/cf-api.js';
|
||||
|
||||
export async function cmdValidate(filePath: string): Promise<void> {
|
||||
export async function cmdValidate(filePath: string, options: { offline?: boolean } = {}): Promise<void> {
|
||||
const config = loadConfig();
|
||||
let allPassed = true;
|
||||
|
||||
@@ -48,15 +48,13 @@ export async function cmdValidate(filePath: string): Promise<void> {
|
||||
allPassed = false;
|
||||
}
|
||||
|
||||
// 4. 所有節點在 config 中有對應(Input/Output 節點除外)
|
||||
// 4. 所有節點在 config 中有對應
|
||||
// 規則:除了名稱為 "input" 的保留字節點,其餘所有節點都必須有 config
|
||||
// 注意:不能用「只出現在 subject 或只出現在 object」來判斷是否需要 config,
|
||||
// 因為像 a >> b >> c 中 a、b、c 都可能需要 config
|
||||
const nodeNames = getNodeNames(triplets);
|
||||
const inputNodes = new Set(triplets.map(t => t.subject).filter(s =>
|
||||
!triplets.some(t => t.object === s)
|
||||
));
|
||||
const outputNodes = new Set(triplets.map(t => t.object).filter(o =>
|
||||
!triplets.some(t => t.subject === o)
|
||||
));
|
||||
const componentNodes = nodeNames.filter(n => !inputNodes.has(n) && !outputNodes.has(n));
|
||||
const RESERVED_NODES = new Set(['input']); // 保留字節點不需要 config
|
||||
const componentNodes = nodeNames.filter(n => !RESERVED_NODES.has(n));
|
||||
|
||||
const missingConfigs = componentNodes.filter(n => !(workflow.config ?? {})[n]);
|
||||
if (missingConfigs.length > 0) {
|
||||
@@ -66,35 +64,41 @@ export async function cmdValidate(filePath: string): Promise<void> {
|
||||
check('config 完整性', true, `${componentNodes.length} 個節點均有 config`);
|
||||
}
|
||||
|
||||
// 5. 零件存在於 WASM_BUCKET(透過 cypher/search 確認)
|
||||
const executorUrl = getCypherExecutorUrl(config);
|
||||
try {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (config.api_key) headers['X-Arcrun-API-Key'] = config.api_key;
|
||||
// 5. 零件存在性(--offline 時跳過)
|
||||
if (options.offline || config.mode === 'local') {
|
||||
check('零件存在性', true, '離線模式,跳過遠端檢查');
|
||||
} else {
|
||||
const executorUrl = getCypherExecutorUrl(config);
|
||||
try {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (config.api_key) headers['X-Arcrun-API-Key'] = config.api_key;
|
||||
|
||||
const res = await fetch(`${executorUrl}/cypher/search`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ triplets: workflow.flow }),
|
||||
});
|
||||
const res = await fetch(`${executorUrl}/cypher/search`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ triplets: workflow.flow }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json() as { missing: string[] };
|
||||
if (data.missing.length > 0) {
|
||||
check('零件存在性', false, `WASM_BUCKET 中找不到:${data.missing.join(', ')}`);
|
||||
allPassed = false;
|
||||
if (res.ok) {
|
||||
const data = await res.json() as { missing: string[] };
|
||||
if (data.missing.length > 0) {
|
||||
check('零件存在性', false, `WASM_BUCKET 中找不到:${data.missing.join(', ')}`);
|
||||
allPassed = false;
|
||||
} else {
|
||||
check('零件存在性', true, '所有零件均已在 WASM_BUCKET');
|
||||
}
|
||||
} else {
|
||||
check('零件存在性', true, '所有零件均已在 WASM_BUCKET');
|
||||
check('零件存在性', false, `無法連線 ${executorUrl}(加 --offline 跳過此檢查)`);
|
||||
}
|
||||
} else {
|
||||
check('零件存在性', false, `無法連線 ${executorUrl},跳過驗證`);
|
||||
} catch {
|
||||
check('零件存在性', false, `無法連線 ${executorUrl}(加 --offline 跳過此檢查)`);
|
||||
}
|
||||
} catch {
|
||||
check('零件存在性', false, `無法連線 ${executorUrl},跳過驗證`);
|
||||
}
|
||||
|
||||
// 6. 所需 credentials 已上傳至 CREDENTIALS_KV(僅在有 KV 設定時執行)
|
||||
if (config.cloudflare_account_id && config.cf_api_token) {
|
||||
// 6. Credentials 上傳檢查(--offline 或 local 模式時跳過)
|
||||
if (options.offline || config.mode === 'local') {
|
||||
// 離線模式略過
|
||||
} else if (config.cloudflare_account_id && config.cf_api_token) {
|
||||
const namespaceId = config.mode === 'standard'
|
||||
? config.user_kv_namespace_id
|
||||
: config.credentials_kv_namespace_id;
|
||||
@@ -106,12 +110,9 @@ export async function cmdValidate(filePath: string): Promise<void> {
|
||||
apiToken: config.cf_api_token,
|
||||
});
|
||||
|
||||
// 查詢已上傳的 credentials
|
||||
try {
|
||||
const kvKeys = await kv.list('cred:');
|
||||
const uploadedCreds = new Set(kvKeys.map(k => k.name.replace('cred:', '')));
|
||||
|
||||
// 比對 workflow config 中引用的 credential key
|
||||
const usedCreds = extractCredentialRefs(workflow.config ?? {});
|
||||
const missingCreds = usedCreds.filter(k => !uploadedCreds.has(k));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user