@el-j/google-sheet-translations 2.2.0-beta.4 → 2.2.0-beta.5
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 +17 -0
- package/dist/esm/index.js +220 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +223 -0
- package/dist/setup/cli.d.ts +13 -0
- package/dist/setup/cli.d.ts.map +1 -0
- package/dist/setup/wifSetup.d.ts +95 -0
- package/dist/setup/wifSetup.d.ts.map +1 -0
- package/dist/utils/docIngester.d.ts.map +1 -1
- package/dist-cli/setup-wif.mjs +18948 -0
- package/package.json +25 -20
package/README.md
CHANGED
|
@@ -378,6 +378,23 @@ jobs:
|
|
|
378
378
|
| `spreadsheet-title` | ❌ | `google-sheet-translations` | Title for the auto-created spreadsheet |
|
|
379
379
|
| `source-locale` | ❌ | `en` | Source locale used as the base for auto-translate formulas |
|
|
380
380
|
| `target-locales` | ❌ | `de,fr,es,it,pt,ja,zh` | Comma-separated target locales added to the auto-created spreadsheet |
|
|
381
|
+
| `drive-folder-id` | ❌ | `''` | Drive folder ID used to auto-discover spreadsheets, docs, and images |
|
|
382
|
+
| `scan-for-spreadsheets` | ❌ | `true` | When `drive-folder-id` is set, scan the folder recursively for spreadsheets |
|
|
383
|
+
| `spreadsheet-ids` | ❌ | `''` | Optional extra spreadsheet IDs to merge with any IDs discovered in Drive |
|
|
384
|
+
| `sync-images` | ❌ | `false` | Download Drive images alongside translation sync |
|
|
385
|
+
| `image-output-path` | ❌ | `./public/remote-images` | Output directory for downloaded Drive images when `sync-images` is enabled |
|
|
386
|
+
|
|
387
|
+
### Drive Folder Mode
|
|
388
|
+
|
|
389
|
+
When `drive-folder-id` is set, the action switches from single-spreadsheet mode to `manageDriveTranslations()`. That enables recursive spreadsheet discovery, optional image sync, and optional explicit `spreadsheet-ids` on the same run.
|
|
390
|
+
|
|
391
|
+
Operational notes:
|
|
392
|
+
|
|
393
|
+
- If the Drive folder is empty and `auto-create` remains enabled, the package creates a spreadsheet and then tries to move it into the target folder.
|
|
394
|
+
- `spreadsheetNameFilter` only applies to discovered sheets in the programmatic API. Explicit `spreadsheet-ids` are never filtered out.
|
|
395
|
+
- `sync-images` supports incremental freshness checks and optional `cleanSync` deletion in the programmatic API. See the Drive Folder guide for those semantics before enabling destructive cleanup.
|
|
396
|
+
|
|
397
|
+
See the full Drive Folder guide in `website/guide/drive-folder.md` for the complete API surface, image sync semantics, and Google Doc ingestion details.
|
|
381
398
|
|
|
382
399
|
### Outputs
|
|
383
400
|
|
package/dist/esm/index.js
CHANGED
|
@@ -2070,6 +2070,9 @@ function entriesToSeedKeys(entries) {
|
|
|
2070
2070
|
return keys;
|
|
2071
2071
|
}
|
|
2072
2072
|
function entriesToTranslationData(entries, locale) {
|
|
2073
|
+
if (locale === "__proto__" || locale === "constructor" || locale === "prototype") {
|
|
2074
|
+
return {};
|
|
2075
|
+
}
|
|
2073
2076
|
const data = {};
|
|
2074
2077
|
data[locale] = {};
|
|
2075
2078
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -2398,11 +2401,226 @@ async function manageDriveTranslations(options) {
|
|
|
2398
2401
|
return { translations, spreadsheetIds: filteredIds, imageSync, manifest, docIngestResults };
|
|
2399
2402
|
}
|
|
2400
2403
|
|
|
2404
|
+
// src/setup/wifSetup.ts
|
|
2405
|
+
import { GoogleAuth as GoogleAuth4 } from "google-auth-library";
|
|
2406
|
+
var GcpApiError = class extends Error {
|
|
2407
|
+
constructor(message, status) {
|
|
2408
|
+
super(message);
|
|
2409
|
+
this.status = status;
|
|
2410
|
+
this.name = "GcpApiError";
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
async function getAccessToken4(keyFilePath) {
|
|
2414
|
+
const auth = new GoogleAuth4({
|
|
2415
|
+
...keyFilePath ? { keyFilename: keyFilePath } : {},
|
|
2416
|
+
scopes: ["https://www.googleapis.com/auth/cloud-platform"]
|
|
2417
|
+
});
|
|
2418
|
+
const client = await auth.getClient();
|
|
2419
|
+
const tokenResponse = await client.getAccessToken();
|
|
2420
|
+
if (!tokenResponse.token) {
|
|
2421
|
+
throw new Error(
|
|
2422
|
+
"Failed to obtain a Google Cloud access token. Ensure you are authenticated via Application Default Credentials (run: gcloud auth application-default login) or provide --key-file pointing to a service account JSON key."
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2425
|
+
return tokenResponse.token;
|
|
2426
|
+
}
|
|
2427
|
+
async function gcpFetch(url, token, method = "GET", body) {
|
|
2428
|
+
const response = await fetch(url, {
|
|
2429
|
+
method,
|
|
2430
|
+
headers: {
|
|
2431
|
+
Authorization: `Bearer ${token}`,
|
|
2432
|
+
"Content-Type": "application/json"
|
|
2433
|
+
},
|
|
2434
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
2435
|
+
});
|
|
2436
|
+
const data = await response.json();
|
|
2437
|
+
if (!response.ok) {
|
|
2438
|
+
const errData = data;
|
|
2439
|
+
const message = errData.error?.message ?? `HTTP ${response.status}`;
|
|
2440
|
+
throw new GcpApiError(message, response.status);
|
|
2441
|
+
}
|
|
2442
|
+
return data;
|
|
2443
|
+
}
|
|
2444
|
+
async function pollOperation(operationName, token, maxWaitMs = 6e4) {
|
|
2445
|
+
const opUrl = operationName.startsWith("http") ? operationName : `https://iam.googleapis.com/v1/${operationName}`;
|
|
2446
|
+
const deadline = Date.now() + maxWaitMs;
|
|
2447
|
+
const maxWaitSecs = Math.round(maxWaitMs / 1e3);
|
|
2448
|
+
while (Date.now() < deadline) {
|
|
2449
|
+
const op = await gcpFetch(opUrl, token);
|
|
2450
|
+
if (op.done) {
|
|
2451
|
+
if (op.error) {
|
|
2452
|
+
throw new Error(`Operation failed: ${op.error.message}`);
|
|
2453
|
+
}
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
2457
|
+
}
|
|
2458
|
+
if (Date.now() >= deadline) {
|
|
2459
|
+
throw new Error(
|
|
2460
|
+
`Operation timed out after ${maxWaitSecs} s. The resources may still be provisioning in the background \u2013 re-running the command is safe (existing resources are reused).`
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
async function getProjectNumber(projectId, token) {
|
|
2465
|
+
const data = await gcpFetch(
|
|
2466
|
+
`https://cloudresourcemanager.googleapis.com/v1/projects/${encodeURIComponent(projectId)}`,
|
|
2467
|
+
token
|
|
2468
|
+
);
|
|
2469
|
+
return data.projectNumber;
|
|
2470
|
+
}
|
|
2471
|
+
async function createOrGetWifPool(projectId, poolId, token) {
|
|
2472
|
+
try {
|
|
2473
|
+
const op = await gcpFetch(
|
|
2474
|
+
`https://iam.googleapis.com/v1/projects/${encodeURIComponent(projectId)}/locations/global/workloadIdentityPools?workloadIdentityPoolId=${encodeURIComponent(poolId)}`,
|
|
2475
|
+
token,
|
|
2476
|
+
"POST",
|
|
2477
|
+
{
|
|
2478
|
+
displayName: "GitHub Actions Pool",
|
|
2479
|
+
description: "Pool for GitHub Actions OIDC authentication",
|
|
2480
|
+
disabled: false
|
|
2481
|
+
}
|
|
2482
|
+
);
|
|
2483
|
+
if (!op.done) {
|
|
2484
|
+
await pollOperation(op.name, token);
|
|
2485
|
+
} else if (op.error) {
|
|
2486
|
+
throw new Error(`Operation failed: ${op.error.message}`);
|
|
2487
|
+
}
|
|
2488
|
+
} catch (err) {
|
|
2489
|
+
if (err instanceof GcpApiError && err.status === 409) {
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
throw err;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
async function createOrGetWifProvider(projectId, poolId, providerId, githubRepo, token) {
|
|
2496
|
+
try {
|
|
2497
|
+
const op = await gcpFetch(
|
|
2498
|
+
`https://iam.googleapis.com/v1/projects/${encodeURIComponent(projectId)}/locations/global/workloadIdentityPools/${encodeURIComponent(poolId)}/providers?workloadIdentityPoolProviderId=${encodeURIComponent(providerId)}`,
|
|
2499
|
+
token,
|
|
2500
|
+
"POST",
|
|
2501
|
+
{
|
|
2502
|
+
displayName: "GitHub OIDC Provider",
|
|
2503
|
+
disabled: false,
|
|
2504
|
+
attributeMapping: {
|
|
2505
|
+
"google.subject": "assertion.sub",
|
|
2506
|
+
"attribute.actor": "assertion.actor",
|
|
2507
|
+
"attribute.repository": "assertion.repository"
|
|
2508
|
+
},
|
|
2509
|
+
// Scope the provider to this exact repository for security
|
|
2510
|
+
attributeCondition: `assertion.repository=='${githubRepo}'`,
|
|
2511
|
+
oidc: {
|
|
2512
|
+
issuerUri: "https://token.actions.githubusercontent.com"
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
);
|
|
2516
|
+
if (!op.done) {
|
|
2517
|
+
await pollOperation(op.name, token);
|
|
2518
|
+
} else if (op.error) {
|
|
2519
|
+
throw new Error(`Operation failed: ${op.error.message}`);
|
|
2520
|
+
}
|
|
2521
|
+
} catch (err) {
|
|
2522
|
+
if (err instanceof GcpApiError && err.status === 409) {
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
throw err;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
async function bindServiceAccount(projectId, serviceAccountEmail, projectNumber, poolId, githubRepo, token) {
|
|
2529
|
+
const saResource = `projects/${encodeURIComponent(projectId)}/serviceAccounts/${encodeURIComponent(serviceAccountEmail)}`;
|
|
2530
|
+
const principal = `principalSet://iam.googleapis.com/projects/${projectNumber}/locations/global/workloadIdentityPools/${poolId}/attribute.repository/${githubRepo}`;
|
|
2531
|
+
const policy = await gcpFetch(
|
|
2532
|
+
`https://iam.googleapis.com/v1/${saResource}:getIamPolicy`,
|
|
2533
|
+
token,
|
|
2534
|
+
"POST",
|
|
2535
|
+
{}
|
|
2536
|
+
);
|
|
2537
|
+
const bindings = policy.bindings ?? [];
|
|
2538
|
+
const role = "roles/iam.workloadIdentityUser";
|
|
2539
|
+
const existing = bindings.find((b) => b.role === role);
|
|
2540
|
+
if (existing) {
|
|
2541
|
+
if (existing.members.includes(principal)) {
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
existing.members.push(principal);
|
|
2545
|
+
} else {
|
|
2546
|
+
bindings.push({ role, members: [principal] });
|
|
2547
|
+
}
|
|
2548
|
+
await gcpFetch(
|
|
2549
|
+
`https://iam.googleapis.com/v1/${saResource}:setIamPolicy`,
|
|
2550
|
+
token,
|
|
2551
|
+
"POST",
|
|
2552
|
+
{ policy: { ...policy, bindings } }
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
async function setupWIF(options) {
|
|
2556
|
+
const poolId = options.poolId ?? "github-actions";
|
|
2557
|
+
const providerId = options.providerId ?? "github-oidc";
|
|
2558
|
+
const log = options.onProgress ?? (() => void 0);
|
|
2559
|
+
if (!options.githubRepo.includes("/")) {
|
|
2560
|
+
throw new Error(
|
|
2561
|
+
`githubRepo must be in "owner/repo" format, got: "${options.githubRepo}"`
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
log("Authenticating with Google Cloud...");
|
|
2565
|
+
const token = await getAccessToken4(options.keyFilePath);
|
|
2566
|
+
log("Fetching project number...");
|
|
2567
|
+
const projectNumber = await getProjectNumber(options.projectId, token);
|
|
2568
|
+
log(`Creating Workload Identity Pool "${poolId}"...`);
|
|
2569
|
+
await createOrGetWifPool(options.projectId, poolId, token);
|
|
2570
|
+
log(`Creating OIDC Provider "${providerId}"...`);
|
|
2571
|
+
await createOrGetWifProvider(
|
|
2572
|
+
options.projectId,
|
|
2573
|
+
poolId,
|
|
2574
|
+
providerId,
|
|
2575
|
+
options.githubRepo,
|
|
2576
|
+
token
|
|
2577
|
+
);
|
|
2578
|
+
log("Binding service account permissions...");
|
|
2579
|
+
await bindServiceAccount(
|
|
2580
|
+
options.projectId,
|
|
2581
|
+
options.serviceAccountEmail,
|
|
2582
|
+
projectNumber,
|
|
2583
|
+
poolId,
|
|
2584
|
+
options.githubRepo,
|
|
2585
|
+
token
|
|
2586
|
+
);
|
|
2587
|
+
const wifProvider = `projects/${projectNumber}/locations/global/workloadIdentityPools/${poolId}/providers/${providerId}`;
|
|
2588
|
+
return { wifProvider, projectNumber, poolId, providerId };
|
|
2589
|
+
}
|
|
2590
|
+
async function grantDrivePermissions(options) {
|
|
2591
|
+
const token = await getAccessToken4(options.keyFilePath);
|
|
2592
|
+
const policy = await gcpFetch(
|
|
2593
|
+
`https://cloudresourcemanager.googleapis.com/v1/projects/${encodeURIComponent(options.projectId)}:getIamPolicy`,
|
|
2594
|
+
token,
|
|
2595
|
+
"POST",
|
|
2596
|
+
{}
|
|
2597
|
+
);
|
|
2598
|
+
const bindings = policy.bindings ?? [];
|
|
2599
|
+
const role = "roles/drive.file";
|
|
2600
|
+
const member = `serviceAccount:${options.serviceAccountEmail}`;
|
|
2601
|
+
const existing = bindings.find((b) => b.role === role);
|
|
2602
|
+
if (existing) {
|
|
2603
|
+
if (existing.members.includes(member)) {
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
existing.members.push(member);
|
|
2607
|
+
} else {
|
|
2608
|
+
bindings.push({ role, members: [member] });
|
|
2609
|
+
}
|
|
2610
|
+
await gcpFetch(
|
|
2611
|
+
`https://cloudresourcemanager.googleapis.com/v1/projects/${encodeURIComponent(options.projectId)}:setIamPolicy`,
|
|
2612
|
+
token,
|
|
2613
|
+
"POST",
|
|
2614
|
+
{ policy: { ...policy, bindings } }
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2401
2618
|
// src/index.ts
|
|
2402
2619
|
var index_default = getSpreadSheetData;
|
|
2403
2620
|
export {
|
|
2404
2621
|
DEFAULT_IMAGE_EXTENSIONS,
|
|
2405
2622
|
DEFAULT_WAIT_SECONDS,
|
|
2623
|
+
GcpApiError,
|
|
2406
2624
|
buildGoogleAuth,
|
|
2407
2625
|
buildManifest,
|
|
2408
2626
|
convertFromDataJsonFormat,
|
|
@@ -2424,6 +2642,7 @@ export {
|
|
|
2424
2642
|
getOriginalHeaderForLocale,
|
|
2425
2643
|
getSpreadSheetData,
|
|
2426
2644
|
getTranslationSummary,
|
|
2645
|
+
grantDrivePermissions,
|
|
2427
2646
|
handleBidirectionalSync,
|
|
2428
2647
|
inferLocaleFromDocName,
|
|
2429
2648
|
ingestDoc,
|
|
@@ -2441,6 +2660,7 @@ export {
|
|
|
2441
2660
|
resolveLocaleWithFallback,
|
|
2442
2661
|
scanDriveFolderForDocs,
|
|
2443
2662
|
scanDriveFolderForSpreadsheets,
|
|
2663
|
+
setupWIF,
|
|
2444
2664
|
slugifyKey,
|
|
2445
2665
|
syncDriveImages,
|
|
2446
2666
|
updateSpreadsheetWithLocalChanges,
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export { parseDocContent, slugifyKey } from './utils/docParser';
|
|
|
44
44
|
export type { ParsedDocEntry, DocKeyStrategy, ParseDocOptions } from './utils/docParser';
|
|
45
45
|
export { ingestDoc, exportDoc, entriesToSeedKeys, entriesToTranslationData } from './utils/docIngester';
|
|
46
46
|
export type { DocIngesterOptions, DocIngestResult, DocUpdateMode } from './utils/docIngester';
|
|
47
|
+
export { setupWIF, grantDrivePermissions, GcpApiError } from './setup/wifSetup';
|
|
48
|
+
export type { WifSetupOptions, WifSetupResult, GrantDrivePermissionsOptions } from './setup/wifSetup';
|
|
47
49
|
import { getSpreadSheetData } from './getSpreadSheetData';
|
|
48
50
|
export default getSpreadSheetData;
|
|
49
51
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
DEFAULT_IMAGE_EXTENSIONS: () => DEFAULT_IMAGE_EXTENSIONS,
|
|
34
34
|
DEFAULT_WAIT_SECONDS: () => DEFAULT_WAIT_SECONDS,
|
|
35
|
+
GcpApiError: () => GcpApiError,
|
|
35
36
|
buildGoogleAuth: () => buildGoogleAuth,
|
|
36
37
|
buildManifest: () => buildManifest,
|
|
37
38
|
convertFromDataJsonFormat: () => convertFromDataJsonFormat,
|
|
@@ -53,6 +54,7 @@ __export(index_exports, {
|
|
|
53
54
|
getOriginalHeaderForLocale: () => getOriginalHeaderForLocale,
|
|
54
55
|
getSpreadSheetData: () => getSpreadSheetData,
|
|
55
56
|
getTranslationSummary: () => getTranslationSummary,
|
|
57
|
+
grantDrivePermissions: () => grantDrivePermissions,
|
|
56
58
|
handleBidirectionalSync: () => handleBidirectionalSync,
|
|
57
59
|
inferLocaleFromDocName: () => inferLocaleFromDocName,
|
|
58
60
|
ingestDoc: () => ingestDoc,
|
|
@@ -70,6 +72,7 @@ __export(index_exports, {
|
|
|
70
72
|
resolveLocaleWithFallback: () => resolveLocaleWithFallback,
|
|
71
73
|
scanDriveFolderForDocs: () => scanDriveFolderForDocs,
|
|
72
74
|
scanDriveFolderForSpreadsheets: () => scanDriveFolderForSpreadsheets,
|
|
75
|
+
setupWIF: () => setupWIF,
|
|
73
76
|
slugifyKey: () => slugifyKey,
|
|
74
77
|
syncDriveImages: () => syncDriveImages,
|
|
75
78
|
updateSpreadsheetWithLocalChanges: () => updateSpreadsheetWithLocalChanges,
|
|
@@ -2158,6 +2161,9 @@ function entriesToSeedKeys(entries) {
|
|
|
2158
2161
|
return keys;
|
|
2159
2162
|
}
|
|
2160
2163
|
function entriesToTranslationData(entries, locale) {
|
|
2164
|
+
if (locale === "__proto__" || locale === "constructor" || locale === "prototype") {
|
|
2165
|
+
return {};
|
|
2166
|
+
}
|
|
2161
2167
|
const data = {};
|
|
2162
2168
|
data[locale] = {};
|
|
2163
2169
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -2486,12 +2492,227 @@ async function manageDriveTranslations(options) {
|
|
|
2486
2492
|
return { translations, spreadsheetIds: filteredIds, imageSync, manifest, docIngestResults };
|
|
2487
2493
|
}
|
|
2488
2494
|
|
|
2495
|
+
// src/setup/wifSetup.ts
|
|
2496
|
+
var import_google_auth_library4 = require("google-auth-library");
|
|
2497
|
+
var GcpApiError = class extends Error {
|
|
2498
|
+
constructor(message, status) {
|
|
2499
|
+
super(message);
|
|
2500
|
+
this.status = status;
|
|
2501
|
+
this.name = "GcpApiError";
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
async function getAccessToken4(keyFilePath) {
|
|
2505
|
+
const auth = new import_google_auth_library4.GoogleAuth({
|
|
2506
|
+
...keyFilePath ? { keyFilename: keyFilePath } : {},
|
|
2507
|
+
scopes: ["https://www.googleapis.com/auth/cloud-platform"]
|
|
2508
|
+
});
|
|
2509
|
+
const client = await auth.getClient();
|
|
2510
|
+
const tokenResponse = await client.getAccessToken();
|
|
2511
|
+
if (!tokenResponse.token) {
|
|
2512
|
+
throw new Error(
|
|
2513
|
+
"Failed to obtain a Google Cloud access token. Ensure you are authenticated via Application Default Credentials (run: gcloud auth application-default login) or provide --key-file pointing to a service account JSON key."
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
return tokenResponse.token;
|
|
2517
|
+
}
|
|
2518
|
+
async function gcpFetch(url, token, method = "GET", body) {
|
|
2519
|
+
const response = await fetch(url, {
|
|
2520
|
+
method,
|
|
2521
|
+
headers: {
|
|
2522
|
+
Authorization: `Bearer ${token}`,
|
|
2523
|
+
"Content-Type": "application/json"
|
|
2524
|
+
},
|
|
2525
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
2526
|
+
});
|
|
2527
|
+
const data = await response.json();
|
|
2528
|
+
if (!response.ok) {
|
|
2529
|
+
const errData = data;
|
|
2530
|
+
const message = errData.error?.message ?? `HTTP ${response.status}`;
|
|
2531
|
+
throw new GcpApiError(message, response.status);
|
|
2532
|
+
}
|
|
2533
|
+
return data;
|
|
2534
|
+
}
|
|
2535
|
+
async function pollOperation(operationName, token, maxWaitMs = 6e4) {
|
|
2536
|
+
const opUrl = operationName.startsWith("http") ? operationName : `https://iam.googleapis.com/v1/${operationName}`;
|
|
2537
|
+
const deadline = Date.now() + maxWaitMs;
|
|
2538
|
+
const maxWaitSecs = Math.round(maxWaitMs / 1e3);
|
|
2539
|
+
while (Date.now() < deadline) {
|
|
2540
|
+
const op = await gcpFetch(opUrl, token);
|
|
2541
|
+
if (op.done) {
|
|
2542
|
+
if (op.error) {
|
|
2543
|
+
throw new Error(`Operation failed: ${op.error.message}`);
|
|
2544
|
+
}
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
2548
|
+
}
|
|
2549
|
+
if (Date.now() >= deadline) {
|
|
2550
|
+
throw new Error(
|
|
2551
|
+
`Operation timed out after ${maxWaitSecs} s. The resources may still be provisioning in the background \u2013 re-running the command is safe (existing resources are reused).`
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
async function getProjectNumber(projectId, token) {
|
|
2556
|
+
const data = await gcpFetch(
|
|
2557
|
+
`https://cloudresourcemanager.googleapis.com/v1/projects/${encodeURIComponent(projectId)}`,
|
|
2558
|
+
token
|
|
2559
|
+
);
|
|
2560
|
+
return data.projectNumber;
|
|
2561
|
+
}
|
|
2562
|
+
async function createOrGetWifPool(projectId, poolId, token) {
|
|
2563
|
+
try {
|
|
2564
|
+
const op = await gcpFetch(
|
|
2565
|
+
`https://iam.googleapis.com/v1/projects/${encodeURIComponent(projectId)}/locations/global/workloadIdentityPools?workloadIdentityPoolId=${encodeURIComponent(poolId)}`,
|
|
2566
|
+
token,
|
|
2567
|
+
"POST",
|
|
2568
|
+
{
|
|
2569
|
+
displayName: "GitHub Actions Pool",
|
|
2570
|
+
description: "Pool for GitHub Actions OIDC authentication",
|
|
2571
|
+
disabled: false
|
|
2572
|
+
}
|
|
2573
|
+
);
|
|
2574
|
+
if (!op.done) {
|
|
2575
|
+
await pollOperation(op.name, token);
|
|
2576
|
+
} else if (op.error) {
|
|
2577
|
+
throw new Error(`Operation failed: ${op.error.message}`);
|
|
2578
|
+
}
|
|
2579
|
+
} catch (err) {
|
|
2580
|
+
if (err instanceof GcpApiError && err.status === 409) {
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2583
|
+
throw err;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
async function createOrGetWifProvider(projectId, poolId, providerId, githubRepo, token) {
|
|
2587
|
+
try {
|
|
2588
|
+
const op = await gcpFetch(
|
|
2589
|
+
`https://iam.googleapis.com/v1/projects/${encodeURIComponent(projectId)}/locations/global/workloadIdentityPools/${encodeURIComponent(poolId)}/providers?workloadIdentityPoolProviderId=${encodeURIComponent(providerId)}`,
|
|
2590
|
+
token,
|
|
2591
|
+
"POST",
|
|
2592
|
+
{
|
|
2593
|
+
displayName: "GitHub OIDC Provider",
|
|
2594
|
+
disabled: false,
|
|
2595
|
+
attributeMapping: {
|
|
2596
|
+
"google.subject": "assertion.sub",
|
|
2597
|
+
"attribute.actor": "assertion.actor",
|
|
2598
|
+
"attribute.repository": "assertion.repository"
|
|
2599
|
+
},
|
|
2600
|
+
// Scope the provider to this exact repository for security
|
|
2601
|
+
attributeCondition: `assertion.repository=='${githubRepo}'`,
|
|
2602
|
+
oidc: {
|
|
2603
|
+
issuerUri: "https://token.actions.githubusercontent.com"
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
);
|
|
2607
|
+
if (!op.done) {
|
|
2608
|
+
await pollOperation(op.name, token);
|
|
2609
|
+
} else if (op.error) {
|
|
2610
|
+
throw new Error(`Operation failed: ${op.error.message}`);
|
|
2611
|
+
}
|
|
2612
|
+
} catch (err) {
|
|
2613
|
+
if (err instanceof GcpApiError && err.status === 409) {
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
throw err;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
async function bindServiceAccount(projectId, serviceAccountEmail, projectNumber, poolId, githubRepo, token) {
|
|
2620
|
+
const saResource = `projects/${encodeURIComponent(projectId)}/serviceAccounts/${encodeURIComponent(serviceAccountEmail)}`;
|
|
2621
|
+
const principal = `principalSet://iam.googleapis.com/projects/${projectNumber}/locations/global/workloadIdentityPools/${poolId}/attribute.repository/${githubRepo}`;
|
|
2622
|
+
const policy = await gcpFetch(
|
|
2623
|
+
`https://iam.googleapis.com/v1/${saResource}:getIamPolicy`,
|
|
2624
|
+
token,
|
|
2625
|
+
"POST",
|
|
2626
|
+
{}
|
|
2627
|
+
);
|
|
2628
|
+
const bindings = policy.bindings ?? [];
|
|
2629
|
+
const role = "roles/iam.workloadIdentityUser";
|
|
2630
|
+
const existing = bindings.find((b) => b.role === role);
|
|
2631
|
+
if (existing) {
|
|
2632
|
+
if (existing.members.includes(principal)) {
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
existing.members.push(principal);
|
|
2636
|
+
} else {
|
|
2637
|
+
bindings.push({ role, members: [principal] });
|
|
2638
|
+
}
|
|
2639
|
+
await gcpFetch(
|
|
2640
|
+
`https://iam.googleapis.com/v1/${saResource}:setIamPolicy`,
|
|
2641
|
+
token,
|
|
2642
|
+
"POST",
|
|
2643
|
+
{ policy: { ...policy, bindings } }
|
|
2644
|
+
);
|
|
2645
|
+
}
|
|
2646
|
+
async function setupWIF(options) {
|
|
2647
|
+
const poolId = options.poolId ?? "github-actions";
|
|
2648
|
+
const providerId = options.providerId ?? "github-oidc";
|
|
2649
|
+
const log = options.onProgress ?? (() => void 0);
|
|
2650
|
+
if (!options.githubRepo.includes("/")) {
|
|
2651
|
+
throw new Error(
|
|
2652
|
+
`githubRepo must be in "owner/repo" format, got: "${options.githubRepo}"`
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
log("Authenticating with Google Cloud...");
|
|
2656
|
+
const token = await getAccessToken4(options.keyFilePath);
|
|
2657
|
+
log("Fetching project number...");
|
|
2658
|
+
const projectNumber = await getProjectNumber(options.projectId, token);
|
|
2659
|
+
log(`Creating Workload Identity Pool "${poolId}"...`);
|
|
2660
|
+
await createOrGetWifPool(options.projectId, poolId, token);
|
|
2661
|
+
log(`Creating OIDC Provider "${providerId}"...`);
|
|
2662
|
+
await createOrGetWifProvider(
|
|
2663
|
+
options.projectId,
|
|
2664
|
+
poolId,
|
|
2665
|
+
providerId,
|
|
2666
|
+
options.githubRepo,
|
|
2667
|
+
token
|
|
2668
|
+
);
|
|
2669
|
+
log("Binding service account permissions...");
|
|
2670
|
+
await bindServiceAccount(
|
|
2671
|
+
options.projectId,
|
|
2672
|
+
options.serviceAccountEmail,
|
|
2673
|
+
projectNumber,
|
|
2674
|
+
poolId,
|
|
2675
|
+
options.githubRepo,
|
|
2676
|
+
token
|
|
2677
|
+
);
|
|
2678
|
+
const wifProvider = `projects/${projectNumber}/locations/global/workloadIdentityPools/${poolId}/providers/${providerId}`;
|
|
2679
|
+
return { wifProvider, projectNumber, poolId, providerId };
|
|
2680
|
+
}
|
|
2681
|
+
async function grantDrivePermissions(options) {
|
|
2682
|
+
const token = await getAccessToken4(options.keyFilePath);
|
|
2683
|
+
const policy = await gcpFetch(
|
|
2684
|
+
`https://cloudresourcemanager.googleapis.com/v1/projects/${encodeURIComponent(options.projectId)}:getIamPolicy`,
|
|
2685
|
+
token,
|
|
2686
|
+
"POST",
|
|
2687
|
+
{}
|
|
2688
|
+
);
|
|
2689
|
+
const bindings = policy.bindings ?? [];
|
|
2690
|
+
const role = "roles/drive.file";
|
|
2691
|
+
const member = `serviceAccount:${options.serviceAccountEmail}`;
|
|
2692
|
+
const existing = bindings.find((b) => b.role === role);
|
|
2693
|
+
if (existing) {
|
|
2694
|
+
if (existing.members.includes(member)) {
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
existing.members.push(member);
|
|
2698
|
+
} else {
|
|
2699
|
+
bindings.push({ role, members: [member] });
|
|
2700
|
+
}
|
|
2701
|
+
await gcpFetch(
|
|
2702
|
+
`https://cloudresourcemanager.googleapis.com/v1/projects/${encodeURIComponent(options.projectId)}:setIamPolicy`,
|
|
2703
|
+
token,
|
|
2704
|
+
"POST",
|
|
2705
|
+
{ policy: { ...policy, bindings } }
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2489
2709
|
// src/index.ts
|
|
2490
2710
|
var index_default = getSpreadSheetData;
|
|
2491
2711
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2492
2712
|
0 && (module.exports = {
|
|
2493
2713
|
DEFAULT_IMAGE_EXTENSIONS,
|
|
2494
2714
|
DEFAULT_WAIT_SECONDS,
|
|
2715
|
+
GcpApiError,
|
|
2495
2716
|
buildGoogleAuth,
|
|
2496
2717
|
buildManifest,
|
|
2497
2718
|
convertFromDataJsonFormat,
|
|
@@ -2512,6 +2733,7 @@ var index_default = getSpreadSheetData;
|
|
|
2512
2733
|
getOriginalHeaderForLocale,
|
|
2513
2734
|
getSpreadSheetData,
|
|
2514
2735
|
getTranslationSummary,
|
|
2736
|
+
grantDrivePermissions,
|
|
2515
2737
|
handleBidirectionalSync,
|
|
2516
2738
|
inferLocaleFromDocName,
|
|
2517
2739
|
ingestDoc,
|
|
@@ -2529,6 +2751,7 @@ var index_default = getSpreadSheetData;
|
|
|
2529
2751
|
resolveLocaleWithFallback,
|
|
2530
2752
|
scanDriveFolderForDocs,
|
|
2531
2753
|
scanDriveFolderForSpreadsheets,
|
|
2754
|
+
setupWIF,
|
|
2532
2755
|
slugifyKey,
|
|
2533
2756
|
syncDriveImages,
|
|
2534
2757
|
updateSpreadsheetWithLocalChanges,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gst-setup-wif – Interactive CLI for configuring Workload Identity Federation.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx -p @el-j/google-sheet-translations gst-setup-wif
|
|
6
|
+
* npx -p @el-j/google-sheet-translations gst-setup-wif \
|
|
7
|
+
* --project=my-gcp-project \
|
|
8
|
+
* --service-account=deploy@my-gcp-project.iam.gserviceaccount.com \
|
|
9
|
+
* --repo=myorg/myrepo \
|
|
10
|
+
* --key-file=./service-account-key.json
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/setup/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export interface WifSetupOptions {
|
|
2
|
+
/** Google Cloud project ID (e.g. "my-gcp-project") */
|
|
3
|
+
projectId: string;
|
|
4
|
+
/** Service account email (e.g. "deploy@my-gcp-project.iam.gserviceaccount.com") */
|
|
5
|
+
serviceAccountEmail: string;
|
|
6
|
+
/** GitHub repository in "owner/repo" format (e.g. "myorg/myrepo") */
|
|
7
|
+
githubRepo: string;
|
|
8
|
+
/** Workload Identity Pool ID (default: "github-actions") */
|
|
9
|
+
poolId?: string;
|
|
10
|
+
/** OIDC Provider ID (default: "github-oidc") */
|
|
11
|
+
providerId?: string;
|
|
12
|
+
/** Path to a service account JSON key file used for bootstrapping.
|
|
13
|
+
* If omitted, Application Default Credentials (ADC) are used. */
|
|
14
|
+
keyFilePath?: string;
|
|
15
|
+
/** Optional callback invoked before each setup step for progress reporting. */
|
|
16
|
+
onProgress?: (step: string) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface WifSetupResult {
|
|
19
|
+
/** Full WIF provider resource name – set this as the `WIF_PROVIDER` env var */
|
|
20
|
+
wifProvider: string;
|
|
21
|
+
/** Numeric Google Cloud project number */
|
|
22
|
+
projectNumber: string;
|
|
23
|
+
/** Workload Identity Pool ID used */
|
|
24
|
+
poolId: string;
|
|
25
|
+
/** OIDC Provider ID used */
|
|
26
|
+
providerId: string;
|
|
27
|
+
}
|
|
28
|
+
export interface GrantDrivePermissionsOptions {
|
|
29
|
+
/** Google Cloud project ID */
|
|
30
|
+
projectId: string;
|
|
31
|
+
/** Service account email to grant Drive access */
|
|
32
|
+
serviceAccountEmail: string;
|
|
33
|
+
/** Path to a service account JSON key file used for bootstrapping. */
|
|
34
|
+
keyFilePath?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare class GcpApiError extends Error {
|
|
37
|
+
readonly status: number;
|
|
38
|
+
constructor(message: string, status: number);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Configures Workload Identity Federation (WIF) on Google Cloud so that
|
|
42
|
+
* GitHub Actions can authenticate without a long-lived service account key.
|
|
43
|
+
*
|
|
44
|
+
* This function is **idempotent**: re-running it when resources already exist
|
|
45
|
+
* is safe – existing pools, providers and IAM bindings are reused.
|
|
46
|
+
*
|
|
47
|
+
* Steps performed:
|
|
48
|
+
* 1. Resolves the numeric Google Cloud project number.
|
|
49
|
+
* 2. Creates (or reuses) a Workload Identity Pool named `poolId`.
|
|
50
|
+
* 3. Creates (or reuses) an OIDC Provider scoped to `githubRepo`.
|
|
51
|
+
* 4. Grants the service account the `roles/iam.workloadIdentityUser` role
|
|
52
|
+
* for tokens originating from `githubRepo`.
|
|
53
|
+
*
|
|
54
|
+
* The returned {@link WifSetupResult.wifProvider} is the full provider resource
|
|
55
|
+
* name that should be set as the `WIF_PROVIDER` environment variable / GitHub
|
|
56
|
+
* Actions environment variable.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import { setupWIF } from '@el-j/google-sheet-translations';
|
|
61
|
+
*
|
|
62
|
+
* const result = await setupWIF({
|
|
63
|
+
* projectId: 'my-gcp-project',
|
|
64
|
+
* serviceAccountEmail: 'deploy@my-gcp-project.iam.gserviceaccount.com',
|
|
65
|
+
* githubRepo: 'myorg/myrepo',
|
|
66
|
+
* keyFilePath: './service-account-key.json',
|
|
67
|
+
* onProgress: (step) => console.log(' ⏳', step),
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* console.log('WIF_PROVIDER =', result.wifProvider);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function setupWIF(options: WifSetupOptions): Promise<WifSetupResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Grants the service account `roles/drive.file` on the Google Cloud project.
|
|
76
|
+
*
|
|
77
|
+
* This is required when using the Drive-folder features of
|
|
78
|
+
* `@el-j/google-sheet-translations` (e.g. `manageDriveTranslations()`)
|
|
79
|
+
* so the service account can create and access spreadsheets inside a shared
|
|
80
|
+
* Drive folder.
|
|
81
|
+
*
|
|
82
|
+
* This function is **idempotent** – it checks the existing policy before
|
|
83
|
+
* adding the binding.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* await grantDrivePermissions({
|
|
88
|
+
* projectId: 'my-gcp-project',
|
|
89
|
+
* serviceAccountEmail: 'deploy@my-gcp-project.iam.gserviceaccount.com',
|
|
90
|
+
* keyFilePath: './service-account-key.json',
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function grantDrivePermissions(options: GrantDrivePermissionsOptions): Promise<void>;
|
|
95
|
+
//# sourceMappingURL=wifSetup.d.ts.map
|