firebase-dataconnect-bootstrap 1.2.0 → 1.2.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/README.md +27 -5
- package/dist/cli.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/main.js +37 -15
- package/dist/prompt.js +190 -80
- package/dist/types.d.ts +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,28 @@ npx firebase-dataconnect-bootstrap
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
対話モードは日本語で質問が表示されます。
|
|
23
|
-
同じリポジトリで再実行すると、保存済み設定を初期値として設定変更できます。
|
|
23
|
+
同じリポジトリで再実行すると、保存済み設定を初期値として設定変更できます。
|
|
24
|
+
既存設定がある場合は、対話中に「新しいコレクション設定を追加するか」を選べます。
|
|
25
|
+
|
|
26
|
+
## 追加コレクションを非対話で追加する例
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx firebase-dataconnect-bootstrap \
|
|
30
|
+
--yes \
|
|
31
|
+
--add-collection \
|
|
32
|
+
--target . \
|
|
33
|
+
--project your-firebase-project-id \
|
|
34
|
+
--document 'articles/{articleId}' \
|
|
35
|
+
--function onArticlesWritten \
|
|
36
|
+
--vector-search \
|
|
37
|
+
--vector-collection 'articles' \
|
|
38
|
+
--source-text-field 'body' \
|
|
39
|
+
--vector-field 'embedding' \
|
|
40
|
+
--search-fields 'title,body,updatedAt' \
|
|
41
|
+
--search-function 'searchArticlesByVector' \
|
|
42
|
+
--top-k 8 \
|
|
43
|
+
--no-install
|
|
44
|
+
```
|
|
24
45
|
|
|
25
46
|
## 非対話モード例
|
|
26
47
|
|
|
@@ -47,6 +68,7 @@ npx firebase-dataconnect-bootstrap \
|
|
|
47
68
|
## 主なオプション
|
|
48
69
|
|
|
49
70
|
- `--config <name>`: 設定保存ファイル名(既定: `.firebase-dataconnect-bootstrap.json`)
|
|
71
|
+
- `--add-collection`: 再実行時に既存設定へ新しいコレクション設定を追加
|
|
50
72
|
- `--vector-search` / `--no-vector-search`: ベクトル検索 scaffold の有効/無効
|
|
51
73
|
- `--vector-collection <path>`: 検索対象コレクション
|
|
52
74
|
- `--source-text-field <name>`: 埋め込み更新対象のテキストフィールド
|
|
@@ -55,13 +77,13 @@ npx firebase-dataconnect-bootstrap \
|
|
|
55
77
|
- `--search-function <name>`: 生成する `onRequest` 関数名
|
|
56
78
|
- `--top-k <number>`: 検索時のデフォルト上位件数
|
|
57
79
|
|
|
58
|
-
##
|
|
80
|
+
## 生成される主な関数(コレクションごと)
|
|
59
81
|
|
|
60
|
-
- `
|
|
82
|
+
- `onDocumentWritten_<functionName>.*`
|
|
61
83
|
- `sourceTextField` の内容を `embedText` に渡し、`vectorField` へ保存
|
|
62
|
-
- `
|
|
84
|
+
- `vectorSearchOnRequest_<searchFunctionName>.*`
|
|
63
85
|
- HTTP リクエストの `vector`(または `query`)でコサイン類似度検索
|
|
64
|
-
- `
|
|
86
|
+
- `vectorSearchEmbedding_<functionName>.*`
|
|
65
87
|
- `embedText` を実装する差し替えポイント(デフォルトは `null` を返す)
|
|
66
88
|
|
|
67
89
|
## npm モジュールとして検索クライアントを使う
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ Options:
|
|
|
15
15
|
--document <path> Firestore document path (default: meetingSummaries/{summaryId})
|
|
16
16
|
--function <name> Function export name (default: onMeetingSummaryWritten)
|
|
17
17
|
--config <name> Saved config filename (default: .firebase-dataconnect-bootstrap.json)
|
|
18
|
+
--add-collection Add a new collection setting on re-run
|
|
18
19
|
--install Run npm install in functions directory
|
|
19
20
|
--no-install Skip npm install in functions directory
|
|
20
21
|
--vector-search Enable vector search scaffolding
|
|
@@ -40,6 +41,7 @@ function parseArgs(argv) {
|
|
|
40
41
|
installDependencies: undefined,
|
|
41
42
|
configFileName: undefined,
|
|
42
43
|
vectorSearch: {},
|
|
44
|
+
addCollection: undefined,
|
|
43
45
|
yes: false,
|
|
44
46
|
help: false
|
|
45
47
|
};
|
|
@@ -69,6 +71,10 @@ function parseArgs(argv) {
|
|
|
69
71
|
args.vectorSearch = { ...(args.vectorSearch ?? {}), enabled: false };
|
|
70
72
|
continue;
|
|
71
73
|
}
|
|
74
|
+
if (token === "--add-collection") {
|
|
75
|
+
args.addCollection = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
72
78
|
if (token.startsWith("--target=")) {
|
|
73
79
|
args.targetDir = token.slice("--target=".length);
|
|
74
80
|
continue;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { run } from "./main.js";
|
|
|
2
2
|
export { runCli } from "./cli.js";
|
|
3
3
|
export { collectConfig } from "./prompt.js";
|
|
4
4
|
export { createVectorSearchClient, searchByVector } from "./search-client.js";
|
|
5
|
-
export type { CliArgs, RunResult, SetupConfig, VectorSearchConfig } from "./types.js";
|
|
5
|
+
export type { CliArgs, PersistedBootstrapConfig, RunResult, SetupConfig, SetupTargetConfig, VectorSearchConfig } from "./types.js";
|
|
6
6
|
export type { SearchByVectorInput, VectorSearchClientOptions, VectorSearchHit, VectorSearchResponse } from "./search-client.js";
|
package/dist/main.js
CHANGED
|
@@ -73,6 +73,10 @@ function buildGeneratedImportPath(moduleStyle, extension, moduleBaseName) {
|
|
|
73
73
|
}
|
|
74
74
|
return `./${moduleBaseName}${extension}`;
|
|
75
75
|
}
|
|
76
|
+
function toSafeFileSegment(value) {
|
|
77
|
+
const normalized = value.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
78
|
+
return normalized || "generated";
|
|
79
|
+
}
|
|
76
80
|
function buildDataconnectYaml(config) {
|
|
77
81
|
return `specVersion: "v1alpha"
|
|
78
82
|
serviceId: "${config.serviceId}"
|
|
@@ -155,7 +159,7 @@ export async function embedText(_text) {
|
|
|
155
159
|
}
|
|
156
160
|
`;
|
|
157
161
|
}
|
|
158
|
-
function buildFunctionModule({ style, config }) {
|
|
162
|
+
function buildFunctionModule({ style, config, embeddingImportPath }) {
|
|
159
163
|
if (style === "cjs") {
|
|
160
164
|
if (!config.vectorSearch.enabled) {
|
|
161
165
|
return `const { onDocumentWritten } = require("firebase-functions/v2/firestore");
|
|
@@ -179,7 +183,7 @@ module.exports = { ${config.functionName} };
|
|
|
179
183
|
}
|
|
180
184
|
return `const { onDocumentWritten } = require("firebase-functions/v2/firestore");
|
|
181
185
|
const logger = require("firebase-functions/logger");
|
|
182
|
-
const { embedText } = require("
|
|
186
|
+
const { embedText } = require("${embeddingImportPath}");
|
|
183
187
|
|
|
184
188
|
const SOURCE_TEXT_FIELD = ${JSON.stringify(config.vectorSearch.sourceTextField)};
|
|
185
189
|
const VECTOR_FIELD = ${JSON.stringify(config.vectorSearch.vectorField)};
|
|
@@ -243,7 +247,7 @@ export const ${config.functionName} = onDocumentWritten(
|
|
|
243
247
|
}
|
|
244
248
|
return `import { onDocumentWritten } from "firebase-functions/v2/firestore";
|
|
245
249
|
import * as logger from "firebase-functions/logger";
|
|
246
|
-
import { embedText } from "
|
|
250
|
+
import { embedText } from "${embeddingImportPath}";
|
|
247
251
|
|
|
248
252
|
const SOURCE_TEXT_FIELD = ${JSON.stringify(config.vectorSearch.sourceTextField)};
|
|
249
253
|
const VECTOR_FIELD = ${JSON.stringify(config.vectorSearch.vectorField)};
|
|
@@ -285,9 +289,8 @@ export const ${config.functionName} = onDocumentWritten(
|
|
|
285
289
|
);
|
|
286
290
|
`;
|
|
287
291
|
}
|
|
288
|
-
function buildVectorSearchOnRequestModule(style, config,
|
|
292
|
+
function buildVectorSearchOnRequestModule(style, config, embeddingImportPath) {
|
|
289
293
|
const returnFields = JSON.stringify(config.vectorSearch.returnFields);
|
|
290
|
-
const embeddingImportPath = buildGeneratedImportPath(style, extension, "vectorSearchEmbedding");
|
|
291
294
|
if (style === "cjs") {
|
|
292
295
|
return `const { onRequest } = require("firebase-functions/v2/https");
|
|
293
296
|
const logger = require("firebase-functions/logger");
|
|
@@ -619,13 +622,17 @@ async function resolveEntryContext(targetDir, entryRelativePath) {
|
|
|
619
622
|
};
|
|
620
623
|
}
|
|
621
624
|
async function ensureFunctionExport(targetDir, config, context) {
|
|
622
|
-
const
|
|
625
|
+
const onWriteModuleBaseName = `onDocumentWritten_${toSafeFileSegment(config.functionName)}`;
|
|
626
|
+
const generatedFileName = `${onWriteModuleBaseName}${context.extension}`;
|
|
623
627
|
const generatedRelativePath = path.join(path.dirname(context.entryRelativePath), generatedFileName);
|
|
624
|
-
const generatedImportPath = buildGeneratedImportPath(context.moduleStyle, context.extension,
|
|
628
|
+
const generatedImportPath = buildGeneratedImportPath(context.moduleStyle, context.extension, onWriteModuleBaseName);
|
|
629
|
+
const embeddingModuleBaseName = `vectorSearchEmbedding_${toSafeFileSegment(config.functionName)}`;
|
|
630
|
+
const embeddingImportPath = buildGeneratedImportPath(context.moduleStyle, context.extension, embeddingModuleBaseName);
|
|
625
631
|
const generatedFullPath = path.join(targetDir, generatedRelativePath);
|
|
626
632
|
const functionModuleText = buildFunctionModule({
|
|
627
633
|
style: context.moduleStyle,
|
|
628
|
-
config
|
|
634
|
+
config,
|
|
635
|
+
embeddingImportPath
|
|
629
636
|
});
|
|
630
637
|
await writeFile(generatedFullPath, functionModuleText, "utf8");
|
|
631
638
|
const entryText = (await readFile(context.entryFullPath, "utf8")).trimEnd();
|
|
@@ -647,14 +654,17 @@ async function ensureVectorSearchScaffold(targetDir, config, context) {
|
|
|
647
654
|
return null;
|
|
648
655
|
}
|
|
649
656
|
const folder = path.dirname(context.entryRelativePath);
|
|
650
|
-
const
|
|
651
|
-
const
|
|
657
|
+
const onRequestModuleBaseName = `vectorSearchOnRequest_${toSafeFileSegment(config.vectorSearch.functionName)}`;
|
|
658
|
+
const embeddingModuleBaseName = `vectorSearchEmbedding_${toSafeFileSegment(config.functionName)}`;
|
|
659
|
+
const onRequestFileName = `${onRequestModuleBaseName}${context.extension}`;
|
|
660
|
+
const embeddingFileName = `${embeddingModuleBaseName}${context.extension}`;
|
|
652
661
|
const onRequestRelativePath = path.join(folder, onRequestFileName);
|
|
653
662
|
const embeddingRelativePath = path.join(folder, embeddingFileName);
|
|
654
663
|
const onRequestFullPath = path.join(targetDir, onRequestRelativePath);
|
|
655
664
|
const embeddingFullPath = path.join(targetDir, embeddingRelativePath);
|
|
656
|
-
const
|
|
657
|
-
|
|
665
|
+
const embeddingImportPath = buildGeneratedImportPath(context.moduleStyle, context.extension, embeddingModuleBaseName);
|
|
666
|
+
const onRequestImportPath = buildGeneratedImportPath(context.moduleStyle, context.extension, onRequestModuleBaseName);
|
|
667
|
+
await writeFile(onRequestFullPath, buildVectorSearchOnRequestModule(context.moduleStyle, config, embeddingImportPath), "utf8");
|
|
658
668
|
const embeddingCreated = await writeTextIfMissing(embeddingFullPath, buildEmbeddingModule(context.moduleStyle));
|
|
659
669
|
const entryText = (await readFile(context.entryFullPath, "utf8")).trimEnd();
|
|
660
670
|
const exportLine = context.moduleStyle === "cjs"
|
|
@@ -674,16 +684,28 @@ async function ensureVectorSearchScaffold(targetDir, config, context) {
|
|
|
674
684
|
}
|
|
675
685
|
async function persistBootstrapConfig(targetDir, config) {
|
|
676
686
|
const filePath = path.join(targetDir, config.configFileName);
|
|
687
|
+
const currentTarget = {
|
|
688
|
+
documentPath: config.documentPath,
|
|
689
|
+
functionName: config.functionName,
|
|
690
|
+
vectorSearch: config.vectorSearch
|
|
691
|
+
};
|
|
692
|
+
const mergedTargets = [...config.existingTargets];
|
|
693
|
+
const existingIndex = mergedTargets.findIndex((target) => target.functionName === currentTarget.functionName);
|
|
694
|
+
if (existingIndex >= 0) {
|
|
695
|
+
mergedTargets[existingIndex] = currentTarget;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
mergedTargets.push(currentTarget);
|
|
699
|
+
}
|
|
677
700
|
const persisted = {
|
|
678
701
|
projectId: config.projectId,
|
|
679
702
|
region: config.region,
|
|
680
703
|
serviceId: config.serviceId,
|
|
681
704
|
location: config.location,
|
|
682
|
-
documentPath: config.documentPath,
|
|
683
|
-
functionName: config.functionName,
|
|
684
705
|
installDependencies: config.installDependencies,
|
|
685
706
|
configFileName: config.configFileName,
|
|
686
|
-
|
|
707
|
+
targets: mergedTargets,
|
|
708
|
+
lastTargetFunctionName: currentTarget.functionName
|
|
687
709
|
};
|
|
688
710
|
await writeJson(filePath, persisted);
|
|
689
711
|
return toPosixPath(config.configFileName);
|
package/dist/prompt.js
CHANGED
|
@@ -4,6 +4,8 @@ import { createInterface } from "node:readline/promises";
|
|
|
4
4
|
import { stdin as input, stdout as output } from "node:process";
|
|
5
5
|
const DEFAULT_CONFIG_FILE = ".firebase-dataconnect-bootstrap.json";
|
|
6
6
|
const DEFAULT_RETURN_FIELDS = ["title", "summary", "createdAt"];
|
|
7
|
+
const DEFAULT_DOCUMENT_PATH = "meetingSummaries/{summaryId}";
|
|
8
|
+
const NEW_COLLECTION_DOCUMENT_PATH = "newCollection/{docId}";
|
|
7
9
|
function normalizeFunctionName(name) {
|
|
8
10
|
const cleaned = name.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
9
11
|
if (!cleaned) {
|
|
@@ -37,13 +39,31 @@ function deriveCollectionPath(documentPath) {
|
|
|
37
39
|
}
|
|
38
40
|
return segments.join("/");
|
|
39
41
|
}
|
|
42
|
+
function toPascalCase(input) {
|
|
43
|
+
const tokens = input
|
|
44
|
+
.split(/[^A-Za-z0-9]+/)
|
|
45
|
+
.map((token) => token.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
if (tokens.length === 0) {
|
|
48
|
+
return "Collection";
|
|
49
|
+
}
|
|
50
|
+
return tokens.map((token) => `${token[0].toUpperCase()}${token.slice(1)}`).join("");
|
|
51
|
+
}
|
|
52
|
+
function deriveWriteFunctionName(documentPath) {
|
|
53
|
+
const collection = deriveCollectionPath(documentPath);
|
|
54
|
+
return normalizeFunctionName(`on${toPascalCase(collection)}Written`);
|
|
55
|
+
}
|
|
56
|
+
function deriveSearchFunctionName(documentPath) {
|
|
57
|
+
const collection = deriveCollectionPath(documentPath);
|
|
58
|
+
return normalizeFunctionName(`search${toPascalCase(collection)}ByVector`);
|
|
59
|
+
}
|
|
40
60
|
function normalizeVectorSearch(partialVectorSearch, documentPath) {
|
|
41
61
|
const source = partialVectorSearch ?? {};
|
|
42
62
|
const collectionPath = source.collectionPath?.trim() || deriveCollectionPath(documentPath);
|
|
43
63
|
const sourceTextField = source.sourceTextField?.trim() || "summary";
|
|
44
64
|
const vectorField = source.vectorField?.trim() || "embedding";
|
|
45
|
-
const returnFields = source.returnFields?.length ? source.returnFields : DEFAULT_RETURN_FIELDS;
|
|
46
|
-
const functionName = normalizeFunctionName(source.functionName?.trim() ||
|
|
65
|
+
const returnFields = source.returnFields?.length ? source.returnFields : [...DEFAULT_RETURN_FIELDS];
|
|
66
|
+
const functionName = normalizeFunctionName(source.functionName?.trim() || deriveSearchFunctionName(documentPath));
|
|
47
67
|
const rawTopK = typeof source.defaultTopK === "number" && Number.isFinite(source.defaultTopK)
|
|
48
68
|
? Math.floor(source.defaultTopK)
|
|
49
69
|
: 5;
|
|
@@ -59,76 +79,145 @@ function normalizeVectorSearch(partialVectorSearch, documentPath) {
|
|
|
59
79
|
defaultTopK
|
|
60
80
|
};
|
|
61
81
|
}
|
|
62
|
-
function
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const serviceId = partial.serviceId?.trim() || `${projectId || "my-project"}-service`;
|
|
67
|
-
const location = partial.location?.trim() || region;
|
|
68
|
-
const documentPath = partial.documentPath?.trim() || "meetingSummaries/{summaryId}";
|
|
69
|
-
const functionName = normalizeFunctionName(partial.functionName?.trim() || "onMeetingSummaryWritten");
|
|
70
|
-
const configFileName = partial.configFileName?.trim() || DEFAULT_CONFIG_FILE;
|
|
71
|
-
const installDependencies = typeof partial.installDependencies === "boolean"
|
|
72
|
-
? partial.installDependencies
|
|
73
|
-
: true;
|
|
74
|
-
const vectorSearch = normalizeVectorSearch(partial.vectorSearch, documentPath);
|
|
82
|
+
function normalizeTarget(partialTarget) {
|
|
83
|
+
const documentPath = partialTarget.documentPath?.trim() || DEFAULT_DOCUMENT_PATH;
|
|
84
|
+
const functionName = normalizeFunctionName(partialTarget.functionName?.trim() || deriveWriteFunctionName(documentPath));
|
|
85
|
+
const vectorSearch = normalizeVectorSearch(partialTarget.vectorSearch, documentPath);
|
|
75
86
|
return {
|
|
76
|
-
targetDir,
|
|
77
|
-
projectId,
|
|
78
|
-
region,
|
|
79
|
-
serviceId,
|
|
80
|
-
location,
|
|
81
87
|
documentPath,
|
|
82
88
|
functionName,
|
|
83
|
-
installDependencies,
|
|
84
|
-
configFileName,
|
|
85
89
|
vectorSearch
|
|
86
90
|
};
|
|
87
91
|
}
|
|
92
|
+
function normalizeSavedConfig(raw, configFileName) {
|
|
93
|
+
const source = typeof raw === "object" && raw !== null ? raw : {};
|
|
94
|
+
const rawTargets = Array.isArray(source.targets)
|
|
95
|
+
? source.targets
|
|
96
|
+
: source.documentPath
|
|
97
|
+
? [
|
|
98
|
+
{
|
|
99
|
+
documentPath: source.documentPath,
|
|
100
|
+
functionName: source.functionName,
|
|
101
|
+
vectorSearch: source.vectorSearch
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
: [];
|
|
105
|
+
const targets = rawTargets
|
|
106
|
+
.filter((entry) => typeof entry === "object" && entry !== null)
|
|
107
|
+
.map((entry) => normalizeTarget(entry));
|
|
108
|
+
return {
|
|
109
|
+
projectId: typeof source.projectId === "string" ? source.projectId : "",
|
|
110
|
+
region: typeof source.region === "string" ? source.region : "us-central1",
|
|
111
|
+
serviceId: typeof source.serviceId === "string" ? source.serviceId : "",
|
|
112
|
+
location: typeof source.location === "string" ? source.location : "",
|
|
113
|
+
installDependencies: typeof source.installDependencies === "boolean" ? source.installDependencies : true,
|
|
114
|
+
configFileName: typeof source.configFileName === "string" && source.configFileName
|
|
115
|
+
? source.configFileName
|
|
116
|
+
: configFileName,
|
|
117
|
+
targets,
|
|
118
|
+
lastTargetFunctionName: typeof source.lastTargetFunctionName === "string" ? source.lastTargetFunctionName : undefined
|
|
119
|
+
};
|
|
120
|
+
}
|
|
88
121
|
async function loadSavedConfig(targetDir, configFileName) {
|
|
89
122
|
try {
|
|
90
123
|
const filePath = resolve(targetDir, configFileName);
|
|
91
|
-
const
|
|
92
|
-
const parsed = JSON.parse(
|
|
93
|
-
|
|
94
|
-
return {};
|
|
95
|
-
}
|
|
96
|
-
return parsed;
|
|
124
|
+
const rawText = await readFile(filePath, "utf8");
|
|
125
|
+
const parsed = JSON.parse(rawText);
|
|
126
|
+
return normalizeSavedConfig(parsed, configFileName);
|
|
97
127
|
}
|
|
98
128
|
catch {
|
|
99
|
-
return {};
|
|
129
|
+
return normalizeSavedConfig({}, configFileName);
|
|
100
130
|
}
|
|
101
131
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
function pickDefaultTarget(saved) {
|
|
133
|
+
if (saved.targets.length === 0) {
|
|
134
|
+
return normalizeTarget({});
|
|
135
|
+
}
|
|
136
|
+
if (saved.lastTargetFunctionName) {
|
|
137
|
+
const last = saved.targets.find((target) => target.functionName === saved.lastTargetFunctionName);
|
|
138
|
+
if (last) {
|
|
139
|
+
return last;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return saved.targets[0];
|
|
143
|
+
}
|
|
144
|
+
function hasTargetOverrides(parsed) {
|
|
145
|
+
if (parsed.documentPath || parsed.functionName) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
if (!parsed.vectorSearch) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const vector = parsed.vectorSearch;
|
|
152
|
+
return Boolean(vector.enabled !== undefined ||
|
|
153
|
+
vector.collectionPath ||
|
|
154
|
+
vector.sourceTextField ||
|
|
155
|
+
vector.vectorField ||
|
|
156
|
+
vector.returnFields?.length ||
|
|
157
|
+
vector.functionName ||
|
|
158
|
+
vector.defaultTopK !== undefined);
|
|
159
|
+
}
|
|
160
|
+
function mergeTarget(base, parsed) {
|
|
161
|
+
return normalizeTarget({
|
|
162
|
+
...base,
|
|
163
|
+
documentPath: parsed.documentPath ?? base.documentPath,
|
|
164
|
+
functionName: parsed.functionName ?? base.functionName,
|
|
116
165
|
vectorSearch: {
|
|
117
|
-
...
|
|
166
|
+
...base.vectorSearch,
|
|
118
167
|
...(parsed.vectorSearch ?? {})
|
|
119
168
|
}
|
|
120
|
-
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function buildAddTargetDefaults(parsed) {
|
|
172
|
+
const documentPath = parsed.documentPath ?? NEW_COLLECTION_DOCUMENT_PATH;
|
|
173
|
+
return normalizeTarget({
|
|
174
|
+
documentPath,
|
|
175
|
+
functionName: parsed.functionName ?? deriveWriteFunctionName(documentPath),
|
|
176
|
+
vectorSearch: {
|
|
177
|
+
enabled: parsed.vectorSearch?.enabled ?? false,
|
|
178
|
+
collectionPath: parsed.vectorSearch?.collectionPath ?? deriveCollectionPath(documentPath),
|
|
179
|
+
sourceTextField: parsed.vectorSearch?.sourceTextField ?? "summary",
|
|
180
|
+
vectorField: parsed.vectorSearch?.vectorField ?? "embedding",
|
|
181
|
+
returnFields: parsed.vectorSearch?.returnFields ?? [...DEFAULT_RETURN_FIELDS],
|
|
182
|
+
functionName: parsed.vectorSearch?.functionName ?? deriveSearchFunctionName(documentPath),
|
|
183
|
+
defaultTopK: parsed.vectorSearch?.defaultTopK ?? 5
|
|
184
|
+
}
|
|
185
|
+
});
|
|
121
186
|
}
|
|
122
187
|
export async function collectConfig(parsed) {
|
|
123
188
|
const targetDir = resolve(parsed.targetDir ?? process.cwd());
|
|
124
189
|
const configFileName = parsed.configFileName?.trim() || DEFAULT_CONFIG_FILE;
|
|
125
190
|
const saved = await loadSavedConfig(targetDir, configFileName);
|
|
126
|
-
const
|
|
191
|
+
const projectId = parsed.projectId?.trim() || saved.projectId || "";
|
|
192
|
+
const region = parsed.region?.trim() || saved.region || "us-central1";
|
|
193
|
+
const serviceId = parsed.serviceId?.trim() || saved.serviceId || `${projectId || "my-project"}-service`;
|
|
194
|
+
const location = parsed.location?.trim() || saved.location || region;
|
|
195
|
+
const installDependencies = typeof parsed.installDependencies === "boolean" ? parsed.installDependencies : saved.installDependencies;
|
|
196
|
+
const defaultTarget = pickDefaultTarget(saved);
|
|
197
|
+
const addCollectionDefault = parsed.addCollection ?? false;
|
|
127
198
|
if (parsed.yes) {
|
|
128
|
-
if (!
|
|
199
|
+
if (!projectId) {
|
|
129
200
|
throw new Error("`--yes` を使う場合は `--project` が必須です。");
|
|
130
201
|
}
|
|
131
|
-
|
|
202
|
+
if (parsed.addCollection && !hasTargetOverrides(parsed)) {
|
|
203
|
+
throw new Error("`--add-collection --yes` を使う場合は --document など追加ターゲットの情報を指定してください。");
|
|
204
|
+
}
|
|
205
|
+
const resolvedTarget = parsed.addCollection
|
|
206
|
+
? mergeTarget(buildAddTargetDefaults(parsed), parsed)
|
|
207
|
+
: mergeTarget(defaultTarget, parsed);
|
|
208
|
+
return {
|
|
209
|
+
targetDir,
|
|
210
|
+
projectId,
|
|
211
|
+
region,
|
|
212
|
+
serviceId,
|
|
213
|
+
location,
|
|
214
|
+
documentPath: resolvedTarget.documentPath,
|
|
215
|
+
functionName: resolvedTarget.functionName,
|
|
216
|
+
installDependencies,
|
|
217
|
+
configFileName,
|
|
218
|
+
vectorSearch: resolvedTarget.vectorSearch,
|
|
219
|
+
existingTargets: saved.targets
|
|
220
|
+
};
|
|
132
221
|
}
|
|
133
222
|
const rl = createInterface({ input, output });
|
|
134
223
|
try {
|
|
@@ -153,46 +242,66 @@ export async function collectConfig(parsed) {
|
|
|
153
242
|
};
|
|
154
243
|
console.log("Firebase Data Connect / Cloud Functions セットアップ");
|
|
155
244
|
console.log("");
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
245
|
+
const selectedTargetDir = resolve(await ask("対象リポジトリのパス", targetDir, true));
|
|
246
|
+
const selectedProjectId = await ask("Firebase プロジェクトID", projectId || "my-project", true);
|
|
247
|
+
const selectedRegion = await ask("Functions のリージョン", region, true);
|
|
248
|
+
const selectedServiceId = await ask("Data Connect サービスID", serviceId || `${selectedProjectId}-service`, true);
|
|
249
|
+
const selectedLocation = await ask("Data Connect ロケーション", location || selectedRegion, true);
|
|
250
|
+
const selectedInstallDependencies = await askBoolean("functions ディレクトリで npm install を実行しますか? (y/n)", installDependencies);
|
|
251
|
+
let addCollection = addCollectionDefault;
|
|
252
|
+
if (saved.targets.length > 0 && parsed.addCollection !== true) {
|
|
253
|
+
addCollection = await askBoolean("既存設定に新しいコレクション設定を追加しますか? (y/n)", true);
|
|
254
|
+
}
|
|
255
|
+
let targetDefaults = defaultTarget;
|
|
256
|
+
if (addCollection) {
|
|
257
|
+
targetDefaults = buildAddTargetDefaults(parsed);
|
|
258
|
+
}
|
|
259
|
+
else if (saved.targets.length > 1) {
|
|
260
|
+
console.log("");
|
|
261
|
+
console.log("既存ターゲット:");
|
|
262
|
+
saved.targets.forEach((target, index) => {
|
|
263
|
+
console.log(` ${index + 1}. ${target.functionName} (${target.documentPath})`);
|
|
264
|
+
});
|
|
265
|
+
const defaultIndex = Math.max(0, saved.targets.findIndex((target) => target.functionName === saved.lastTargetFunctionName));
|
|
266
|
+
const selectedIndexRaw = await ask("編集するターゲット番号", String(defaultIndex + 1), true);
|
|
267
|
+
const selectedIndex = Number.parseInt(selectedIndexRaw, 10);
|
|
268
|
+
if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > saved.targets.length) {
|
|
269
|
+
throw new Error("ターゲット番号が不正です。");
|
|
270
|
+
}
|
|
271
|
+
targetDefaults = saved.targets[selectedIndex - 1];
|
|
272
|
+
}
|
|
273
|
+
const baseTarget = mergeTarget(targetDefaults, parsed);
|
|
274
|
+
const documentPath = await ask("Firestore ドキュメントパス", baseTarget.documentPath, true);
|
|
275
|
+
const functionName = normalizeFunctionName(await ask("onDocumentWritten 関数のエクスポート名", baseTarget.functionName, true));
|
|
276
|
+
const vectorEnabled = await askBoolean("ベクトル検索と onRequest 検索関数を有効化しますか? (y/n)", baseTarget.vectorSearch.enabled);
|
|
277
|
+
let vectorCollectionPath = baseTarget.vectorSearch.collectionPath;
|
|
278
|
+
let sourceTextField = baseTarget.vectorSearch.sourceTextField;
|
|
279
|
+
let vectorField = baseTarget.vectorSearch.vectorField;
|
|
280
|
+
let returnFields = baseTarget.vectorSearch.returnFields;
|
|
281
|
+
let searchFunctionName = baseTarget.vectorSearch.functionName;
|
|
282
|
+
let defaultTopK = baseTarget.vectorSearch.defaultTopK;
|
|
173
283
|
if (vectorEnabled) {
|
|
174
|
-
vectorCollectionPath = await ask("ベクトル検索対象のコレクションパス",
|
|
175
|
-
sourceTextField = await ask("埋め込み作成対象のテキストフィールド",
|
|
176
|
-
vectorField = await ask("ベクトル保存先フィールド名",
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const defaultTopKText = await ask("ベクトル検索のデフォルト topK", String(defaults.vectorSearch.defaultTopK), true);
|
|
284
|
+
vectorCollectionPath = await ask("ベクトル検索対象のコレクションパス", baseTarget.vectorSearch.collectionPath, true);
|
|
285
|
+
sourceTextField = await ask("埋め込み作成対象のテキストフィールド", baseTarget.vectorSearch.sourceTextField, true);
|
|
286
|
+
vectorField = await ask("ベクトル保存先フィールド名", baseTarget.vectorSearch.vectorField, true);
|
|
287
|
+
returnFields = parseCsvFields(await ask("検索結果に返すフィールド(カンマ区切り)", toCsv(baseTarget.vectorSearch.returnFields), true));
|
|
288
|
+
searchFunctionName = normalizeFunctionName(await ask("onRequest 検索関数名", baseTarget.vectorSearch.functionName, true));
|
|
289
|
+
const defaultTopKText = await ask("ベクトル検索のデフォルト topK", String(baseTarget.vectorSearch.defaultTopK), true);
|
|
181
290
|
defaultTopK = Number.parseInt(defaultTopKText, 10);
|
|
182
291
|
if (Number.isNaN(defaultTopK) || defaultTopK <= 0) {
|
|
183
292
|
throw new Error("topK は 1 以上の整数で入力してください。");
|
|
184
293
|
}
|
|
185
294
|
}
|
|
186
295
|
return {
|
|
187
|
-
targetDir,
|
|
188
|
-
projectId,
|
|
189
|
-
region,
|
|
190
|
-
serviceId,
|
|
191
|
-
location,
|
|
296
|
+
targetDir: selectedTargetDir,
|
|
297
|
+
projectId: selectedProjectId,
|
|
298
|
+
region: selectedRegion,
|
|
299
|
+
serviceId: selectedServiceId,
|
|
300
|
+
location: selectedLocation,
|
|
192
301
|
documentPath,
|
|
193
302
|
functionName,
|
|
194
|
-
installDependencies,
|
|
195
|
-
configFileName
|
|
303
|
+
installDependencies: selectedInstallDependencies,
|
|
304
|
+
configFileName,
|
|
196
305
|
vectorSearch: {
|
|
197
306
|
enabled: vectorEnabled,
|
|
198
307
|
collectionPath: vectorCollectionPath,
|
|
@@ -201,7 +310,8 @@ export async function collectConfig(parsed) {
|
|
|
201
310
|
returnFields,
|
|
202
311
|
functionName: searchFunctionName,
|
|
203
312
|
defaultTopK
|
|
204
|
-
}
|
|
313
|
+
},
|
|
314
|
+
existingTargets: selectedTargetDir === targetDir ? saved.targets : []
|
|
205
315
|
};
|
|
206
316
|
}
|
|
207
317
|
finally {
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface CliArgs {
|
|
|
9
9
|
installDependencies?: boolean;
|
|
10
10
|
configFileName?: string;
|
|
11
11
|
vectorSearch?: Partial<VectorSearchConfig>;
|
|
12
|
+
addCollection?: boolean;
|
|
12
13
|
yes: boolean;
|
|
13
14
|
help: boolean;
|
|
14
15
|
}
|
|
@@ -32,8 +33,24 @@ export interface SetupConfig {
|
|
|
32
33
|
installDependencies: boolean;
|
|
33
34
|
configFileName: string;
|
|
34
35
|
vectorSearch: VectorSearchConfig;
|
|
36
|
+
existingTargets: SetupTargetConfig[];
|
|
35
37
|
}
|
|
36
38
|
export interface RunResult {
|
|
37
39
|
targetDir: string;
|
|
38
40
|
summaryLines: string[];
|
|
39
41
|
}
|
|
42
|
+
export interface SetupTargetConfig {
|
|
43
|
+
documentPath: string;
|
|
44
|
+
functionName: string;
|
|
45
|
+
vectorSearch: VectorSearchConfig;
|
|
46
|
+
}
|
|
47
|
+
export interface PersistedBootstrapConfig {
|
|
48
|
+
projectId: string;
|
|
49
|
+
region: string;
|
|
50
|
+
serviceId: string;
|
|
51
|
+
location: string;
|
|
52
|
+
installDependencies: boolean;
|
|
53
|
+
configFileName: string;
|
|
54
|
+
targets: SetupTargetConfig[];
|
|
55
|
+
lastTargetFunctionName?: string;
|
|
56
|
+
}
|