@vocoder/cli 0.2.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vocoder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/bin.mjs CHANGED
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);
2
3
  import {
3
4
  StringExtractor,
4
5
  buildInstallCommand,
5
6
  detectLocalEcosystem,
6
7
  getPackagesToInstall,
7
- getSetupSnippets
8
- } from "./chunk-TFAPB25S.mjs";
8
+ getSetupSnippets,
9
+ loadVocoderConfig
10
+ } from "./chunk-7LFRSUPU.mjs";
9
11
 
10
12
  // src/bin.ts
11
13
  import { Command } from "commander";
@@ -122,6 +124,7 @@ var VocoderAPI = class {
122
124
  return {
123
125
  projectName: data.projectName,
124
126
  organizationName: data.organizationName,
127
+ shortCode: data.shortCode,
125
128
  sourceLocale: data.sourceLocale,
126
129
  targetLocales: data.targetLocales,
127
130
  targetBranches: data.targetBranches ?? ["main"],
@@ -184,7 +187,7 @@ var VocoderAPI = class {
184
187
  branch,
185
188
  stringEntries,
186
189
  targetLocales,
187
- stringsHash,
190
+ ...options?.force ? {} : { stringsHash },
188
191
  ...options?.requestedMode ? { requestedMode: options.requestedMode } : {},
189
192
  ...typeof options?.requestedMaxWaitMs === "number" ? { requestedMaxWaitMs: options.requestedMaxWaitMs } : {},
190
193
  ...options?.clientRunId ? { clientRunId: options.clientRunId } : {},
@@ -2932,7 +2935,6 @@ function matchBranchPattern(branch, pattern) {
2932
2935
  import * as p7 from "@clack/prompts";
2933
2936
  import chalk7 from "chalk";
2934
2937
  import { config as loadEnv2 } from "dotenv";
2935
- import { loadVocoderConfig } from "@vocoder/extractor";
2936
2938
  loadEnv2();
2937
2939
  function validateLocalConfig(config) {
2938
2940
  if (!config.apiKey || config.apiKey.length === 0) {
@@ -3092,20 +3094,9 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
3092
3094
  }
3093
3095
 
3094
3096
  // src/commands/sync.ts
3095
- function computeStringsHash(texts) {
3097
+ function computeFingerprint(shortCode, texts) {
3096
3098
  const sorted = [...texts].sort();
3097
- return createHash("sha256").update(sorted.join("\0")).digest("hex").slice(0, 16);
3098
- }
3099
- function readCachedStringsHash(projectRoot, branch) {
3100
- const filePath = getCacheFilePath(projectRoot, branch);
3101
- if (!existsSync3(filePath)) return null;
3102
- try {
3103
- const raw = JSON.parse(readFileSync3(filePath, "utf-8"));
3104
- if (isRecord(raw) && typeof raw.stringsHash === "string")
3105
- return raw.stringsHash;
3106
- } catch {
3107
- }
3108
- return null;
3099
+ return createHash("sha256").update(`${shortCode}:${sorted.join("\0")}`).digest("hex").slice(0, 12);
3109
3100
  }
3110
3101
  function isRecord(value) {
3111
3102
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3150,69 +3141,52 @@ function parseTranslations(value) {
3150
3141
  }
3151
3142
  return Object.keys(translations).length > 0 ? translations : null;
3152
3143
  }
3153
- function getCacheFilePath(projectRoot, branch) {
3154
- const branchHash = createHash("sha1").update(branch).digest("hex").slice(0, 12);
3155
- return join4(
3156
- projectRoot,
3157
- "node_modules",
3158
- ".vocoder",
3159
- "cache",
3160
- "sync",
3161
- `${branchHash}.json`
3162
- );
3144
+ function getCacheFilePath(projectRoot, fingerprint) {
3145
+ return join4(projectRoot, "node_modules", ".vocoder", "cache", `${fingerprint}.json`);
3163
3146
  }
3164
- function readLocalSnapshotCache(params) {
3165
- const candidateBranches = params.branch === "main" ? ["main"] : [params.branch, "main"];
3166
- for (const candidateBranch of candidateBranches) {
3167
- const cacheFilePath = getCacheFilePath(params.projectRoot, candidateBranch);
3168
- if (!existsSync3(cacheFilePath)) {
3169
- continue;
3170
- }
3171
- try {
3172
- const raw = readFileSync3(cacheFilePath, "utf-8");
3173
- const parsed = JSON.parse(raw);
3174
- if (!isRecord(parsed)) {
3175
- continue;
3176
- }
3177
- const translations = parseTranslations(parsed.translations);
3178
- if (!translations) {
3179
- continue;
3180
- }
3181
- const localeMetadata = parseLocaleMetadata(parsed.localeMetadata);
3182
- return {
3183
- source: "local-cache",
3184
- translations,
3185
- localeMetadata,
3186
- snapshotBatchId: typeof parsed.snapshotBatchId === "string" ? parsed.snapshotBatchId : void 0,
3187
- completedAt: typeof parsed.completedAt === "string" ? parsed.completedAt : null,
3188
- cacheBranch: candidateBranch
3189
- };
3190
- } catch {
3147
+ function buildTranslationData(params) {
3148
+ const textToHash = new Map(params.stringEntries.map((e) => [e.text, e.key]));
3149
+ const hashKeyed = {};
3150
+ for (const [locale, localeMap] of Object.entries(params.translations)) {
3151
+ hashKeyed[locale] = {};
3152
+ for (const [text2, translation] of Object.entries(localeMap)) {
3153
+ const hash = textToHash.get(text2);
3154
+ if (hash) hashKeyed[locale][hash] = translation;
3191
3155
  }
3192
3156
  }
3193
- return null;
3194
- }
3195
- function writeLocalSnapshotCache(params) {
3196
- const cacheFilePath = getCacheFilePath(params.projectRoot, params.branch);
3197
- mkdirSync2(
3198
- join4(params.projectRoot, "node_modules", ".vocoder", "cache", "sync"),
3199
- {
3200
- recursive: true
3201
- }
3202
- );
3203
- const payload = {
3204
- version: 1,
3205
- branch: params.branch,
3206
- sourceLocale: params.sourceLocale,
3207
- targetLocales: params.targetLocales,
3208
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
3209
- ...params.stringsHash ? { stringsHash: params.stringsHash } : {},
3210
- ...params.snapshotBatchId ? { snapshotBatchId: params.snapshotBatchId } : {},
3211
- ...params.completedAt ? { completedAt: params.completedAt } : {},
3212
- ...params.localeMetadata ? { localeMetadata: params.localeMetadata } : {},
3213
- translations: params.translations
3157
+ const locales = {};
3158
+ for (const code of [params.sourceLocale, ...params.targetLocales]) {
3159
+ const meta = params.localeMetadata?.[code];
3160
+ if (meta) locales[code] = { nativeName: meta.nativeName, ...meta.dir ? { dir: meta.dir } : {} };
3161
+ }
3162
+ return {
3163
+ config: { sourceLocale: params.sourceLocale, targetLocales: params.targetLocales, locales },
3164
+ translations: hashKeyed,
3165
+ updatedAt: params.updatedAt
3214
3166
  };
3215
- writeFileSync4(cacheFilePath, JSON.stringify(payload, null, 2), "utf-8");
3167
+ }
3168
+ function readLocalCache(params) {
3169
+ const cacheFilePath = getCacheFilePath(params.projectRoot, params.fingerprint);
3170
+ if (!existsSync3(cacheFilePath)) return null;
3171
+ try {
3172
+ const raw = readFileSync3(cacheFilePath, "utf-8");
3173
+ const parsed = JSON.parse(raw);
3174
+ if (!isRecord(parsed)) return null;
3175
+ const inner = isRecord(parsed.config) ? parsed : null;
3176
+ if (!inner) return null;
3177
+ const translations = parseTranslations(inner.translations);
3178
+ if (!translations) return null;
3179
+ const localeMetadata = isRecord(inner.config) ? parseLocaleMetadata(inner.config.locales) : void 0;
3180
+ return { source: "local-cache", translations, localeMetadata };
3181
+ } catch {
3182
+ return null;
3183
+ }
3184
+ }
3185
+ function writeCache(params) {
3186
+ const cacheDir = join4(params.projectRoot, "node_modules", ".vocoder", "cache");
3187
+ mkdirSync2(cacheDir, { recursive: true });
3188
+ const cacheFilePath = getCacheFilePath(params.projectRoot, params.fingerprint);
3189
+ writeFileSync4(cacheFilePath, JSON.stringify(params.data), "utf-8");
3216
3190
  return cacheFilePath;
3217
3191
  }
3218
3192
  function resolveEffectiveModeFromPolicy(params) {
@@ -3469,30 +3443,20 @@ async function sync(options = {}) {
3469
3443
  `Deduped ${extractedStrings.length} extracted entries into ${stringEntries.length} unique source strings`
3470
3444
  );
3471
3445
  }
3472
- const currentHash = computeStringsHash(sourceStrings);
3446
+ const fingerprint = computeFingerprint(config.shortCode, sourceStrings);
3473
3447
  if (!options.force) {
3474
- const cachedHash = readCachedStringsHash(projectRoot, branch);
3475
- if (options.verbose) {
3476
- const cacheFile = getCacheFilePath(projectRoot, branch);
3477
- if (cachedHash) {
3478
- p8.log.info(
3479
- `Local cache: ${chalk8.dim(cacheFile)}
3480
- cached hash ${chalk8.cyan(cachedHash.slice(0, 8))}\u2026 vs current ${chalk8.cyan(currentHash.slice(0, 8))}\u2026 \u2014 ${cachedHash === currentHash ? chalk8.green("match") : chalk8.yellow("changed")}`
3481
- );
3482
- } else {
3483
- p8.log.info(`No local cache found at ${chalk8.dim(cacheFile)} \u2014 will submit to API`);
3484
- }
3485
- }
3486
- if (cachedHash && cachedHash === currentHash) {
3448
+ const cacheFile = getCacheFilePath(projectRoot, fingerprint);
3449
+ if (existsSync3(cacheFile)) {
3487
3450
  if (options.verbose) {
3488
- p8.log.info(
3489
- "Skipping API submission \u2014 delete node_modules/.vocoder to force a fresh sync"
3490
- );
3451
+ p8.log.info(`Cache hit: ${chalk8.dim(cacheFile)} (fingerprint ${chalk8.cyan(fingerprint)})`);
3491
3452
  }
3492
3453
  const duration2 = ((Date.now() - startTime) / 1e3).toFixed(1);
3493
3454
  p8.outro(`Up to date (${duration2}s)`);
3494
3455
  return 0;
3495
3456
  }
3457
+ if (options.verbose) {
3458
+ p8.log.info(`No cache for fingerprint ${chalk8.cyan(fingerprint)} \u2014 will submit to API`);
3459
+ }
3496
3460
  }
3497
3461
  spinner4.start("Submitting strings to Vocoder API");
3498
3462
  const batchResponse = await api.submitTranslation(
@@ -3502,7 +3466,8 @@ async function sync(options = {}) {
3502
3466
  {
3503
3467
  requestedMode,
3504
3468
  requestedMaxWaitMs: waitTimeoutMs,
3505
- clientRunId: randomUUID()
3469
+ clientRunId: randomUUID(),
3470
+ force: options.force
3506
3471
  },
3507
3472
  repoIdentity ? { ...repoIdentity, commitSha } : { commitSha }
3508
3473
  );
@@ -3587,14 +3552,13 @@ async function sync(options = {}) {
3587
3552
  );
3588
3553
  }
3589
3554
  spinner4.start("Loading fallback translations");
3590
- const localFallback = readLocalSnapshotCache({
3555
+ const localFallback = readLocalCache({
3591
3556
  projectRoot,
3592
- branch
3557
+ fingerprint
3593
3558
  });
3594
3559
  if (localFallback) {
3595
3560
  artifacts = localFallback;
3596
- const cacheBranchLabel = localFallback.cacheBranch && localFallback.cacheBranch !== branch ? `${localFallback.cacheBranch} fallback` : localFallback.cacheBranch || branch;
3597
- spinner4.stop(`Using local cached snapshot (${cacheBranchLabel})`);
3561
+ spinner4.stop(`Using local cached snapshot (${fingerprint})`);
3598
3562
  } else {
3599
3563
  try {
3600
3564
  const apiSnapshot = await fetchApiSnapshot(api, {
@@ -3633,24 +3597,22 @@ async function sync(options = {}) {
3633
3597
  translations: artifacts.translations
3634
3598
  });
3635
3599
  try {
3636
- const cachePath = writeLocalSnapshotCache({
3637
- projectRoot,
3638
- branch,
3600
+ const data = buildTranslationData({
3639
3601
  sourceLocale: config.sourceLocale,
3640
3602
  targetLocales: config.targetLocales,
3603
+ stringEntries,
3641
3604
  translations: finalTranslations,
3642
3605
  localeMetadata: artifacts.localeMetadata,
3643
- stringsHash: currentHash,
3644
- snapshotBatchId: artifacts.snapshotBatchId ?? (artifacts.source === "fresh" ? batchResponse.batchId : batchResponse.latestCompletedBatchId),
3645
- completedAt: artifacts.completedAt ?? (artifacts.source === "fresh" ? (/* @__PURE__ */ new Date()).toISOString() : null)
3606
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3646
3607
  });
3608
+ const cachePath = writeCache({ projectRoot, fingerprint, data });
3647
3609
  if (options.verbose) {
3648
- p8.log.info(`Cached snapshot: ${cachePath}`);
3610
+ p8.log.info(`Cache written: ${cachePath}`);
3649
3611
  }
3650
3612
  } catch (error) {
3651
3613
  if (options.verbose) {
3652
3614
  const message = error instanceof Error ? error.message : "Unknown cache write error";
3653
- p8.log.warn(`Failed to write local snapshot cache: ${message}`);
3615
+ p8.log.warn(`Failed to write cache: ${message}`);
3654
3616
  }
3655
3617
  }
3656
3618
  if (artifacts.source !== "fresh") {