@intlpullhq/cli 0.1.8 → 0.1.10
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 +4 -0
- package/dist/api-77ZWU4IB.js +47 -0
- package/dist/chunk-BULIQM4U.js +73 -0
- package/dist/{chunk-GQADIM7O.js → chunk-IWYURZV2.js} +11 -2
- package/dist/chunk-KCZQUMQP.js +64 -0
- package/dist/chunk-KIDP7N6D.js +149 -0
- package/dist/chunk-WSY27J6N.js +116 -0
- package/dist/{chunk-2MCKJWPD.js → chunk-WVCVQFBI.js} +340 -305
- package/dist/{config-DX34FMWV.js → config-F3O3XWYX.js} +1 -1
- package/dist/index.js +3144 -195
- package/dist/keys-P3F5IKS2.js +8 -0
- package/dist/projects-AMQMORAR.js +8 -0
- package/dist/translations-7GWEFVRG.js +8 -0
- package/package.json +7 -2
- package/dist/api-K3AY4VAI.js +0 -29
package/dist/index.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
ContributorsApi,
|
|
4
|
+
SnapshotsApi,
|
|
5
|
+
TMApi,
|
|
6
|
+
WebhooksApi,
|
|
3
7
|
createApiClient
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WVCVQFBI.js";
|
|
9
|
+
import {
|
|
10
|
+
ProjectsApi
|
|
11
|
+
} from "./chunk-KCZQUMQP.js";
|
|
12
|
+
import "./chunk-WSY27J6N.js";
|
|
13
|
+
import {
|
|
14
|
+
KeysApi
|
|
15
|
+
} from "./chunk-BULIQM4U.js";
|
|
16
|
+
import "./chunk-KIDP7N6D.js";
|
|
5
17
|
import {
|
|
6
18
|
detectFramework,
|
|
7
19
|
getAuthErrorMessage,
|
|
@@ -15,10 +27,10 @@ import {
|
|
|
15
27
|
saveAuthConfig,
|
|
16
28
|
saveProjectConfig,
|
|
17
29
|
setCustomEnvFile
|
|
18
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-IWYURZV2.js";
|
|
19
31
|
|
|
20
32
|
// src/index.tsx
|
|
21
|
-
import { Command as
|
|
33
|
+
import { Command as Command7 } from "commander";
|
|
22
34
|
|
|
23
35
|
// src/commands/init.tsx
|
|
24
36
|
import { useState, useEffect } from "react";
|
|
@@ -1059,10 +1071,10 @@ function runStatus() {
|
|
|
1059
1071
|
render3(/* @__PURE__ */ jsx8(StatusCommand, {}));
|
|
1060
1072
|
}
|
|
1061
1073
|
|
|
1062
|
-
// src/commands/
|
|
1074
|
+
// src/commands/download.tsx
|
|
1063
1075
|
import { render as render4 } from "ink";
|
|
1064
1076
|
|
|
1065
|
-
// src/commands/
|
|
1077
|
+
// src/commands/download/components/interactive-pull.tsx
|
|
1066
1078
|
import { useState as useState6, useEffect as useEffect5, useCallback as useCallback2 } from "react";
|
|
1067
1079
|
import { Box as Box9, Text as Text10, useApp as useApp2 } from "ink";
|
|
1068
1080
|
import SelectInput2 from "ink-select-input";
|
|
@@ -1070,10 +1082,13 @@ import Spinner3 from "ink-spinner";
|
|
|
1070
1082
|
import { mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
|
|
1071
1083
|
import { join as join5, dirname as dirname3 } from "path";
|
|
1072
1084
|
|
|
1073
|
-
// src/
|
|
1085
|
+
// src/lib/api/fetch-utils.ts
|
|
1074
1086
|
var DEFAULT_TIMEOUT = 3e4;
|
|
1075
1087
|
var MAX_RETRIES = 3;
|
|
1076
1088
|
var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
1089
|
+
function sleep(ms) {
|
|
1090
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1091
|
+
}
|
|
1077
1092
|
async function fetchWithRetry(url, options = {}, retries = MAX_RETRIES) {
|
|
1078
1093
|
const { timeout = DEFAULT_TIMEOUT, ...fetchOptions } = options;
|
|
1079
1094
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
@@ -1085,6 +1100,24 @@ async function fetchWithRetry(url, options = {}, retries = MAX_RETRIES) {
|
|
|
1085
1100
|
signal: controller.signal
|
|
1086
1101
|
});
|
|
1087
1102
|
clearTimeout(timeoutId);
|
|
1103
|
+
if (response.status === 429 && attempt < retries) {
|
|
1104
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
1105
|
+
let delayMs = RETRY_DELAYS[attempt] || 4e3;
|
|
1106
|
+
if (retryAfter) {
|
|
1107
|
+
const retryAfterSeconds = parseInt(retryAfter, 10);
|
|
1108
|
+
if (!isNaN(retryAfterSeconds)) {
|
|
1109
|
+
delayMs = retryAfterSeconds * 1e3;
|
|
1110
|
+
} else {
|
|
1111
|
+
const retryDate = new Date(retryAfter);
|
|
1112
|
+
if (!isNaN(retryDate.getTime())) {
|
|
1113
|
+
delayMs = Math.max(0, retryDate.getTime() - Date.now());
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
delayMs = Math.min(delayMs, 3e4);
|
|
1118
|
+
await sleep(delayMs);
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1088
1121
|
if (response.status >= 500 && attempt < retries) {
|
|
1089
1122
|
await sleep(RETRY_DELAYS[attempt] || 4e3);
|
|
1090
1123
|
continue;
|
|
@@ -1108,9 +1141,8 @@ async function fetchWithRetry(url, options = {}, retries = MAX_RETRIES) {
|
|
|
1108
1141
|
}
|
|
1109
1142
|
throw new Error("Max retries exceeded");
|
|
1110
1143
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1144
|
+
|
|
1145
|
+
// src/commands/download/api.ts
|
|
1114
1146
|
async function fetchProjects2(apiUrl, apiKey) {
|
|
1115
1147
|
const headers = { Accept: "application/json" };
|
|
1116
1148
|
if (apiKey) headers["X-API-Key"] = apiKey;
|
|
@@ -1264,7 +1296,14 @@ async function fetchTranslationsParallel(projectId, apiUrl, apiKey, options) {
|
|
|
1264
1296
|
const availableLanguages = projectData.languages?.map((l) => l.code) || ["en"];
|
|
1265
1297
|
const targetLanguages = options?.languages?.length ? options.languages : availableLanguages;
|
|
1266
1298
|
const namespacesResult = await fetchNamespaces(projectId, apiUrl, apiKey);
|
|
1267
|
-
|
|
1299
|
+
let namespaces = namespacesResult.namespaces;
|
|
1300
|
+
if (options?.namespaces?.length) {
|
|
1301
|
+
const requestedNamespaces = new Set(options.namespaces);
|
|
1302
|
+
namespaces = namespaces.filter((ns) => requestedNamespaces.has(ns));
|
|
1303
|
+
const missingNamespaces = options.namespaces.filter((ns) => !namespacesResult.namespaces.includes(ns));
|
|
1304
|
+
if (missingNamespaces.length > 0) {
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1268
1307
|
const taskParams = [];
|
|
1269
1308
|
for (const lang of targetLanguages) {
|
|
1270
1309
|
for (const ns of namespaces) {
|
|
@@ -1327,7 +1366,7 @@ async function fetchProjectInfo(projectId, apiUrl, apiKey) {
|
|
|
1327
1366
|
};
|
|
1328
1367
|
}
|
|
1329
1368
|
|
|
1330
|
-
// src/commands/
|
|
1369
|
+
// src/commands/download/formatters.ts
|
|
1331
1370
|
function formatAsJson(translations) {
|
|
1332
1371
|
return JSON.stringify(translations, null, 2);
|
|
1333
1372
|
}
|
|
@@ -1377,7 +1416,7 @@ function getFileExtension(format) {
|
|
|
1377
1416
|
}
|
|
1378
1417
|
}
|
|
1379
1418
|
|
|
1380
|
-
// src/commands/
|
|
1419
|
+
// src/commands/download/components/multi-select.tsx
|
|
1381
1420
|
import { useState as useState5 } from "react";
|
|
1382
1421
|
import { Box as Box8, Text as Text9, useInput as useInput2 } from "ink";
|
|
1383
1422
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
@@ -1411,7 +1450,7 @@ function MultiSelect({ items, selected, onToggle, onSubmit, isActive = true }) {
|
|
|
1411
1450
|
] });
|
|
1412
1451
|
}
|
|
1413
1452
|
|
|
1414
|
-
// src/commands/
|
|
1453
|
+
// src/commands/download/hooks/useInteractivePull.ts
|
|
1415
1454
|
import { useReducer, useCallback } from "react";
|
|
1416
1455
|
var initialState = {
|
|
1417
1456
|
step: "project",
|
|
@@ -1732,7 +1771,7 @@ function cleanupStaleTempFiles(dir, maxAgeMs = TEMP_FILE_MAX_AGE_MS) {
|
|
|
1732
1771
|
return result;
|
|
1733
1772
|
}
|
|
1734
1773
|
|
|
1735
|
-
// src/commands/
|
|
1774
|
+
// src/commands/download/components/interactive-pull.tsx
|
|
1736
1775
|
import { Fragment, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1737
1776
|
function InteractivePull({ initialOptions }) {
|
|
1738
1777
|
const { exit } = useApp2();
|
|
@@ -2206,7 +2245,7 @@ ${writeErrors.join("\n")}`);
|
|
|
2206
2245
|
] });
|
|
2207
2246
|
}
|
|
2208
2247
|
|
|
2209
|
-
// src/commands/
|
|
2248
|
+
// src/commands/download/components/smart-pull.tsx
|
|
2210
2249
|
import { useState as useState7, useEffect as useEffect6 } from "react";
|
|
2211
2250
|
import { Box as Box10, Text as Text11 } from "ink";
|
|
2212
2251
|
import Spinner4 from "ink-spinner";
|
|
@@ -2342,7 +2381,140 @@ var LIBRARIES2 = {
|
|
|
2342
2381
|
}
|
|
2343
2382
|
};
|
|
2344
2383
|
|
|
2345
|
-
// src/
|
|
2384
|
+
// src/lib/key-scanner.ts
|
|
2385
|
+
import { glob } from "glob";
|
|
2386
|
+
import { readFile } from "fs/promises";
|
|
2387
|
+
var DEFAULT_EXTRACTORS = [
|
|
2388
|
+
// i18next: t('key.name') or t("key.name")
|
|
2389
|
+
{
|
|
2390
|
+
name: "i18next",
|
|
2391
|
+
pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
|
|
2392
|
+
extract: (m) => m[1]
|
|
2393
|
+
},
|
|
2394
|
+
// react-i18next: t('key.name')
|
|
2395
|
+
{
|
|
2396
|
+
name: "react-i18next",
|
|
2397
|
+
pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
|
|
2398
|
+
extract: (m) => m[1]
|
|
2399
|
+
},
|
|
2400
|
+
// vue-i18n: $t('key.name') or {{ $t('key.name') }}
|
|
2401
|
+
{
|
|
2402
|
+
name: "vue-i18n",
|
|
2403
|
+
pattern: /\$t\s*\(\s*['"]([^'"]+)['"]/g,
|
|
2404
|
+
extract: (m) => m[1]
|
|
2405
|
+
},
|
|
2406
|
+
// react-intl: formatMessage({ id: 'key.name' })
|
|
2407
|
+
{
|
|
2408
|
+
name: "react-intl-formatMessage",
|
|
2409
|
+
pattern: /formatMessage\s*\(\s*\{\s*id:\s*['"]([^'"]+)['"]/g,
|
|
2410
|
+
extract: (m) => m[1]
|
|
2411
|
+
},
|
|
2412
|
+
// react-intl: <FormattedMessage id="key.name" />
|
|
2413
|
+
{
|
|
2414
|
+
name: "react-intl-component",
|
|
2415
|
+
pattern: /<FormattedMessage\s+id=["']([^"']+)["']/g,
|
|
2416
|
+
extract: (m) => m[1]
|
|
2417
|
+
},
|
|
2418
|
+
// next-intl: t('key.name')
|
|
2419
|
+
{
|
|
2420
|
+
name: "next-intl",
|
|
2421
|
+
pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
|
|
2422
|
+
extract: (m) => m[1]
|
|
2423
|
+
},
|
|
2424
|
+
// lingui: t`key.name` or t({ id: 'key.name' })
|
|
2425
|
+
{
|
|
2426
|
+
name: "lingui-macro",
|
|
2427
|
+
pattern: /\bt\s*`([^`]+)`/g,
|
|
2428
|
+
extract: (m) => m[1]
|
|
2429
|
+
},
|
|
2430
|
+
{
|
|
2431
|
+
name: "lingui-object",
|
|
2432
|
+
pattern: /\bt\s*\(\s*\{\s*id:\s*['"]([^'"]+)['"]/g,
|
|
2433
|
+
extract: (m) => m[1]
|
|
2434
|
+
},
|
|
2435
|
+
// svelte-i18n: $t('key.name') or {$t('key.name')}
|
|
2436
|
+
{
|
|
2437
|
+
name: "svelte-i18n",
|
|
2438
|
+
pattern: /\$t\s*\(\s*['"]([^'"]+)['"]/g,
|
|
2439
|
+
extract: (m) => m[1]
|
|
2440
|
+
},
|
|
2441
|
+
// astro-i18n: t('key.name')
|
|
2442
|
+
{
|
|
2443
|
+
name: "astro-i18n",
|
|
2444
|
+
pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
|
|
2445
|
+
extract: (m) => m[1]
|
|
2446
|
+
}
|
|
2447
|
+
];
|
|
2448
|
+
async function scanForKeys(rootDir, options = {}) {
|
|
2449
|
+
const patterns = options.patterns || ["**/*.{ts,tsx,js,jsx,vue,svelte,astro}"];
|
|
2450
|
+
const exclude = options.exclude || ["node_modules/**", "dist/**", ".next/**", "build/**", ".svelte-kit/**", "out/**"];
|
|
2451
|
+
const extractors = options.extractors || DEFAULT_EXTRACTORS;
|
|
2452
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
2453
|
+
try {
|
|
2454
|
+
const files = await glob(patterns, { cwd: rootDir, ignore: exclude });
|
|
2455
|
+
const batchSize = 50;
|
|
2456
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
2457
|
+
const batch = files.slice(i, i + batchSize);
|
|
2458
|
+
await Promise.all(
|
|
2459
|
+
batch.map(async (file) => {
|
|
2460
|
+
try {
|
|
2461
|
+
const content = await readFile(`${rootDir}/${file}`, "utf-8");
|
|
2462
|
+
for (const extractor of extractors) {
|
|
2463
|
+
const matches = content.matchAll(extractor.pattern);
|
|
2464
|
+
for (const match of matches) {
|
|
2465
|
+
const key = extractor.extract(match);
|
|
2466
|
+
if (key) {
|
|
2467
|
+
usedKeys.add(key);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
} catch (err) {
|
|
2472
|
+
if (err instanceof Error && !err.message.includes("ENOENT")) {
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
})
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
} catch (err) {
|
|
2479
|
+
throw new Error(`Failed to scan files: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
2480
|
+
}
|
|
2481
|
+
return usedKeys;
|
|
2482
|
+
}
|
|
2483
|
+
function validatePrefix(prefix) {
|
|
2484
|
+
return /^[a-zA-Z0-9.]+$/.test(prefix);
|
|
2485
|
+
}
|
|
2486
|
+
function applyPrefix(keys, prefix) {
|
|
2487
|
+
const result = {};
|
|
2488
|
+
const prefixWithDot = `${prefix}.`;
|
|
2489
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
2490
|
+
result[`${prefixWithDot}${key}`] = value;
|
|
2491
|
+
}
|
|
2492
|
+
return result;
|
|
2493
|
+
}
|
|
2494
|
+
function stripPrefix(keys, prefix) {
|
|
2495
|
+
const result = {};
|
|
2496
|
+
const prefixWithDot = `${prefix}.`;
|
|
2497
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
2498
|
+
if (key.startsWith(prefixWithDot)) {
|
|
2499
|
+
result[key.substring(prefixWithDot.length)] = value;
|
|
2500
|
+
} else {
|
|
2501
|
+
result[key] = value;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return result;
|
|
2505
|
+
}
|
|
2506
|
+
function filterByPrefix(keys, prefix) {
|
|
2507
|
+
const result = {};
|
|
2508
|
+
const prefixWithDot = `${prefix}.`;
|
|
2509
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
2510
|
+
if (key.startsWith(prefixWithDot) || key === prefix) {
|
|
2511
|
+
result[key] = value;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
return result;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
// src/commands/download/components/smart-pull.tsx
|
|
2346
2518
|
import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2347
2519
|
function SmartPull({ options }) {
|
|
2348
2520
|
const [state, setState] = useState7({
|
|
@@ -2361,6 +2533,12 @@ function SmartPull({ options }) {
|
|
|
2361
2533
|
if (!apiKey) {
|
|
2362
2534
|
throw new Error("Not authenticated. Run `npx @intlpullhq/cli login` or set INTLPULL_API_KEY.");
|
|
2363
2535
|
}
|
|
2536
|
+
if (options.translationKeyPrefix && !validatePrefix(options.translationKeyPrefix)) {
|
|
2537
|
+
throw new Error('Invalid prefix format. Use alphanumeric characters and dots only (e.g., "checkout" or "app.checkout")');
|
|
2538
|
+
}
|
|
2539
|
+
if (options.filterByPrefix && !validatePrefix(options.filterByPrefix)) {
|
|
2540
|
+
throw new Error("Invalid filter prefix format. Use alphanumeric characters and dots only");
|
|
2541
|
+
}
|
|
2364
2542
|
setState((s) => ({ ...s, status: "detecting", message: "Fetching project info..." }));
|
|
2365
2543
|
let projectId = options.project || projectConfig?.projectId;
|
|
2366
2544
|
let projectInfo = null;
|
|
@@ -2428,6 +2606,7 @@ Or use a project-scoped API key for automatic selection.`
|
|
|
2428
2606
|
let version;
|
|
2429
2607
|
let namespaceCount = 0;
|
|
2430
2608
|
let namespaces = [];
|
|
2609
|
+
const targetNamespaces = options.namespaces ? options.namespaces.split(",").map((n) => n.trim()).filter(Boolean) : void 0;
|
|
2431
2610
|
if (useParallel) {
|
|
2432
2611
|
const result = await fetchTranslationsParallel(
|
|
2433
2612
|
projectId,
|
|
@@ -2437,6 +2616,7 @@ Or use a project-scoped API key for automatic selection.`
|
|
|
2437
2616
|
languages: targetLanguages,
|
|
2438
2617
|
platform,
|
|
2439
2618
|
branch,
|
|
2619
|
+
namespaces: targetNamespaces,
|
|
2440
2620
|
onProgress: (completed, total) => {
|
|
2441
2621
|
setState((s) => ({
|
|
2442
2622
|
...s,
|
|
@@ -2474,6 +2654,38 @@ Or use a project-scoped API key for automatic selection.`
|
|
|
2474
2654
|
bundle = result.bundle;
|
|
2475
2655
|
version = result.version;
|
|
2476
2656
|
}
|
|
2657
|
+
if (options.filterByPrefix) {
|
|
2658
|
+
setState((s) => ({ ...s, message: `Filtering keys by prefix "${options.filterByPrefix}"...` }));
|
|
2659
|
+
for (const locale in bundle) {
|
|
2660
|
+
bundle[locale] = filterByPrefix(bundle[locale], options.filterByPrefix);
|
|
2661
|
+
}
|
|
2662
|
+
if (namespacedBundle) {
|
|
2663
|
+
for (const locale in namespacedBundle) {
|
|
2664
|
+
for (const namespace in namespacedBundle[locale]) {
|
|
2665
|
+
namespacedBundle[locale][namespace] = filterByPrefix(
|
|
2666
|
+
namespacedBundle[locale][namespace],
|
|
2667
|
+
options.filterByPrefix
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
if (options.translationKeyPrefix) {
|
|
2674
|
+
setState((s) => ({ ...s, message: `Stripping prefix "${options.translationKeyPrefix}"...` }));
|
|
2675
|
+
for (const locale in bundle) {
|
|
2676
|
+
bundle[locale] = stripPrefix(bundle[locale], options.translationKeyPrefix);
|
|
2677
|
+
}
|
|
2678
|
+
if (namespacedBundle) {
|
|
2679
|
+
for (const locale in namespacedBundle) {
|
|
2680
|
+
for (const namespace in namespacedBundle[locale]) {
|
|
2681
|
+
namespacedBundle[locale][namespace] = stripPrefix(
|
|
2682
|
+
namespacedBundle[locale][namespace],
|
|
2683
|
+
options.translationKeyPrefix
|
|
2684
|
+
);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2477
2689
|
setState((s) => ({ ...s, message: "Writing translation files..." }));
|
|
2478
2690
|
const ext = getFileExtension(format);
|
|
2479
2691
|
const writtenFiles = [];
|
|
@@ -2662,9 +2874,9 @@ ${writeErrors.join("\n")}`);
|
|
|
2662
2874
|
] });
|
|
2663
2875
|
}
|
|
2664
2876
|
|
|
2665
|
-
// src/commands/
|
|
2877
|
+
// src/commands/download.tsx
|
|
2666
2878
|
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
2667
|
-
function
|
|
2879
|
+
function runDownload(options) {
|
|
2668
2880
|
if (options.interactive) {
|
|
2669
2881
|
render4(/* @__PURE__ */ jsx12(InteractivePull, { initialOptions: options }));
|
|
2670
2882
|
} else {
|
|
@@ -2672,16 +2884,16 @@ function runPull(options) {
|
|
|
2672
2884
|
}
|
|
2673
2885
|
}
|
|
2674
2886
|
|
|
2675
|
-
// src/commands/
|
|
2887
|
+
// src/commands/upload.tsx
|
|
2676
2888
|
import { render as render5 } from "ink";
|
|
2677
2889
|
|
|
2678
|
-
// src/commands/
|
|
2890
|
+
// src/commands/upload/components/PushComponent.tsx
|
|
2679
2891
|
import { useEffect as useEffect7, useCallback as useCallback5 } from "react";
|
|
2680
2892
|
import { Box as Box14, Text as Text15 } from "ink";
|
|
2681
2893
|
import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
|
|
2682
|
-
import {
|
|
2894
|
+
import { basename as basename3, extname as extname3, isAbsolute, resolve } from "path";
|
|
2683
2895
|
|
|
2684
|
-
// src/commands/
|
|
2896
|
+
// src/commands/upload/hooks/usePushState.ts
|
|
2685
2897
|
import { useReducer as useReducer2, useCallback as useCallback3 } from "react";
|
|
2686
2898
|
var initialState2 = {
|
|
2687
2899
|
status: "checking",
|
|
@@ -2836,50 +3048,10 @@ function usePushState() {
|
|
|
2836
3048
|
};
|
|
2837
3049
|
}
|
|
2838
3050
|
|
|
2839
|
-
// src/commands/
|
|
3051
|
+
// src/commands/upload/hooks/useLimitCheck.ts
|
|
2840
3052
|
import { useState as useState8, useCallback as useCallback4 } from "react";
|
|
2841
3053
|
|
|
2842
|
-
// src/commands/
|
|
2843
|
-
var DEFAULT_TIMEOUT2 = 3e4;
|
|
2844
|
-
var MAX_RETRIES2 = 3;
|
|
2845
|
-
var RETRY_DELAYS2 = [1e3, 2e3, 4e3];
|
|
2846
|
-
async function fetchWithRetry2(url, options = {}, retries = MAX_RETRIES2) {
|
|
2847
|
-
const { timeout = DEFAULT_TIMEOUT2, ...fetchOptions } = options;
|
|
2848
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
2849
|
-
const controller = new AbortController();
|
|
2850
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2851
|
-
try {
|
|
2852
|
-
const response = await fetch(url, {
|
|
2853
|
-
...fetchOptions,
|
|
2854
|
-
signal: controller.signal
|
|
2855
|
-
});
|
|
2856
|
-
clearTimeout(timeoutId);
|
|
2857
|
-
if (response.status >= 500 && attempt < retries) {
|
|
2858
|
-
await sleep2(RETRY_DELAYS2[attempt] || 4e3);
|
|
2859
|
-
continue;
|
|
2860
|
-
}
|
|
2861
|
-
return response;
|
|
2862
|
-
} catch (error) {
|
|
2863
|
-
clearTimeout(timeoutId);
|
|
2864
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2865
|
-
if (attempt < retries) {
|
|
2866
|
-
await sleep2(RETRY_DELAYS2[attempt] || 4e3);
|
|
2867
|
-
continue;
|
|
2868
|
-
}
|
|
2869
|
-
throw new Error(`Request timed out after ${timeout}ms`);
|
|
2870
|
-
}
|
|
2871
|
-
if (attempt < retries) {
|
|
2872
|
-
await sleep2(RETRY_DELAYS2[attempt] || 4e3);
|
|
2873
|
-
continue;
|
|
2874
|
-
}
|
|
2875
|
-
throw error;
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
throw new Error("Max retries exceeded");
|
|
2879
|
-
}
|
|
2880
|
-
function sleep2(ms) {
|
|
2881
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2882
|
-
}
|
|
3054
|
+
// src/commands/upload/api.ts
|
|
2883
3055
|
function getApiHeaders() {
|
|
2884
3056
|
const resolved = getResolvedApiKey();
|
|
2885
3057
|
const headers = {
|
|
@@ -2898,7 +3070,7 @@ function getApiUrl() {
|
|
|
2898
3070
|
async function fetchProjects3() {
|
|
2899
3071
|
const apiUrl = getApiUrl();
|
|
2900
3072
|
const headers = getApiHeaders();
|
|
2901
|
-
const response = await
|
|
3073
|
+
const response = await fetchWithRetry(`${apiUrl}/api/v1/projects`, { headers });
|
|
2902
3074
|
if (!response.ok) {
|
|
2903
3075
|
if (response.status === 401) {
|
|
2904
3076
|
throw new Error("Authentication failed. Run `npx @intlpullhq/cli login`");
|
|
@@ -2916,7 +3088,7 @@ async function fetchProjects3() {
|
|
|
2916
3088
|
async function fetchProject(projectId) {
|
|
2917
3089
|
const apiUrl = getApiUrl();
|
|
2918
3090
|
const headers = getApiHeaders();
|
|
2919
|
-
const response = await
|
|
3091
|
+
const response = await fetchWithRetry(`${apiUrl}/api/v1/projects/${projectId}`, { headers });
|
|
2920
3092
|
if (!response.ok) {
|
|
2921
3093
|
if (response.status === 404) {
|
|
2922
3094
|
throw new Error("Project not found. Check your project ID.");
|
|
@@ -2928,7 +3100,7 @@ async function fetchProject(projectId) {
|
|
|
2928
3100
|
async function fetchBranches(projectId) {
|
|
2929
3101
|
const apiUrl = getApiUrl();
|
|
2930
3102
|
const headers = getApiHeaders();
|
|
2931
|
-
const response = await
|
|
3103
|
+
const response = await fetchWithRetry(`${apiUrl}/api/v1/projects/${projectId}/branches`, {
|
|
2932
3104
|
headers
|
|
2933
3105
|
});
|
|
2934
3106
|
if (!response.ok) {
|
|
@@ -2940,7 +3112,7 @@ async function fetchBranches(projectId) {
|
|
|
2940
3112
|
async function checkUsageLimit(keyCount) {
|
|
2941
3113
|
const apiUrl = getApiUrl();
|
|
2942
3114
|
const headers = getApiHeaders();
|
|
2943
|
-
const response = await
|
|
3115
|
+
const response = await fetchWithRetry(`${apiUrl}/api/v1/billing/usage/check`, {
|
|
2944
3116
|
method: "POST",
|
|
2945
3117
|
headers,
|
|
2946
3118
|
body: JSON.stringify({ key_count: keyCount })
|
|
@@ -2958,28 +3130,40 @@ async function checkUsageLimit(keyCount) {
|
|
|
2958
3130
|
}
|
|
2959
3131
|
async function pushKeys(projectId, keys, language, namespace, options) {
|
|
2960
3132
|
const apiUrl = getApiUrl();
|
|
2961
|
-
const
|
|
2962
|
-
const
|
|
3133
|
+
const resolved = getResolvedApiKey();
|
|
3134
|
+
const requestBody = {
|
|
2963
3135
|
keys,
|
|
2964
3136
|
language,
|
|
2965
3137
|
namespace
|
|
2966
3138
|
};
|
|
2967
3139
|
if (options?.branchId) {
|
|
2968
|
-
|
|
3140
|
+
requestBody.branch_id = options.branchId;
|
|
2969
3141
|
}
|
|
2970
3142
|
if (options?.platforms?.length) {
|
|
2971
|
-
|
|
3143
|
+
requestBody.platforms = options.platforms;
|
|
3144
|
+
}
|
|
3145
|
+
const headers = {
|
|
3146
|
+
"Accept": "application/json",
|
|
3147
|
+
"Content-Type": "application/json"
|
|
3148
|
+
};
|
|
3149
|
+
if (resolved?.key) {
|
|
3150
|
+
headers["X-API-Key"] = resolved.key;
|
|
2972
3151
|
}
|
|
2973
|
-
const response = await
|
|
3152
|
+
const response = await fetchWithRetry(`${apiUrl}/api/v1/projects/${projectId}/keys/bulk`, {
|
|
2974
3153
|
method: "POST",
|
|
2975
3154
|
headers,
|
|
2976
|
-
body: JSON.stringify(
|
|
3155
|
+
body: JSON.stringify(requestBody)
|
|
2977
3156
|
});
|
|
2978
3157
|
if (!response.ok) {
|
|
2979
3158
|
const error = await response.json().catch(() => ({ error: `HTTP ${response.status}` }));
|
|
2980
3159
|
throw new Error(error.error || `Push failed: ${response.status}`);
|
|
2981
3160
|
}
|
|
2982
|
-
|
|
3161
|
+
const result = await response.json();
|
|
3162
|
+
return {
|
|
3163
|
+
keys_inserted: result.keys_inserted || 0,
|
|
3164
|
+
keys_updated: result.keys_updated || 0,
|
|
3165
|
+
keys_skipped: result.keys_skipped || 0
|
|
3166
|
+
};
|
|
2983
3167
|
}
|
|
2984
3168
|
async function pushKeysParallel(projectId, namespaces, language, options) {
|
|
2985
3169
|
const concurrency = options?.concurrency || 3;
|
|
@@ -3020,7 +3204,7 @@ async function pushKeysParallel(projectId, namespaces, language, options) {
|
|
|
3020
3204
|
async function addLanguagesBulk(projectId, languages, autoTranslate = false) {
|
|
3021
3205
|
const apiUrl = getApiUrl();
|
|
3022
3206
|
const headers = getApiHeaders();
|
|
3023
|
-
const response = await
|
|
3207
|
+
const response = await fetchWithRetry(
|
|
3024
3208
|
`${apiUrl}/api/v1/projects/${projectId}/language-settings/bulk`,
|
|
3025
3209
|
{
|
|
3026
3210
|
method: "POST",
|
|
@@ -3037,7 +3221,7 @@ async function addLanguagesBulk(projectId, languages, autoTranslate = false) {
|
|
|
3037
3221
|
}
|
|
3038
3222
|
}
|
|
3039
3223
|
|
|
3040
|
-
// src/commands/
|
|
3224
|
+
// src/commands/upload/hooks/useLimitCheck.ts
|
|
3041
3225
|
function useLimitCheck(verbose = false) {
|
|
3042
3226
|
const [state, setState] = useState8({
|
|
3043
3227
|
checking: false
|
|
@@ -3143,7 +3327,7 @@ function calculateUploadableKeys(namespaces, availableSlots) {
|
|
|
3143
3327
|
return { limited, totalKeys };
|
|
3144
3328
|
}
|
|
3145
3329
|
|
|
3146
|
-
// src/commands/
|
|
3330
|
+
// src/commands/upload/components/LimitConfirmation.tsx
|
|
3147
3331
|
import { Box as Box11, Text as Text12, useInput as useInput3 } from "ink";
|
|
3148
3332
|
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3149
3333
|
function LimitConfirmation({ limitExceeded, onConfirm, onCancel }) {
|
|
@@ -3211,7 +3395,7 @@ function LimitConfirmation({ limitExceeded, onConfirm, onCancel }) {
|
|
|
3211
3395
|
] });
|
|
3212
3396
|
}
|
|
3213
3397
|
|
|
3214
|
-
// src/commands/
|
|
3398
|
+
// src/commands/upload/components/PushProgress.tsx
|
|
3215
3399
|
import { Box as Box12, Text as Text13 } from "ink";
|
|
3216
3400
|
import Spinner5 from "ink-spinner";
|
|
3217
3401
|
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
@@ -3284,7 +3468,7 @@ function PushProgress({
|
|
|
3284
3468
|
] });
|
|
3285
3469
|
}
|
|
3286
3470
|
|
|
3287
|
-
// src/commands/
|
|
3471
|
+
// src/commands/upload/components/PushResult.tsx
|
|
3288
3472
|
import { Box as Box13, Text as Text14 } from "ink";
|
|
3289
3473
|
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3290
3474
|
function calculateTotals(allLanguages, languageResults, namespaceResults) {
|
|
@@ -4068,7 +4252,7 @@ function readAllTranslationsByLanguage(projectRoot = process.cwd()) {
|
|
|
4068
4252
|
};
|
|
4069
4253
|
}
|
|
4070
4254
|
|
|
4071
|
-
// src/commands/
|
|
4255
|
+
// src/commands/upload/components/PushComponent.tsx
|
|
4072
4256
|
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4073
4257
|
var isInteractiveTerminal2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
4074
4258
|
function PushComponent({ options }) {
|
|
@@ -4098,12 +4282,16 @@ function PushComponent({ options }) {
|
|
|
4098
4282
|
cancel: cancelLimit,
|
|
4099
4283
|
userConfirmed
|
|
4100
4284
|
} = useLimitCheck(options.verbose);
|
|
4101
|
-
const
|
|
4285
|
+
const runPush = useCallback5(async () => {
|
|
4102
4286
|
const resolved = getResolvedApiKey();
|
|
4103
4287
|
if (!resolved?.key) {
|
|
4104
4288
|
setError("Not authenticated. Run `npx @intlpullhq/cli login` or set INTLPULL_API_KEY in .env");
|
|
4105
4289
|
return;
|
|
4106
4290
|
}
|
|
4291
|
+
if (options.translationKeyPrefix && !validatePrefix(options.translationKeyPrefix)) {
|
|
4292
|
+
setError('Invalid prefix format. Use alphanumeric characters and dots only (e.g., "checkout" or "app.checkout")');
|
|
4293
|
+
return;
|
|
4294
|
+
}
|
|
4107
4295
|
const config = getProjectConfig();
|
|
4108
4296
|
let projectId = options.project || config?.projectId;
|
|
4109
4297
|
if (!projectId) {
|
|
@@ -4179,9 +4367,9 @@ To create this branch, go to the IntlPull dashboard.`
|
|
|
4179
4367
|
let sourceLanguage = config?.sourceLanguage || "en";
|
|
4180
4368
|
if (options.file) {
|
|
4181
4369
|
setStatus("reading", `Reading ${options.file}...`);
|
|
4182
|
-
const filePath =
|
|
4370
|
+
const filePath = isAbsolute(options.file) ? options.file : resolve(cwd, options.file);
|
|
4183
4371
|
if (!existsSync8(filePath)) {
|
|
4184
|
-
setError(`File not found: ${
|
|
4372
|
+
setError(`File not found: ${filePath}`);
|
|
4185
4373
|
return;
|
|
4186
4374
|
}
|
|
4187
4375
|
try {
|
|
@@ -4278,9 +4466,16 @@ To create this branch, go to the IntlPull dashboard.`
|
|
|
4278
4466
|
for (let langIdx = 0; langIdx < allLangResult.languages.length; langIdx++) {
|
|
4279
4467
|
const langData = allLangResult.languages[langIdx];
|
|
4280
4468
|
updateLanguageResult(langIdx, { status: "pushing" });
|
|
4469
|
+
let namespacesToPush2 = langData.namespaces;
|
|
4470
|
+
if (options.translationKeyPrefix) {
|
|
4471
|
+
namespacesToPush2 = langData.namespaces.map((ns) => ({
|
|
4472
|
+
...ns,
|
|
4473
|
+
keys: applyPrefix(ns.keys, options.translationKeyPrefix)
|
|
4474
|
+
}));
|
|
4475
|
+
}
|
|
4281
4476
|
const { results: results2, totals: totals2 } = await pushKeysParallel(
|
|
4282
4477
|
projectId,
|
|
4283
|
-
|
|
4478
|
+
namespacesToPush2,
|
|
4284
4479
|
langData.language,
|
|
4285
4480
|
{
|
|
4286
4481
|
branchId,
|
|
@@ -4377,7 +4572,14 @@ To create this branch, go to the IntlPull dashboard.`
|
|
|
4377
4572
|
return;
|
|
4378
4573
|
}
|
|
4379
4574
|
setStatus("pushing", `Pushing ${totalKeys} keys to IntlPull...`);
|
|
4380
|
-
|
|
4575
|
+
let namespacesToPush = namespaces;
|
|
4576
|
+
if (options.translationKeyPrefix) {
|
|
4577
|
+
namespacesToPush = namespaces.map((ns) => ({
|
|
4578
|
+
...ns,
|
|
4579
|
+
keys: applyPrefix(ns.keys, options.translationKeyPrefix)
|
|
4580
|
+
}));
|
|
4581
|
+
}
|
|
4582
|
+
const { results, totals } = await pushKeysParallel(projectId, namespacesToPush, sourceLanguage, {
|
|
4381
4583
|
branchId,
|
|
4382
4584
|
platforms: options.platform && options.platform !== "all" ? [options.platform] : void 0
|
|
4383
4585
|
});
|
|
@@ -4405,7 +4607,7 @@ To create this branch, go to the IntlPull dashboard.`
|
|
|
4405
4607
|
complete(results.some((r) => r.error));
|
|
4406
4608
|
}, [options, limitState]);
|
|
4407
4609
|
useEffect7(() => {
|
|
4408
|
-
|
|
4610
|
+
runPush();
|
|
4409
4611
|
}, []);
|
|
4410
4612
|
useEffect7(() => {
|
|
4411
4613
|
if (userConfirmed && state.status === "waiting_confirmation" && state.pushContext) {
|
|
@@ -4427,13 +4629,27 @@ To create this branch, go to the IntlPull dashboard.`
|
|
|
4427
4629
|
const sourceLanguageData = allLangResult.languages.find((l) => l.language === sourceLanguage);
|
|
4428
4630
|
if (sourceLanguageData) {
|
|
4429
4631
|
const { limited } = calculateUploadableKeys(sourceLanguageData.namespaces, availableSlots);
|
|
4430
|
-
|
|
4632
|
+
let limitedToPost = limited;
|
|
4633
|
+
if (options.translationKeyPrefix) {
|
|
4634
|
+
limitedToPost = limited.map((ns) => ({
|
|
4635
|
+
...ns,
|
|
4636
|
+
keys: applyPrefix(ns.keys, options.translationKeyPrefix)
|
|
4637
|
+
}));
|
|
4638
|
+
}
|
|
4639
|
+
const { results, totals } = await pushKeysParallel(projectId, limitedToPost, sourceLanguage, {
|
|
4431
4640
|
branchId,
|
|
4432
4641
|
platforms: platform && platform !== "all" ? [platform] : void 0
|
|
4433
4642
|
});
|
|
4434
4643
|
for (const langData of allLangResult.languages) {
|
|
4435
4644
|
if (langData.language === sourceLanguage) continue;
|
|
4436
|
-
|
|
4645
|
+
let namespacesToPush = langData.namespaces;
|
|
4646
|
+
if (options.translationKeyPrefix) {
|
|
4647
|
+
namespacesToPush = langData.namespaces.map((ns) => ({
|
|
4648
|
+
...ns,
|
|
4649
|
+
keys: applyPrefix(ns.keys, options.translationKeyPrefix)
|
|
4650
|
+
}));
|
|
4651
|
+
}
|
|
4652
|
+
await pushKeysParallel(projectId, namespacesToPush, langData.language, {
|
|
4437
4653
|
branchId,
|
|
4438
4654
|
platforms: platform && platform !== "all" ? [platform] : void 0
|
|
4439
4655
|
});
|
|
@@ -4442,7 +4658,14 @@ To create this branch, go to the IntlPull dashboard.`
|
|
|
4442
4658
|
}
|
|
4443
4659
|
} else if (namespaces) {
|
|
4444
4660
|
const { limited, totalKeys } = calculateUploadableKeys(namespaces, availableSlots);
|
|
4445
|
-
|
|
4661
|
+
let limitedToPost = limited;
|
|
4662
|
+
if (options.translationKeyPrefix) {
|
|
4663
|
+
limitedToPost = limited.map((ns) => ({
|
|
4664
|
+
...ns,
|
|
4665
|
+
keys: applyPrefix(ns.keys, options.translationKeyPrefix)
|
|
4666
|
+
}));
|
|
4667
|
+
}
|
|
4668
|
+
const { results } = await pushKeysParallel(projectId, limitedToPost, sourceLanguage, {
|
|
4446
4669
|
branchId,
|
|
4447
4670
|
platforms: platform && platform !== "all" ? [platform] : void 0
|
|
4448
4671
|
});
|
|
@@ -4510,9 +4733,9 @@ To create this branch, go to the IntlPull dashboard.`
|
|
|
4510
4733
|
);
|
|
4511
4734
|
}
|
|
4512
4735
|
|
|
4513
|
-
// src/commands/
|
|
4736
|
+
// src/commands/upload.tsx
|
|
4514
4737
|
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
4515
|
-
function
|
|
4738
|
+
function runUpload(options) {
|
|
4516
4739
|
render5(/* @__PURE__ */ jsx17(PushComponent, { options }));
|
|
4517
4740
|
}
|
|
4518
4741
|
|
|
@@ -4589,7 +4812,20 @@ function ExportComponent({ options }) {
|
|
|
4589
4812
|
platform: options.platform
|
|
4590
4813
|
};
|
|
4591
4814
|
const blob = await api.exportTranslations(projectId, exportOptions);
|
|
4592
|
-
const
|
|
4815
|
+
const extensionMap = {
|
|
4816
|
+
json: "json",
|
|
4817
|
+
yaml: "yaml",
|
|
4818
|
+
ts: "ts",
|
|
4819
|
+
android: "zip",
|
|
4820
|
+
ios: "zip",
|
|
4821
|
+
xliff: "xliff",
|
|
4822
|
+
po: "po",
|
|
4823
|
+
arb: "arb",
|
|
4824
|
+
stringsdict: "stringsdict",
|
|
4825
|
+
xcstrings: "xcstrings",
|
|
4826
|
+
csv: "csv"
|
|
4827
|
+
};
|
|
4828
|
+
const extension = extensionMap[format] || format;
|
|
4593
4829
|
const outputFile = options.output || `translations.${extension}`;
|
|
4594
4830
|
setState({
|
|
4595
4831
|
status: "saving",
|
|
@@ -4684,11 +4920,44 @@ function detectFormatFromExtension(filename) {
|
|
|
4684
4920
|
case ".yaml":
|
|
4685
4921
|
case ".yml":
|
|
4686
4922
|
return "yaml";
|
|
4923
|
+
case ".xliff":
|
|
4924
|
+
case ".xlf":
|
|
4925
|
+
return "xliff";
|
|
4926
|
+
case ".po":
|
|
4927
|
+
case ".pot":
|
|
4928
|
+
return "po";
|
|
4929
|
+
case ".arb":
|
|
4930
|
+
return "arb";
|
|
4931
|
+
case ".stringsdict":
|
|
4932
|
+
return "stringsdict";
|
|
4933
|
+
case ".xcstrings":
|
|
4934
|
+
return "xcstrings";
|
|
4935
|
+
case ".csv":
|
|
4936
|
+
return "csv";
|
|
4937
|
+
case ".strings":
|
|
4938
|
+
return "ios";
|
|
4939
|
+
case ".xml":
|
|
4940
|
+
return "android";
|
|
4687
4941
|
case ".json":
|
|
4688
4942
|
default:
|
|
4689
4943
|
return "json";
|
|
4690
4944
|
}
|
|
4691
4945
|
}
|
|
4946
|
+
var SUPPORTED_EXTENSIONS = [
|
|
4947
|
+
".json",
|
|
4948
|
+
".yaml",
|
|
4949
|
+
".yml",
|
|
4950
|
+
".xliff",
|
|
4951
|
+
".xlf",
|
|
4952
|
+
".po",
|
|
4953
|
+
".pot",
|
|
4954
|
+
".arb",
|
|
4955
|
+
".stringsdict",
|
|
4956
|
+
".xcstrings",
|
|
4957
|
+
".csv",
|
|
4958
|
+
".strings",
|
|
4959
|
+
".xml"
|
|
4960
|
+
];
|
|
4692
4961
|
function findTranslationFiles(dir, pattern) {
|
|
4693
4962
|
const files = [];
|
|
4694
4963
|
try {
|
|
@@ -4696,7 +4965,7 @@ function findTranslationFiles(dir, pattern) {
|
|
|
4696
4965
|
for (const entry of entries) {
|
|
4697
4966
|
if (entry.isFile()) {
|
|
4698
4967
|
const ext = extname4(entry.name).toLowerCase();
|
|
4699
|
-
if (
|
|
4968
|
+
if (SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
4700
4969
|
if (pattern) {
|
|
4701
4970
|
const regex = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, "."));
|
|
4702
4971
|
if (!regex.test(entry.name)) continue;
|
|
@@ -5812,7 +6081,7 @@ var LokaliseMigrator = class {
|
|
|
5812
6081
|
hasMore = keys.length === limit;
|
|
5813
6082
|
page++;
|
|
5814
6083
|
if (hasMore) {
|
|
5815
|
-
await new Promise((
|
|
6084
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
5816
6085
|
}
|
|
5817
6086
|
}
|
|
5818
6087
|
return allKeys;
|
|
@@ -5881,7 +6150,7 @@ var LokaliseMigrator = class {
|
|
|
5881
6150
|
hasMore = entries.length === limit;
|
|
5882
6151
|
page++;
|
|
5883
6152
|
if (hasMore) {
|
|
5884
|
-
await new Promise((
|
|
6153
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
5885
6154
|
}
|
|
5886
6155
|
}
|
|
5887
6156
|
} catch {
|
|
@@ -5929,7 +6198,7 @@ var LokaliseMigrator = class {
|
|
|
5929
6198
|
hasMore = terms.length === limit;
|
|
5930
6199
|
page++;
|
|
5931
6200
|
if (hasMore) {
|
|
5932
|
-
await new Promise((
|
|
6201
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
5933
6202
|
}
|
|
5934
6203
|
}
|
|
5935
6204
|
}
|
|
@@ -6148,7 +6417,7 @@ var CrowdinMigrator = class {
|
|
|
6148
6417
|
hasMore = strings.length === limit;
|
|
6149
6418
|
offset += limit;
|
|
6150
6419
|
if (hasMore) {
|
|
6151
|
-
await new Promise((
|
|
6420
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
6152
6421
|
}
|
|
6153
6422
|
}
|
|
6154
6423
|
return allKeys;
|
|
@@ -6246,7 +6515,7 @@ var CrowdinMigrator = class {
|
|
|
6246
6515
|
hasMore = segments.length === limit;
|
|
6247
6516
|
offset += limit;
|
|
6248
6517
|
if (hasMore) {
|
|
6249
|
-
await new Promise((
|
|
6518
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
6250
6519
|
}
|
|
6251
6520
|
} catch {
|
|
6252
6521
|
hasMore = false;
|
|
@@ -6294,7 +6563,7 @@ var CrowdinMigrator = class {
|
|
|
6294
6563
|
hasMore = terms.length === limit;
|
|
6295
6564
|
offset += limit;
|
|
6296
6565
|
if (hasMore) {
|
|
6297
|
-
await new Promise((
|
|
6566
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
6298
6567
|
}
|
|
6299
6568
|
}
|
|
6300
6569
|
}
|
|
@@ -6482,7 +6751,7 @@ var PhraseMigrator = class {
|
|
|
6482
6751
|
hasMore = projects2.length === perPage;
|
|
6483
6752
|
page++;
|
|
6484
6753
|
if (hasMore) {
|
|
6485
|
-
await new Promise((
|
|
6754
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
6486
6755
|
}
|
|
6487
6756
|
}
|
|
6488
6757
|
return allProjects;
|
|
@@ -6538,7 +6807,7 @@ var PhraseMigrator = class {
|
|
|
6538
6807
|
hasMore = keys.length === perPage;
|
|
6539
6808
|
page++;
|
|
6540
6809
|
if (hasMore) {
|
|
6541
|
-
await new Promise((
|
|
6810
|
+
await new Promise((resolve2) => setTimeout(resolve2, 300));
|
|
6542
6811
|
}
|
|
6543
6812
|
}
|
|
6544
6813
|
return allKeys;
|
|
@@ -6624,7 +6893,7 @@ var PhraseMigrator = class {
|
|
|
6624
6893
|
hasMore = terms.length === perPage;
|
|
6625
6894
|
page++;
|
|
6626
6895
|
if (hasMore) {
|
|
6627
|
-
await new Promise((
|
|
6896
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
6628
6897
|
}
|
|
6629
6898
|
}
|
|
6630
6899
|
}
|
|
@@ -7747,7 +8016,7 @@ Make sure your files follow naming conventions like:
|
|
|
7747
8016
|
});
|
|
7748
8017
|
try {
|
|
7749
8018
|
if (optionsDryRun) {
|
|
7750
|
-
await new Promise((
|
|
8019
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
7751
8020
|
setUploadStatuses((prev) => {
|
|
7752
8021
|
const updated = [...prev];
|
|
7753
8022
|
updated[index] = {
|
|
@@ -8039,7 +8308,7 @@ function runMigrateFiles(path4, options) {
|
|
|
8039
8308
|
// src/commands/check.tsx
|
|
8040
8309
|
import { useState as useState15, useEffect as useEffect12 } from "react";
|
|
8041
8310
|
import { render as render10, Box as Box27, Text as Text28 } from "ink";
|
|
8042
|
-
import { glob } from "glob";
|
|
8311
|
+
import { glob as glob2 } from "glob";
|
|
8043
8312
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
8044
8313
|
|
|
8045
8314
|
// src/components/TaskList.tsx
|
|
@@ -8180,7 +8449,7 @@ function CheckCommand({ options }) {
|
|
|
8180
8449
|
];
|
|
8181
8450
|
let localeFiles = [];
|
|
8182
8451
|
for (const pattern of patterns) {
|
|
8183
|
-
const files = await
|
|
8452
|
+
const files = await glob2(pattern, { absolute: true });
|
|
8184
8453
|
if (files.length > 0) {
|
|
8185
8454
|
localeFiles = files;
|
|
8186
8455
|
break;
|
|
@@ -8776,7 +9045,7 @@ function runDiff(options) {
|
|
|
8776
9045
|
import { useState as useState17, useEffect as useEffect14 } from "react";
|
|
8777
9046
|
import { render as render12, Box as Box29, Text as Text30 } from "ink";
|
|
8778
9047
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
8779
|
-
import { glob as
|
|
9048
|
+
import { glob as glob3 } from "glob";
|
|
8780
9049
|
import { jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
8781
9050
|
function FixCommand({ options }) {
|
|
8782
9051
|
const [tasks, setTasks] = useState17([
|
|
@@ -8816,7 +9085,7 @@ function FixCommand({ options }) {
|
|
|
8816
9085
|
];
|
|
8817
9086
|
let localeFiles = [];
|
|
8818
9087
|
for (const pattern of patterns) {
|
|
8819
|
-
const files = await
|
|
9088
|
+
const files = await glob3(pattern, { absolute: true });
|
|
8820
9089
|
if (files.length > 0) {
|
|
8821
9090
|
localeFiles = files;
|
|
8822
9091
|
break;
|
|
@@ -11319,101 +11588,2767 @@ documentsCommand.command("download").description("Download a translated document
|
|
|
11319
11588
|
});
|
|
11320
11589
|
});
|
|
11321
11590
|
|
|
11322
|
-
// src/index.tsx
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11591
|
+
// src/commands/keys/index.tsx
|
|
11592
|
+
import { Command as Command2 } from "commander";
|
|
11593
|
+
|
|
11594
|
+
// src/commands/keys/list.tsx
|
|
11595
|
+
import { useState as useState26, useEffect as useEffect23 } from "react";
|
|
11596
|
+
import { render as render21, Box as Box36, Text as Text39, Newline as Newline2 } from "ink";
|
|
11597
|
+
import Spinner18 from "ink-spinner";
|
|
11598
|
+
import { jsx as jsx42, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
11599
|
+
function SimpleTable2({ data }) {
|
|
11600
|
+
if (data.length === 0) return null;
|
|
11601
|
+
const headers = Object.keys(data[0]);
|
|
11602
|
+
const colWidths = headers.map(
|
|
11603
|
+
(h) => Math.max(h.length, ...data.map((row) => String(row[h] || "").length))
|
|
11604
|
+
);
|
|
11605
|
+
const separator = "\u2500".repeat(colWidths.reduce((a, b) => a + b + 3, 1));
|
|
11606
|
+
return /* @__PURE__ */ jsxs39(Box36, { flexDirection: "column", children: [
|
|
11607
|
+
/* @__PURE__ */ jsx42(Text39, { children: separator }),
|
|
11608
|
+
/* @__PURE__ */ jsxs39(Text39, { children: [
|
|
11609
|
+
"\u2502 ",
|
|
11610
|
+
headers.map((h, i) => h.padEnd(colWidths[i])).join(" \u2502 "),
|
|
11611
|
+
" \u2502"
|
|
11612
|
+
] }),
|
|
11613
|
+
/* @__PURE__ */ jsx42(Text39, { children: separator }),
|
|
11614
|
+
data.map((row, rowIdx) => /* @__PURE__ */ jsxs39(Text39, { children: [
|
|
11615
|
+
"\u2502 ",
|
|
11616
|
+
headers.map((h, i) => String(row[h] || "").padEnd(colWidths[i])).join(" \u2502 "),
|
|
11617
|
+
" \u2502"
|
|
11618
|
+
] }, rowIdx)),
|
|
11619
|
+
/* @__PURE__ */ jsx42(Text39, { children: separator })
|
|
11620
|
+
] });
|
|
11621
|
+
}
|
|
11622
|
+
var KeysList = ({ options }) => {
|
|
11623
|
+
const [keys, setKeys] = useState26([]);
|
|
11624
|
+
const [isLoading, setIsLoading] = useState26(true);
|
|
11625
|
+
const [error, setError] = useState26(null);
|
|
11626
|
+
useEffect23(() => {
|
|
11627
|
+
const fetchKeys = async () => {
|
|
11628
|
+
try {
|
|
11629
|
+
const config = getProjectConfig();
|
|
11630
|
+
const projectId = options.project || config?.projectId;
|
|
11631
|
+
if (!projectId) {
|
|
11632
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
11633
|
+
setIsLoading(false);
|
|
11634
|
+
return;
|
|
11635
|
+
}
|
|
11636
|
+
const api = new KeysApi();
|
|
11637
|
+
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : void 0;
|
|
11638
|
+
const limit = options.limit ? parseInt(options.limit, 10) : void 0;
|
|
11639
|
+
const result = await api.listKeys(projectId, {
|
|
11640
|
+
namespace: options.namespace,
|
|
11641
|
+
search: options.search,
|
|
11642
|
+
tags,
|
|
11643
|
+
limit
|
|
11644
|
+
});
|
|
11645
|
+
setKeys(result.keys);
|
|
11646
|
+
} catch (err) {
|
|
11647
|
+
setError(err instanceof Error ? err.message : "Failed to fetch keys");
|
|
11648
|
+
} finally {
|
|
11649
|
+
setIsLoading(false);
|
|
11650
|
+
}
|
|
11651
|
+
};
|
|
11652
|
+
fetchKeys();
|
|
11653
|
+
}, [options]);
|
|
11654
|
+
if (error) {
|
|
11655
|
+
return /* @__PURE__ */ jsxs39(Text39, { color: "red", children: [
|
|
11656
|
+
"Error: ",
|
|
11657
|
+
error
|
|
11658
|
+
] });
|
|
11328
11659
|
}
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11660
|
+
if (isLoading) {
|
|
11661
|
+
return /* @__PURE__ */ jsxs39(Text39, { children: [
|
|
11662
|
+
/* @__PURE__ */ jsx42(Text39, { color: "green", children: /* @__PURE__ */ jsx42(Spinner18, { type: "dots" }) }),
|
|
11663
|
+
" Loading keys..."
|
|
11664
|
+
] });
|
|
11332
11665
|
}
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
runInit({
|
|
11336
|
-
framework: options.framework,
|
|
11337
|
-
library: options.library,
|
|
11338
|
-
output: options.output,
|
|
11339
|
-
project: options.project,
|
|
11340
|
-
yes: options.yes
|
|
11341
|
-
});
|
|
11342
|
-
});
|
|
11343
|
-
program.command("status").description("Show project status and translation progress").action(() => {
|
|
11344
|
-
runStatus();
|
|
11345
|
-
});
|
|
11346
|
-
program.command("login").description("Authenticate with IntlPull").option("--token <token>", "Use API token (prefer INTLPULL_API_KEY env var for security)").action((options) => {
|
|
11347
|
-
runLogin({ token: options.token });
|
|
11348
|
-
});
|
|
11349
|
-
program.command("logout").description("Clear stored credentials").action(async () => {
|
|
11350
|
-
const { clearAuthConfig } = await import("./config-DX34FMWV.js");
|
|
11351
|
-
clearAuthConfig();
|
|
11352
|
-
console.log("\u2713 Logged out successfully");
|
|
11353
|
-
});
|
|
11354
|
-
program.command("whoami").description("Show current authenticated user").action(async () => {
|
|
11355
|
-
const { getResolvedApiKey: getResolvedApiKey2, getAuthErrorMessage: getAuthErrorMessage2 } = await import("./config-DX34FMWV.js");
|
|
11356
|
-
const { createApiClient: createApiClient2 } = await import("./api-K3AY4VAI.js");
|
|
11357
|
-
const resolved = getResolvedApiKey2();
|
|
11358
|
-
if (!resolved) {
|
|
11359
|
-
console.log(getAuthErrorMessage2());
|
|
11360
|
-
return;
|
|
11666
|
+
if (keys.length === 0) {
|
|
11667
|
+
return /* @__PURE__ */ jsx42(Text39, { children: "No keys found." });
|
|
11361
11668
|
}
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
const sourceLabel = resolved.source === "env" ? "INTLPULL_API_KEY env var" : "~/.intlpull/auth.json";
|
|
11366
|
-
console.log("\n\u2713 Authenticated successfully\n");
|
|
11367
|
-
console.log(" API Key:", resolved.key.substring(0, 20) + "...");
|
|
11368
|
-
console.log(" Source:", sourceLabel);
|
|
11369
|
-
console.log(" Projects:", projects2.length);
|
|
11370
|
-
if (projects2.length > 0) {
|
|
11371
|
-
console.log("\n Your projects:");
|
|
11372
|
-
for (const p of projects2.slice(0, 5)) {
|
|
11373
|
-
console.log(` - ${p.name} (${p.slug})`);
|
|
11374
|
-
}
|
|
11375
|
-
if (projects2.length > 5) {
|
|
11376
|
-
console.log(` ... and ${projects2.length - 5} more`);
|
|
11377
|
-
}
|
|
11378
|
-
}
|
|
11379
|
-
console.log("");
|
|
11380
|
-
} catch (err) {
|
|
11381
|
-
console.log("API Key:", resolved.key.substring(0, 15) + "...");
|
|
11382
|
-
console.error("Error verifying API key:", err instanceof Error ? err.message : "Unknown error");
|
|
11669
|
+
if (options.format === "json") {
|
|
11670
|
+
console.log(JSON.stringify(keys, null, 2));
|
|
11671
|
+
return null;
|
|
11383
11672
|
}
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
})
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11673
|
+
const data = keys.map((key) => ({
|
|
11674
|
+
Key: key.key,
|
|
11675
|
+
Namespace: key.namespace || "-",
|
|
11676
|
+
Description: key.description ? key.description.length > 40 ? key.description.substring(0, 37) + "..." : key.description : "-",
|
|
11677
|
+
Tags: key.tags && key.tags.length > 0 ? key.tags.join(", ") : "-"
|
|
11678
|
+
}));
|
|
11679
|
+
return /* @__PURE__ */ jsxs39(Box36, { flexDirection: "column", children: [
|
|
11680
|
+
/* @__PURE__ */ jsxs39(Text39, { children: [
|
|
11681
|
+
"Found ",
|
|
11682
|
+
keys.length,
|
|
11683
|
+
" key(s)"
|
|
11684
|
+
] }),
|
|
11685
|
+
/* @__PURE__ */ jsx42(Newline2, {}),
|
|
11686
|
+
/* @__PURE__ */ jsx42(SimpleTable2, { data })
|
|
11687
|
+
] });
|
|
11688
|
+
};
|
|
11689
|
+
function runKeysList(options) {
|
|
11690
|
+
render21(/* @__PURE__ */ jsx42(KeysList, { options }));
|
|
11691
|
+
}
|
|
11692
|
+
|
|
11693
|
+
// src/commands/keys/create.tsx
|
|
11694
|
+
import { useState as useState27, useEffect as useEffect24 } from "react";
|
|
11695
|
+
import { render as render22, Box as Box37, Text as Text40 } from "ink";
|
|
11696
|
+
import Spinner19 from "ink-spinner";
|
|
11697
|
+
import { jsx as jsx43, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
11698
|
+
var KeysCreate = ({ keyName, options }) => {
|
|
11699
|
+
const [result, setResult] = useState27(null);
|
|
11700
|
+
const [isLoading, setIsLoading] = useState27(true);
|
|
11701
|
+
const [error, setError] = useState27(null);
|
|
11702
|
+
useEffect24(() => {
|
|
11703
|
+
const createKey = async () => {
|
|
11704
|
+
try {
|
|
11705
|
+
const config = getProjectConfig();
|
|
11706
|
+
const projectId = options.project || config?.projectId;
|
|
11707
|
+
if (!projectId) {
|
|
11708
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
11709
|
+
setIsLoading(false);
|
|
11710
|
+
return;
|
|
11711
|
+
}
|
|
11712
|
+
if (!options.value) {
|
|
11713
|
+
setError("Value is required. Use --value <translation>");
|
|
11714
|
+
setIsLoading(false);
|
|
11715
|
+
return;
|
|
11716
|
+
}
|
|
11717
|
+
const api = new KeysApi();
|
|
11718
|
+
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : void 0;
|
|
11719
|
+
const { ProjectsApi: ProjectsApi2 } = await import("./projects-AMQMORAR.js");
|
|
11720
|
+
const projectsApi = new ProjectsApi2();
|
|
11721
|
+
const project = await projectsApi.getProject(projectId);
|
|
11722
|
+
const created = await api.createKey(projectId, {
|
|
11723
|
+
namespace: options.namespace || "default",
|
|
11724
|
+
key: keyName,
|
|
11725
|
+
value: options.value,
|
|
11726
|
+
language: project.source_language,
|
|
11727
|
+
context: options.context,
|
|
11728
|
+
tags
|
|
11729
|
+
});
|
|
11730
|
+
setResult(created);
|
|
11731
|
+
} catch (err) {
|
|
11732
|
+
setError(err instanceof Error ? err.message : "Failed to create key");
|
|
11733
|
+
} finally {
|
|
11734
|
+
setIsLoading(false);
|
|
11735
|
+
}
|
|
11736
|
+
};
|
|
11737
|
+
createKey();
|
|
11738
|
+
}, [keyName, options]);
|
|
11739
|
+
if (error) {
|
|
11740
|
+
return /* @__PURE__ */ jsxs40(Text40, { color: "red", children: [
|
|
11741
|
+
"Error: ",
|
|
11742
|
+
error
|
|
11743
|
+
] });
|
|
11744
|
+
}
|
|
11745
|
+
if (isLoading) {
|
|
11746
|
+
return /* @__PURE__ */ jsxs40(Text40, { children: [
|
|
11747
|
+
/* @__PURE__ */ jsx43(Text40, { color: "green", children: /* @__PURE__ */ jsx43(Spinner19, { type: "dots" }) }),
|
|
11748
|
+
" Creating key..."
|
|
11749
|
+
] });
|
|
11750
|
+
}
|
|
11751
|
+
if (!result) {
|
|
11752
|
+
return /* @__PURE__ */ jsx43(Text40, { color: "red", children: "Failed to create key" });
|
|
11753
|
+
}
|
|
11754
|
+
return /* @__PURE__ */ jsxs40(Box37, { flexDirection: "column", children: [
|
|
11755
|
+
/* @__PURE__ */ jsx43(Text40, { color: "green", children: "\u2713 Key created successfully" }),
|
|
11756
|
+
/* @__PURE__ */ jsxs40(Text40, { children: [
|
|
11757
|
+
"Key: ",
|
|
11758
|
+
result.key
|
|
11759
|
+
] }),
|
|
11760
|
+
/* @__PURE__ */ jsxs40(Text40, { children: [
|
|
11761
|
+
"Namespace: ",
|
|
11762
|
+
result.namespace || "-"
|
|
11763
|
+
] }),
|
|
11764
|
+
result.description && /* @__PURE__ */ jsxs40(Text40, { children: [
|
|
11765
|
+
"Description: ",
|
|
11766
|
+
result.description
|
|
11767
|
+
] }),
|
|
11768
|
+
result.tags && result.tags.length > 0 && /* @__PURE__ */ jsxs40(Text40, { children: [
|
|
11769
|
+
"Tags: ",
|
|
11770
|
+
result.tags.join(", ")
|
|
11771
|
+
] })
|
|
11772
|
+
] });
|
|
11773
|
+
};
|
|
11774
|
+
function runKeysCreate(keyName, options) {
|
|
11775
|
+
render22(/* @__PURE__ */ jsx43(KeysCreate, { keyName, options }));
|
|
11776
|
+
}
|
|
11777
|
+
|
|
11778
|
+
// src/commands/keys/update.tsx
|
|
11779
|
+
import { useState as useState28, useEffect as useEffect25 } from "react";
|
|
11780
|
+
import { render as render23, Box as Box38, Text as Text41 } from "ink";
|
|
11781
|
+
import Spinner20 from "ink-spinner";
|
|
11782
|
+
import { jsx as jsx44, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
11783
|
+
var KeysUpdate = ({ keyName, options }) => {
|
|
11784
|
+
const [result, setResult] = useState28(null);
|
|
11785
|
+
const [isLoading, setIsLoading] = useState28(true);
|
|
11786
|
+
const [error, setError] = useState28(null);
|
|
11787
|
+
useEffect25(() => {
|
|
11788
|
+
const updateKey = async () => {
|
|
11789
|
+
try {
|
|
11790
|
+
const config = getProjectConfig();
|
|
11791
|
+
const projectId = options.project || config?.projectId;
|
|
11792
|
+
if (!projectId) {
|
|
11793
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
11794
|
+
setIsLoading(false);
|
|
11795
|
+
return;
|
|
11796
|
+
}
|
|
11797
|
+
const api = new KeysApi();
|
|
11798
|
+
const keysResult = await api.listKeys(projectId, { search: keyName });
|
|
11799
|
+
const existingKey = keysResult.keys.find((k) => k.key === keyName);
|
|
11800
|
+
if (!existingKey) {
|
|
11801
|
+
setError(`Key "${keyName}" not found`);
|
|
11802
|
+
setIsLoading(false);
|
|
11803
|
+
return;
|
|
11804
|
+
}
|
|
11805
|
+
const updateData = {};
|
|
11806
|
+
if (options.context !== void 0) {
|
|
11807
|
+
updateData.context = options.context;
|
|
11808
|
+
}
|
|
11809
|
+
if (options.tags !== void 0) {
|
|
11810
|
+
updateData.tags = options.tags.split(",").map((t) => t.trim());
|
|
11811
|
+
}
|
|
11812
|
+
if (Object.keys(updateData).length === 0) {
|
|
11813
|
+
setError("No update fields provided. Use --context or --tags");
|
|
11814
|
+
setIsLoading(false);
|
|
11815
|
+
return;
|
|
11816
|
+
}
|
|
11817
|
+
const updated = await api.updateKey(projectId, existingKey.id, updateData);
|
|
11818
|
+
setResult(updated);
|
|
11819
|
+
} catch (err) {
|
|
11820
|
+
setError(err instanceof Error ? err.message : "Failed to update key");
|
|
11821
|
+
} finally {
|
|
11822
|
+
setIsLoading(false);
|
|
11823
|
+
}
|
|
11824
|
+
};
|
|
11825
|
+
updateKey();
|
|
11826
|
+
}, [keyName, options]);
|
|
11827
|
+
if (error) {
|
|
11828
|
+
return /* @__PURE__ */ jsxs41(Text41, { color: "red", children: [
|
|
11829
|
+
"Error: ",
|
|
11830
|
+
error
|
|
11831
|
+
] });
|
|
11832
|
+
}
|
|
11833
|
+
if (isLoading) {
|
|
11834
|
+
return /* @__PURE__ */ jsxs41(Text41, { children: [
|
|
11835
|
+
/* @__PURE__ */ jsx44(Text41, { color: "green", children: /* @__PURE__ */ jsx44(Spinner20, { type: "dots" }) }),
|
|
11836
|
+
" Updating key..."
|
|
11837
|
+
] });
|
|
11838
|
+
}
|
|
11839
|
+
if (!result) {
|
|
11840
|
+
return /* @__PURE__ */ jsx44(Text41, { color: "red", children: "Failed to update key" });
|
|
11841
|
+
}
|
|
11842
|
+
return /* @__PURE__ */ jsxs41(Box38, { flexDirection: "column", children: [
|
|
11843
|
+
/* @__PURE__ */ jsx44(Text41, { color: "green", children: "\u2713 Key updated successfully" }),
|
|
11844
|
+
/* @__PURE__ */ jsxs41(Text41, { children: [
|
|
11845
|
+
"Key: ",
|
|
11846
|
+
result.key
|
|
11847
|
+
] }),
|
|
11848
|
+
/* @__PURE__ */ jsxs41(Text41, { children: [
|
|
11849
|
+
"Namespace: ",
|
|
11850
|
+
result.namespace || "-"
|
|
11851
|
+
] }),
|
|
11852
|
+
result.description && /* @__PURE__ */ jsxs41(Text41, { children: [
|
|
11853
|
+
"Description: ",
|
|
11854
|
+
result.description
|
|
11855
|
+
] }),
|
|
11856
|
+
result.tags && result.tags.length > 0 && /* @__PURE__ */ jsxs41(Text41, { children: [
|
|
11857
|
+
"Tags: ",
|
|
11858
|
+
result.tags.join(", ")
|
|
11859
|
+
] })
|
|
11860
|
+
] });
|
|
11861
|
+
};
|
|
11862
|
+
function runKeysUpdate(keyName, options) {
|
|
11863
|
+
render23(/* @__PURE__ */ jsx44(KeysUpdate, { keyName, options }));
|
|
11864
|
+
}
|
|
11865
|
+
|
|
11866
|
+
// src/commands/keys/delete.tsx
|
|
11867
|
+
import { useState as useState29, useEffect as useEffect26 } from "react";
|
|
11868
|
+
import { render as render24, Box as Box39, Text as Text42, useInput as useInput8 } from "ink";
|
|
11869
|
+
import Spinner21 from "ink-spinner";
|
|
11870
|
+
import { jsx as jsx45, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
11871
|
+
var KeysDelete = ({ keyName, options }) => {
|
|
11872
|
+
const [key, setKey] = useState29(null);
|
|
11873
|
+
const [isLoading, setIsLoading] = useState29(true);
|
|
11874
|
+
const [error, setError] = useState29(null);
|
|
11875
|
+
const [needsConfirmation, setNeedsConfirmation] = useState29(!options.force);
|
|
11876
|
+
const [isDeleting, setIsDeleting] = useState29(false);
|
|
11877
|
+
const [deleted, setDeleted] = useState29(false);
|
|
11878
|
+
useEffect26(() => {
|
|
11879
|
+
const findKey = async () => {
|
|
11880
|
+
try {
|
|
11881
|
+
const config = getProjectConfig();
|
|
11882
|
+
const projectId = options.project || config?.projectId;
|
|
11883
|
+
if (!projectId) {
|
|
11884
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
11885
|
+
setIsLoading(false);
|
|
11886
|
+
return;
|
|
11887
|
+
}
|
|
11888
|
+
const api = new KeysApi();
|
|
11889
|
+
const keysResult = await api.listKeys(projectId, { search: keyName });
|
|
11890
|
+
const existingKey = keysResult.keys.find((k) => k.key === keyName);
|
|
11891
|
+
if (!existingKey) {
|
|
11892
|
+
setError(`Key "${keyName}" not found`);
|
|
11893
|
+
setIsLoading(false);
|
|
11894
|
+
return;
|
|
11895
|
+
}
|
|
11896
|
+
setKey(existingKey);
|
|
11897
|
+
setIsLoading(false);
|
|
11898
|
+
if (options.force) {
|
|
11899
|
+
await performDelete(projectId, existingKey.id, api);
|
|
11900
|
+
}
|
|
11901
|
+
} catch (err) {
|
|
11902
|
+
setError(err instanceof Error ? err.message : "Failed to find key");
|
|
11903
|
+
setIsLoading(false);
|
|
11904
|
+
}
|
|
11905
|
+
};
|
|
11906
|
+
findKey();
|
|
11907
|
+
}, [keyName, options.force, options.project]);
|
|
11908
|
+
const performDelete = async (projectId, keyId, api) => {
|
|
11909
|
+
try {
|
|
11910
|
+
setIsDeleting(true);
|
|
11911
|
+
await api.deleteKey(projectId, keyId);
|
|
11912
|
+
setDeleted(true);
|
|
11913
|
+
setNeedsConfirmation(false);
|
|
11914
|
+
} catch (err) {
|
|
11915
|
+
setError(err instanceof Error ? err.message : "Failed to delete key");
|
|
11916
|
+
} finally {
|
|
11917
|
+
setIsDeleting(false);
|
|
11918
|
+
}
|
|
11919
|
+
};
|
|
11920
|
+
useInput8((input, inputKey) => {
|
|
11921
|
+
if (!needsConfirmation || isDeleting) return;
|
|
11922
|
+
if (input.toLowerCase() === "y") {
|
|
11923
|
+
const config = getProjectConfig();
|
|
11924
|
+
const projectId = options.project || config?.projectId;
|
|
11925
|
+
if (projectId && key) {
|
|
11926
|
+
const api = new KeysApi();
|
|
11927
|
+
performDelete(projectId, key.id, api);
|
|
11928
|
+
}
|
|
11929
|
+
} else if (input.toLowerCase() === "n" || inputKey.escape) {
|
|
11930
|
+
process.exit(0);
|
|
11931
|
+
}
|
|
11932
|
+
});
|
|
11933
|
+
if (error) {
|
|
11934
|
+
return /* @__PURE__ */ jsxs42(Text42, { color: "red", children: [
|
|
11935
|
+
"Error: ",
|
|
11936
|
+
error
|
|
11937
|
+
] });
|
|
11938
|
+
}
|
|
11939
|
+
if (isLoading) {
|
|
11940
|
+
return /* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11941
|
+
/* @__PURE__ */ jsx45(Text42, { color: "green", children: /* @__PURE__ */ jsx45(Spinner21, { type: "dots" }) }),
|
|
11942
|
+
" Finding key..."
|
|
11943
|
+
] });
|
|
11944
|
+
}
|
|
11945
|
+
if (isDeleting) {
|
|
11946
|
+
return /* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11947
|
+
/* @__PURE__ */ jsx45(Text42, { color: "green", children: /* @__PURE__ */ jsx45(Spinner21, { type: "dots" }) }),
|
|
11948
|
+
" Deleting key..."
|
|
11949
|
+
] });
|
|
11950
|
+
}
|
|
11951
|
+
if (deleted) {
|
|
11952
|
+
return /* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", children: [
|
|
11953
|
+
/* @__PURE__ */ jsx45(Text42, { color: "green", children: "\u2713 Key deleted successfully" }),
|
|
11954
|
+
/* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11955
|
+
"Key: ",
|
|
11956
|
+
keyName
|
|
11957
|
+
] })
|
|
11958
|
+
] });
|
|
11959
|
+
}
|
|
11960
|
+
if (needsConfirmation && key) {
|
|
11961
|
+
return /* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", children: [
|
|
11962
|
+
/* @__PURE__ */ jsx45(Box39, { marginBottom: 1, children: /* @__PURE__ */ jsx45(Text42, { color: "yellow", children: "Warning: Delete Key" }) }),
|
|
11963
|
+
/* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
11964
|
+
/* @__PURE__ */ jsx45(Text42, { children: "You are about to delete the following key:" }),
|
|
11965
|
+
/* @__PURE__ */ jsx45(Text42, { dimColor: true }),
|
|
11966
|
+
/* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11967
|
+
" Key: ",
|
|
11968
|
+
/* @__PURE__ */ jsx45(Text42, { bold: true, children: key.key })
|
|
11969
|
+
] }),
|
|
11970
|
+
/* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11971
|
+
" Namespace: ",
|
|
11972
|
+
/* @__PURE__ */ jsx45(Text42, { bold: true, children: key.namespace || "-" })
|
|
11973
|
+
] }),
|
|
11974
|
+
key.description && /* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11975
|
+
" Description: ",
|
|
11976
|
+
key.description
|
|
11977
|
+
] }),
|
|
11978
|
+
key.tags && key.tags.length > 0 && /* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11979
|
+
" Tags: ",
|
|
11980
|
+
key.tags.join(", ")
|
|
11981
|
+
] })
|
|
11982
|
+
] }),
|
|
11983
|
+
/* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
11984
|
+
/* @__PURE__ */ jsx45(Text42, { color: "red", children: "This will delete the key and all its translations." }),
|
|
11985
|
+
/* @__PURE__ */ jsx45(Text42, { dimColor: true, children: "This action cannot be undone." })
|
|
11986
|
+
] }),
|
|
11987
|
+
/* @__PURE__ */ jsx45(Box39, { marginLeft: 2, children: /* @__PURE__ */ jsxs42(Text42, { children: [
|
|
11988
|
+
"Press",
|
|
11989
|
+
" ",
|
|
11990
|
+
/* @__PURE__ */ jsx45(Text42, { bold: true, color: "green", children: "Y" }),
|
|
11991
|
+
" ",
|
|
11992
|
+
"to confirm,",
|
|
11993
|
+
" ",
|
|
11994
|
+
/* @__PURE__ */ jsx45(Text42, { bold: true, color: "red", children: "N" }),
|
|
11995
|
+
" ",
|
|
11996
|
+
"to cancel"
|
|
11997
|
+
] }) })
|
|
11998
|
+
] });
|
|
11999
|
+
}
|
|
12000
|
+
return null;
|
|
12001
|
+
};
|
|
12002
|
+
function runKeysDelete(keyName, options) {
|
|
12003
|
+
render24(/* @__PURE__ */ jsx45(KeysDelete, { keyName, options }));
|
|
12004
|
+
}
|
|
12005
|
+
|
|
12006
|
+
// src/commands/keys/bulk-delete.tsx
|
|
12007
|
+
import { useState as useState30, useEffect as useEffect27 } from "react";
|
|
12008
|
+
import { render as render25, Box as Box40, Text as Text43, useInput as useInput9 } from "ink";
|
|
12009
|
+
import Spinner22 from "ink-spinner";
|
|
12010
|
+
import { jsx as jsx46, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
12011
|
+
var KeysBulkDelete = ({ options }) => {
|
|
12012
|
+
const [keys, setKeys] = useState30([]);
|
|
12013
|
+
const [isLoading, setIsLoading] = useState30(true);
|
|
12014
|
+
const [error, setError] = useState30(null);
|
|
12015
|
+
const [needsConfirmation, setNeedsConfirmation] = useState30(!options.force);
|
|
12016
|
+
const [isDeleting, setIsDeleting] = useState30(false);
|
|
12017
|
+
const [deleted, setDeleted] = useState30(false);
|
|
12018
|
+
const [deletedCount, setDeletedCount] = useState30(0);
|
|
12019
|
+
useEffect27(() => {
|
|
12020
|
+
const findKeys = async () => {
|
|
12021
|
+
try {
|
|
12022
|
+
const config = getProjectConfig();
|
|
12023
|
+
const projectId = options.project || config?.projectId;
|
|
12024
|
+
if (!projectId) {
|
|
12025
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
12026
|
+
setIsLoading(false);
|
|
12027
|
+
return;
|
|
12028
|
+
}
|
|
12029
|
+
if (!options.query) {
|
|
12030
|
+
setError("Query is required. Use --query <search-term>");
|
|
12031
|
+
setIsLoading(false);
|
|
12032
|
+
return;
|
|
12033
|
+
}
|
|
12034
|
+
const api = new KeysApi();
|
|
12035
|
+
const keysResult = await api.listKeys(projectId, { search: options.query });
|
|
12036
|
+
if (keysResult.keys.length === 0) {
|
|
12037
|
+
setError(`No keys found matching query "${options.query}"`);
|
|
12038
|
+
setIsLoading(false);
|
|
12039
|
+
return;
|
|
12040
|
+
}
|
|
12041
|
+
setKeys(keysResult.keys);
|
|
12042
|
+
setIsLoading(false);
|
|
12043
|
+
if (options.dryRun) {
|
|
12044
|
+
return;
|
|
12045
|
+
}
|
|
12046
|
+
if (options.force) {
|
|
12047
|
+
await performBulkDelete(projectId, keysResult.keys.map((k) => k.id), api);
|
|
12048
|
+
}
|
|
12049
|
+
} catch (err) {
|
|
12050
|
+
setError(err instanceof Error ? err.message : "Failed to find keys");
|
|
12051
|
+
setIsLoading(false);
|
|
12052
|
+
}
|
|
12053
|
+
};
|
|
12054
|
+
findKeys();
|
|
12055
|
+
}, [options]);
|
|
12056
|
+
const performBulkDelete = async (projectId, keyIds, api) => {
|
|
12057
|
+
try {
|
|
12058
|
+
setIsDeleting(true);
|
|
12059
|
+
const result = await api.bulkDeleteKeys(projectId, keyIds);
|
|
12060
|
+
setDeletedCount(result.deleted);
|
|
12061
|
+
setDeleted(true);
|
|
12062
|
+
setNeedsConfirmation(false);
|
|
12063
|
+
} catch (err) {
|
|
12064
|
+
setError(err instanceof Error ? err.message : "Failed to delete keys");
|
|
12065
|
+
} finally {
|
|
12066
|
+
setIsDeleting(false);
|
|
12067
|
+
}
|
|
12068
|
+
};
|
|
12069
|
+
useInput9((input, inputKey) => {
|
|
12070
|
+
if (!needsConfirmation || isDeleting || options.dryRun) return;
|
|
12071
|
+
if (input.toLowerCase() === "y") {
|
|
12072
|
+
const config = getProjectConfig();
|
|
12073
|
+
const projectId = options.project || config?.projectId;
|
|
12074
|
+
if (projectId && keys.length > 0) {
|
|
12075
|
+
const api = new KeysApi();
|
|
12076
|
+
performBulkDelete(projectId, keys.map((k) => k.id), api);
|
|
12077
|
+
}
|
|
12078
|
+
} else if (input.toLowerCase() === "n" || inputKey.escape) {
|
|
12079
|
+
process.exit(0);
|
|
12080
|
+
}
|
|
12081
|
+
});
|
|
12082
|
+
if (error) {
|
|
12083
|
+
return /* @__PURE__ */ jsxs43(Text43, { color: "red", children: [
|
|
12084
|
+
"Error: ",
|
|
12085
|
+
error
|
|
12086
|
+
] });
|
|
12087
|
+
}
|
|
12088
|
+
if (isLoading) {
|
|
12089
|
+
return /* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12090
|
+
/* @__PURE__ */ jsx46(Text43, { color: "green", children: /* @__PURE__ */ jsx46(Spinner22, { type: "dots" }) }),
|
|
12091
|
+
" Searching for keys..."
|
|
12092
|
+
] });
|
|
12093
|
+
}
|
|
12094
|
+
if (isDeleting) {
|
|
12095
|
+
return /* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12096
|
+
/* @__PURE__ */ jsx46(Text43, { color: "green", children: /* @__PURE__ */ jsx46(Spinner22, { type: "dots" }) }),
|
|
12097
|
+
" Deleting ",
|
|
12098
|
+
keys.length,
|
|
12099
|
+
" key(s)..."
|
|
12100
|
+
] });
|
|
12101
|
+
}
|
|
12102
|
+
if (deleted) {
|
|
12103
|
+
return /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", children: [
|
|
12104
|
+
/* @__PURE__ */ jsx46(Text43, { color: "green", children: "\u2713 Bulk delete completed successfully" }),
|
|
12105
|
+
/* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12106
|
+
"Deleted ",
|
|
12107
|
+
deletedCount,
|
|
12108
|
+
" key(s)"
|
|
12109
|
+
] })
|
|
12110
|
+
] });
|
|
12111
|
+
}
|
|
12112
|
+
if (options.dryRun) {
|
|
12113
|
+
return /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", children: [
|
|
12114
|
+
/* @__PURE__ */ jsx46(Box40, { marginBottom: 1, children: /* @__PURE__ */ jsx46(Text43, { color: "cyan", children: "Dry Run Mode" }) }),
|
|
12115
|
+
/* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", marginLeft: 2, children: [
|
|
12116
|
+
/* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12117
|
+
"Found ",
|
|
12118
|
+
keys.length,
|
|
12119
|
+
' key(s) matching query "',
|
|
12120
|
+
options.query,
|
|
12121
|
+
'":'
|
|
12122
|
+
] }),
|
|
12123
|
+
/* @__PURE__ */ jsx46(Text43, { dimColor: true }),
|
|
12124
|
+
keys.slice(0, 10).map((key) => /* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12125
|
+
" \u2022 ",
|
|
12126
|
+
key.key,
|
|
12127
|
+
" (",
|
|
12128
|
+
key.namespace || "default",
|
|
12129
|
+
")"
|
|
12130
|
+
] }, key.id)),
|
|
12131
|
+
keys.length > 10 && /* @__PURE__ */ jsxs43(Text43, { dimColor: true, children: [
|
|
12132
|
+
" ... and ",
|
|
12133
|
+
keys.length - 10,
|
|
12134
|
+
" more"
|
|
12135
|
+
] })
|
|
12136
|
+
] }),
|
|
12137
|
+
/* @__PURE__ */ jsx46(Box40, { marginTop: 1, marginLeft: 2, children: /* @__PURE__ */ jsx46(Text43, { dimColor: true, children: "Run without --dry-run to delete these keys." }) })
|
|
12138
|
+
] });
|
|
12139
|
+
}
|
|
12140
|
+
if (needsConfirmation && keys.length > 0) {
|
|
12141
|
+
return /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", children: [
|
|
12142
|
+
/* @__PURE__ */ jsx46(Box40, { marginBottom: 1, children: /* @__PURE__ */ jsx46(Text43, { color: "yellow", children: "Warning: Bulk Delete" }) }),
|
|
12143
|
+
/* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
12144
|
+
/* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12145
|
+
"You are about to delete ",
|
|
12146
|
+
keys.length,
|
|
12147
|
+
' key(s) matching query "',
|
|
12148
|
+
options.query,
|
|
12149
|
+
'":'
|
|
12150
|
+
] }),
|
|
12151
|
+
/* @__PURE__ */ jsx46(Text43, { dimColor: true }),
|
|
12152
|
+
keys.slice(0, 5).map((key) => /* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12153
|
+
" \u2022 ",
|
|
12154
|
+
key.key,
|
|
12155
|
+
" (",
|
|
12156
|
+
key.namespace || "default",
|
|
12157
|
+
")"
|
|
12158
|
+
] }, key.id)),
|
|
12159
|
+
keys.length > 5 && /* @__PURE__ */ jsxs43(Text43, { dimColor: true, children: [
|
|
12160
|
+
" ... and ",
|
|
12161
|
+
keys.length - 5,
|
|
12162
|
+
" more"
|
|
12163
|
+
] })
|
|
12164
|
+
] }),
|
|
12165
|
+
/* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
12166
|
+
/* @__PURE__ */ jsx46(Text43, { color: "red", children: "This will delete all matching keys and their translations." }),
|
|
12167
|
+
/* @__PURE__ */ jsx46(Text43, { dimColor: true, children: "This action cannot be undone." })
|
|
12168
|
+
] }),
|
|
12169
|
+
/* @__PURE__ */ jsx46(Box40, { marginLeft: 2, children: /* @__PURE__ */ jsxs43(Text43, { children: [
|
|
12170
|
+
"Press",
|
|
12171
|
+
" ",
|
|
12172
|
+
/* @__PURE__ */ jsx46(Text43, { bold: true, color: "green", children: "Y" }),
|
|
12173
|
+
" ",
|
|
12174
|
+
"to confirm,",
|
|
12175
|
+
" ",
|
|
12176
|
+
/* @__PURE__ */ jsx46(Text43, { bold: true, color: "red", children: "N" }),
|
|
12177
|
+
" ",
|
|
12178
|
+
"to cancel"
|
|
12179
|
+
] }) })
|
|
12180
|
+
] });
|
|
12181
|
+
}
|
|
12182
|
+
return null;
|
|
12183
|
+
};
|
|
12184
|
+
function runKeysBulkDelete(options) {
|
|
12185
|
+
render25(/* @__PURE__ */ jsx46(KeysBulkDelete, { options }));
|
|
12186
|
+
}
|
|
12187
|
+
|
|
12188
|
+
// src/commands/keys/index.tsx
|
|
12189
|
+
var keysCommand = new Command2("keys").description("Manage translation keys");
|
|
12190
|
+
keysCommand.command("list").description("List translation keys").option("--project <id>", "Project ID").option("--namespace <namespace>", "Filter by namespace").option("--search <query>", "Search keys by name").option("--tags <tags>", "Filter by tags (comma-separated)").option("--limit <n>", "Limit results").option("--format <format>", "Output format (table|json)", "table").action((options) => {
|
|
12191
|
+
runKeysList({
|
|
12192
|
+
project: options.project,
|
|
12193
|
+
namespace: options.namespace,
|
|
12194
|
+
search: options.search,
|
|
12195
|
+
tags: options.tags,
|
|
12196
|
+
limit: options.limit,
|
|
12197
|
+
format: options.format
|
|
12198
|
+
});
|
|
12199
|
+
});
|
|
12200
|
+
keysCommand.command("create <key>").description("Create a new translation key").requiredOption("--value <text>", "Translation value").option("--project <id>", "Project ID").option("--namespace <namespace>", "Namespace (default: default)").option("--context <text>", "Context description").option("--tags <tags>", "Tags (comma-separated)").action((key, options) => {
|
|
12201
|
+
runKeysCreate(key, {
|
|
12202
|
+
value: options.value,
|
|
12203
|
+
project: options.project,
|
|
12204
|
+
namespace: options.namespace,
|
|
12205
|
+
context: options.context,
|
|
12206
|
+
tags: options.tags
|
|
12207
|
+
});
|
|
12208
|
+
});
|
|
12209
|
+
keysCommand.command("update <key>").description("Update an existing translation key").option("--project <id>", "Project ID").option("--context <text>", "Update context description").option("--tags <tags>", "Update tags (comma-separated)").action((key, options) => {
|
|
12210
|
+
runKeysUpdate(key, {
|
|
12211
|
+
project: options.project,
|
|
12212
|
+
context: options.context,
|
|
12213
|
+
tags: options.tags
|
|
12214
|
+
});
|
|
12215
|
+
});
|
|
12216
|
+
keysCommand.command("delete <key>").description("Delete a translation key").option("--project <id>", "Project ID").option("--force", "Skip confirmation prompt").action((key, options) => {
|
|
12217
|
+
runKeysDelete(key, {
|
|
12218
|
+
project: options.project,
|
|
12219
|
+
force: options.force
|
|
12220
|
+
});
|
|
12221
|
+
});
|
|
12222
|
+
keysCommand.command("bulk-delete").description("Delete multiple keys matching a query").requiredOption("--query <search>", "Search query to match keys").option("--project <id>", "Project ID").option("--dry-run", "Show what would be deleted without deleting").option("--force", "Skip confirmation prompt").action((options) => {
|
|
12223
|
+
runKeysBulkDelete({
|
|
12224
|
+
query: options.query,
|
|
12225
|
+
project: options.project,
|
|
12226
|
+
dryRun: options.dryRun,
|
|
12227
|
+
force: options.force
|
|
12228
|
+
});
|
|
12229
|
+
});
|
|
12230
|
+
|
|
12231
|
+
// src/commands/tm/index.tsx
|
|
12232
|
+
import { Command as Command3 } from "commander";
|
|
12233
|
+
|
|
12234
|
+
// src/commands/tm/apply.tsx
|
|
12235
|
+
import { useState as useState31, useEffect as useEffect28 } from "react";
|
|
12236
|
+
import { render as render26, Box as Box41, Text as Text44, Newline as Newline3 } from "ink";
|
|
12237
|
+
import Spinner23 from "ink-spinner";
|
|
12238
|
+
import { Fragment as Fragment7, jsx as jsx47, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
12239
|
+
var TMApply = ({ options }) => {
|
|
12240
|
+
const [result, setResult] = useState31(null);
|
|
12241
|
+
const [isLoading, setIsLoading] = useState31(true);
|
|
12242
|
+
const [error, setError] = useState31(null);
|
|
12243
|
+
useEffect28(() => {
|
|
12244
|
+
const applyTM = async () => {
|
|
12245
|
+
try {
|
|
12246
|
+
const config = getProjectConfig();
|
|
12247
|
+
const projectId = options.project || config?.projectId;
|
|
12248
|
+
if (!projectId) {
|
|
12249
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
12250
|
+
setIsLoading(false);
|
|
12251
|
+
return;
|
|
12252
|
+
}
|
|
12253
|
+
if (!options.language) {
|
|
12254
|
+
setError("Target language is required. Use --language <code>");
|
|
12255
|
+
setIsLoading(false);
|
|
12256
|
+
return;
|
|
12257
|
+
}
|
|
12258
|
+
const threshold2 = options.threshold ? parseInt(options.threshold, 10) : 85;
|
|
12259
|
+
const api = new TMApi();
|
|
12260
|
+
const tmResult = await api.applyTMMatches(projectId, {
|
|
12261
|
+
target_language: options.language,
|
|
12262
|
+
namespace: options.namespace,
|
|
12263
|
+
min_threshold: threshold2,
|
|
12264
|
+
dry_run: options.dryRun
|
|
12265
|
+
});
|
|
12266
|
+
setResult(tmResult);
|
|
12267
|
+
} catch (err) {
|
|
12268
|
+
setError(err instanceof Error ? err.message : "Failed to apply TM");
|
|
12269
|
+
} finally {
|
|
12270
|
+
setIsLoading(false);
|
|
12271
|
+
}
|
|
12272
|
+
};
|
|
12273
|
+
applyTM();
|
|
12274
|
+
}, [options]);
|
|
12275
|
+
if (error) {
|
|
12276
|
+
return /* @__PURE__ */ jsxs44(Text44, { color: "red", children: [
|
|
12277
|
+
"Error: ",
|
|
12278
|
+
error
|
|
12279
|
+
] });
|
|
12280
|
+
}
|
|
12281
|
+
if (isLoading) {
|
|
12282
|
+
return /* @__PURE__ */ jsxs44(Text44, { children: [
|
|
12283
|
+
/* @__PURE__ */ jsx47(Text44, { color: "green", children: /* @__PURE__ */ jsx47(Spinner23, { type: "dots" }) }),
|
|
12284
|
+
" ",
|
|
12285
|
+
options.dryRun ? "Previewing TM matches..." : "Applying TM matches..."
|
|
12286
|
+
] });
|
|
12287
|
+
}
|
|
12288
|
+
if (!result) {
|
|
12289
|
+
return /* @__PURE__ */ jsx47(Text44, { children: "No results" });
|
|
12290
|
+
}
|
|
12291
|
+
const threshold = options.threshold ? parseInt(options.threshold, 10) : 85;
|
|
12292
|
+
const qualifyingMatches = result.matches.filter((m) => m.score >= threshold);
|
|
12293
|
+
return /* @__PURE__ */ jsxs44(Box41, { flexDirection: "column", children: [
|
|
12294
|
+
/* @__PURE__ */ jsx47(Text44, { bold: true, children: options.dryRun ? "TM Match Preview" : "TM Applied" }),
|
|
12295
|
+
/* @__PURE__ */ jsx47(Newline3, {}),
|
|
12296
|
+
/* @__PURE__ */ jsxs44(Text44, { children: [
|
|
12297
|
+
"Threshold: ",
|
|
12298
|
+
threshold,
|
|
12299
|
+
"%"
|
|
12300
|
+
] }),
|
|
12301
|
+
/* @__PURE__ */ jsxs44(Text44, { children: [
|
|
12302
|
+
"Language: ",
|
|
12303
|
+
options.language
|
|
12304
|
+
] }),
|
|
12305
|
+
options.namespace && /* @__PURE__ */ jsxs44(Text44, { children: [
|
|
12306
|
+
"Namespace: ",
|
|
12307
|
+
options.namespace
|
|
12308
|
+
] }),
|
|
12309
|
+
/* @__PURE__ */ jsx47(Newline3, {}),
|
|
12310
|
+
/* @__PURE__ */ jsxs44(Text44, { children: [
|
|
12311
|
+
"Qualifying matches: ",
|
|
12312
|
+
qualifyingMatches.length
|
|
12313
|
+
] }),
|
|
12314
|
+
!options.dryRun && /* @__PURE__ */ jsxs44(Fragment7, { children: [
|
|
12315
|
+
/* @__PURE__ */ jsxs44(Text44, { color: "green", children: [
|
|
12316
|
+
"Applied: ",
|
|
12317
|
+
result.applied
|
|
12318
|
+
] }),
|
|
12319
|
+
/* @__PURE__ */ jsxs44(Text44, { color: "yellow", children: [
|
|
12320
|
+
"Skipped: ",
|
|
12321
|
+
result.skipped
|
|
12322
|
+
] })
|
|
12323
|
+
] }),
|
|
12324
|
+
/* @__PURE__ */ jsx47(Newline3, {}),
|
|
12325
|
+
qualifyingMatches.length > 0 && /* @__PURE__ */ jsxs44(Fragment7, { children: [
|
|
12326
|
+
/* @__PURE__ */ jsx47(Text44, { bold: true, children: "Matches:" }),
|
|
12327
|
+
/* @__PURE__ */ jsx47(Newline3, {}),
|
|
12328
|
+
qualifyingMatches.slice(0, 20).map((match, idx) => /* @__PURE__ */ jsxs44(Box41, { flexDirection: "column", marginBottom: 1, children: [
|
|
12329
|
+
/* @__PURE__ */ jsxs44(Text44, { children: [
|
|
12330
|
+
/* @__PURE__ */ jsx47(Text44, { color: "cyan", children: match.key }),
|
|
12331
|
+
" (",
|
|
12332
|
+
match.score,
|
|
12333
|
+
"%)"
|
|
12334
|
+
] }),
|
|
12335
|
+
/* @__PURE__ */ jsxs44(Text44, { dimColor: true, children: [
|
|
12336
|
+
" Source: ",
|
|
12337
|
+
match.source
|
|
12338
|
+
] }),
|
|
12339
|
+
/* @__PURE__ */ jsxs44(Text44, { dimColor: true, children: [
|
|
12340
|
+
" Translation: ",
|
|
12341
|
+
match.translation
|
|
12342
|
+
] }),
|
|
12343
|
+
match.applied && /* @__PURE__ */ jsx47(Text44, { color: "green", children: " \u2713 Applied" })
|
|
12344
|
+
] }, idx)),
|
|
12345
|
+
qualifyingMatches.length > 20 && /* @__PURE__ */ jsxs44(Text44, { dimColor: true, children: [
|
|
12346
|
+
"... and ",
|
|
12347
|
+
qualifyingMatches.length - 20,
|
|
12348
|
+
" more matches"
|
|
12349
|
+
] })
|
|
12350
|
+
] }),
|
|
12351
|
+
qualifyingMatches.length === 0 && /* @__PURE__ */ jsx47(Text44, { dimColor: true, children: "No matches found above threshold." })
|
|
12352
|
+
] });
|
|
12353
|
+
};
|
|
12354
|
+
function runTMApply(options) {
|
|
12355
|
+
render26(/* @__PURE__ */ jsx47(TMApply, { options }));
|
|
12356
|
+
}
|
|
12357
|
+
|
|
12358
|
+
// src/commands/tm/search.tsx
|
|
12359
|
+
import { useState as useState32, useEffect as useEffect29 } from "react";
|
|
12360
|
+
import { render as render27, Box as Box42, Text as Text45, Newline as Newline4 } from "ink";
|
|
12361
|
+
import Spinner24 from "ink-spinner";
|
|
12362
|
+
import { jsx as jsx48, jsxs as jsxs45 } from "react/jsx-runtime";
|
|
12363
|
+
var TMSearch = ({ options }) => {
|
|
12364
|
+
const [matches, setMatches] = useState32([]);
|
|
12365
|
+
const [isLoading, setIsLoading] = useState32(true);
|
|
12366
|
+
const [error, setError] = useState32(null);
|
|
12367
|
+
useEffect29(() => {
|
|
12368
|
+
const searchTM = async () => {
|
|
12369
|
+
try {
|
|
12370
|
+
if (!options.text) {
|
|
12371
|
+
setError("Search text is required");
|
|
12372
|
+
setIsLoading(false);
|
|
12373
|
+
return;
|
|
12374
|
+
}
|
|
12375
|
+
if (!options.source) {
|
|
12376
|
+
setError("Source language is required. Use --source <lang>");
|
|
12377
|
+
setIsLoading(false);
|
|
12378
|
+
return;
|
|
12379
|
+
}
|
|
12380
|
+
if (!options.target) {
|
|
12381
|
+
setError("Target language is required. Use --target <lang>");
|
|
12382
|
+
setIsLoading(false);
|
|
12383
|
+
return;
|
|
12384
|
+
}
|
|
12385
|
+
const api = new TMApi();
|
|
12386
|
+
const minQuality = options.minQuality ? parseInt(options.minQuality, 10) : 0;
|
|
12387
|
+
const result = await api.searchTM({
|
|
12388
|
+
text: options.text,
|
|
12389
|
+
source_lang: options.source,
|
|
12390
|
+
target_lang: options.target,
|
|
12391
|
+
min_quality: minQuality
|
|
12392
|
+
});
|
|
12393
|
+
setMatches(result.matches);
|
|
12394
|
+
} catch (err) {
|
|
12395
|
+
setError(err instanceof Error ? err.message : "Failed to search TM");
|
|
12396
|
+
} finally {
|
|
12397
|
+
setIsLoading(false);
|
|
12398
|
+
}
|
|
12399
|
+
};
|
|
12400
|
+
searchTM();
|
|
12401
|
+
}, [options]);
|
|
12402
|
+
if (error) {
|
|
12403
|
+
return /* @__PURE__ */ jsxs45(Text45, { color: "red", children: [
|
|
12404
|
+
"Error: ",
|
|
12405
|
+
error
|
|
12406
|
+
] });
|
|
12407
|
+
}
|
|
12408
|
+
if (isLoading) {
|
|
12409
|
+
return /* @__PURE__ */ jsxs45(Text45, { children: [
|
|
12410
|
+
/* @__PURE__ */ jsx48(Text45, { color: "green", children: /* @__PURE__ */ jsx48(Spinner24, { type: "dots" }) }),
|
|
12411
|
+
" ",
|
|
12412
|
+
"Searching Translation Memory..."
|
|
12413
|
+
] });
|
|
12414
|
+
}
|
|
12415
|
+
if (matches.length === 0) {
|
|
12416
|
+
return /* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", children: [
|
|
12417
|
+
/* @__PURE__ */ jsxs45(Text45, { children: [
|
|
12418
|
+
"No matches found for: ",
|
|
12419
|
+
/* @__PURE__ */ jsx48(Text45, { bold: true, children: options.text })
|
|
12420
|
+
] }),
|
|
12421
|
+
/* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
|
|
12422
|
+
"Source: ",
|
|
12423
|
+
options.source,
|
|
12424
|
+
" \u2192 Target: ",
|
|
12425
|
+
options.target
|
|
12426
|
+
] })
|
|
12427
|
+
] });
|
|
12428
|
+
}
|
|
12429
|
+
const sortedMatches = [...matches].sort((a, b) => b.quality - a.quality);
|
|
12430
|
+
return /* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", children: [
|
|
12431
|
+
/* @__PURE__ */ jsx48(Text45, { bold: true, children: "Translation Memory Matches" }),
|
|
12432
|
+
/* @__PURE__ */ jsx48(Newline4, {}),
|
|
12433
|
+
/* @__PURE__ */ jsxs45(Text45, { children: [
|
|
12434
|
+
"Query: ",
|
|
12435
|
+
/* @__PURE__ */ jsx48(Text45, { bold: true, children: options.text })
|
|
12436
|
+
] }),
|
|
12437
|
+
/* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
|
|
12438
|
+
options.source,
|
|
12439
|
+
" \u2192 ",
|
|
12440
|
+
options.target
|
|
12441
|
+
] }),
|
|
12442
|
+
/* @__PURE__ */ jsx48(Newline4, {}),
|
|
12443
|
+
/* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", children: [
|
|
12444
|
+
/* @__PURE__ */ jsx48(Text45, { bold: true, children: "Score Translation" }),
|
|
12445
|
+
/* @__PURE__ */ jsx48(Text45, { children: "\u2500".repeat(60) }),
|
|
12446
|
+
sortedMatches.map((match, idx) => /* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", marginBottom: 1, children: [
|
|
12447
|
+
/* @__PURE__ */ jsxs45(Text45, { children: [
|
|
12448
|
+
/* @__PURE__ */ jsxs45(Text45, { color: match.quality >= 90 ? "green" : match.quality >= 75 ? "yellow" : "white", children: [
|
|
12449
|
+
match.quality,
|
|
12450
|
+
"%"
|
|
12451
|
+
] }),
|
|
12452
|
+
" ",
|
|
12453
|
+
match.target_text
|
|
12454
|
+
] }),
|
|
12455
|
+
match.context && /* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
|
|
12456
|
+
" Context: ",
|
|
12457
|
+
match.context
|
|
12458
|
+
] })
|
|
12459
|
+
] }, idx))
|
|
12460
|
+
] }),
|
|
12461
|
+
/* @__PURE__ */ jsx48(Newline4, {}),
|
|
12462
|
+
/* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
|
|
12463
|
+
"Found ",
|
|
12464
|
+
matches.length,
|
|
12465
|
+
" match(es)"
|
|
12466
|
+
] })
|
|
12467
|
+
] });
|
|
12468
|
+
};
|
|
12469
|
+
function runTMSearch(options) {
|
|
12470
|
+
render27(/* @__PURE__ */ jsx48(TMSearch, { options }));
|
|
12471
|
+
}
|
|
12472
|
+
|
|
12473
|
+
// src/commands/tm/add.tsx
|
|
12474
|
+
import { useState as useState33, useEffect as useEffect30 } from "react";
|
|
12475
|
+
import { render as render28, Box as Box43, Text as Text46, Newline as Newline5 } from "ink";
|
|
12476
|
+
import Spinner25 from "ink-spinner";
|
|
12477
|
+
import { jsx as jsx49, jsxs as jsxs46 } from "react/jsx-runtime";
|
|
12478
|
+
var TMAdd = ({ options }) => {
|
|
12479
|
+
const [entry, setEntry] = useState33(null);
|
|
12480
|
+
const [isLoading, setIsLoading] = useState33(true);
|
|
12481
|
+
const [error, setError] = useState33(null);
|
|
12482
|
+
useEffect30(() => {
|
|
12483
|
+
const addEntry = async () => {
|
|
12484
|
+
try {
|
|
12485
|
+
if (!options.sourceText) {
|
|
12486
|
+
setError("Source text is required");
|
|
12487
|
+
setIsLoading(false);
|
|
12488
|
+
return;
|
|
12489
|
+
}
|
|
12490
|
+
if (!options.targetText) {
|
|
12491
|
+
setError("Target text is required");
|
|
12492
|
+
setIsLoading(false);
|
|
12493
|
+
return;
|
|
12494
|
+
}
|
|
12495
|
+
if (!options.sourceLang) {
|
|
12496
|
+
setError("Source language is required. Use --source-lang <lang>");
|
|
12497
|
+
setIsLoading(false);
|
|
12498
|
+
return;
|
|
12499
|
+
}
|
|
12500
|
+
if (!options.targetLang) {
|
|
12501
|
+
setError("Target language is required. Use --target-lang <lang>");
|
|
12502
|
+
setIsLoading(false);
|
|
12503
|
+
return;
|
|
12504
|
+
}
|
|
12505
|
+
const api = new TMApi();
|
|
12506
|
+
const quality = options.quality ? parseInt(options.quality, 10) : 100;
|
|
12507
|
+
const result = await api.addTMEntry({
|
|
12508
|
+
source_text: options.sourceText,
|
|
12509
|
+
target_text: options.targetText,
|
|
12510
|
+
source_lang: options.sourceLang,
|
|
12511
|
+
target_lang: options.targetLang,
|
|
12512
|
+
quality,
|
|
12513
|
+
context: options.context
|
|
12514
|
+
});
|
|
12515
|
+
setEntry(result);
|
|
12516
|
+
} catch (err) {
|
|
12517
|
+
setError(err instanceof Error ? err.message : "Failed to add TM entry");
|
|
12518
|
+
} finally {
|
|
12519
|
+
setIsLoading(false);
|
|
12520
|
+
}
|
|
12521
|
+
};
|
|
12522
|
+
addEntry();
|
|
12523
|
+
}, [options]);
|
|
12524
|
+
if (error) {
|
|
12525
|
+
return /* @__PURE__ */ jsxs46(Text46, { color: "red", children: [
|
|
12526
|
+
"Error: ",
|
|
12527
|
+
error
|
|
12528
|
+
] });
|
|
12529
|
+
}
|
|
12530
|
+
if (isLoading) {
|
|
12531
|
+
return /* @__PURE__ */ jsxs46(Text46, { children: [
|
|
12532
|
+
/* @__PURE__ */ jsx49(Text46, { color: "green", children: /* @__PURE__ */ jsx49(Spinner25, { type: "dots" }) }),
|
|
12533
|
+
" ",
|
|
12534
|
+
"Adding TM entry..."
|
|
12535
|
+
] });
|
|
12536
|
+
}
|
|
12537
|
+
if (!entry) {
|
|
12538
|
+
return /* @__PURE__ */ jsx49(Text46, { children: "No entry created" });
|
|
12539
|
+
}
|
|
12540
|
+
return /* @__PURE__ */ jsxs46(Box43, { flexDirection: "column", children: [
|
|
12541
|
+
/* @__PURE__ */ jsx49(Text46, { color: "green", children: "\u2713 Translation Memory entry added successfully" }),
|
|
12542
|
+
/* @__PURE__ */ jsx49(Newline5, {}),
|
|
12543
|
+
/* @__PURE__ */ jsxs46(Text46, { children: [
|
|
12544
|
+
/* @__PURE__ */ jsxs46(Text46, { bold: true, children: [
|
|
12545
|
+
"Source (",
|
|
12546
|
+
entry.source_lang,
|
|
12547
|
+
"):"
|
|
12548
|
+
] }),
|
|
12549
|
+
" ",
|
|
12550
|
+
entry.source_text
|
|
12551
|
+
] }),
|
|
12552
|
+
/* @__PURE__ */ jsxs46(Text46, { children: [
|
|
12553
|
+
/* @__PURE__ */ jsxs46(Text46, { bold: true, children: [
|
|
12554
|
+
"Target (",
|
|
12555
|
+
entry.target_lang,
|
|
12556
|
+
"):"
|
|
12557
|
+
] }),
|
|
12558
|
+
" ",
|
|
12559
|
+
entry.target_text
|
|
12560
|
+
] }),
|
|
12561
|
+
/* @__PURE__ */ jsxs46(Text46, { children: [
|
|
12562
|
+
/* @__PURE__ */ jsx49(Text46, { bold: true, children: "Quality:" }),
|
|
12563
|
+
" ",
|
|
12564
|
+
entry.quality,
|
|
12565
|
+
"%"
|
|
12566
|
+
] }),
|
|
12567
|
+
entry.context && /* @__PURE__ */ jsxs46(Text46, { children: [
|
|
12568
|
+
/* @__PURE__ */ jsx49(Text46, { bold: true, children: "Context:" }),
|
|
12569
|
+
" ",
|
|
12570
|
+
entry.context
|
|
12571
|
+
] }),
|
|
12572
|
+
/* @__PURE__ */ jsx49(Newline5, {}),
|
|
12573
|
+
/* @__PURE__ */ jsxs46(Text46, { dimColor: true, children: [
|
|
12574
|
+
"ID: ",
|
|
12575
|
+
entry.id
|
|
12576
|
+
] })
|
|
12577
|
+
] });
|
|
12578
|
+
};
|
|
12579
|
+
function runTMAdd(options) {
|
|
12580
|
+
render28(/* @__PURE__ */ jsx49(TMAdd, { options }));
|
|
12581
|
+
}
|
|
12582
|
+
|
|
12583
|
+
// src/commands/tm/stats.tsx
|
|
12584
|
+
import { useState as useState34, useEffect as useEffect31 } from "react";
|
|
12585
|
+
import { render as render29, Box as Box44, Text as Text47, Newline as Newline6 } from "ink";
|
|
12586
|
+
import Spinner26 from "ink-spinner";
|
|
12587
|
+
import { Fragment as Fragment8, jsx as jsx50, jsxs as jsxs47 } from "react/jsx-runtime";
|
|
12588
|
+
var TMStatsComponent = ({ options }) => {
|
|
12589
|
+
const [stats, setStats] = useState34(null);
|
|
12590
|
+
const [isLoading, setIsLoading] = useState34(true);
|
|
12591
|
+
const [error, setError] = useState34(null);
|
|
12592
|
+
useEffect31(() => {
|
|
12593
|
+
const fetchStats = async () => {
|
|
12594
|
+
try {
|
|
12595
|
+
const api = new TMApi();
|
|
12596
|
+
const result = await api.getTMStats();
|
|
12597
|
+
setStats(result);
|
|
12598
|
+
} catch (err) {
|
|
12599
|
+
setError(err instanceof Error ? err.message : "Failed to fetch TM stats");
|
|
12600
|
+
} finally {
|
|
12601
|
+
setIsLoading(false);
|
|
12602
|
+
}
|
|
12603
|
+
};
|
|
12604
|
+
fetchStats();
|
|
12605
|
+
}, [options]);
|
|
12606
|
+
if (error) {
|
|
12607
|
+
return /* @__PURE__ */ jsxs47(Text47, { color: "red", children: [
|
|
12608
|
+
"Error: ",
|
|
12609
|
+
error
|
|
12610
|
+
] });
|
|
12611
|
+
}
|
|
12612
|
+
if (isLoading) {
|
|
12613
|
+
return /* @__PURE__ */ jsxs47(Text47, { children: [
|
|
12614
|
+
/* @__PURE__ */ jsx50(Text47, { color: "green", children: /* @__PURE__ */ jsx50(Spinner26, { type: "dots" }) }),
|
|
12615
|
+
" ",
|
|
12616
|
+
"Loading Translation Memory statistics..."
|
|
12617
|
+
] });
|
|
12618
|
+
}
|
|
12619
|
+
if (!stats) {
|
|
12620
|
+
return /* @__PURE__ */ jsx50(Text47, { children: "No statistics available" });
|
|
12621
|
+
}
|
|
12622
|
+
return /* @__PURE__ */ jsxs47(Box44, { flexDirection: "column", children: [
|
|
12623
|
+
/* @__PURE__ */ jsx50(Text47, { bold: true, children: "Translation Memory Statistics" }),
|
|
12624
|
+
/* @__PURE__ */ jsx50(Newline6, {}),
|
|
12625
|
+
/* @__PURE__ */ jsxs47(Text47, { children: [
|
|
12626
|
+
/* @__PURE__ */ jsx50(Text47, { bold: true, children: "Total Entries:" }),
|
|
12627
|
+
" ",
|
|
12628
|
+
stats.total_entries.toLocaleString()
|
|
12629
|
+
] }),
|
|
12630
|
+
/* @__PURE__ */ jsxs47(Text47, { children: [
|
|
12631
|
+
/* @__PURE__ */ jsx50(Text47, { bold: true, children: "Average Quality:" }),
|
|
12632
|
+
" ",
|
|
12633
|
+
stats.avg_quality.toFixed(1),
|
|
12634
|
+
"%"
|
|
12635
|
+
] }),
|
|
12636
|
+
/* @__PURE__ */ jsx50(Newline6, {}),
|
|
12637
|
+
stats.language_pairs.length > 0 && /* @__PURE__ */ jsxs47(Fragment8, { children: [
|
|
12638
|
+
/* @__PURE__ */ jsx50(Text47, { bold: true, children: "Language Pairs:" }),
|
|
12639
|
+
/* @__PURE__ */ jsx50(Newline6, {}),
|
|
12640
|
+
/* @__PURE__ */ jsx50(Box44, { flexDirection: "column", children: stats.language_pairs.map((pair, idx) => /* @__PURE__ */ jsxs47(Text47, { children: [
|
|
12641
|
+
/* @__PURE__ */ jsxs47(Text47, { color: "cyan", children: [
|
|
12642
|
+
pair.source,
|
|
12643
|
+
" \u2192 ",
|
|
12644
|
+
pair.target
|
|
12645
|
+
] }),
|
|
12646
|
+
": ",
|
|
12647
|
+
pair.count.toLocaleString(),
|
|
12648
|
+
" ",
|
|
12649
|
+
pair.count === 1 ? "entry" : "entries"
|
|
12650
|
+
] }, idx)) })
|
|
12651
|
+
] }),
|
|
12652
|
+
stats.language_pairs.length === 0 && /* @__PURE__ */ jsx50(Text47, { dimColor: true, children: "No language pairs found. Add entries with `intlpull tm add`" })
|
|
12653
|
+
] });
|
|
12654
|
+
};
|
|
12655
|
+
function runTMStats(options) {
|
|
12656
|
+
render29(/* @__PURE__ */ jsx50(TMStatsComponent, { options }));
|
|
12657
|
+
}
|
|
12658
|
+
|
|
12659
|
+
// src/commands/tm/index.tsx
|
|
12660
|
+
var tmCommand = new Command3("tm").description("Manage Translation Memory");
|
|
12661
|
+
tmCommand.command("apply").description("Apply TM matches to project translations").option("--project <id>", "Project ID").option("--language <code>", "Target language code").option("--namespace <namespace>", "Filter by namespace").option("--threshold <n>", "Minimum match score (default: 85)", "85").option("--dry-run", "Preview matches without applying").action((options) => {
|
|
12662
|
+
runTMApply({
|
|
12663
|
+
project: options.project,
|
|
12664
|
+
language: options.language,
|
|
12665
|
+
namespace: options.namespace,
|
|
12666
|
+
threshold: options.threshold,
|
|
12667
|
+
dryRun: options.dryRun
|
|
12668
|
+
});
|
|
12669
|
+
});
|
|
12670
|
+
tmCommand.command("search <text>").description("Search Translation Memory for a specific text").requiredOption("--source <lang>", "Source language code").requiredOption("--target <lang>", "Target language code").option("--min-quality <n>", "Minimum quality score (0-100)", "0").action((text, options) => {
|
|
12671
|
+
runTMSearch({
|
|
12672
|
+
text,
|
|
12673
|
+
source: options.source,
|
|
12674
|
+
target: options.target,
|
|
12675
|
+
minQuality: options.minQuality
|
|
12676
|
+
});
|
|
12677
|
+
});
|
|
12678
|
+
tmCommand.command("add <source> <target>").description("Add a new Translation Memory entry").requiredOption("--source-lang <lang>", "Source language code").requiredOption("--target-lang <lang>", "Target language code").option("--quality <n>", "Quality score (0-100, default: 100)", "100").option("--context <text>", "Context description").action((source, target, options) => {
|
|
12679
|
+
runTMAdd({
|
|
12680
|
+
sourceText: source,
|
|
12681
|
+
targetText: target,
|
|
12682
|
+
sourceLang: options.sourceLang,
|
|
12683
|
+
targetLang: options.targetLang,
|
|
12684
|
+
quality: options.quality,
|
|
12685
|
+
context: options.context
|
|
12686
|
+
});
|
|
12687
|
+
});
|
|
12688
|
+
tmCommand.command("stats").description("Show Translation Memory statistics").action(() => {
|
|
12689
|
+
runTMStats({});
|
|
12690
|
+
});
|
|
12691
|
+
|
|
12692
|
+
// src/commands/cleanup.tsx
|
|
12693
|
+
import { useState as useState35, useEffect as useEffect32 } from "react";
|
|
12694
|
+
import { render as render30, Box as Box45, Text as Text48 } from "ink";
|
|
12695
|
+
import Spinner27 from "ink-spinner";
|
|
12696
|
+
import { jsx as jsx51, jsxs as jsxs48 } from "react/jsx-runtime";
|
|
12697
|
+
var CleanupComponent = ({ options }) => {
|
|
12698
|
+
const [state, setState] = useState35("scanning");
|
|
12699
|
+
const [orphanKeys, setOrphanKeys] = useState35([]);
|
|
12700
|
+
const [totalKeys, setTotalKeys] = useState35(0);
|
|
12701
|
+
const [deleted, setDeleted] = useState35(0);
|
|
12702
|
+
const [error, setError] = useState35(null);
|
|
12703
|
+
useEffect32(() => {
|
|
12704
|
+
async function run() {
|
|
12705
|
+
try {
|
|
12706
|
+
const resolved = getResolvedApiKey();
|
|
12707
|
+
if (!resolved?.key) {
|
|
12708
|
+
setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
|
|
12709
|
+
setState("error");
|
|
12710
|
+
return;
|
|
12711
|
+
}
|
|
12712
|
+
const config = getProjectConfig();
|
|
12713
|
+
let projectId = options.project || config?.projectId;
|
|
12714
|
+
if (!projectId) {
|
|
12715
|
+
const projectsApi = new ProjectsApi();
|
|
12716
|
+
const { projects: projects2 } = await projectsApi.listProjects();
|
|
12717
|
+
if (projects2.length === 0) {
|
|
12718
|
+
setError("No projects found. Create one first.");
|
|
12719
|
+
setState("error");
|
|
12720
|
+
return;
|
|
12721
|
+
}
|
|
12722
|
+
if (projects2.length === 1) {
|
|
12723
|
+
projectId = projects2[0].id;
|
|
12724
|
+
} else {
|
|
12725
|
+
setError(
|
|
12726
|
+
`Multiple projects found. Specify with --project:
|
|
12727
|
+
` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
|
|
12728
|
+
);
|
|
12729
|
+
setState("error");
|
|
12730
|
+
return;
|
|
12731
|
+
}
|
|
12732
|
+
}
|
|
12733
|
+
if (!projectId) {
|
|
12734
|
+
setError("Could not determine project ID");
|
|
12735
|
+
setState("error");
|
|
12736
|
+
return;
|
|
12737
|
+
}
|
|
12738
|
+
const api = new KeysApi();
|
|
12739
|
+
setState("scanning");
|
|
12740
|
+
const { keys: serverKeys } = await api.listKeys(projectId);
|
|
12741
|
+
setTotalKeys(serverKeys.length);
|
|
12742
|
+
if (serverKeys.length === 0) {
|
|
12743
|
+
setState("done");
|
|
12744
|
+
return;
|
|
12745
|
+
}
|
|
12746
|
+
let usedKeys = null;
|
|
12747
|
+
if (options.scanCodebase) {
|
|
12748
|
+
setState("comparing");
|
|
12749
|
+
usedKeys = await scanForKeys(process.cwd());
|
|
12750
|
+
}
|
|
12751
|
+
setState("comparing");
|
|
12752
|
+
const orphans = serverKeys.filter((k) => {
|
|
12753
|
+
if (usedKeys && !usedKeys.has(k.key)) {
|
|
12754
|
+
return true;
|
|
12755
|
+
}
|
|
12756
|
+
if (options.days !== void 0 && k.updated_at) {
|
|
12757
|
+
const daysSinceUpdate = (Date.now() - new Date(k.updated_at).getTime()) / (1e3 * 60 * 60 * 24);
|
|
12758
|
+
return daysSinceUpdate > options.days;
|
|
12759
|
+
}
|
|
12760
|
+
return false;
|
|
12761
|
+
});
|
|
12762
|
+
setOrphanKeys(orphans);
|
|
12763
|
+
if (options.dryRun || orphans.length === 0) {
|
|
12764
|
+
setState("done");
|
|
12765
|
+
return;
|
|
12766
|
+
}
|
|
12767
|
+
if (!options.force) {
|
|
12768
|
+
setState("confirming");
|
|
12769
|
+
if (!process.stdin.isTTY) {
|
|
12770
|
+
setError("Refusing to delete keys in non-interactive mode without --force flag");
|
|
12771
|
+
setState("error");
|
|
12772
|
+
return;
|
|
12773
|
+
}
|
|
12774
|
+
}
|
|
12775
|
+
setState("deleting");
|
|
12776
|
+
const keyIds = orphans.map((k) => k.id);
|
|
12777
|
+
const batchSize = 100;
|
|
12778
|
+
let deletedCount = 0;
|
|
12779
|
+
for (let i = 0; i < keyIds.length; i += batchSize) {
|
|
12780
|
+
const batch = keyIds.slice(i, i + batchSize);
|
|
12781
|
+
await api.bulkDeleteKeys(projectId, batch);
|
|
12782
|
+
deletedCount += batch.length;
|
|
12783
|
+
setDeleted(deletedCount);
|
|
12784
|
+
}
|
|
12785
|
+
setState("done");
|
|
12786
|
+
} catch (err) {
|
|
12787
|
+
setError(err instanceof Error ? err.message : "Failed to cleanup keys");
|
|
12788
|
+
setState("error");
|
|
12789
|
+
}
|
|
12790
|
+
}
|
|
12791
|
+
run();
|
|
12792
|
+
}, [options]);
|
|
12793
|
+
if (state === "error") {
|
|
12794
|
+
return /* @__PURE__ */ jsx51(Box45, { flexDirection: "column", children: /* @__PURE__ */ jsxs48(Text48, { color: "red", children: [
|
|
12795
|
+
"Error: ",
|
|
12796
|
+
error
|
|
12797
|
+
] }) });
|
|
12798
|
+
}
|
|
12799
|
+
if (state === "scanning") {
|
|
12800
|
+
return /* @__PURE__ */ jsxs48(Box45, { children: [
|
|
12801
|
+
/* @__PURE__ */ jsx51(Text48, { color: "green", children: /* @__PURE__ */ jsx51(Spinner27, { type: "dots" }) }),
|
|
12802
|
+
/* @__PURE__ */ jsx51(Text48, { children: " Fetching keys from server..." })
|
|
12803
|
+
] });
|
|
12804
|
+
}
|
|
12805
|
+
if (state === "comparing") {
|
|
12806
|
+
return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
|
|
12807
|
+
/* @__PURE__ */ jsxs48(Box45, { children: [
|
|
12808
|
+
/* @__PURE__ */ jsx51(Text48, { color: "green", children: /* @__PURE__ */ jsx51(Spinner27, { type: "dots" }) }),
|
|
12809
|
+
/* @__PURE__ */ jsxs48(Text48, { children: [
|
|
12810
|
+
" ",
|
|
12811
|
+
options.scanCodebase ? "Scanning codebase for key usage..." : "Analyzing keys..."
|
|
12812
|
+
] })
|
|
12813
|
+
] }),
|
|
12814
|
+
/* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
|
|
12815
|
+
" Found ",
|
|
12816
|
+
totalKeys,
|
|
12817
|
+
" total keys"
|
|
12818
|
+
] })
|
|
12819
|
+
] });
|
|
12820
|
+
}
|
|
12821
|
+
if (state === "confirming") {
|
|
12822
|
+
return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
|
|
12823
|
+
/* @__PURE__ */ jsxs48(Text48, { color: "yellow", children: [
|
|
12824
|
+
"Warning: About to delete ",
|
|
12825
|
+
orphanKeys.length,
|
|
12826
|
+
" keys"
|
|
12827
|
+
] }),
|
|
12828
|
+
/* @__PURE__ */ jsx51(Text48, { dimColor: true, children: "Press Ctrl+C to cancel or add --force flag to skip confirmation" })
|
|
12829
|
+
] });
|
|
12830
|
+
}
|
|
12831
|
+
if (state === "deleting") {
|
|
12832
|
+
return /* @__PURE__ */ jsx51(Box45, { flexDirection: "column", children: /* @__PURE__ */ jsxs48(Box45, { children: [
|
|
12833
|
+
/* @__PURE__ */ jsx51(Text48, { color: "green", children: /* @__PURE__ */ jsx51(Spinner27, { type: "dots" }) }),
|
|
12834
|
+
/* @__PURE__ */ jsxs48(Text48, { children: [
|
|
12835
|
+
" Deleting orphaned keys... (",
|
|
12836
|
+
deleted,
|
|
12837
|
+
"/",
|
|
12838
|
+
orphanKeys.length,
|
|
12839
|
+
")"
|
|
12840
|
+
] })
|
|
12841
|
+
] }) });
|
|
12842
|
+
}
|
|
12843
|
+
if (state === "done") {
|
|
12844
|
+
if (orphanKeys.length === 0) {
|
|
12845
|
+
return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
|
|
12846
|
+
/* @__PURE__ */ jsx51(Text48, { color: "green", children: "\u2713 No orphaned keys found" }),
|
|
12847
|
+
/* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
|
|
12848
|
+
" Total keys: ",
|
|
12849
|
+
totalKeys
|
|
12850
|
+
] }),
|
|
12851
|
+
options.scanCodebase && /* @__PURE__ */ jsx51(Text48, { dimColor: true, children: " All keys are used in codebase" })
|
|
12852
|
+
] });
|
|
12853
|
+
}
|
|
12854
|
+
if (options.dryRun) {
|
|
12855
|
+
return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
|
|
12856
|
+
/* @__PURE__ */ jsxs48(Text48, { color: "yellow", children: [
|
|
12857
|
+
"Dry run: Found ",
|
|
12858
|
+
orphanKeys.length,
|
|
12859
|
+
" orphaned keys"
|
|
12860
|
+
] }),
|
|
12861
|
+
/* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", marginTop: 1, children: [
|
|
12862
|
+
/* @__PURE__ */ jsx51(Text48, { bold: true, children: "Orphaned keys:" }),
|
|
12863
|
+
orphanKeys.slice(0, 20).map((k) => /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
|
|
12864
|
+
k.key,
|
|
12865
|
+
options.days !== void 0 && k.updated_at && /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
|
|
12866
|
+
" ",
|
|
12867
|
+
"(last updated:",
|
|
12868
|
+
" ",
|
|
12869
|
+
Math.floor((Date.now() - new Date(k.updated_at).getTime()) / (1e3 * 60 * 60 * 24)),
|
|
12870
|
+
" ",
|
|
12871
|
+
"days ago)"
|
|
12872
|
+
] })
|
|
12873
|
+
] }, k.id)),
|
|
12874
|
+
orphanKeys.length > 20 && /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
|
|
12875
|
+
" ... and ",
|
|
12876
|
+
orphanKeys.length - 20,
|
|
12877
|
+
" more"
|
|
12878
|
+
] })
|
|
12879
|
+
] }),
|
|
12880
|
+
/* @__PURE__ */ jsx51(Box45, { marginTop: 1, children: /* @__PURE__ */ jsx51(Text48, { dimColor: true, children: "Remove --dry-run to delete these keys" }) })
|
|
12881
|
+
] });
|
|
12882
|
+
}
|
|
12883
|
+
return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
|
|
12884
|
+
/* @__PURE__ */ jsx51(Text48, { color: "green", children: "\u2713 Cleanup complete" }),
|
|
12885
|
+
/* @__PURE__ */ jsxs48(Text48, { children: [
|
|
12886
|
+
" Deleted: ",
|
|
12887
|
+
deleted,
|
|
12888
|
+
" keys"
|
|
12889
|
+
] }),
|
|
12890
|
+
/* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
|
|
12891
|
+
" Remaining: ",
|
|
12892
|
+
totalKeys - deleted,
|
|
12893
|
+
" keys"
|
|
12894
|
+
] })
|
|
12895
|
+
] });
|
|
12896
|
+
}
|
|
12897
|
+
return null;
|
|
12898
|
+
};
|
|
12899
|
+
function runCleanup(options) {
|
|
12900
|
+
render30(/* @__PURE__ */ jsx51(CleanupComponent, { options }));
|
|
12901
|
+
}
|
|
12902
|
+
|
|
12903
|
+
// src/commands/snapshots/index.tsx
|
|
12904
|
+
import { Command as Command4 } from "commander";
|
|
12905
|
+
|
|
12906
|
+
// src/commands/snapshots/create.tsx
|
|
12907
|
+
import { useState as useState36, useEffect as useEffect33 } from "react";
|
|
12908
|
+
import { render as render31, Box as Box46, Text as Text49 } from "ink";
|
|
12909
|
+
import Spinner28 from "ink-spinner";
|
|
12910
|
+
import { jsx as jsx52, jsxs as jsxs49 } from "react/jsx-runtime";
|
|
12911
|
+
var SnapshotsCreate = ({ options }) => {
|
|
12912
|
+
const [result, setResult] = useState36(null);
|
|
12913
|
+
const [isLoading, setIsLoading] = useState36(true);
|
|
12914
|
+
const [error, setError] = useState36(null);
|
|
12915
|
+
useEffect33(() => {
|
|
12916
|
+
const createSnapshot = async () => {
|
|
12917
|
+
try {
|
|
12918
|
+
const resolved = getResolvedApiKey();
|
|
12919
|
+
if (!resolved?.key) {
|
|
12920
|
+
setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
|
|
12921
|
+
setIsLoading(false);
|
|
12922
|
+
return;
|
|
12923
|
+
}
|
|
12924
|
+
const config = getProjectConfig();
|
|
12925
|
+
let projectId = options.project || config?.projectId;
|
|
12926
|
+
if (!projectId) {
|
|
12927
|
+
const projectsApi = new ProjectsApi();
|
|
12928
|
+
const { projects: projects2 } = await projectsApi.listProjects();
|
|
12929
|
+
if (projects2.length === 0) {
|
|
12930
|
+
setError("No projects found. Create one first.");
|
|
12931
|
+
setIsLoading(false);
|
|
12932
|
+
return;
|
|
12933
|
+
}
|
|
12934
|
+
if (projects2.length === 1) {
|
|
12935
|
+
projectId = projects2[0].id;
|
|
12936
|
+
} else {
|
|
12937
|
+
setError(
|
|
12938
|
+
`Multiple projects found. Specify with --project:
|
|
12939
|
+
` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
|
|
12940
|
+
);
|
|
12941
|
+
setIsLoading(false);
|
|
12942
|
+
return;
|
|
12943
|
+
}
|
|
12944
|
+
}
|
|
12945
|
+
if (!projectId) {
|
|
12946
|
+
setError("Could not determine project ID");
|
|
12947
|
+
setIsLoading(false);
|
|
12948
|
+
return;
|
|
12949
|
+
}
|
|
12950
|
+
const api = new SnapshotsApi();
|
|
12951
|
+
const snapshot = await api.createSnapshot(projectId, {
|
|
12952
|
+
name: options.name,
|
|
12953
|
+
description: options.description
|
|
12954
|
+
});
|
|
12955
|
+
setResult(snapshot);
|
|
12956
|
+
} catch (err) {
|
|
12957
|
+
setError(err instanceof Error ? err.message : "Failed to create snapshot");
|
|
12958
|
+
} finally {
|
|
12959
|
+
setIsLoading(false);
|
|
12960
|
+
}
|
|
12961
|
+
};
|
|
12962
|
+
createSnapshot();
|
|
12963
|
+
}, [options]);
|
|
12964
|
+
if (error) {
|
|
12965
|
+
return /* @__PURE__ */ jsxs49(Text49, { color: "red", children: [
|
|
12966
|
+
"Error: ",
|
|
12967
|
+
error
|
|
12968
|
+
] });
|
|
12969
|
+
}
|
|
12970
|
+
if (isLoading) {
|
|
12971
|
+
return /* @__PURE__ */ jsxs49(Box46, { children: [
|
|
12972
|
+
/* @__PURE__ */ jsx52(Text49, { color: "green", children: /* @__PURE__ */ jsx52(Spinner28, { type: "dots" }) }),
|
|
12973
|
+
/* @__PURE__ */ jsx52(Text49, { children: " Creating snapshot..." })
|
|
12974
|
+
] });
|
|
12975
|
+
}
|
|
12976
|
+
if (!result) {
|
|
12977
|
+
return /* @__PURE__ */ jsx52(Text49, { color: "red", children: "Failed to create snapshot" });
|
|
12978
|
+
}
|
|
12979
|
+
return /* @__PURE__ */ jsxs49(Box46, { flexDirection: "column", children: [
|
|
12980
|
+
/* @__PURE__ */ jsx52(Text49, { color: "green", children: "\u2713 Snapshot created successfully" }),
|
|
12981
|
+
/* @__PURE__ */ jsxs49(Text49, { children: [
|
|
12982
|
+
" ID: ",
|
|
12983
|
+
result.id
|
|
12984
|
+
] }),
|
|
12985
|
+
/* @__PURE__ */ jsxs49(Text49, { children: [
|
|
12986
|
+
" Name: ",
|
|
12987
|
+
result.name
|
|
12988
|
+
] }),
|
|
12989
|
+
result.description && /* @__PURE__ */ jsxs49(Text49, { children: [
|
|
12990
|
+
" Description: ",
|
|
12991
|
+
result.description
|
|
12992
|
+
] }),
|
|
12993
|
+
/* @__PURE__ */ jsxs49(Text49, { children: [
|
|
12994
|
+
" Keys: ",
|
|
12995
|
+
result.keys_count.toLocaleString()
|
|
12996
|
+
] }),
|
|
12997
|
+
/* @__PURE__ */ jsxs49(Text49, { children: [
|
|
12998
|
+
" Languages: ",
|
|
12999
|
+
result.languages_count
|
|
13000
|
+
] }),
|
|
13001
|
+
/* @__PURE__ */ jsxs49(Text49, { dimColor: true, children: [
|
|
13002
|
+
" Created: ",
|
|
13003
|
+
new Date(result.created_at).toLocaleString()
|
|
13004
|
+
] })
|
|
13005
|
+
] });
|
|
13006
|
+
};
|
|
13007
|
+
function runSnapshotsCreate(options) {
|
|
13008
|
+
render31(/* @__PURE__ */ jsx52(SnapshotsCreate, { options }));
|
|
13009
|
+
}
|
|
13010
|
+
|
|
13011
|
+
// src/commands/snapshots/list.tsx
|
|
13012
|
+
import { useState as useState37, useEffect as useEffect34 } from "react";
|
|
13013
|
+
import { render as render32, Box as Box47, Text as Text50 } from "ink";
|
|
13014
|
+
import Spinner29 from "ink-spinner";
|
|
13015
|
+
import { jsx as jsx53, jsxs as jsxs50 } from "react/jsx-runtime";
|
|
13016
|
+
var SnapshotsList = ({ options }) => {
|
|
13017
|
+
const [snapshots, setSnapshots] = useState37([]);
|
|
13018
|
+
const [isLoading, setIsLoading] = useState37(true);
|
|
13019
|
+
const [error, setError] = useState37(null);
|
|
13020
|
+
useEffect34(() => {
|
|
13021
|
+
const listSnapshots = async () => {
|
|
13022
|
+
try {
|
|
13023
|
+
const resolved = getResolvedApiKey();
|
|
13024
|
+
if (!resolved?.key) {
|
|
13025
|
+
setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
|
|
13026
|
+
setIsLoading(false);
|
|
13027
|
+
return;
|
|
13028
|
+
}
|
|
13029
|
+
const config = getProjectConfig();
|
|
13030
|
+
let projectId = options.project || config?.projectId;
|
|
13031
|
+
if (!projectId) {
|
|
13032
|
+
const projectsApi = new ProjectsApi();
|
|
13033
|
+
const { projects: projects2 } = await projectsApi.listProjects();
|
|
13034
|
+
if (projects2.length === 0) {
|
|
13035
|
+
setError("No projects found");
|
|
13036
|
+
setIsLoading(false);
|
|
13037
|
+
return;
|
|
13038
|
+
}
|
|
13039
|
+
if (projects2.length === 1) {
|
|
13040
|
+
projectId = projects2[0].id;
|
|
13041
|
+
} else {
|
|
13042
|
+
setError(
|
|
13043
|
+
`Multiple projects found. Specify with --project:
|
|
13044
|
+
` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
|
|
13045
|
+
);
|
|
13046
|
+
setIsLoading(false);
|
|
13047
|
+
return;
|
|
13048
|
+
}
|
|
13049
|
+
}
|
|
13050
|
+
if (!projectId) {
|
|
13051
|
+
setError("Could not determine project ID");
|
|
13052
|
+
setIsLoading(false);
|
|
13053
|
+
return;
|
|
13054
|
+
}
|
|
13055
|
+
const api = new SnapshotsApi();
|
|
13056
|
+
const result = await api.listSnapshots(projectId);
|
|
13057
|
+
setSnapshots(result);
|
|
13058
|
+
} catch (err) {
|
|
13059
|
+
setError(err instanceof Error ? err.message : "Failed to list snapshots");
|
|
13060
|
+
} finally {
|
|
13061
|
+
setIsLoading(false);
|
|
13062
|
+
}
|
|
13063
|
+
};
|
|
13064
|
+
listSnapshots();
|
|
13065
|
+
}, [options]);
|
|
13066
|
+
if (error) {
|
|
13067
|
+
return /* @__PURE__ */ jsxs50(Text50, { color: "red", children: [
|
|
13068
|
+
"Error: ",
|
|
13069
|
+
error
|
|
13070
|
+
] });
|
|
13071
|
+
}
|
|
13072
|
+
if (isLoading) {
|
|
13073
|
+
return /* @__PURE__ */ jsxs50(Box47, { children: [
|
|
13074
|
+
/* @__PURE__ */ jsx53(Text50, { color: "green", children: /* @__PURE__ */ jsx53(Spinner29, { type: "dots" }) }),
|
|
13075
|
+
/* @__PURE__ */ jsx53(Text50, { children: " Loading snapshots..." })
|
|
13076
|
+
] });
|
|
13077
|
+
}
|
|
13078
|
+
if (snapshots.length === 0) {
|
|
13079
|
+
return /* @__PURE__ */ jsxs50(Box47, { flexDirection: "column", children: [
|
|
13080
|
+
/* @__PURE__ */ jsx53(Text50, { children: "No snapshots found" }),
|
|
13081
|
+
/* @__PURE__ */ jsx53(Text50, { dimColor: true, children: 'Create one with: intlpull snapshots create --name="my-snapshot"' })
|
|
13082
|
+
] });
|
|
13083
|
+
}
|
|
13084
|
+
const formatDate2 = (date) => {
|
|
13085
|
+
const d = new Date(date);
|
|
13086
|
+
const now = /* @__PURE__ */ new Date();
|
|
13087
|
+
const diffMs = now.getTime() - d.getTime();
|
|
13088
|
+
const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
13089
|
+
if (diffDays === 0) {
|
|
13090
|
+
const diffHours = Math.floor(diffMs / (1e3 * 60 * 60));
|
|
13091
|
+
if (diffHours === 0) {
|
|
13092
|
+
const diffMins = Math.floor(diffMs / (1e3 * 60));
|
|
13093
|
+
return `${diffMins} min${diffMins === 1 ? "" : "s"} ago`;
|
|
13094
|
+
}
|
|
13095
|
+
return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
|
|
13096
|
+
}
|
|
13097
|
+
if (diffDays < 7) {
|
|
13098
|
+
return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
|
|
13099
|
+
}
|
|
13100
|
+
return d.toLocaleDateString();
|
|
13101
|
+
};
|
|
13102
|
+
return /* @__PURE__ */ jsxs50(Box47, { flexDirection: "column", children: [
|
|
13103
|
+
/* @__PURE__ */ jsxs50(Text50, { bold: true, children: [
|
|
13104
|
+
"Snapshots (",
|
|
13105
|
+
snapshots.length,
|
|
13106
|
+
")"
|
|
13107
|
+
] }),
|
|
13108
|
+
/* @__PURE__ */ jsxs50(Text50, { dimColor: true, children: [
|
|
13109
|
+
"ID",
|
|
13110
|
+
" ".repeat(34),
|
|
13111
|
+
"Name",
|
|
13112
|
+
" ".repeat(26),
|
|
13113
|
+
"Created",
|
|
13114
|
+
" ".repeat(10),
|
|
13115
|
+
"Keys",
|
|
13116
|
+
" ".repeat(5),
|
|
13117
|
+
"Languages"
|
|
13118
|
+
] }),
|
|
13119
|
+
snapshots.map((s) => /* @__PURE__ */ jsxs50(Text50, { children: [
|
|
13120
|
+
s.id.substring(0, 8),
|
|
13121
|
+
"...",
|
|
13122
|
+
" ".repeat(25),
|
|
13123
|
+
s.name.substring(0, 25).padEnd(30),
|
|
13124
|
+
formatDate2(s.created_at).padEnd(17),
|
|
13125
|
+
s.keys_count.toLocaleString().padEnd(9),
|
|
13126
|
+
s.languages_count
|
|
13127
|
+
] }, s.id))
|
|
13128
|
+
] });
|
|
13129
|
+
};
|
|
13130
|
+
function runSnapshotsList(options) {
|
|
13131
|
+
render32(/* @__PURE__ */ jsx53(SnapshotsList, { options }));
|
|
13132
|
+
}
|
|
13133
|
+
|
|
13134
|
+
// src/commands/snapshots/restore.tsx
|
|
13135
|
+
import { useState as useState38, useEffect as useEffect35 } from "react";
|
|
13136
|
+
import { render as render33, Box as Box48, Text as Text51, useInput as useInput10 } from "ink";
|
|
13137
|
+
import Spinner30 from "ink-spinner";
|
|
13138
|
+
import { jsx as jsx54, jsxs as jsxs51 } from "react/jsx-runtime";
|
|
13139
|
+
var SnapshotsRestore = ({ options }) => {
|
|
13140
|
+
const [result, setResult] = useState38(null);
|
|
13141
|
+
const [isLoading, setIsLoading] = useState38(true);
|
|
13142
|
+
const [error, setError] = useState38(null);
|
|
13143
|
+
const [awaitingConfirmation, setAwaitingConfirmation] = useState38(false);
|
|
13144
|
+
const [projectId, setProjectId] = useState38(null);
|
|
13145
|
+
useInput10((input, key) => {
|
|
13146
|
+
if (!awaitingConfirmation) return;
|
|
13147
|
+
if (input.toLowerCase() === "y") {
|
|
13148
|
+
setAwaitingConfirmation(false);
|
|
13149
|
+
setIsLoading(true);
|
|
13150
|
+
performRestore();
|
|
13151
|
+
} else if (input.toLowerCase() === "n" || key.escape) {
|
|
13152
|
+
setError("Restore cancelled by user");
|
|
13153
|
+
setAwaitingConfirmation(false);
|
|
13154
|
+
}
|
|
13155
|
+
});
|
|
13156
|
+
const performRestore = async () => {
|
|
13157
|
+
if (!projectId) return;
|
|
13158
|
+
try {
|
|
13159
|
+
const api = new SnapshotsApi();
|
|
13160
|
+
const response = await api.restoreSnapshot(projectId, options.snapshotId);
|
|
13161
|
+
setResult(response);
|
|
13162
|
+
} catch (err) {
|
|
13163
|
+
setError(err instanceof Error ? err.message : "Failed to restore snapshot");
|
|
13164
|
+
} finally {
|
|
13165
|
+
setIsLoading(false);
|
|
13166
|
+
}
|
|
13167
|
+
};
|
|
13168
|
+
useEffect35(() => {
|
|
13169
|
+
const initRestore = async () => {
|
|
13170
|
+
try {
|
|
13171
|
+
const resolved = getResolvedApiKey();
|
|
13172
|
+
if (!resolved?.key) {
|
|
13173
|
+
setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
|
|
13174
|
+
setIsLoading(false);
|
|
13175
|
+
return;
|
|
13176
|
+
}
|
|
13177
|
+
const config = getProjectConfig();
|
|
13178
|
+
let resolvedProjectId = options.project || config?.projectId;
|
|
13179
|
+
if (!resolvedProjectId) {
|
|
13180
|
+
const projectsApi = new ProjectsApi();
|
|
13181
|
+
const { projects: projects2 } = await projectsApi.listProjects();
|
|
13182
|
+
if (projects2.length === 0) {
|
|
13183
|
+
setError("No projects found");
|
|
13184
|
+
setIsLoading(false);
|
|
13185
|
+
return;
|
|
13186
|
+
}
|
|
13187
|
+
if (projects2.length === 1) {
|
|
13188
|
+
resolvedProjectId = projects2[0].id;
|
|
13189
|
+
} else {
|
|
13190
|
+
setError(
|
|
13191
|
+
`Multiple projects found. Specify with --project:
|
|
13192
|
+
` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
|
|
13193
|
+
);
|
|
13194
|
+
setIsLoading(false);
|
|
13195
|
+
return;
|
|
13196
|
+
}
|
|
13197
|
+
}
|
|
13198
|
+
if (!resolvedProjectId) {
|
|
13199
|
+
setError("Could not determine project ID");
|
|
13200
|
+
setIsLoading(false);
|
|
13201
|
+
return;
|
|
13202
|
+
}
|
|
13203
|
+
setProjectId(resolvedProjectId);
|
|
13204
|
+
if (!options.force && !process.stdin.isTTY) {
|
|
13205
|
+
setError("Refusing to restore in non-interactive mode without --force flag");
|
|
13206
|
+
setIsLoading(false);
|
|
13207
|
+
return;
|
|
13208
|
+
}
|
|
13209
|
+
if (options.force) {
|
|
13210
|
+
const api = new SnapshotsApi();
|
|
13211
|
+
const response = await api.restoreSnapshot(resolvedProjectId, options.snapshotId);
|
|
13212
|
+
setResult(response);
|
|
13213
|
+
setIsLoading(false);
|
|
13214
|
+
} else {
|
|
13215
|
+
setIsLoading(false);
|
|
13216
|
+
setAwaitingConfirmation(true);
|
|
13217
|
+
}
|
|
13218
|
+
} catch (err) {
|
|
13219
|
+
setError(err instanceof Error ? err.message : "Failed to restore snapshot");
|
|
13220
|
+
setIsLoading(false);
|
|
13221
|
+
}
|
|
13222
|
+
};
|
|
13223
|
+
initRestore();
|
|
13224
|
+
}, [options]);
|
|
13225
|
+
if (error) {
|
|
13226
|
+
return /* @__PURE__ */ jsxs51(Text51, { color: "red", children: [
|
|
13227
|
+
"Error: ",
|
|
13228
|
+
error
|
|
13229
|
+
] });
|
|
13230
|
+
}
|
|
13231
|
+
if (awaitingConfirmation) {
|
|
13232
|
+
return /* @__PURE__ */ jsxs51(Box48, { flexDirection: "column", children: [
|
|
13233
|
+
/* @__PURE__ */ jsx54(Text51, { color: "yellow", children: "\u26A0 WARNING: This will overwrite the current project state!" }),
|
|
13234
|
+
/* @__PURE__ */ jsxs51(Text51, { children: [
|
|
13235
|
+
"Snapshot ID: ",
|
|
13236
|
+
options.snapshotId
|
|
13237
|
+
] }),
|
|
13238
|
+
/* @__PURE__ */ jsx54(Box48, { marginTop: 1, children: /* @__PURE__ */ jsx54(Text51, { bold: true, children: "Are you sure you want to restore this snapshot? (Y/N): " }) })
|
|
13239
|
+
] });
|
|
13240
|
+
}
|
|
13241
|
+
if (isLoading) {
|
|
13242
|
+
return /* @__PURE__ */ jsxs51(Box48, { children: [
|
|
13243
|
+
/* @__PURE__ */ jsx54(Text51, { color: "green", children: /* @__PURE__ */ jsx54(Spinner30, { type: "dots" }) }),
|
|
13244
|
+
/* @__PURE__ */ jsx54(Text51, { children: " Restoring snapshot..." })
|
|
13245
|
+
] });
|
|
13246
|
+
}
|
|
13247
|
+
if (!result) {
|
|
13248
|
+
return /* @__PURE__ */ jsx54(Text51, { color: "red", children: "Failed to restore snapshot" });
|
|
13249
|
+
}
|
|
13250
|
+
return /* @__PURE__ */ jsxs51(Box48, { flexDirection: "column", children: [
|
|
13251
|
+
/* @__PURE__ */ jsx54(Text51, { color: "green", children: "\u2713 Snapshot restored successfully" }),
|
|
13252
|
+
/* @__PURE__ */ jsxs51(Text51, { children: [
|
|
13253
|
+
" Restored keys: ",
|
|
13254
|
+
result.restored_keys.toLocaleString()
|
|
13255
|
+
] }),
|
|
13256
|
+
/* @__PURE__ */ jsxs51(Text51, { children: [
|
|
13257
|
+
" Restored translations: ",
|
|
13258
|
+
result.restored_translations.toLocaleString()
|
|
13259
|
+
] }),
|
|
13260
|
+
/* @__PURE__ */ jsx54(Box48, { marginTop: 1, children: /* @__PURE__ */ jsx54(Text51, { dimColor: true, children: "Note: This operation overwrites current project state" }) })
|
|
13261
|
+
] });
|
|
13262
|
+
};
|
|
13263
|
+
function runSnapshotsRestore(options) {
|
|
13264
|
+
render33(/* @__PURE__ */ jsx54(SnapshotsRestore, { options }));
|
|
13265
|
+
}
|
|
13266
|
+
|
|
13267
|
+
// src/commands/snapshots/delete.tsx
|
|
13268
|
+
import { useState as useState39, useEffect as useEffect36 } from "react";
|
|
13269
|
+
import { render as render34, Box as Box49, Text as Text52 } from "ink";
|
|
13270
|
+
import Spinner31 from "ink-spinner";
|
|
13271
|
+
import { jsx as jsx55, jsxs as jsxs52 } from "react/jsx-runtime";
|
|
13272
|
+
var SnapshotsDelete = ({ options }) => {
|
|
13273
|
+
const [isLoading, setIsLoading] = useState39(true);
|
|
13274
|
+
const [error, setError] = useState39(null);
|
|
13275
|
+
const [success, setSuccess] = useState39(false);
|
|
13276
|
+
useEffect36(() => {
|
|
13277
|
+
const deleteSnapshot = async () => {
|
|
13278
|
+
try {
|
|
13279
|
+
const resolved = getResolvedApiKey();
|
|
13280
|
+
if (!resolved?.key) {
|
|
13281
|
+
setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
|
|
13282
|
+
setIsLoading(false);
|
|
13283
|
+
return;
|
|
13284
|
+
}
|
|
13285
|
+
const config = getProjectConfig();
|
|
13286
|
+
let projectId = options.project || config?.projectId;
|
|
13287
|
+
if (!projectId) {
|
|
13288
|
+
const projectsApi = new ProjectsApi();
|
|
13289
|
+
const { projects: projects2 } = await projectsApi.listProjects();
|
|
13290
|
+
if (projects2.length === 0) {
|
|
13291
|
+
setError("No projects found");
|
|
13292
|
+
setIsLoading(false);
|
|
13293
|
+
return;
|
|
13294
|
+
}
|
|
13295
|
+
if (projects2.length === 1) {
|
|
13296
|
+
projectId = projects2[0].id;
|
|
13297
|
+
} else {
|
|
13298
|
+
setError(
|
|
13299
|
+
`Multiple projects found. Specify with --project:
|
|
13300
|
+
` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
|
|
13301
|
+
);
|
|
13302
|
+
setIsLoading(false);
|
|
13303
|
+
return;
|
|
13304
|
+
}
|
|
13305
|
+
}
|
|
13306
|
+
if (!projectId) {
|
|
13307
|
+
setError("Could not determine project ID");
|
|
13308
|
+
setIsLoading(false);
|
|
13309
|
+
return;
|
|
13310
|
+
}
|
|
13311
|
+
if (!options.force && !process.stdin.isTTY) {
|
|
13312
|
+
setError("Refusing to delete in non-interactive mode without --force flag");
|
|
13313
|
+
setIsLoading(false);
|
|
13314
|
+
return;
|
|
13315
|
+
}
|
|
13316
|
+
const api = new SnapshotsApi();
|
|
13317
|
+
await api.deleteSnapshot(projectId, options.snapshotId);
|
|
13318
|
+
setSuccess(true);
|
|
13319
|
+
} catch (err) {
|
|
13320
|
+
setError(err instanceof Error ? err.message : "Failed to delete snapshot");
|
|
13321
|
+
} finally {
|
|
13322
|
+
setIsLoading(false);
|
|
13323
|
+
}
|
|
13324
|
+
};
|
|
13325
|
+
deleteSnapshot();
|
|
13326
|
+
}, [options]);
|
|
13327
|
+
if (error) {
|
|
13328
|
+
return /* @__PURE__ */ jsxs52(Text52, { color: "red", children: [
|
|
13329
|
+
"Error: ",
|
|
13330
|
+
error
|
|
13331
|
+
] });
|
|
13332
|
+
}
|
|
13333
|
+
if (isLoading) {
|
|
13334
|
+
return /* @__PURE__ */ jsxs52(Box49, { children: [
|
|
13335
|
+
/* @__PURE__ */ jsx55(Text52, { color: "green", children: /* @__PURE__ */ jsx55(Spinner31, { type: "dots" }) }),
|
|
13336
|
+
/* @__PURE__ */ jsx55(Text52, { children: " Deleting snapshot..." })
|
|
13337
|
+
] });
|
|
13338
|
+
}
|
|
13339
|
+
if (success) {
|
|
13340
|
+
return /* @__PURE__ */ jsx55(Text52, { color: "green", children: "\u2713 Snapshot deleted successfully" });
|
|
13341
|
+
}
|
|
13342
|
+
return /* @__PURE__ */ jsx55(Text52, { color: "red", children: "Failed to delete snapshot" });
|
|
13343
|
+
};
|
|
13344
|
+
function runSnapshotsDelete(options) {
|
|
13345
|
+
render34(/* @__PURE__ */ jsx55(SnapshotsDelete, { options }));
|
|
13346
|
+
}
|
|
13347
|
+
|
|
13348
|
+
// src/commands/snapshots/index.tsx
|
|
13349
|
+
var snapshotsCommand = new Command4("snapshots").description("Manage project snapshots (capture and restore project state)");
|
|
13350
|
+
snapshotsCommand.command("create").description("Create a snapshot of current project state").requiredOption("--name <name>", "Snapshot name").option("--description <desc>", "Snapshot description").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").action((options) => {
|
|
13351
|
+
runSnapshotsCreate({
|
|
13352
|
+
name: options.name,
|
|
13353
|
+
description: options.description,
|
|
13354
|
+
project: options.project
|
|
13355
|
+
});
|
|
13356
|
+
});
|
|
13357
|
+
snapshotsCommand.command("list").description("List all snapshots for a project").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").action((options) => {
|
|
13358
|
+
runSnapshotsList({
|
|
13359
|
+
project: options.project
|
|
13360
|
+
});
|
|
13361
|
+
});
|
|
13362
|
+
snapshotsCommand.command("restore <snapshotId>").description("Restore project to snapshot state").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("-f, --force", "Skip confirmation prompt").action((snapshotId, options) => {
|
|
13363
|
+
runSnapshotsRestore({
|
|
13364
|
+
snapshotId,
|
|
13365
|
+
project: options.project,
|
|
13366
|
+
force: options.force
|
|
13367
|
+
});
|
|
13368
|
+
});
|
|
13369
|
+
snapshotsCommand.command("delete <snapshotId>").description("Delete a snapshot").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("-f, --force", "Skip confirmation prompt").action((snapshotId, options) => {
|
|
13370
|
+
runSnapshotsDelete({
|
|
13371
|
+
snapshotId,
|
|
13372
|
+
project: options.project,
|
|
13373
|
+
force: options.force
|
|
13374
|
+
});
|
|
13375
|
+
});
|
|
13376
|
+
|
|
13377
|
+
// src/commands/webhooks/index.tsx
|
|
13378
|
+
import { Command as Command5 } from "commander";
|
|
13379
|
+
|
|
13380
|
+
// src/commands/webhooks/list.tsx
|
|
13381
|
+
import { useState as useState40, useEffect as useEffect37 } from "react";
|
|
13382
|
+
import { render as render35, Box as Box50, Text as Text53, Newline as Newline7 } from "ink";
|
|
13383
|
+
import Spinner32 from "ink-spinner";
|
|
13384
|
+
import { jsx as jsx56, jsxs as jsxs53 } from "react/jsx-runtime";
|
|
13385
|
+
function SimpleTable3({ data }) {
|
|
13386
|
+
if (data.length === 0) return null;
|
|
13387
|
+
const headers = Object.keys(data[0]);
|
|
13388
|
+
const colWidths = headers.map(
|
|
13389
|
+
(h) => Math.max(h.length, ...data.map((row) => String(row[h] || "").length))
|
|
13390
|
+
);
|
|
13391
|
+
const separator = "\u2500".repeat(colWidths.reduce((a, b) => a + b + 3, 1));
|
|
13392
|
+
return /* @__PURE__ */ jsxs53(Box50, { flexDirection: "column", children: [
|
|
13393
|
+
/* @__PURE__ */ jsx56(Text53, { children: separator }),
|
|
13394
|
+
/* @__PURE__ */ jsxs53(Text53, { children: [
|
|
13395
|
+
"\u2502 ",
|
|
13396
|
+
headers.map((h, i) => h.padEnd(colWidths[i])).join(" \u2502 "),
|
|
13397
|
+
" \u2502"
|
|
13398
|
+
] }),
|
|
13399
|
+
/* @__PURE__ */ jsx56(Text53, { children: separator }),
|
|
13400
|
+
data.map((row, rowIdx) => /* @__PURE__ */ jsxs53(Text53, { children: [
|
|
13401
|
+
"\u2502 ",
|
|
13402
|
+
headers.map((h, i) => String(row[h] || "").padEnd(colWidths[i])).join(" \u2502 "),
|
|
13403
|
+
" \u2502"
|
|
13404
|
+
] }, rowIdx)),
|
|
13405
|
+
/* @__PURE__ */ jsx56(Text53, { children: separator })
|
|
13406
|
+
] });
|
|
13407
|
+
}
|
|
13408
|
+
var WebhooksList = ({ options }) => {
|
|
13409
|
+
const [webhooks, setWebhooks] = useState40([]);
|
|
13410
|
+
const [isLoading, setIsLoading] = useState40(true);
|
|
13411
|
+
const [error, setError] = useState40(null);
|
|
13412
|
+
useEffect37(() => {
|
|
13413
|
+
const fetchWebhooks = async () => {
|
|
13414
|
+
try {
|
|
13415
|
+
const config = getProjectConfig();
|
|
13416
|
+
const projectId = options.project || config?.projectId;
|
|
13417
|
+
if (!projectId) {
|
|
13418
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
13419
|
+
setIsLoading(false);
|
|
13420
|
+
return;
|
|
13421
|
+
}
|
|
13422
|
+
const api = new WebhooksApi();
|
|
13423
|
+
const result = await api.listWebhooks(projectId);
|
|
13424
|
+
setWebhooks(result.webhooks);
|
|
13425
|
+
} catch (err) {
|
|
13426
|
+
setError(err instanceof Error ? err.message : "Failed to fetch webhooks");
|
|
13427
|
+
} finally {
|
|
13428
|
+
setIsLoading(false);
|
|
13429
|
+
}
|
|
13430
|
+
};
|
|
13431
|
+
fetchWebhooks();
|
|
13432
|
+
}, [options]);
|
|
13433
|
+
if (error) {
|
|
13434
|
+
return /* @__PURE__ */ jsxs53(Text53, { color: "red", children: [
|
|
13435
|
+
"Error: ",
|
|
13436
|
+
error
|
|
13437
|
+
] });
|
|
13438
|
+
}
|
|
13439
|
+
if (isLoading) {
|
|
13440
|
+
return /* @__PURE__ */ jsxs53(Text53, { children: [
|
|
13441
|
+
/* @__PURE__ */ jsx56(Text53, { color: "green", children: /* @__PURE__ */ jsx56(Spinner32, { type: "dots" }) }),
|
|
13442
|
+
" Loading webhooks..."
|
|
13443
|
+
] });
|
|
13444
|
+
}
|
|
13445
|
+
if (webhooks.length === 0) {
|
|
13446
|
+
return /* @__PURE__ */ jsx56(Text53, { children: "No webhooks found." });
|
|
13447
|
+
}
|
|
13448
|
+
if (options.format === "json") {
|
|
13449
|
+
console.log(JSON.stringify(webhooks, null, 2));
|
|
13450
|
+
return null;
|
|
13451
|
+
}
|
|
13452
|
+
const data = webhooks.map((webhook) => {
|
|
13453
|
+
const eventsDisplay = webhook.events.length > 2 ? webhook.events.slice(0, 2).join(", ") + ` +${webhook.events.length - 2}` : webhook.events.join(", ");
|
|
13454
|
+
return {
|
|
13455
|
+
ID: webhook.id.substring(0, 12) + "...",
|
|
13456
|
+
URL: webhook.url.length > 35 ? webhook.url.substring(0, 32) + "..." : webhook.url,
|
|
13457
|
+
Events: eventsDisplay,
|
|
13458
|
+
Status: webhook.enabled ? "Active" : "Disabled",
|
|
13459
|
+
"Last Triggered": webhook.last_triggered_at || "Never"
|
|
13460
|
+
};
|
|
13461
|
+
});
|
|
13462
|
+
return /* @__PURE__ */ jsxs53(Box50, { flexDirection: "column", children: [
|
|
13463
|
+
/* @__PURE__ */ jsxs53(Text53, { children: [
|
|
13464
|
+
"Found ",
|
|
13465
|
+
webhooks.length,
|
|
13466
|
+
" webhook(s)"
|
|
13467
|
+
] }),
|
|
13468
|
+
/* @__PURE__ */ jsx56(Newline7, {}),
|
|
13469
|
+
/* @__PURE__ */ jsx56(SimpleTable3, { data })
|
|
13470
|
+
] });
|
|
13471
|
+
};
|
|
13472
|
+
function runWebhooksList(options) {
|
|
13473
|
+
render35(/* @__PURE__ */ jsx56(WebhooksList, { options }));
|
|
13474
|
+
}
|
|
13475
|
+
|
|
13476
|
+
// src/commands/webhooks/create.tsx
|
|
13477
|
+
import { useState as useState41, useEffect as useEffect38 } from "react";
|
|
13478
|
+
import { render as render36, Box as Box51, Text as Text54 } from "ink";
|
|
13479
|
+
import Spinner33 from "ink-spinner";
|
|
13480
|
+
import { jsx as jsx57, jsxs as jsxs54 } from "react/jsx-runtime";
|
|
13481
|
+
var WebhooksCreate = ({ options }) => {
|
|
13482
|
+
const [result, setResult] = useState41(null);
|
|
13483
|
+
const [isLoading, setIsLoading] = useState41(true);
|
|
13484
|
+
const [error, setError] = useState41(null);
|
|
13485
|
+
useEffect38(() => {
|
|
13486
|
+
const createWebhook = async () => {
|
|
13487
|
+
try {
|
|
13488
|
+
const config = getProjectConfig();
|
|
13489
|
+
const projectId = options.project || config?.projectId;
|
|
13490
|
+
if (!projectId) {
|
|
13491
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
13492
|
+
setIsLoading(false);
|
|
13493
|
+
return;
|
|
13494
|
+
}
|
|
13495
|
+
if (!options.url) {
|
|
13496
|
+
setError("Webhook URL is required. Use --url <url>");
|
|
13497
|
+
setIsLoading(false);
|
|
13498
|
+
return;
|
|
13499
|
+
}
|
|
13500
|
+
if (!options.events) {
|
|
13501
|
+
setError("Events are required. Use --events <event1,event2,...>");
|
|
13502
|
+
setIsLoading(false);
|
|
13503
|
+
return;
|
|
13504
|
+
}
|
|
13505
|
+
const api = new WebhooksApi();
|
|
13506
|
+
const events = options.events.split(",").map((e) => e.trim());
|
|
13507
|
+
const created = await api.createWebhook(projectId, {
|
|
13508
|
+
url: options.url,
|
|
13509
|
+
events,
|
|
13510
|
+
secret: options.secret,
|
|
13511
|
+
enabled: options.enabled !== false
|
|
13512
|
+
});
|
|
13513
|
+
setResult(created);
|
|
13514
|
+
} catch (err) {
|
|
13515
|
+
setError(err instanceof Error ? err.message : "Failed to create webhook");
|
|
13516
|
+
} finally {
|
|
13517
|
+
setIsLoading(false);
|
|
13518
|
+
}
|
|
13519
|
+
};
|
|
13520
|
+
createWebhook();
|
|
13521
|
+
}, [options]);
|
|
13522
|
+
if (error) {
|
|
13523
|
+
return /* @__PURE__ */ jsxs54(Text54, { color: "red", children: [
|
|
13524
|
+
"Error: ",
|
|
13525
|
+
error
|
|
13526
|
+
] });
|
|
13527
|
+
}
|
|
13528
|
+
if (isLoading) {
|
|
13529
|
+
return /* @__PURE__ */ jsxs54(Text54, { children: [
|
|
13530
|
+
/* @__PURE__ */ jsx57(Text54, { color: "green", children: /* @__PURE__ */ jsx57(Spinner33, { type: "dots" }) }),
|
|
13531
|
+
" Creating webhook..."
|
|
13532
|
+
] });
|
|
13533
|
+
}
|
|
13534
|
+
if (!result) {
|
|
13535
|
+
return /* @__PURE__ */ jsx57(Text54, { color: "red", children: "Failed to create webhook" });
|
|
13536
|
+
}
|
|
13537
|
+
const { webhook, secret } = result;
|
|
13538
|
+
return /* @__PURE__ */ jsxs54(Box51, { flexDirection: "column", children: [
|
|
13539
|
+
/* @__PURE__ */ jsx57(Text54, { color: "green", children: "\u2713 Webhook created successfully" }),
|
|
13540
|
+
/* @__PURE__ */ jsxs54(Text54, { children: [
|
|
13541
|
+
"ID: ",
|
|
13542
|
+
webhook.id
|
|
13543
|
+
] }),
|
|
13544
|
+
/* @__PURE__ */ jsxs54(Text54, { children: [
|
|
13545
|
+
"URL: ",
|
|
13546
|
+
webhook.url
|
|
13547
|
+
] }),
|
|
13548
|
+
/* @__PURE__ */ jsxs54(Text54, { children: [
|
|
13549
|
+
"Events: ",
|
|
13550
|
+
webhook.events.join(", ")
|
|
13551
|
+
] }),
|
|
13552
|
+
/* @__PURE__ */ jsxs54(Text54, { children: [
|
|
13553
|
+
"Status: ",
|
|
13554
|
+
webhook.enabled ? "Active" : "Disabled"
|
|
13555
|
+
] }),
|
|
13556
|
+
secret && /* @__PURE__ */ jsxs54(Box51, { flexDirection: "column", marginTop: 1, children: [
|
|
13557
|
+
/* @__PURE__ */ jsx57(Text54, { color: "yellow", children: "\u26A0 SECURITY WARNING" }),
|
|
13558
|
+
/* @__PURE__ */ jsx57(Text54, { color: "yellow", children: "Webhook secret shown below. Save it securely - it will NOT be shown again." }),
|
|
13559
|
+
/* @__PURE__ */ jsx57(Text54, { color: "yellow", children: "Clear your terminal history after copying." }),
|
|
13560
|
+
/* @__PURE__ */ jsx57(Box51, { marginTop: 1, borderStyle: "single", borderColor: "yellow", paddingX: 1, children: /* @__PURE__ */ jsx57(Text54, { bold: true, children: secret }) })
|
|
13561
|
+
] })
|
|
13562
|
+
] });
|
|
13563
|
+
};
|
|
13564
|
+
function runWebhooksCreate(options) {
|
|
13565
|
+
render36(/* @__PURE__ */ jsx57(WebhooksCreate, { options }));
|
|
13566
|
+
}
|
|
13567
|
+
|
|
13568
|
+
// src/commands/webhooks/test.tsx
|
|
13569
|
+
import { useState as useState42, useEffect as useEffect39 } from "react";
|
|
13570
|
+
import { render as render37, Box as Box52, Text as Text55 } from "ink";
|
|
13571
|
+
import Spinner34 from "ink-spinner";
|
|
13572
|
+
import { jsx as jsx58, jsxs as jsxs55 } from "react/jsx-runtime";
|
|
13573
|
+
var WebhooksTest = ({ webhookId, options }) => {
|
|
13574
|
+
const [result, setResult] = useState42(null);
|
|
13575
|
+
const [isLoading, setIsLoading] = useState42(true);
|
|
13576
|
+
const [error, setError] = useState42(null);
|
|
13577
|
+
useEffect39(() => {
|
|
13578
|
+
const testWebhook = async () => {
|
|
13579
|
+
try {
|
|
13580
|
+
const config = getProjectConfig();
|
|
13581
|
+
const projectId = options.project || config?.projectId;
|
|
13582
|
+
if (!projectId) {
|
|
13583
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
13584
|
+
setIsLoading(false);
|
|
13585
|
+
return;
|
|
13586
|
+
}
|
|
13587
|
+
const api = new WebhooksApi();
|
|
13588
|
+
const testResult = await api.testWebhook(projectId, webhookId);
|
|
13589
|
+
setResult(testResult);
|
|
13590
|
+
} catch (err) {
|
|
13591
|
+
setError(err instanceof Error ? err.message : "Failed to test webhook");
|
|
13592
|
+
} finally {
|
|
13593
|
+
setIsLoading(false);
|
|
13594
|
+
}
|
|
13595
|
+
};
|
|
13596
|
+
testWebhook();
|
|
13597
|
+
}, [webhookId, options]);
|
|
13598
|
+
if (error) {
|
|
13599
|
+
return /* @__PURE__ */ jsxs55(Text55, { color: "red", children: [
|
|
13600
|
+
"Error: ",
|
|
13601
|
+
error
|
|
13602
|
+
] });
|
|
13603
|
+
}
|
|
13604
|
+
if (isLoading) {
|
|
13605
|
+
return /* @__PURE__ */ jsxs55(Text55, { children: [
|
|
13606
|
+
/* @__PURE__ */ jsx58(Text55, { color: "green", children: /* @__PURE__ */ jsx58(Spinner34, { type: "dots" }) }),
|
|
13607
|
+
" Testing webhook..."
|
|
13608
|
+
] });
|
|
13609
|
+
}
|
|
13610
|
+
if (!result) {
|
|
13611
|
+
return /* @__PURE__ */ jsx58(Text55, { color: "red", children: "Failed to test webhook" });
|
|
13612
|
+
}
|
|
13613
|
+
if (result.success) {
|
|
13614
|
+
return /* @__PURE__ */ jsxs55(Box52, { flexDirection: "column", children: [
|
|
13615
|
+
/* @__PURE__ */ jsx58(Text55, { color: "green", children: "\u2713 Webhook test successful" }),
|
|
13616
|
+
result.status_code && /* @__PURE__ */ jsxs55(Text55, { children: [
|
|
13617
|
+
"Status Code: ",
|
|
13618
|
+
result.status_code
|
|
13619
|
+
] }),
|
|
13620
|
+
result.response_time_ms && /* @__PURE__ */ jsxs55(Text55, { children: [
|
|
13621
|
+
"Response Time: ",
|
|
13622
|
+
result.response_time_ms,
|
|
13623
|
+
"ms"
|
|
13624
|
+
] })
|
|
13625
|
+
] });
|
|
13626
|
+
}
|
|
13627
|
+
return /* @__PURE__ */ jsxs55(Box52, { flexDirection: "column", children: [
|
|
13628
|
+
/* @__PURE__ */ jsx58(Text55, { color: "red", children: "\u2717 Webhook test failed" }),
|
|
13629
|
+
result.status_code && /* @__PURE__ */ jsxs55(Text55, { children: [
|
|
13630
|
+
"Status Code: ",
|
|
13631
|
+
result.status_code
|
|
13632
|
+
] }),
|
|
13633
|
+
result.response_time_ms && /* @__PURE__ */ jsxs55(Text55, { children: [
|
|
13634
|
+
"Response Time: ",
|
|
13635
|
+
result.response_time_ms,
|
|
13636
|
+
"ms"
|
|
13637
|
+
] }),
|
|
13638
|
+
result.error && /* @__PURE__ */ jsxs55(Text55, { children: [
|
|
13639
|
+
"Error: ",
|
|
13640
|
+
result.error
|
|
13641
|
+
] })
|
|
13642
|
+
] });
|
|
13643
|
+
};
|
|
13644
|
+
function runWebhooksTest(webhookId, options) {
|
|
13645
|
+
render37(/* @__PURE__ */ jsx58(WebhooksTest, { webhookId, options }));
|
|
13646
|
+
}
|
|
13647
|
+
|
|
13648
|
+
// src/commands/webhooks/delete.tsx
|
|
13649
|
+
import { useState as useState43, useEffect as useEffect40 } from "react";
|
|
13650
|
+
import { render as render38, Box as Box53, Text as Text56, useInput as useInput11 } from "ink";
|
|
13651
|
+
import Spinner35 from "ink-spinner";
|
|
13652
|
+
import { jsx as jsx59, jsxs as jsxs56 } from "react/jsx-runtime";
|
|
13653
|
+
var WebhooksDelete = ({ webhookId, options }) => {
|
|
13654
|
+
const [webhook, setWebhook] = useState43(null);
|
|
13655
|
+
const [isLoading, setIsLoading] = useState43(true);
|
|
13656
|
+
const [error, setError] = useState43(null);
|
|
13657
|
+
const [needsConfirmation, setNeedsConfirmation] = useState43(!options.force);
|
|
13658
|
+
const [isDeleting, setIsDeleting] = useState43(false);
|
|
13659
|
+
const [deleted, setDeleted] = useState43(false);
|
|
13660
|
+
useEffect40(() => {
|
|
13661
|
+
const findWebhook = async () => {
|
|
13662
|
+
try {
|
|
13663
|
+
const config = getProjectConfig();
|
|
13664
|
+
const projectId = options.project || config?.projectId;
|
|
13665
|
+
if (!projectId) {
|
|
13666
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
13667
|
+
setIsLoading(false);
|
|
13668
|
+
return;
|
|
13669
|
+
}
|
|
13670
|
+
const api = new WebhooksApi();
|
|
13671
|
+
const webhookData = await api.getWebhook(projectId, webhookId);
|
|
13672
|
+
setWebhook(webhookData);
|
|
13673
|
+
setIsLoading(false);
|
|
13674
|
+
if (options.force) {
|
|
13675
|
+
await performDelete(projectId, webhookId, api);
|
|
13676
|
+
}
|
|
13677
|
+
} catch (err) {
|
|
13678
|
+
setError(err instanceof Error ? err.message : "Failed to find webhook");
|
|
13679
|
+
setIsLoading(false);
|
|
13680
|
+
}
|
|
13681
|
+
};
|
|
13682
|
+
findWebhook();
|
|
13683
|
+
}, [webhookId, options.force, options.project]);
|
|
13684
|
+
const performDelete = async (projectId, id, api) => {
|
|
13685
|
+
try {
|
|
13686
|
+
setIsDeleting(true);
|
|
13687
|
+
await api.deleteWebhook(projectId, id);
|
|
13688
|
+
setDeleted(true);
|
|
13689
|
+
setNeedsConfirmation(false);
|
|
13690
|
+
} catch (err) {
|
|
13691
|
+
setError(err instanceof Error ? err.message : "Failed to delete webhook");
|
|
13692
|
+
} finally {
|
|
13693
|
+
setIsDeleting(false);
|
|
13694
|
+
}
|
|
13695
|
+
};
|
|
13696
|
+
useInput11((input, inputKey) => {
|
|
13697
|
+
if (!needsConfirmation || isDeleting) return;
|
|
13698
|
+
if (input.toLowerCase() === "y") {
|
|
13699
|
+
const config = getProjectConfig();
|
|
13700
|
+
const projectId = options.project || config?.projectId;
|
|
13701
|
+
if (projectId) {
|
|
13702
|
+
const api = new WebhooksApi();
|
|
13703
|
+
performDelete(projectId, webhookId, api);
|
|
13704
|
+
}
|
|
13705
|
+
} else if (input.toLowerCase() === "n" || inputKey.escape) {
|
|
13706
|
+
process.exit(0);
|
|
13707
|
+
}
|
|
13708
|
+
});
|
|
13709
|
+
if (error) {
|
|
13710
|
+
return /* @__PURE__ */ jsxs56(Text56, { color: "red", children: [
|
|
13711
|
+
"Error: ",
|
|
13712
|
+
error
|
|
13713
|
+
] });
|
|
13714
|
+
}
|
|
13715
|
+
if (isLoading) {
|
|
13716
|
+
return /* @__PURE__ */ jsxs56(Text56, { children: [
|
|
13717
|
+
/* @__PURE__ */ jsx59(Text56, { color: "green", children: /* @__PURE__ */ jsx59(Spinner35, { type: "dots" }) }),
|
|
13718
|
+
" Finding webhook..."
|
|
13719
|
+
] });
|
|
13720
|
+
}
|
|
13721
|
+
if (isDeleting) {
|
|
13722
|
+
return /* @__PURE__ */ jsxs56(Text56, { children: [
|
|
13723
|
+
/* @__PURE__ */ jsx59(Text56, { color: "green", children: /* @__PURE__ */ jsx59(Spinner35, { type: "dots" }) }),
|
|
13724
|
+
" Deleting webhook..."
|
|
13725
|
+
] });
|
|
13726
|
+
}
|
|
13727
|
+
if (deleted) {
|
|
13728
|
+
return /* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", children: [
|
|
13729
|
+
/* @__PURE__ */ jsx59(Text56, { color: "green", children: "\u2713 Webhook deleted successfully" }),
|
|
13730
|
+
/* @__PURE__ */ jsxs56(Text56, { children: [
|
|
13731
|
+
"ID: ",
|
|
13732
|
+
webhookId
|
|
13733
|
+
] })
|
|
13734
|
+
] });
|
|
13735
|
+
}
|
|
13736
|
+
if (needsConfirmation && webhook) {
|
|
13737
|
+
return /* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", children: [
|
|
13738
|
+
/* @__PURE__ */ jsx59(Box53, { marginBottom: 1, children: /* @__PURE__ */ jsx59(Text56, { color: "yellow", children: "Warning: Delete Webhook" }) }),
|
|
13739
|
+
/* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
13740
|
+
/* @__PURE__ */ jsx59(Text56, { children: "You are about to delete the following webhook:" }),
|
|
13741
|
+
/* @__PURE__ */ jsx59(Text56, { dimColor: true }),
|
|
13742
|
+
/* @__PURE__ */ jsxs56(Text56, { children: [
|
|
13743
|
+
" ID: ",
|
|
13744
|
+
/* @__PURE__ */ jsx59(Text56, { bold: true, children: webhook.id })
|
|
13745
|
+
] }),
|
|
13746
|
+
/* @__PURE__ */ jsxs56(Text56, { children: [
|
|
13747
|
+
" URL: ",
|
|
13748
|
+
/* @__PURE__ */ jsx59(Text56, { bold: true, children: webhook.url })
|
|
13749
|
+
] }),
|
|
13750
|
+
/* @__PURE__ */ jsxs56(Text56, { children: [
|
|
13751
|
+
" Events: ",
|
|
13752
|
+
webhook.events.join(", ")
|
|
13753
|
+
] })
|
|
13754
|
+
] }),
|
|
13755
|
+
/* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
13756
|
+
/* @__PURE__ */ jsx59(Text56, { color: "red", children: "This will permanently remove the webhook." }),
|
|
13757
|
+
/* @__PURE__ */ jsx59(Text56, { dimColor: true, children: "This action cannot be undone." })
|
|
13758
|
+
] }),
|
|
13759
|
+
/* @__PURE__ */ jsx59(Box53, { marginLeft: 2, children: /* @__PURE__ */ jsxs56(Text56, { children: [
|
|
13760
|
+
"Press",
|
|
13761
|
+
" ",
|
|
13762
|
+
/* @__PURE__ */ jsx59(Text56, { bold: true, color: "green", children: "Y" }),
|
|
13763
|
+
" ",
|
|
13764
|
+
"to confirm,",
|
|
13765
|
+
" ",
|
|
13766
|
+
/* @__PURE__ */ jsx59(Text56, { bold: true, color: "red", children: "N" }),
|
|
13767
|
+
" ",
|
|
13768
|
+
"to cancel"
|
|
13769
|
+
] }) })
|
|
13770
|
+
] });
|
|
13771
|
+
}
|
|
13772
|
+
return null;
|
|
13773
|
+
};
|
|
13774
|
+
function runWebhooksDelete(webhookId, options) {
|
|
13775
|
+
render38(/* @__PURE__ */ jsx59(WebhooksDelete, { webhookId, options }));
|
|
13776
|
+
}
|
|
13777
|
+
|
|
13778
|
+
// src/commands/webhooks/index.tsx
|
|
13779
|
+
var webhooksCommand = new Command5("webhooks").description("Manage webhooks");
|
|
13780
|
+
webhooksCommand.command("list").description("List all webhooks").option("--project <id>", "Project ID").option("--format <format>", "Output format (table|json)", "table").action((options) => {
|
|
13781
|
+
runWebhooksList({
|
|
13782
|
+
project: options.project,
|
|
13783
|
+
format: options.format
|
|
13784
|
+
});
|
|
13785
|
+
});
|
|
13786
|
+
webhooksCommand.command("create").description("Create a new webhook").requiredOption("--url <url>", "Webhook URL endpoint").requiredOption("--events <events>", "Comma-separated list of events (e.g., translation.created,key.updated)").option("--project <id>", "Project ID").option("--secret <secret>", "Webhook secret for payload signing").option("--enabled", "Enable webhook immediately (default: true)", true).action((options) => {
|
|
13787
|
+
runWebhooksCreate({
|
|
13788
|
+
project: options.project,
|
|
13789
|
+
url: options.url,
|
|
13790
|
+
events: options.events,
|
|
13791
|
+
secret: options.secret,
|
|
13792
|
+
enabled: options.enabled
|
|
13793
|
+
});
|
|
13794
|
+
});
|
|
13795
|
+
webhooksCommand.command("test <id>").description("Test a webhook by sending a test event").option("--project <id>", "Project ID").action((id, options) => {
|
|
13796
|
+
runWebhooksTest(id, {
|
|
13797
|
+
project: options.project
|
|
13798
|
+
});
|
|
13799
|
+
});
|
|
13800
|
+
webhooksCommand.command("delete <id>").description("Delete a webhook").option("--project <id>", "Project ID").option("--force", "Skip confirmation prompt").action((id, options) => {
|
|
13801
|
+
runWebhooksDelete(id, {
|
|
13802
|
+
project: options.project,
|
|
13803
|
+
force: options.force
|
|
13804
|
+
});
|
|
13805
|
+
});
|
|
13806
|
+
|
|
13807
|
+
// src/commands/contributors/index.tsx
|
|
13808
|
+
import { Command as Command6 } from "commander";
|
|
13809
|
+
|
|
13810
|
+
// src/commands/contributors/list.tsx
|
|
13811
|
+
import { useState as useState44, useEffect as useEffect41 } from "react";
|
|
13812
|
+
import { render as render39, Box as Box54, Text as Text57, Newline as Newline8 } from "ink";
|
|
13813
|
+
import Spinner36 from "ink-spinner";
|
|
13814
|
+
import { jsx as jsx60, jsxs as jsxs57 } from "react/jsx-runtime";
|
|
13815
|
+
function SimpleTable4({ data }) {
|
|
13816
|
+
if (data.length === 0) return null;
|
|
13817
|
+
const headers = Object.keys(data[0]);
|
|
13818
|
+
const colWidths = headers.map(
|
|
13819
|
+
(h) => Math.max(h.length, ...data.map((row) => String(row[h] || "").length))
|
|
13820
|
+
);
|
|
13821
|
+
const separator = "\u2500".repeat(colWidths.reduce((a, b) => a + b + 3, 1));
|
|
13822
|
+
return /* @__PURE__ */ jsxs57(Box54, { flexDirection: "column", children: [
|
|
13823
|
+
/* @__PURE__ */ jsx60(Text57, { children: separator }),
|
|
13824
|
+
/* @__PURE__ */ jsxs57(Text57, { children: [
|
|
13825
|
+
"\u2502 ",
|
|
13826
|
+
headers.map((h, i) => h.padEnd(colWidths[i])).join(" \u2502 "),
|
|
13827
|
+
" \u2502"
|
|
13828
|
+
] }),
|
|
13829
|
+
/* @__PURE__ */ jsx60(Text57, { children: separator }),
|
|
13830
|
+
data.map((row, rowIdx) => /* @__PURE__ */ jsxs57(Text57, { children: [
|
|
13831
|
+
"\u2502 ",
|
|
13832
|
+
headers.map((h, i) => String(row[h] || "").padEnd(colWidths[i])).join(" \u2502 "),
|
|
13833
|
+
" \u2502"
|
|
13834
|
+
] }, rowIdx)),
|
|
13835
|
+
/* @__PURE__ */ jsx60(Text57, { children: separator })
|
|
13836
|
+
] });
|
|
13837
|
+
}
|
|
13838
|
+
var ContributorsList = ({ options }) => {
|
|
13839
|
+
const [members, setMembers] = useState44([]);
|
|
13840
|
+
const [isLoading, setIsLoading] = useState44(true);
|
|
13841
|
+
const [error, setError] = useState44(null);
|
|
13842
|
+
useEffect41(() => {
|
|
13843
|
+
const fetchMembers = async () => {
|
|
13844
|
+
try {
|
|
13845
|
+
const config = getProjectConfig();
|
|
13846
|
+
const projectId = options.project || config?.projectId;
|
|
13847
|
+
if (!projectId) {
|
|
13848
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
13849
|
+
setIsLoading(false);
|
|
13850
|
+
return;
|
|
13851
|
+
}
|
|
13852
|
+
const api = new ContributorsApi();
|
|
13853
|
+
const result = await api.listTeamMembers(projectId);
|
|
13854
|
+
setMembers(result.members);
|
|
13855
|
+
} catch (err) {
|
|
13856
|
+
setError(err instanceof Error ? err.message : "Failed to fetch team members");
|
|
13857
|
+
} finally {
|
|
13858
|
+
setIsLoading(false);
|
|
13859
|
+
}
|
|
13860
|
+
};
|
|
13861
|
+
fetchMembers();
|
|
13862
|
+
}, [options]);
|
|
13863
|
+
if (error) {
|
|
13864
|
+
return /* @__PURE__ */ jsxs57(Text57, { color: "red", children: [
|
|
13865
|
+
"Error: ",
|
|
13866
|
+
error
|
|
13867
|
+
] });
|
|
13868
|
+
}
|
|
13869
|
+
if (isLoading) {
|
|
13870
|
+
return /* @__PURE__ */ jsxs57(Text57, { children: [
|
|
13871
|
+
/* @__PURE__ */ jsx60(Text57, { color: "green", children: /* @__PURE__ */ jsx60(Spinner36, { type: "dots" }) }),
|
|
13872
|
+
" Loading team members..."
|
|
13873
|
+
] });
|
|
13874
|
+
}
|
|
13875
|
+
if (members.length === 0) {
|
|
13876
|
+
return /* @__PURE__ */ jsx60(Text57, { children: "No team members found." });
|
|
13877
|
+
}
|
|
13878
|
+
if (options.format === "json") {
|
|
13879
|
+
console.log(JSON.stringify(members, null, 2));
|
|
13880
|
+
return null;
|
|
13881
|
+
}
|
|
13882
|
+
const data = members.map((member) => {
|
|
13883
|
+
const emailDisplay = member.email.length > 25 ? member.email.substring(0, 22) + "..." : member.email;
|
|
13884
|
+
const languagesDisplay = member.languages && member.languages.length > 0 ? member.languages.length > 3 ? member.languages.slice(0, 3).join(",") + ` +${member.languages.length - 3}` : member.languages.join(",") : "All";
|
|
13885
|
+
const joinedDate = new Date(member.joined_at).toLocaleDateString();
|
|
13886
|
+
return {
|
|
13887
|
+
Email: emailDisplay,
|
|
13888
|
+
Name: member.name || "-",
|
|
13889
|
+
Role: member.role.charAt(0).toUpperCase() + member.role.slice(1),
|
|
13890
|
+
Languages: languagesDisplay,
|
|
13891
|
+
Joined: joinedDate
|
|
13892
|
+
};
|
|
13893
|
+
});
|
|
13894
|
+
return /* @__PURE__ */ jsxs57(Box54, { flexDirection: "column", children: [
|
|
13895
|
+
/* @__PURE__ */ jsxs57(Text57, { children: [
|
|
13896
|
+
"Found ",
|
|
13897
|
+
members.length,
|
|
13898
|
+
" team member(s)"
|
|
13899
|
+
] }),
|
|
13900
|
+
/* @__PURE__ */ jsx60(Newline8, {}),
|
|
13901
|
+
/* @__PURE__ */ jsx60(SimpleTable4, { data })
|
|
13902
|
+
] });
|
|
13903
|
+
};
|
|
13904
|
+
function runContributorsList(options) {
|
|
13905
|
+
render39(/* @__PURE__ */ jsx60(ContributorsList, { options }));
|
|
13906
|
+
}
|
|
13907
|
+
|
|
13908
|
+
// src/commands/contributors/add.tsx
|
|
13909
|
+
import { useState as useState45, useEffect as useEffect42 } from "react";
|
|
13910
|
+
import { render as render40, Box as Box55, Text as Text58 } from "ink";
|
|
13911
|
+
import Spinner37 from "ink-spinner";
|
|
13912
|
+
import { jsx as jsx61, jsxs as jsxs58 } from "react/jsx-runtime";
|
|
13913
|
+
var ContributorsAdd = ({ options }) => {
|
|
13914
|
+
const [result, setResult] = useState45(null);
|
|
13915
|
+
const [isLoading, setIsLoading] = useState45(true);
|
|
13916
|
+
const [error, setError] = useState45(null);
|
|
13917
|
+
useEffect42(() => {
|
|
13918
|
+
const addContributor = async () => {
|
|
13919
|
+
try {
|
|
13920
|
+
const config = getProjectConfig();
|
|
13921
|
+
const projectId = options.project || config?.projectId;
|
|
13922
|
+
if (!projectId) {
|
|
13923
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
13924
|
+
setIsLoading(false);
|
|
13925
|
+
return;
|
|
13926
|
+
}
|
|
13927
|
+
if (!options.email) {
|
|
13928
|
+
setError("Email is required. Use --email <email>");
|
|
13929
|
+
setIsLoading(false);
|
|
13930
|
+
return;
|
|
13931
|
+
}
|
|
13932
|
+
if (!options.role) {
|
|
13933
|
+
setError("Role is required. Use --role <owner|admin|member|viewer>");
|
|
13934
|
+
setIsLoading(false);
|
|
13935
|
+
return;
|
|
13936
|
+
}
|
|
13937
|
+
const validRoles = ["owner", "admin", "member", "viewer"];
|
|
13938
|
+
if (!validRoles.includes(options.role)) {
|
|
13939
|
+
setError("Invalid role. Must be one of: owner, admin, member, viewer");
|
|
13940
|
+
setIsLoading(false);
|
|
13941
|
+
return;
|
|
13942
|
+
}
|
|
13943
|
+
const api = new ContributorsApi();
|
|
13944
|
+
const languages = options.languages ? options.languages.split(",").map((l) => l.trim()) : void 0;
|
|
13945
|
+
const invited = await api.inviteTeamMember(projectId, {
|
|
13946
|
+
email: options.email,
|
|
13947
|
+
role: options.role,
|
|
13948
|
+
languages,
|
|
13949
|
+
message: options.message
|
|
13950
|
+
});
|
|
13951
|
+
setResult(invited.member);
|
|
13952
|
+
} catch (err) {
|
|
13953
|
+
setError(err instanceof Error ? err.message : "Failed to invite team member");
|
|
13954
|
+
} finally {
|
|
13955
|
+
setIsLoading(false);
|
|
13956
|
+
}
|
|
13957
|
+
};
|
|
13958
|
+
addContributor();
|
|
13959
|
+
}, [options]);
|
|
13960
|
+
if (error) {
|
|
13961
|
+
return /* @__PURE__ */ jsxs58(Text58, { color: "red", children: [
|
|
13962
|
+
"Error: ",
|
|
13963
|
+
error
|
|
13964
|
+
] });
|
|
13965
|
+
}
|
|
13966
|
+
if (isLoading) {
|
|
13967
|
+
return /* @__PURE__ */ jsxs58(Text58, { children: [
|
|
13968
|
+
/* @__PURE__ */ jsx61(Text58, { color: "green", children: /* @__PURE__ */ jsx61(Spinner37, { type: "dots" }) }),
|
|
13969
|
+
" Inviting team member..."
|
|
13970
|
+
] });
|
|
13971
|
+
}
|
|
13972
|
+
if (!result) {
|
|
13973
|
+
return /* @__PURE__ */ jsx61(Text58, { color: "red", children: "Failed to invite team member" });
|
|
13974
|
+
}
|
|
13975
|
+
return /* @__PURE__ */ jsxs58(Box55, { flexDirection: "column", children: [
|
|
13976
|
+
/* @__PURE__ */ jsx61(Text58, { color: "green", children: "\u2713 Team member invited successfully" }),
|
|
13977
|
+
/* @__PURE__ */ jsxs58(Text58, { children: [
|
|
13978
|
+
"Email: ",
|
|
13979
|
+
result.email
|
|
13980
|
+
] }),
|
|
13981
|
+
/* @__PURE__ */ jsxs58(Text58, { children: [
|
|
13982
|
+
"Role: ",
|
|
13983
|
+
result.role
|
|
13984
|
+
] }),
|
|
13985
|
+
result.languages && result.languages.length > 0 && /* @__PURE__ */ jsxs58(Text58, { children: [
|
|
13986
|
+
"Languages: ",
|
|
13987
|
+
result.languages.join(", ")
|
|
13988
|
+
] }),
|
|
13989
|
+
/* @__PURE__ */ jsx61(Box55, { marginTop: 1, children: /* @__PURE__ */ jsxs58(Text58, { dimColor: true, children: [
|
|
13990
|
+
"An invitation email has been sent to ",
|
|
13991
|
+
result.email
|
|
13992
|
+
] }) })
|
|
13993
|
+
] });
|
|
13994
|
+
};
|
|
13995
|
+
function runContributorsAdd(options) {
|
|
13996
|
+
render40(/* @__PURE__ */ jsx61(ContributorsAdd, { options }));
|
|
13997
|
+
}
|
|
13998
|
+
|
|
13999
|
+
// src/commands/contributors/update.tsx
|
|
14000
|
+
import { useState as useState46, useEffect as useEffect43 } from "react";
|
|
14001
|
+
import { render as render41, Box as Box56, Text as Text59 } from "ink";
|
|
14002
|
+
import Spinner38 from "ink-spinner";
|
|
14003
|
+
import { jsx as jsx62, jsxs as jsxs59 } from "react/jsx-runtime";
|
|
14004
|
+
var ContributorsUpdate = ({ userId, options }) => {
|
|
14005
|
+
const [result, setResult] = useState46(null);
|
|
14006
|
+
const [isLoading, setIsLoading] = useState46(true);
|
|
14007
|
+
const [error, setError] = useState46(null);
|
|
14008
|
+
useEffect43(() => {
|
|
14009
|
+
const updateContributor = async () => {
|
|
14010
|
+
try {
|
|
14011
|
+
const config = getProjectConfig();
|
|
14012
|
+
const projectId = options.project || config?.projectId;
|
|
14013
|
+
if (!projectId) {
|
|
14014
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
14015
|
+
setIsLoading(false);
|
|
14016
|
+
return;
|
|
14017
|
+
}
|
|
14018
|
+
if (!options.role && !options.languages) {
|
|
14019
|
+
setError("At least one of --role or --languages is required");
|
|
14020
|
+
setIsLoading(false);
|
|
14021
|
+
return;
|
|
14022
|
+
}
|
|
14023
|
+
const validRoles = ["owner", "admin", "member", "viewer"];
|
|
14024
|
+
if (options.role && !validRoles.includes(options.role)) {
|
|
14025
|
+
setError("Invalid role. Must be one of: owner, admin, member, viewer");
|
|
14026
|
+
setIsLoading(false);
|
|
14027
|
+
return;
|
|
14028
|
+
}
|
|
14029
|
+
const api = new ContributorsApi();
|
|
14030
|
+
const languages = options.languages ? options.languages.split(",").map((l) => l.trim()) : void 0;
|
|
14031
|
+
const updated = await api.updateTeamMember(projectId, userId, {
|
|
14032
|
+
role: options.role,
|
|
14033
|
+
languages
|
|
14034
|
+
});
|
|
14035
|
+
setResult(updated.member);
|
|
14036
|
+
} catch (err) {
|
|
14037
|
+
setError(err instanceof Error ? err.message : "Failed to update team member");
|
|
14038
|
+
} finally {
|
|
14039
|
+
setIsLoading(false);
|
|
14040
|
+
}
|
|
14041
|
+
};
|
|
14042
|
+
updateContributor();
|
|
14043
|
+
}, [userId, options]);
|
|
14044
|
+
if (error) {
|
|
14045
|
+
return /* @__PURE__ */ jsxs59(Text59, { color: "red", children: [
|
|
14046
|
+
"Error: ",
|
|
14047
|
+
error
|
|
14048
|
+
] });
|
|
14049
|
+
}
|
|
14050
|
+
if (isLoading) {
|
|
14051
|
+
return /* @__PURE__ */ jsxs59(Text59, { children: [
|
|
14052
|
+
/* @__PURE__ */ jsx62(Text59, { color: "green", children: /* @__PURE__ */ jsx62(Spinner38, { type: "dots" }) }),
|
|
14053
|
+
" Updating team member..."
|
|
14054
|
+
] });
|
|
14055
|
+
}
|
|
14056
|
+
if (!result) {
|
|
14057
|
+
return /* @__PURE__ */ jsx62(Text59, { color: "red", children: "Failed to update team member" });
|
|
14058
|
+
}
|
|
14059
|
+
return /* @__PURE__ */ jsxs59(Box56, { flexDirection: "column", children: [
|
|
14060
|
+
/* @__PURE__ */ jsx62(Text59, { color: "green", children: "\u2713 Team member updated successfully" }),
|
|
14061
|
+
/* @__PURE__ */ jsxs59(Text59, { children: [
|
|
14062
|
+
"Email: ",
|
|
14063
|
+
result.email
|
|
14064
|
+
] }),
|
|
14065
|
+
/* @__PURE__ */ jsxs59(Text59, { children: [
|
|
14066
|
+
"Role: ",
|
|
14067
|
+
result.role
|
|
14068
|
+
] }),
|
|
14069
|
+
result.languages && result.languages.length > 0 && /* @__PURE__ */ jsxs59(Text59, { children: [
|
|
14070
|
+
"Languages: ",
|
|
14071
|
+
result.languages.join(", ")
|
|
14072
|
+
] })
|
|
14073
|
+
] });
|
|
14074
|
+
};
|
|
14075
|
+
function runContributorsUpdate(userId, options) {
|
|
14076
|
+
render41(/* @__PURE__ */ jsx62(ContributorsUpdate, { userId, options }));
|
|
14077
|
+
}
|
|
14078
|
+
|
|
14079
|
+
// src/commands/contributors/remove.tsx
|
|
14080
|
+
import { useState as useState47, useEffect as useEffect44 } from "react";
|
|
14081
|
+
import { render as render42, Box as Box57, Text as Text60, useInput as useInput12 } from "ink";
|
|
14082
|
+
import Spinner39 from "ink-spinner";
|
|
14083
|
+
import { jsx as jsx63, jsxs as jsxs60 } from "react/jsx-runtime";
|
|
14084
|
+
var ContributorsRemove = ({ userId, options }) => {
|
|
14085
|
+
const [member, setMember] = useState47(null);
|
|
14086
|
+
const [isLoading, setIsLoading] = useState47(true);
|
|
14087
|
+
const [error, setError] = useState47(null);
|
|
14088
|
+
const [needsConfirmation, setNeedsConfirmation] = useState47(!options.force);
|
|
14089
|
+
const [isRemoving, setIsRemoving] = useState47(false);
|
|
14090
|
+
const [removed, setRemoved] = useState47(false);
|
|
14091
|
+
useEffect44(() => {
|
|
14092
|
+
const findMember = async () => {
|
|
14093
|
+
try {
|
|
14094
|
+
const config = getProjectConfig();
|
|
14095
|
+
const projectId = options.project || config?.projectId;
|
|
14096
|
+
if (!projectId) {
|
|
14097
|
+
setError("Project ID is required. Use --project <id> or run from a linked directory.");
|
|
14098
|
+
setIsLoading(false);
|
|
14099
|
+
return;
|
|
14100
|
+
}
|
|
14101
|
+
const api = new ContributorsApi();
|
|
14102
|
+
const result = await api.listTeamMembers(projectId);
|
|
14103
|
+
const existingMember = result.members.find((m) => m.id === userId || m.email === userId);
|
|
14104
|
+
if (!existingMember) {
|
|
14105
|
+
setError(`Team member "${userId}" not found`);
|
|
14106
|
+
setIsLoading(false);
|
|
14107
|
+
return;
|
|
14108
|
+
}
|
|
14109
|
+
setMember(existingMember);
|
|
14110
|
+
setIsLoading(false);
|
|
14111
|
+
if (options.force) {
|
|
14112
|
+
await performRemove(projectId, existingMember.id, api);
|
|
14113
|
+
}
|
|
14114
|
+
} catch (err) {
|
|
14115
|
+
setError(err instanceof Error ? err.message : "Failed to find team member");
|
|
14116
|
+
setIsLoading(false);
|
|
14117
|
+
}
|
|
14118
|
+
};
|
|
14119
|
+
findMember();
|
|
14120
|
+
}, [userId, options.force, options.project]);
|
|
14121
|
+
const performRemove = async (projectId, memberId, api) => {
|
|
14122
|
+
try {
|
|
14123
|
+
setIsRemoving(true);
|
|
14124
|
+
await api.removeTeamMember(projectId, memberId);
|
|
14125
|
+
setRemoved(true);
|
|
14126
|
+
setNeedsConfirmation(false);
|
|
14127
|
+
} catch (err) {
|
|
14128
|
+
setError(err instanceof Error ? err.message : "Failed to remove team member");
|
|
14129
|
+
} finally {
|
|
14130
|
+
setIsRemoving(false);
|
|
14131
|
+
}
|
|
14132
|
+
};
|
|
14133
|
+
useInput12((input, inputKey) => {
|
|
14134
|
+
if (!needsConfirmation || isRemoving) return;
|
|
14135
|
+
if (input.toLowerCase() === "y") {
|
|
14136
|
+
const config = getProjectConfig();
|
|
14137
|
+
const projectId = options.project || config?.projectId;
|
|
14138
|
+
if (projectId && member) {
|
|
14139
|
+
const api = new ContributorsApi();
|
|
14140
|
+
performRemove(projectId, member.id, api);
|
|
14141
|
+
}
|
|
14142
|
+
} else if (input.toLowerCase() === "n" || inputKey.escape) {
|
|
14143
|
+
process.exit(0);
|
|
14144
|
+
}
|
|
14145
|
+
});
|
|
14146
|
+
if (error) {
|
|
14147
|
+
return /* @__PURE__ */ jsxs60(Text60, { color: "red", children: [
|
|
14148
|
+
"Error: ",
|
|
14149
|
+
error
|
|
14150
|
+
] });
|
|
14151
|
+
}
|
|
14152
|
+
if (isLoading) {
|
|
14153
|
+
return /* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14154
|
+
/* @__PURE__ */ jsx63(Text60, { color: "green", children: /* @__PURE__ */ jsx63(Spinner39, { type: "dots" }) }),
|
|
14155
|
+
" Finding team member..."
|
|
14156
|
+
] });
|
|
14157
|
+
}
|
|
14158
|
+
if (isRemoving) {
|
|
14159
|
+
return /* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14160
|
+
/* @__PURE__ */ jsx63(Text60, { color: "green", children: /* @__PURE__ */ jsx63(Spinner39, { type: "dots" }) }),
|
|
14161
|
+
" Removing team member..."
|
|
14162
|
+
] });
|
|
14163
|
+
}
|
|
14164
|
+
if (removed) {
|
|
14165
|
+
return /* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", children: [
|
|
14166
|
+
/* @__PURE__ */ jsx63(Text60, { color: "green", children: "\u2713 Team member removed successfully" }),
|
|
14167
|
+
/* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14168
|
+
"Email: ",
|
|
14169
|
+
member?.email
|
|
14170
|
+
] })
|
|
14171
|
+
] });
|
|
14172
|
+
}
|
|
14173
|
+
if (needsConfirmation && member) {
|
|
14174
|
+
return /* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", children: [
|
|
14175
|
+
/* @__PURE__ */ jsx63(Box57, { marginBottom: 1, children: /* @__PURE__ */ jsx63(Text60, { color: "yellow", children: "Warning: Remove Team Member" }) }),
|
|
14176
|
+
/* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
14177
|
+
/* @__PURE__ */ jsx63(Text60, { children: "You are about to remove the following team member:" }),
|
|
14178
|
+
/* @__PURE__ */ jsx63(Text60, { dimColor: true }),
|
|
14179
|
+
/* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14180
|
+
" Email: ",
|
|
14181
|
+
/* @__PURE__ */ jsx63(Text60, { bold: true, children: member.email })
|
|
14182
|
+
] }),
|
|
14183
|
+
/* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14184
|
+
" Name: ",
|
|
14185
|
+
/* @__PURE__ */ jsx63(Text60, { bold: true, children: member.name || "-" })
|
|
14186
|
+
] }),
|
|
14187
|
+
/* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14188
|
+
" Role: ",
|
|
14189
|
+
member.role
|
|
14190
|
+
] }),
|
|
14191
|
+
member.languages && member.languages.length > 0 && /* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14192
|
+
" Languages: ",
|
|
14193
|
+
member.languages.join(", ")
|
|
14194
|
+
] })
|
|
14195
|
+
] }),
|
|
14196
|
+
/* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
14197
|
+
/* @__PURE__ */ jsx63(Text60, { color: "red", children: "This will revoke their access to the project." }),
|
|
14198
|
+
/* @__PURE__ */ jsx63(Text60, { dimColor: true, children: "This action cannot be undone." })
|
|
14199
|
+
] }),
|
|
14200
|
+
/* @__PURE__ */ jsx63(Box57, { marginLeft: 2, children: /* @__PURE__ */ jsxs60(Text60, { children: [
|
|
14201
|
+
"Press",
|
|
14202
|
+
" ",
|
|
14203
|
+
/* @__PURE__ */ jsx63(Text60, { bold: true, color: "green", children: "Y" }),
|
|
14204
|
+
" ",
|
|
14205
|
+
"to confirm,",
|
|
14206
|
+
" ",
|
|
14207
|
+
/* @__PURE__ */ jsx63(Text60, { bold: true, color: "red", children: "N" }),
|
|
14208
|
+
" ",
|
|
14209
|
+
"to cancel"
|
|
14210
|
+
] }) })
|
|
14211
|
+
] });
|
|
14212
|
+
}
|
|
14213
|
+
return null;
|
|
14214
|
+
};
|
|
14215
|
+
function runContributorsRemove(userId, options) {
|
|
14216
|
+
render42(/* @__PURE__ */ jsx63(ContributorsRemove, { userId, options }));
|
|
14217
|
+
}
|
|
14218
|
+
|
|
14219
|
+
// src/commands/contributors/index.tsx
|
|
14220
|
+
var contributorsCommand = new Command6("contributors").description("Manage team contributors");
|
|
14221
|
+
contributorsCommand.command("list").description("List all team members").option("--project <id>", "Project ID").option("--format <format>", "Output format (table|json)", "table").action((options) => {
|
|
14222
|
+
runContributorsList({
|
|
14223
|
+
project: options.project,
|
|
14224
|
+
format: options.format
|
|
14225
|
+
});
|
|
14226
|
+
});
|
|
14227
|
+
contributorsCommand.command("add").description("Invite a new team member").requiredOption("--email <email>", "Email address of the person to invite").requiredOption("--role <role>", "Role: owner, admin, member, or viewer").option("--project <id>", "Project ID").option("--languages <langs>", "Languages the member can access (comma-separated, default: all)").option("--message <message>", "Custom invitation message").action((options) => {
|
|
14228
|
+
runContributorsAdd({
|
|
14229
|
+
project: options.project,
|
|
14230
|
+
email: options.email,
|
|
14231
|
+
role: options.role,
|
|
14232
|
+
languages: options.languages,
|
|
14233
|
+
message: options.message
|
|
14234
|
+
});
|
|
14235
|
+
});
|
|
14236
|
+
contributorsCommand.command("update <userId>").description("Update a team member's role or permissions").option("--project <id>", "Project ID").option("--role <role>", "New role: owner, admin, member, or viewer").option("--languages <langs>", "Languages the member can access (comma-separated)").action((userId, options) => {
|
|
14237
|
+
runContributorsUpdate(userId, {
|
|
14238
|
+
project: options.project,
|
|
14239
|
+
role: options.role,
|
|
14240
|
+
languages: options.languages
|
|
14241
|
+
});
|
|
14242
|
+
});
|
|
14243
|
+
contributorsCommand.command("remove <userId>").description("Remove a team member").option("--project <id>", "Project ID").option("--force", "Skip confirmation prompt").action((userId, options) => {
|
|
14244
|
+
runContributorsRemove(userId, {
|
|
14245
|
+
project: options.project,
|
|
14246
|
+
force: options.force
|
|
14247
|
+
});
|
|
14248
|
+
});
|
|
14249
|
+
|
|
14250
|
+
// src/index.tsx
|
|
14251
|
+
var program = new Command7();
|
|
14252
|
+
program.name("intlpull").description("Intelligent i18n CLI for modern apps").version("0.1.5").option("--env-file <path>", "Path to custom env file (e.g., .env.production)").hook("preAction", (thisCommand) => {
|
|
14253
|
+
const envFile = thisCommand.opts().envFile;
|
|
14254
|
+
if (envFile) {
|
|
14255
|
+
setCustomEnvFile(envFile);
|
|
14256
|
+
}
|
|
14257
|
+
const { loaded } = loadEnvFiles();
|
|
14258
|
+
if (loaded.length > 0 && process.env.INTLPULL_DEBUG) {
|
|
14259
|
+
console.log(`Loaded env from: ${loaded.join(", ")}`);
|
|
14260
|
+
}
|
|
14261
|
+
});
|
|
14262
|
+
program.command("init").description("Initialize IntlPull in your project").option("--framework <framework>", "Framework (next|react|vue|svelte|astro)").option("--library <library>", "i18n library (next-intl|react-i18next|vue-i18n)").option("--output <dir>", "Output directory for translation files").option("--project <id>", "Project ID to link").option("-y, --yes", "Auto-detect and initialize without prompts (non-interactive mode)").action((options) => {
|
|
14263
|
+
runInit({
|
|
14264
|
+
framework: options.framework,
|
|
14265
|
+
library: options.library,
|
|
14266
|
+
output: options.output,
|
|
14267
|
+
project: options.project,
|
|
14268
|
+
yes: options.yes
|
|
14269
|
+
});
|
|
14270
|
+
});
|
|
14271
|
+
program.command("status").description("Show project status and translation progress").action(() => {
|
|
14272
|
+
runStatus();
|
|
14273
|
+
});
|
|
14274
|
+
program.command("login").description("Authenticate with IntlPull").option("--token <token>", "Use API token (prefer INTLPULL_API_KEY env var for security)").action((options) => {
|
|
14275
|
+
runLogin({ token: options.token });
|
|
14276
|
+
});
|
|
14277
|
+
program.command("logout").description("Clear stored credentials").action(async () => {
|
|
14278
|
+
const { clearAuthConfig } = await import("./config-F3O3XWYX.js");
|
|
14279
|
+
clearAuthConfig();
|
|
14280
|
+
console.log("\u2713 Logged out successfully");
|
|
14281
|
+
});
|
|
14282
|
+
program.command("whoami").description("Show current authenticated user").action(async () => {
|
|
14283
|
+
const { getResolvedApiKey: getResolvedApiKey2, getAuthErrorMessage: getAuthErrorMessage2 } = await import("./config-F3O3XWYX.js");
|
|
14284
|
+
const { createApiClient: createApiClient2 } = await import("./api-77ZWU4IB.js");
|
|
14285
|
+
const resolved = getResolvedApiKey2();
|
|
14286
|
+
if (!resolved) {
|
|
14287
|
+
console.log(getAuthErrorMessage2());
|
|
14288
|
+
return;
|
|
14289
|
+
}
|
|
14290
|
+
try {
|
|
14291
|
+
const api = createApiClient2();
|
|
14292
|
+
const { projects: projects2 } = await api.listProjects();
|
|
14293
|
+
const sourceLabel = resolved.source === "env" ? "INTLPULL_API_KEY env var" : "~/.intlpull/auth.json";
|
|
14294
|
+
console.log("\n\u2713 Authenticated successfully\n");
|
|
14295
|
+
console.log(" API Key:", resolved.key.substring(0, 20) + "...");
|
|
14296
|
+
console.log(" Source:", sourceLabel);
|
|
14297
|
+
console.log(" Projects:", projects2.length);
|
|
14298
|
+
if (projects2.length > 0) {
|
|
14299
|
+
console.log("\n Your projects:");
|
|
14300
|
+
for (const p of projects2.slice(0, 5)) {
|
|
14301
|
+
console.log(` - ${p.name} (${p.slug})`);
|
|
14302
|
+
}
|
|
14303
|
+
if (projects2.length > 5) {
|
|
14304
|
+
console.log(` ... and ${projects2.length - 5} more`);
|
|
14305
|
+
}
|
|
14306
|
+
}
|
|
14307
|
+
console.log("");
|
|
14308
|
+
} catch (err) {
|
|
14309
|
+
console.log("API Key:", resolved.key.substring(0, 15) + "...");
|
|
14310
|
+
console.error("Error verifying API key:", err instanceof Error ? err.message : "Unknown error");
|
|
14311
|
+
}
|
|
14312
|
+
});
|
|
14313
|
+
program.command("upload").description("Upload translation keys to IntlPull (auto-detects project and files)").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--file <path>", "Specific file to upload (e.g., messages/admin.json)").option("--branch <name>", "Branch name (auto-detected from git, pushes to IntlPull branch)").option("--platform <platform>", "Platform to tag keys with (ios, android, web)").option("--all-languages", "Upload translations for ALL languages (default: true)", true).option("--source-only", "Upload only source language keys", false).option("--dry-run", "Preview without uploading", false).option("-v, --verbose", "Show detailed detection info", false).option("-q, --quiet", "Suppress output except errors (CI mode)").option("--translation-key-prefix <prefix>", 'Prepend prefix to all keys (e.g., "checkout" makes "submit" \u2192 "checkout.submit")').option("--apply-tm", "Apply Translation Memory matches after pushing", false).option("--tm-threshold <n>", "Minimum TM match score (default: 85)", "85").option("--tm-dry-run", "Preview TM matches without applying", false).action((options) => {
|
|
14314
|
+
runUpload({
|
|
14315
|
+
project: options.project,
|
|
14316
|
+
file: options.file,
|
|
14317
|
+
branch: options.branch,
|
|
14318
|
+
platform: options.platform,
|
|
14319
|
+
// --source-only overrides --all-languages
|
|
14320
|
+
allLanguages: options.sourceOnly ? false : options.allLanguages,
|
|
14321
|
+
dryRun: options.dryRun,
|
|
14322
|
+
verbose: options.verbose,
|
|
14323
|
+
quiet: options.quiet,
|
|
14324
|
+
translationKeyPrefix: options.translationKeyPrefix,
|
|
14325
|
+
applyTm: options.applyTm,
|
|
14326
|
+
tmThreshold: options.tmThreshold,
|
|
14327
|
+
tmDryRun: options.tmDryRun
|
|
14328
|
+
});
|
|
14329
|
+
});
|
|
14330
|
+
program.command("download").description("Download translations from IntlPull (auto-detects project, framework, and languages)").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--format <format>", "Output format (json|yaml|ts)", "json").option("--output <dir>", "Output directory (auto-detected from framework)").option("--languages <langs>", "Languages to download (comma-separated, defaults to all)").option("--namespace <namespaces>", "Namespaces to download (comma-separated, defaults to all)").option("--branch <branch>", "Translation branch to download from").option("--platform <platform>", "Platform variant (default|ios|android|web)").option("--no-parallel", "Disable parallel fetching (use single request)").option("-i, --interactive", "Run in interactive mode with prompts").option("-q, --quiet", "Suppress output except errors").option("--clean-orphans [mode]", 'Remove orphaned files (files not in remote). Use "dry-run" to preview').option("-f, --force", "Skip confirmation prompts for destructive operations").option("--translation-key-prefix <prefix>", 'Strip this prefix from keys (e.g., "checkout" makes "checkout.submit" \u2192 "submit")').option("--filter-by-prefix <prefix>", "Only download keys starting with this prefix").action((options) => {
|
|
14331
|
+
let cleanOrphans;
|
|
14332
|
+
if (options.cleanOrphans === true || options.cleanOrphans === "") {
|
|
14333
|
+
cleanOrphans = true;
|
|
14334
|
+
} else if (options.cleanOrphans === "dry-run") {
|
|
11403
14335
|
cleanOrphans = "dry-run";
|
|
11404
14336
|
}
|
|
11405
|
-
|
|
14337
|
+
runDownload({
|
|
11406
14338
|
project: options.project,
|
|
11407
14339
|
format: options.format,
|
|
11408
14340
|
output: options.output,
|
|
11409
14341
|
languages: options.languages,
|
|
14342
|
+
namespaces: options.namespace,
|
|
11410
14343
|
branch: options.branch,
|
|
11411
14344
|
platform: options.platform,
|
|
11412
14345
|
parallel: options.parallel,
|
|
11413
14346
|
interactive: options.interactive,
|
|
11414
14347
|
quiet: options.quiet,
|
|
11415
14348
|
cleanOrphans,
|
|
11416
|
-
force: options.force
|
|
14349
|
+
force: options.force,
|
|
14350
|
+
translationKeyPrefix: options.translationKeyPrefix,
|
|
14351
|
+
filterByPrefix: options.filterByPrefix
|
|
11417
14352
|
});
|
|
11418
14353
|
});
|
|
11419
14354
|
program.command("listen").alias("watch").description("Watch for translation updates and auto-sync to local files").option("--project <id>", "Project ID").option("--output <dir>", "Output directory (auto-detected from .intlpull.json)").option("--format <format>", "Output format (json|yaml|ts)", "json").option("--languages <langs>", "Languages to sync (comma-separated)").option("--branch <branch>", "Translation branch").option("--platform <platform>", "Platform variant").option("--interval <seconds>", "Polling interval in seconds", "5").option("--timeout <seconds>", "Auto-stop after N seconds (default: 300 = 5 min)").option("--once", "Sync once and exit (no watching)").option("--quiet", "Minimal output").option("--no-parallel", "Disable parallel fetching (use single request)").action((options) => {
|
|
@@ -11461,7 +14396,7 @@ program.command("releases [action] [releaseId]").description("Manage Over-The-Ai
|
|
|
11461
14396
|
quiet: options.quiet
|
|
11462
14397
|
});
|
|
11463
14398
|
});
|
|
11464
|
-
program.command("export").description("Export translations
|
|
14399
|
+
program.command("export").description("Export translations in various formats").option("--format <format>", "Export format (json|yaml|ts|android|ios|xliff|po|arb|stringsdict|xcstrings|csv)", "json").option("--languages <langs>", "Languages to export (comma-separated)").option("--output <file>", "Output file path").option("--version <version>", "Version to export").option("--branch <branch>", "Translation branch").option("--platform <platform>", "Platform variant").option("--project <id>", "Project ID").action((options) => {
|
|
11465
14400
|
runExport({
|
|
11466
14401
|
format: options.format,
|
|
11467
14402
|
languages: options.languages,
|
|
@@ -11472,7 +14407,7 @@ program.command("export").description("Export translations as ZIP").option("--fo
|
|
|
11472
14407
|
project: options.project
|
|
11473
14408
|
});
|
|
11474
14409
|
});
|
|
11475
|
-
program.command("import").description("Import existing translation files").option("--path <path>", "Path to file or directory to import").option("--pattern <pattern>", 'Glob pattern for multi-file import (e.g., "*.json")').option("--format <format>", "File format (json|yaml)", "json").option("--project <id>", "Project ID").option("--language <code>", "Target language code (auto-detect if omitted)").option("--namespace <name>", "Target namespace", "common").option("--branch <name>", "Branch to import translations to").option("--platform <platform>", "Platform to tag imported keys with (ios, android, web)").option("--dry-run", "Preview without importing", false).option("--update-existing", "Update existing translations", true).option("--skip-existing", "Skip if key already exists", false).option("--detect-icu-plurals", "Detect ICU plural syntax", true).action((options) => {
|
|
14410
|
+
program.command("import").description("Import existing translation files").option("--path <path>", "Path to file or directory to import").option("--pattern <pattern>", 'Glob pattern for multi-file import (e.g., "*.json")').option("--format <format>", "File format (json|yaml|xliff|po|arb|stringsdict|xcstrings|csv)", "json").option("--project <id>", "Project ID").option("--language <code>", "Target language code (auto-detect if omitted)").option("--namespace <name>", "Target namespace", "common").option("--branch <name>", "Branch to import translations to").option("--platform <platform>", "Platform to tag imported keys with (ios, android, web)").option("--dry-run", "Preview without importing", false).option("--update-existing", "Update existing translations", true).option("--skip-existing", "Skip if key already exists", false).option("--detect-icu-plurals", "Detect ICU plural syntax", true).action((options) => {
|
|
11476
14411
|
runImport({
|
|
11477
14412
|
path: options.path,
|
|
11478
14413
|
pattern: options.pattern,
|
|
@@ -11526,7 +14461,7 @@ program.command("fix").description("Auto-fix missing translations").option("--so
|
|
|
11526
14461
|
dryRun: options.dryRun
|
|
11527
14462
|
});
|
|
11528
14463
|
});
|
|
11529
|
-
program.command("diff").description("Show what would change on
|
|
14464
|
+
program.command("diff").description("Show what would change on upload/download").option("--source <lang>", "Source language").option("--target <lang>", "Target language to compare").action((options) => {
|
|
11530
14465
|
runDiff({
|
|
11531
14466
|
source: options.source,
|
|
11532
14467
|
target: options.target
|
|
@@ -11534,12 +14469,12 @@ program.command("diff").description("Show what would change on push/pull").optio
|
|
|
11534
14469
|
});
|
|
11535
14470
|
var projects = program.command("projects").description("Manage projects");
|
|
11536
14471
|
projects.command("list").description("List all projects").action(async () => {
|
|
11537
|
-
const { isAuthenticated } = await import("./config-
|
|
14472
|
+
const { isAuthenticated } = await import("./config-F3O3XWYX.js");
|
|
11538
14473
|
if (!isAuthenticated()) {
|
|
11539
14474
|
console.log("Not authenticated. Run `npx @intlpullhq/cli login` first.");
|
|
11540
14475
|
return;
|
|
11541
14476
|
}
|
|
11542
|
-
const { createApiClient: createApiClient2 } = await import("./api-
|
|
14477
|
+
const { createApiClient: createApiClient2 } = await import("./api-77ZWU4IB.js");
|
|
11543
14478
|
try {
|
|
11544
14479
|
const api = createApiClient2();
|
|
11545
14480
|
const { projects: projects2 } = await api.listProjects();
|
|
@@ -11560,12 +14495,12 @@ projects.command("list").description("List all projects").action(async () => {
|
|
|
11560
14495
|
}
|
|
11561
14496
|
});
|
|
11562
14497
|
projects.command("create <name>").description("Create a new project").option("--source-language <lang>", "Source language", "en").option("--languages <langs>", "Supported languages (comma-separated)", "en").action(async (name, options) => {
|
|
11563
|
-
const { isAuthenticated } = await import("./config-
|
|
14498
|
+
const { isAuthenticated } = await import("./config-F3O3XWYX.js");
|
|
11564
14499
|
if (!isAuthenticated()) {
|
|
11565
14500
|
console.log("Not authenticated. Run `npx @intlpullhq/cli login` first.");
|
|
11566
14501
|
return;
|
|
11567
14502
|
}
|
|
11568
|
-
const { createApiClient: createApiClient2 } = await import("./api-
|
|
14503
|
+
const { createApiClient: createApiClient2 } = await import("./api-77ZWU4IB.js");
|
|
11569
14504
|
try {
|
|
11570
14505
|
const api = createApiClient2();
|
|
11571
14506
|
const languages = options.languages.split(",").map((l) => l.trim());
|
|
@@ -11584,7 +14519,7 @@ projects.command("create <name>").description("Create a new project").option("--
|
|
|
11584
14519
|
}
|
|
11585
14520
|
});
|
|
11586
14521
|
projects.command("link <id>").description("Link current directory to a project").action(async (id) => {
|
|
11587
|
-
const { getProjectConfig: getProjectConfig2, saveProjectConfig: saveProjectConfig3, detectFramework: detectFramework2 } = await import("./config-
|
|
14522
|
+
const { getProjectConfig: getProjectConfig2, saveProjectConfig: saveProjectConfig3, detectFramework: detectFramework2 } = await import("./config-F3O3XWYX.js");
|
|
11588
14523
|
const existing = getProjectConfig2();
|
|
11589
14524
|
const detected = detectFramework2();
|
|
11590
14525
|
const config = {
|
|
@@ -11666,4 +14601,18 @@ zendesk.command("disconnect").description("Disconnect Zendesk integration").acti
|
|
|
11666
14601
|
runZendeskDisconnect();
|
|
11667
14602
|
});
|
|
11668
14603
|
program.addCommand(documentsCommand);
|
|
14604
|
+
program.addCommand(keysCommand);
|
|
14605
|
+
program.addCommand(tmCommand);
|
|
14606
|
+
program.addCommand(snapshotsCommand);
|
|
14607
|
+
program.addCommand(webhooksCommand);
|
|
14608
|
+
program.addCommand(contributorsCommand);
|
|
14609
|
+
program.command("cleanup").description("Detect and delete orphaned translation keys").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--scan-codebase", "Scan local files for key usage patterns", false).option("--dry-run", "Preview orphans without deleting", false).option("--days <n>", "Only consider keys not updated in N days", parseInt).option("-f, --force", "Skip confirmation prompt", false).action((options) => {
|
|
14610
|
+
runCleanup({
|
|
14611
|
+
project: options.project,
|
|
14612
|
+
scanCodebase: options.scanCodebase,
|
|
14613
|
+
dryRun: options.dryRun,
|
|
14614
|
+
days: options.days,
|
|
14615
|
+
force: options.force
|
|
14616
|
+
});
|
|
14617
|
+
});
|
|
11669
14618
|
program.parse();
|