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