@dataformer/env-service 2.3.0 → 3.0.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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +125 -22
- package/package.json +2 -3
- package/src/index.ts +151 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @dataformer/env-service
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- change setSearchEngineId method to setCustomSearchEngineId
|
|
8
|
+
|
|
9
|
+
## 2.4.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- fix circular dependency with LoggerService, EnvService, and SecretManagerClient
|
|
14
|
+
|
|
3
15
|
## 2.3.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ declare const askQuestion: (question: string, defaultValue?: string) => Promise<
|
|
|
2
2
|
declare function obfuscateCred(cred: string | null): string;
|
|
3
3
|
declare function getEnvVar(baseSecretKey: string): Promise<string | null>;
|
|
4
4
|
declare function getCustomSearchApiKey(): Promise<string>;
|
|
5
|
-
declare function
|
|
5
|
+
declare function getCustomSearchEngineId(): Promise<string>;
|
|
6
6
|
declare function getGcpProjectId(): Promise<string>;
|
|
7
7
|
declare function setEnvVar(baseSecretKey: string, value: string): Promise<boolean>;
|
|
8
8
|
declare const setLogToConsole: () => Promise<void>;
|
|
@@ -23,6 +23,7 @@ declare const setJiraUserEmail: () => Promise<void>;
|
|
|
23
23
|
declare const setGeminiApiKey: () => Promise<void>;
|
|
24
24
|
declare const setNpmToken: () => Promise<void>;
|
|
25
25
|
declare const setCustomSearchApiKey: () => Promise<void>;
|
|
26
|
+
declare const setCustomSearchEngineId: () => Promise<void>;
|
|
26
27
|
declare const setFirestoreProjectId: () => Promise<void>;
|
|
27
28
|
declare const getLogToConsole: () => Promise<string | null>;
|
|
28
29
|
declare const getPostgresUser: () => Promise<string | null>;
|
|
@@ -46,4 +47,4 @@ declare const getProjectRoot: () => string;
|
|
|
46
47
|
declare const printEnv: () => Promise<void>;
|
|
47
48
|
declare const run: () => Promise<void>;
|
|
48
49
|
|
|
49
|
-
export { askQuestion, getBigtableInstanceName, getBigtableProjectId, getBigtableTableName, getCustomSearchApiKey, getEnvVar, getFirestoreProjectId, getGcpProjectId, getGeminiApiKey, getJiraApiBaseUrl, getJiraApiToken, getJiraUserEmail, getLogToConsole, getNpmToken, getPostgresAuthType, getPostgresDatabase, getPostgresHostName, getPostgresInstanceConnectionName, getPostgresIpType, getPostgresPassword, getPostgresPoolSizeMax, getPostgresUser, getProjectRoot,
|
|
50
|
+
export { askQuestion, getBigtableInstanceName, getBigtableProjectId, getBigtableTableName, getCustomSearchApiKey, getCustomSearchEngineId, getEnvVar, getFirestoreProjectId, getGcpProjectId, getGeminiApiKey, getJiraApiBaseUrl, getJiraApiToken, getJiraUserEmail, getLogToConsole, getNpmToken, getPostgresAuthType, getPostgresDatabase, getPostgresHostName, getPostgresInstanceConnectionName, getPostgresIpType, getPostgresPassword, getPostgresPoolSizeMax, getPostgresUser, getProjectRoot, obfuscateCred, printEnv, run, setBigtableInstanceName, setBigtableProjectId, setBigtableTableName, setCustomSearchApiKey, setCustomSearchEngineId, setEnvVar, setFirestoreProjectId, setGeminiApiKey, setJiraApiBaseUrl, setJiraApiToken, setJiraUserEmail, setLogToConsole, setNpmToken, setPostgresAuthType, setPostgresDatabase, setPostgresHostName, setPostgresInstanceConnectionName, setPostgresIpType, setPostgresPassword, setPostgresPoolSizeMax, setPostgresUser };
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,60 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import readline from "readline";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
function getGcpProjectIdFromGcloud() {
|
|
7
|
+
try {
|
|
8
|
+
const cmd = "gcloud config get-value core/project";
|
|
9
|
+
const projectId2 = execSync(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
10
|
+
if (projectId2 && projectId2 !== "(unset)") {
|
|
11
|
+
return projectId2;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getProjectId() {
|
|
19
|
+
if (process.env.GCP_PROJECT_ID) {
|
|
20
|
+
return process.env.GCP_PROJECT_ID;
|
|
21
|
+
}
|
|
22
|
+
const gcloudProjectId = getGcpProjectIdFromGcloud();
|
|
23
|
+
if (gcloudProjectId) {
|
|
24
|
+
return gcloudProjectId;
|
|
25
|
+
}
|
|
26
|
+
throw new Error(
|
|
27
|
+
"\u{1F6A8} GCP project ID is required for Secret Manager.\n\nCI/CD DEPLOYMENT (REQUIRED):\n Set GCP_PROJECT_ID as environment variable\n Example: GCP_PROJECT_ID=dataformer-prod\n\nLOCAL DEVELOPMENT:\n Set your gcloud project: gcloud config set project YOUR_PROJECT_ID\n Verify with: gcloud config get-value project"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
var secretManagerClient = null;
|
|
6
31
|
var secretManagerInitialized = false;
|
|
7
|
-
|
|
32
|
+
var projectId = null;
|
|
33
|
+
async function getSecretManagerClient() {
|
|
8
34
|
if (secretManagerInitialized) {
|
|
9
|
-
return
|
|
35
|
+
return secretManagerClient && projectId ? { client: secretManagerClient, projectId } : null;
|
|
10
36
|
}
|
|
11
37
|
try {
|
|
12
|
-
|
|
38
|
+
projectId = getProjectId();
|
|
39
|
+
secretManagerClient = new SecretManagerServiceClient({ projectId });
|
|
13
40
|
secretManagerInitialized = true;
|
|
14
|
-
return
|
|
41
|
+
return { client: secretManagerClient, projectId };
|
|
15
42
|
} catch (error) {
|
|
16
|
-
console.error("[EnvService.ts] Failed to initialize
|
|
43
|
+
console.error("[EnvService.ts] Failed to initialize SecretManagerServiceClient:", error.message);
|
|
17
44
|
console.warn("[EnvService.ts] Secret Manager functionality will be unavailable.");
|
|
18
45
|
secretManagerInitialized = true;
|
|
19
46
|
return null;
|
|
20
47
|
}
|
|
21
48
|
}
|
|
49
|
+
function buildSecretName(projectId2, baseSecretId) {
|
|
50
|
+
return `${projectId2}_${baseSecretId}`;
|
|
51
|
+
}
|
|
52
|
+
function buildSecretPath(projectId2, secretName) {
|
|
53
|
+
return `projects/${projectId2}/secrets/${secretName}`;
|
|
54
|
+
}
|
|
55
|
+
function buildSecretVersionPath(projectId2, secretName, version = "latest") {
|
|
56
|
+
return `projects/${projectId2}/secrets/${secretName}/versions/${version}`;
|
|
57
|
+
}
|
|
22
58
|
var rl = readline.createInterface({
|
|
23
59
|
input: process.stdin,
|
|
24
60
|
output: process.stdout
|
|
@@ -40,12 +76,26 @@ function obfuscateCred(cred) {
|
|
|
40
76
|
}
|
|
41
77
|
}
|
|
42
78
|
async function getEnvVar(baseSecretKey) {
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
79
|
+
const smClient = await getSecretManagerClient();
|
|
80
|
+
if (!smClient) {
|
|
45
81
|
console.warn(`[EnvService.ts] SecretManager not available for getEnvVar (key: ${baseSecretKey})`);
|
|
46
82
|
return null;
|
|
47
83
|
}
|
|
48
|
-
|
|
84
|
+
try {
|
|
85
|
+
const secretName = buildSecretName(smClient.projectId, baseSecretKey);
|
|
86
|
+
const secretVersionPath = buildSecretVersionPath(smClient.projectId, secretName);
|
|
87
|
+
const [response] = await smClient.client.accessSecretVersion({
|
|
88
|
+
name: secretVersionPath
|
|
89
|
+
});
|
|
90
|
+
const secretValue = response.payload?.data?.toString();
|
|
91
|
+
return secretValue || null;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.code === 5) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
console.warn(`[EnvService.ts] Failed to get secret ${baseSecretKey}: ${error.message}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
49
99
|
}
|
|
50
100
|
async function getCustomSearchApiKey() {
|
|
51
101
|
const apiKey = await getEnvVar("CUSTOM_SEARCH_API_KEY");
|
|
@@ -54,10 +104,10 @@ async function getCustomSearchApiKey() {
|
|
|
54
104
|
}
|
|
55
105
|
return apiKey;
|
|
56
106
|
}
|
|
57
|
-
async function
|
|
58
|
-
const searchEngineId = await getEnvVar("
|
|
107
|
+
async function getCustomSearchEngineId() {
|
|
108
|
+
const searchEngineId = await getEnvVar("CUSTOM_SEARCH_ENGINE_ID");
|
|
59
109
|
if (!searchEngineId) {
|
|
60
|
-
throw new Error("
|
|
110
|
+
throw new Error("CUSTOM_SEARCH_ENGINE_ID not found in environment variables.");
|
|
61
111
|
}
|
|
62
112
|
return searchEngineId;
|
|
63
113
|
}
|
|
@@ -67,15 +117,15 @@ async function getGcpProjectId() {
|
|
|
67
117
|
return envProjectId;
|
|
68
118
|
}
|
|
69
119
|
try {
|
|
70
|
-
const { execSync } = await import("child_process");
|
|
71
|
-
const
|
|
120
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
121
|
+
const projectId2 = execSync2("gcloud config get-value project", {
|
|
72
122
|
encoding: "utf8",
|
|
73
123
|
stdio: ["ignore", "pipe", "ignore"]
|
|
74
124
|
}).trim();
|
|
75
|
-
if (!
|
|
125
|
+
if (!projectId2 || projectId2 === "(unset)") {
|
|
76
126
|
throw new Error("No GCP project configured. Run `gcloud config set project YOUR_PROJECT_ID`");
|
|
77
127
|
}
|
|
78
|
-
return
|
|
128
|
+
return projectId2;
|
|
79
129
|
} catch (error) {
|
|
80
130
|
throw new Error(
|
|
81
131
|
"GCP_PROJECT_ID not found. Either:\n1. Set GCP_PROJECT_ID environment variable (for deployed environments), or\n2. Configure gcloud: `gcloud config set project YOUR_PROJECT_ID` (for local development)"
|
|
@@ -83,17 +133,58 @@ async function getGcpProjectId() {
|
|
|
83
133
|
}
|
|
84
134
|
}
|
|
85
135
|
async function setEnvVar(baseSecretKey, value) {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
136
|
+
const smClient = await getSecretManagerClient();
|
|
137
|
+
if (!smClient) {
|
|
88
138
|
console.warn(`[EnvService.ts] SecretManager not available for setEnvVar (key: ${baseSecretKey})`);
|
|
89
139
|
return false;
|
|
90
140
|
}
|
|
91
|
-
|
|
141
|
+
try {
|
|
142
|
+
const secretName = buildSecretName(smClient.projectId, baseSecretKey);
|
|
143
|
+
const addVersion = async () => {
|
|
144
|
+
const secretPath = buildSecretPath(smClient.projectId, secretName);
|
|
145
|
+
await smClient.client.addSecretVersion({
|
|
146
|
+
parent: secretPath,
|
|
147
|
+
payload: {
|
|
148
|
+
data: Buffer.from(value, "utf8")
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
console.log(`\u2705 Secret '${baseSecretKey}' saved in GCP project: ${smClient.projectId}`);
|
|
152
|
+
};
|
|
153
|
+
try {
|
|
154
|
+
await addVersion();
|
|
155
|
+
return true;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err.code === 5) {
|
|
158
|
+
await smClient.client.createSecret({
|
|
159
|
+
parent: `projects/${smClient.projectId}`,
|
|
160
|
+
secretId: secretName,
|
|
161
|
+
secret: {
|
|
162
|
+
replication: {
|
|
163
|
+
automatic: {}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
console.log(`[EnvService.ts] Created secret resource: ${secretName}`);
|
|
168
|
+
await addVersion();
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
console.warn(`[EnvService.ts] setEnvVar failed: ${err.message}`);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.warn(`[EnvService.ts] Failed to set secret ${baseSecretKey}: ${error.message}`);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
92
178
|
}
|
|
93
179
|
var setLogToConsole = async () => {
|
|
94
180
|
const logToConsole = await askQuestion("Use consoleLogger to log to console? (yes, no)", "no");
|
|
95
181
|
if (logToConsole) {
|
|
96
|
-
|
|
182
|
+
const normalizedValue = logToConsole.toLowerCase();
|
|
183
|
+
if (normalizedValue === "yes" || normalizedValue === "no") {
|
|
184
|
+
await setEnvVar("LOG_TO_CONSOLE", normalizedValue);
|
|
185
|
+
} else {
|
|
186
|
+
console.warn(`Invalid LOG_TO_CONSOLE value: '${logToConsole}'. Only 'yes' or 'no' are valid.`);
|
|
187
|
+
}
|
|
97
188
|
}
|
|
98
189
|
};
|
|
99
190
|
var setPostgresUser = async () => {
|
|
@@ -194,6 +285,12 @@ var setCustomSearchApiKey = async () => {
|
|
|
194
285
|
await setEnvVar("CUSTOM_SEARCH_API_KEY", customSearchApiKey);
|
|
195
286
|
}
|
|
196
287
|
};
|
|
288
|
+
var setCustomSearchEngineId = async () => {
|
|
289
|
+
const customSearchEngineId = await askQuestion("Enter Custom Search Engine ID", "");
|
|
290
|
+
if (customSearchEngineId) {
|
|
291
|
+
await setEnvVar("CUSTOM_SEARCH_ENGINE_ID", customSearchEngineId);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
197
294
|
var setFirestoreProjectId = async () => {
|
|
198
295
|
const firestoreProjectId = await askQuestion("Enter Firestore Project ID", "dataformer-prod");
|
|
199
296
|
if (firestoreProjectId) {
|
|
@@ -245,6 +342,7 @@ var printEnv = async () => {
|
|
|
245
342
|
const jiraUserEmail = await getJiraUserEmail();
|
|
246
343
|
const geminiApiKey = await getGeminiApiKey();
|
|
247
344
|
const customSearchApiKey = await getCustomSearchApiKey();
|
|
345
|
+
const customSearchEngineId = await getCustomSearchEngineId();
|
|
248
346
|
const gcpProjectIdVal = await getGcpProjectId();
|
|
249
347
|
const npmToken = await getNpmToken();
|
|
250
348
|
console.log(`Project Root: ${getProjectRoot()}`);
|
|
@@ -266,6 +364,7 @@ var printEnv = async () => {
|
|
|
266
364
|
console.log(`Jira API Base URL: ${jiraApiBaseUrl}`);
|
|
267
365
|
console.log(`Jira User Email: ${jiraUserEmail}`);
|
|
268
366
|
console.log(`Custom Search API Key: ${obfuscateCred(customSearchApiKey)}`);
|
|
367
|
+
console.log(`Custom Search Engine ID: ${customSearchEngineId}`);
|
|
269
368
|
console.log(`Gemini API Key: ${obfuscateCred(geminiApiKey)}`);
|
|
270
369
|
console.log(`NPM Token: ${obfuscateCred(npmToken)}`);
|
|
271
370
|
};
|
|
@@ -329,6 +428,9 @@ var run = async () => {
|
|
|
329
428
|
case "setCustomSearchApiKey":
|
|
330
429
|
await setCustomSearchApiKey();
|
|
331
430
|
break;
|
|
431
|
+
case "setCustomSearchEngineId":
|
|
432
|
+
await setCustomSearchEngineId();
|
|
433
|
+
break;
|
|
332
434
|
case "printEnv":
|
|
333
435
|
await printEnv();
|
|
334
436
|
break;
|
|
@@ -350,6 +452,7 @@ export {
|
|
|
350
452
|
getBigtableProjectId,
|
|
351
453
|
getBigtableTableName,
|
|
352
454
|
getCustomSearchApiKey,
|
|
455
|
+
getCustomSearchEngineId,
|
|
353
456
|
getEnvVar,
|
|
354
457
|
getFirestoreProjectId,
|
|
355
458
|
getGcpProjectId,
|
|
@@ -368,7 +471,6 @@ export {
|
|
|
368
471
|
getPostgresPoolSizeMax,
|
|
369
472
|
getPostgresUser,
|
|
370
473
|
getProjectRoot,
|
|
371
|
-
getSearchEngineId,
|
|
372
474
|
obfuscateCred,
|
|
373
475
|
printEnv,
|
|
374
476
|
run,
|
|
@@ -376,6 +478,7 @@ export {
|
|
|
376
478
|
setBigtableProjectId,
|
|
377
479
|
setBigtableTableName,
|
|
378
480
|
setCustomSearchApiKey,
|
|
481
|
+
setCustomSearchEngineId,
|
|
379
482
|
setEnvVar,
|
|
380
483
|
setFirestoreProjectId,
|
|
381
484
|
setGeminiApiKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dataformer/env-service",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Environment service for Dataformer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@google-cloud/secret-manager": "^5.4.0"
|
|
16
|
-
"@dataformer/secret-manager-client": "^2.3.0"
|
|
15
|
+
"@google-cloud/secret-manager": "^5.4.0"
|
|
17
16
|
},
|
|
18
17
|
"devDependencies": {
|
|
19
18
|
"tsup": "^8.0.0",
|
package/src/index.ts
CHANGED
|
@@ -39,29 +39,83 @@
|
|
|
39
39
|
|
|
40
40
|
import readline from 'readline';
|
|
41
41
|
import path from 'path';
|
|
42
|
-
import {
|
|
42
|
+
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
|
|
43
|
+
import { execSync } from 'child_process';
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
-
|
|
45
|
+
// Helper functions for GCP project ID detection
|
|
46
|
+
function getGcpProjectIdFromGcloud(): string | null {
|
|
47
|
+
try {
|
|
48
|
+
const cmd = 'gcloud config get-value core/project';
|
|
49
|
+
const projectId = execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
50
|
+
if (projectId && projectId !== '(unset)') {
|
|
51
|
+
return projectId;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getProjectId(): string {
|
|
60
|
+
// Project ID resolution priority:
|
|
61
|
+
// 1. GCP_PROJECT_ID env var (REQUIRED for CI/CD)
|
|
62
|
+
// 2. gcloud config project (for local development)
|
|
63
|
+
if (process.env.GCP_PROJECT_ID) {
|
|
64
|
+
return process.env.GCP_PROJECT_ID;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const gcloudProjectId = getGcpProjectIdFromGcloud();
|
|
68
|
+
if (gcloudProjectId) {
|
|
69
|
+
return gcloudProjectId;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
throw new Error(
|
|
73
|
+
'🚨 GCP project ID is required for Secret Manager.\n\n' +
|
|
74
|
+
'CI/CD DEPLOYMENT (REQUIRED):\n' +
|
|
75
|
+
' Set GCP_PROJECT_ID as environment variable\n' +
|
|
76
|
+
' Example: GCP_PROJECT_ID=dataformer-prod\n\n' +
|
|
77
|
+
'LOCAL DEVELOPMENT:\n' +
|
|
78
|
+
' Set your gcloud project: gcloud config set project YOUR_PROJECT_ID\n' +
|
|
79
|
+
' Verify with: gcloud config get-value project'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Secret Manager direct implementation
|
|
84
|
+
let secretManagerClient: SecretManagerServiceClient | null = null;
|
|
46
85
|
let secretManagerInitialized = false;
|
|
86
|
+
let projectId: string | null = null;
|
|
47
87
|
|
|
48
|
-
async function
|
|
88
|
+
async function getSecretManagerClient(): Promise<{ client: SecretManagerServiceClient; projectId: string } | null> {
|
|
49
89
|
if (secretManagerInitialized) {
|
|
50
|
-
return
|
|
90
|
+
return secretManagerClient && projectId ? { client: secretManagerClient, projectId } : null;
|
|
51
91
|
}
|
|
52
92
|
|
|
53
93
|
try {
|
|
54
|
-
|
|
94
|
+
projectId = getProjectId();
|
|
95
|
+
secretManagerClient = new SecretManagerServiceClient({ projectId });
|
|
55
96
|
secretManagerInitialized = true;
|
|
56
|
-
return
|
|
97
|
+
return { client: secretManagerClient, projectId };
|
|
57
98
|
} catch (error) {
|
|
58
|
-
console.error('[EnvService.ts] Failed to initialize
|
|
99
|
+
console.error('[EnvService.ts] Failed to initialize SecretManagerServiceClient:', (error as Error).message);
|
|
59
100
|
console.warn('[EnvService.ts] Secret Manager functionality will be unavailable.');
|
|
60
101
|
secretManagerInitialized = true; // Prevent retrying
|
|
61
102
|
return null;
|
|
62
103
|
}
|
|
63
104
|
}
|
|
64
105
|
|
|
106
|
+
// Helper functions for secret operations
|
|
107
|
+
function buildSecretName(projectId: string, baseSecretId: string): string {
|
|
108
|
+
return `${projectId}_${baseSecretId}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildSecretPath(projectId: string, secretName: string): string {
|
|
112
|
+
return `projects/${projectId}/secrets/${secretName}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildSecretVersionPath(projectId: string, secretName: string, version: string = 'latest'): string {
|
|
116
|
+
return `projects/${projectId}/secrets/${secretName}/versions/${version}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
65
119
|
const rl = readline.createInterface({
|
|
66
120
|
input: process.stdin,
|
|
67
121
|
output: process.stdout,
|
|
@@ -87,12 +141,29 @@ function obfuscateCred(cred: string | null): string {
|
|
|
87
141
|
|
|
88
142
|
// --- Generic Environment Variable Accessors ---
|
|
89
143
|
async function getEnvVar(baseSecretKey: string): Promise<string | null> {
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
144
|
+
const smClient = await getSecretManagerClient();
|
|
145
|
+
if (!smClient) {
|
|
92
146
|
console.warn(`[EnvService.ts] SecretManager not available for getEnvVar (key: ${baseSecretKey})`);
|
|
93
147
|
return null;
|
|
94
148
|
}
|
|
95
|
-
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const secretName = buildSecretName(smClient.projectId, baseSecretKey);
|
|
152
|
+
const secretVersionPath = buildSecretVersionPath(smClient.projectId, secretName);
|
|
153
|
+
|
|
154
|
+
const [response] = await smClient.client.accessSecretVersion({
|
|
155
|
+
name: secretVersionPath,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const secretValue = response.payload?.data?.toString();
|
|
159
|
+
return secretValue || null;
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
if (error.code === 5) { // NOT_FOUND
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
console.warn(`[EnvService.ts] Failed to get secret ${baseSecretKey}: ${error.message}`);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
96
167
|
}
|
|
97
168
|
|
|
98
169
|
export async function getCustomSearchApiKey(): Promise<string> {
|
|
@@ -103,10 +174,10 @@ export async function getCustomSearchApiKey(): Promise<string> {
|
|
|
103
174
|
return apiKey;
|
|
104
175
|
}
|
|
105
176
|
|
|
106
|
-
export async function
|
|
107
|
-
const searchEngineId = await getEnvVar('
|
|
177
|
+
export async function getCustomSearchEngineId(): Promise<string> {
|
|
178
|
+
const searchEngineId = await getEnvVar('CUSTOM_SEARCH_ENGINE_ID');
|
|
108
179
|
if (!searchEngineId) {
|
|
109
|
-
throw new Error('
|
|
180
|
+
throw new Error('CUSTOM_SEARCH_ENGINE_ID not found in environment variables.');
|
|
110
181
|
}
|
|
111
182
|
return searchEngineId;
|
|
112
183
|
}
|
|
@@ -141,21 +212,67 @@ export async function getGcpProjectId(): Promise<string> {
|
|
|
141
212
|
}
|
|
142
213
|
|
|
143
214
|
async function setEnvVar(baseSecretKey: string, value: string): Promise<boolean> {
|
|
144
|
-
const
|
|
145
|
-
if (!
|
|
215
|
+
const smClient = await getSecretManagerClient();
|
|
216
|
+
if (!smClient) {
|
|
146
217
|
console.warn(`[EnvService.ts] SecretManager not available for setEnvVar (key: ${baseSecretKey})`);
|
|
147
218
|
return false; // Indicate failure
|
|
148
219
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const secretName = buildSecretName(smClient.projectId, baseSecretKey);
|
|
223
|
+
|
|
224
|
+
// Try to add a version first
|
|
225
|
+
const addVersion = async (): Promise<void> => {
|
|
226
|
+
const secretPath = buildSecretPath(smClient.projectId, secretName);
|
|
227
|
+
await smClient.client.addSecretVersion({
|
|
228
|
+
parent: secretPath,
|
|
229
|
+
payload: {
|
|
230
|
+
data: Buffer.from(value, 'utf8'),
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
console.log(`✅ Secret '${baseSecretKey}' saved in GCP project: ${smClient.projectId}`);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await addVersion();
|
|
238
|
+
return true;
|
|
239
|
+
} catch (err: any) {
|
|
240
|
+
if (err.code === 5) { // NOT_FOUND → create then retry
|
|
241
|
+
// Create the secret resource first
|
|
242
|
+
await smClient.client.createSecret({
|
|
243
|
+
parent: `projects/${smClient.projectId}`,
|
|
244
|
+
secretId: secretName,
|
|
245
|
+
secret: {
|
|
246
|
+
replication: {
|
|
247
|
+
automatic: {},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
console.log(`[EnvService.ts] Created secret resource: ${secretName}`);
|
|
252
|
+
|
|
253
|
+
await addVersion();
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
console.warn(`[EnvService.ts] setEnvVar failed: ${err.message}`);
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
} catch (error: any) {
|
|
260
|
+
console.warn(`[EnvService.ts] Failed to set secret ${baseSecretKey}: ${error.message}`);
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
152
263
|
}
|
|
153
264
|
|
|
154
265
|
// --- Specific Setters (Now use generic setEnvVar) ---
|
|
155
266
|
const setLogToConsole = async (): Promise<void> => {
|
|
156
267
|
const logToConsole = await askQuestion('Use consoleLogger to log to console? (yes, no)', 'no');
|
|
157
268
|
if (logToConsole) {
|
|
158
|
-
|
|
269
|
+
// Normalize to lowercase and validate - only 'yes' or 'no' are valid
|
|
270
|
+
const normalizedValue = logToConsole.toLowerCase();
|
|
271
|
+
if (normalizedValue === 'yes' || normalizedValue === 'no') {
|
|
272
|
+
await setEnvVar('LOG_TO_CONSOLE', normalizedValue);
|
|
273
|
+
} else {
|
|
274
|
+
console.warn(`Invalid LOG_TO_CONSOLE value: '${logToConsole}'. Only 'yes' or 'no' are valid.`);
|
|
275
|
+
}
|
|
159
276
|
}
|
|
160
277
|
};
|
|
161
278
|
|
|
@@ -274,6 +391,13 @@ const setCustomSearchApiKey = async (): Promise<void> => {
|
|
|
274
391
|
}
|
|
275
392
|
};
|
|
276
393
|
|
|
394
|
+
const setCustomSearchEngineId = async (): Promise<void> => {
|
|
395
|
+
const customSearchEngineId = await askQuestion('Enter Custom Search Engine ID', '');
|
|
396
|
+
if (customSearchEngineId) {
|
|
397
|
+
await setEnvVar('CUSTOM_SEARCH_ENGINE_ID', customSearchEngineId);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
277
401
|
const setFirestoreProjectId = async (): Promise<void> => {
|
|
278
402
|
const firestoreProjectId = await askQuestion('Enter Firestore Project ID', 'dataformer-prod');
|
|
279
403
|
if (firestoreProjectId) {
|
|
@@ -315,12 +439,12 @@ const getProjectRoot = (): string => {
|
|
|
315
439
|
};
|
|
316
440
|
|
|
317
441
|
const _getResolvedGcpProjectId = async (): Promise<string | null> => {
|
|
318
|
-
const
|
|
319
|
-
if (!
|
|
442
|
+
const smClient = await getSecretManagerClient();
|
|
443
|
+
if (!smClient) {
|
|
320
444
|
console.warn('[EnvService.ts] SecretManager not available for _getResolvedGcpProjectId');
|
|
321
445
|
return null;
|
|
322
446
|
}
|
|
323
|
-
return
|
|
447
|
+
return smClient.projectId;
|
|
324
448
|
};
|
|
325
449
|
|
|
326
450
|
const printEnv = async (): Promise<void> => {
|
|
@@ -342,6 +466,7 @@ const printEnv = async (): Promise<void> => {
|
|
|
342
466
|
const jiraUserEmail = await getJiraUserEmail();
|
|
343
467
|
const geminiApiKey = await getGeminiApiKey();
|
|
344
468
|
const customSearchApiKey = await getCustomSearchApiKey();
|
|
469
|
+
const customSearchEngineId = await getCustomSearchEngineId();
|
|
345
470
|
const gcpProjectIdVal = await getGcpProjectId();
|
|
346
471
|
const npmToken = await getNpmToken();
|
|
347
472
|
|
|
@@ -364,6 +489,7 @@ const printEnv = async (): Promise<void> => {
|
|
|
364
489
|
console.log(`Jira API Base URL: ${jiraApiBaseUrl}`);
|
|
365
490
|
console.log(`Jira User Email: ${jiraUserEmail}`);
|
|
366
491
|
console.log(`Custom Search API Key: ${obfuscateCred(customSearchApiKey)}`);
|
|
492
|
+
console.log(`Custom Search Engine ID: ${customSearchEngineId}`);
|
|
367
493
|
console.log(`Gemini API Key: ${obfuscateCred(geminiApiKey)}`);
|
|
368
494
|
console.log(`NPM Token: ${obfuscateCred(npmToken)}`);
|
|
369
495
|
};
|
|
@@ -390,6 +516,7 @@ const run = async (): Promise<void> => {
|
|
|
390
516
|
case 'setGeminiApiKey': await setGeminiApiKey(); break;
|
|
391
517
|
case 'setNpmToken': await setNpmToken(); break;
|
|
392
518
|
case 'setCustomSearchApiKey': await setCustomSearchApiKey(); break;
|
|
519
|
+
case 'setCustomSearchEngineId': await setCustomSearchEngineId(); break;
|
|
393
520
|
case 'printEnv': await printEnv(); break;
|
|
394
521
|
default: console.log('Unknown command. Available commands: setEnv, setBearerToken, ..., printEnv');
|
|
395
522
|
}
|
|
@@ -431,6 +558,7 @@ export {
|
|
|
431
558
|
setGeminiApiKey,
|
|
432
559
|
setNpmToken,
|
|
433
560
|
setCustomSearchApiKey,
|
|
561
|
+
setCustomSearchEngineId,
|
|
434
562
|
|
|
435
563
|
// Getters
|
|
436
564
|
getLogToConsole,
|