@vocoder/cli 0.9.0 → 0.11.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/dist/bin.mjs +165 -885
- package/dist/bin.mjs.map +1 -1
- package/dist/{chunk-IZN5HVYD.mjs → chunk-XF3KGGYQ.mjs} +1024 -220
- package/dist/chunk-XF3KGGYQ.mjs.map +1 -0
- package/dist/lib.d.mts +339 -6
- package/dist/lib.mjs +13 -3
- package/dist/lib.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-IZN5HVYD.mjs.map +0 -1
|
@@ -43568,6 +43568,687 @@ var require_brace_expansion = __commonJS({
|
|
|
43568
43568
|
}
|
|
43569
43569
|
});
|
|
43570
43570
|
|
|
43571
|
+
// src/utils/api.ts
|
|
43572
|
+
function isLimitErrorResponse(value) {
|
|
43573
|
+
if (!value || typeof value !== "object") {
|
|
43574
|
+
return false;
|
|
43575
|
+
}
|
|
43576
|
+
const candidate = value;
|
|
43577
|
+
return typeof candidate.errorCode === "string" && typeof candidate.limitType === "string" && typeof candidate.planId === "string" && typeof candidate.current === "number" && typeof candidate.required === "number" && typeof candidate.upgradeUrl === "string" && typeof candidate.message === "string";
|
|
43578
|
+
}
|
|
43579
|
+
function isSyncPolicyErrorResponse(value) {
|
|
43580
|
+
if (!value || typeof value !== "object") {
|
|
43581
|
+
return false;
|
|
43582
|
+
}
|
|
43583
|
+
const candidate = value;
|
|
43584
|
+
return (candidate.errorCode === "BRANCH_NOT_ALLOWED" || candidate.errorCode === "PROJECT_REPOSITORY_MISMATCH") && typeof candidate.message === "string";
|
|
43585
|
+
}
|
|
43586
|
+
function extractErrorMessage(payload, fallback) {
|
|
43587
|
+
if (!payload || typeof payload !== "object") {
|
|
43588
|
+
return fallback;
|
|
43589
|
+
}
|
|
43590
|
+
const candidate = payload;
|
|
43591
|
+
if (typeof candidate.message === "string") {
|
|
43592
|
+
return candidate.message;
|
|
43593
|
+
}
|
|
43594
|
+
if (typeof candidate.error === "string") {
|
|
43595
|
+
return candidate.error;
|
|
43596
|
+
}
|
|
43597
|
+
return fallback;
|
|
43598
|
+
}
|
|
43599
|
+
function parsePayload(raw) {
|
|
43600
|
+
if (raw.length === 0) {
|
|
43601
|
+
return null;
|
|
43602
|
+
}
|
|
43603
|
+
const trimmed = raw.trimStart();
|
|
43604
|
+
if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html")) {
|
|
43605
|
+
return {
|
|
43606
|
+
message: "Unexpected response from server (received HTML). Check your network connection or try again."
|
|
43607
|
+
};
|
|
43608
|
+
}
|
|
43609
|
+
try {
|
|
43610
|
+
return JSON.parse(raw);
|
|
43611
|
+
} catch {
|
|
43612
|
+
return { message: raw };
|
|
43613
|
+
}
|
|
43614
|
+
}
|
|
43615
|
+
async function readPayload(response) {
|
|
43616
|
+
if (typeof response.text === "function") {
|
|
43617
|
+
const raw = await response.text();
|
|
43618
|
+
return parsePayload(raw);
|
|
43619
|
+
}
|
|
43620
|
+
if (typeof response.json === "function") {
|
|
43621
|
+
return response.json();
|
|
43622
|
+
}
|
|
43623
|
+
return null;
|
|
43624
|
+
}
|
|
43625
|
+
var VocoderAPIError = class extends Error {
|
|
43626
|
+
constructor(params) {
|
|
43627
|
+
super(params.message);
|
|
43628
|
+
this.name = "VocoderAPIError";
|
|
43629
|
+
this.status = params.status;
|
|
43630
|
+
this.payload = params.payload;
|
|
43631
|
+
this.limitError = params.limitError ?? null;
|
|
43632
|
+
this.syncPolicyError = params.syncPolicyError ?? null;
|
|
43633
|
+
}
|
|
43634
|
+
};
|
|
43635
|
+
var VocoderAPI = class {
|
|
43636
|
+
constructor(config) {
|
|
43637
|
+
this.apiUrl = config.apiUrl;
|
|
43638
|
+
this.apiKey = config.apiKey;
|
|
43639
|
+
}
|
|
43640
|
+
async request(path2, init = {}, errorPrefix) {
|
|
43641
|
+
const response = await fetch(`${this.apiUrl}${path2}`, {
|
|
43642
|
+
...init,
|
|
43643
|
+
headers: {
|
|
43644
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
43645
|
+
...init.headers ?? {}
|
|
43646
|
+
}
|
|
43647
|
+
});
|
|
43648
|
+
const payload = await readPayload(response);
|
|
43649
|
+
if (!response.ok) {
|
|
43650
|
+
const limitError = isLimitErrorResponse(payload) ? payload : null;
|
|
43651
|
+
const syncPolicyError = isSyncPolicyErrorResponse(payload) ? payload : null;
|
|
43652
|
+
const baseMessage = extractErrorMessage(
|
|
43653
|
+
payload,
|
|
43654
|
+
`Request failed with status ${response.status}`
|
|
43655
|
+
);
|
|
43656
|
+
throw new VocoderAPIError({
|
|
43657
|
+
message: errorPrefix ? `${errorPrefix}: ${baseMessage}` : baseMessage,
|
|
43658
|
+
status: response.status,
|
|
43659
|
+
payload,
|
|
43660
|
+
limitError,
|
|
43661
|
+
syncPolicyError
|
|
43662
|
+
});
|
|
43663
|
+
}
|
|
43664
|
+
return payload;
|
|
43665
|
+
}
|
|
43666
|
+
/**
|
|
43667
|
+
* Fetch project configuration from API
|
|
43668
|
+
* Project is determined from the API key
|
|
43669
|
+
*/
|
|
43670
|
+
async getProjectConfig() {
|
|
43671
|
+
const data = await this.request("/api/cli/config", {}, "Failed to fetch project config");
|
|
43672
|
+
return {
|
|
43673
|
+
projectName: data.projectName,
|
|
43674
|
+
organizationName: data.organizationName,
|
|
43675
|
+
shortCode: data.shortCode,
|
|
43676
|
+
sourceLocale: data.sourceLocale,
|
|
43677
|
+
targetLocales: data.targetLocales,
|
|
43678
|
+
targetBranches: data.targetBranches ?? ["main"],
|
|
43679
|
+
primaryBranch: data.primaryBranch,
|
|
43680
|
+
syncPolicy: {
|
|
43681
|
+
blockingBranches: data.syncPolicy?.blockingBranches ?? [
|
|
43682
|
+
"main",
|
|
43683
|
+
"master"
|
|
43684
|
+
],
|
|
43685
|
+
blockingMode: data.syncPolicy?.blockingMode ?? "required",
|
|
43686
|
+
nonBlockingMode: data.syncPolicy?.nonBlockingMode ?? "best-effort",
|
|
43687
|
+
defaultMaxWaitMs: data.syncPolicy?.defaultMaxWaitMs ?? 6e4
|
|
43688
|
+
}
|
|
43689
|
+
};
|
|
43690
|
+
}
|
|
43691
|
+
/**
|
|
43692
|
+
* Submit strings for translation
|
|
43693
|
+
* Project is determined from the API key
|
|
43694
|
+
*/
|
|
43695
|
+
stableTextKey(text) {
|
|
43696
|
+
let hash = 2166136261;
|
|
43697
|
+
for (let i = 0; i < text.length; i++) {
|
|
43698
|
+
hash ^= text.charCodeAt(i);
|
|
43699
|
+
hash = Math.imul(hash, 16777619);
|
|
43700
|
+
}
|
|
43701
|
+
return `SK_TEXT_${(hash >>> 0).toString(16).toUpperCase().padStart(8, "0")}`;
|
|
43702
|
+
}
|
|
43703
|
+
normalizeStringEntries(entries) {
|
|
43704
|
+
if (entries.length === 0) {
|
|
43705
|
+
return [];
|
|
43706
|
+
}
|
|
43707
|
+
const first = entries[0];
|
|
43708
|
+
if (typeof first === "string") {
|
|
43709
|
+
return entries.map((text) => ({
|
|
43710
|
+
key: this.stableTextKey(text),
|
|
43711
|
+
text
|
|
43712
|
+
}));
|
|
43713
|
+
}
|
|
43714
|
+
return entries.map((entry, index) => ({
|
|
43715
|
+
key: entry.key || this.stableTextKey(`${entry.text}:${index}`),
|
|
43716
|
+
text: entry.text,
|
|
43717
|
+
...entry.context ? { context: entry.context } : {},
|
|
43718
|
+
...entry.formality ? { formality: entry.formality } : {},
|
|
43719
|
+
...entry.uiRole ? { uiRole: entry.uiRole } : {}
|
|
43720
|
+
}));
|
|
43721
|
+
}
|
|
43722
|
+
async submitTranslation(branch, entries, targetLocales, options, repoIdentity) {
|
|
43723
|
+
const stringEntries = this.normalizeStringEntries(entries);
|
|
43724
|
+
const strings = stringEntries.map((entry) => entry.text);
|
|
43725
|
+
const crypto = await import("crypto");
|
|
43726
|
+
const sortedStrings = [...strings].sort();
|
|
43727
|
+
const stringsHash = crypto.createHash("sha256").update(JSON.stringify(sortedStrings)).digest("hex");
|
|
43728
|
+
return this.request(
|
|
43729
|
+
"/api/cli/sync",
|
|
43730
|
+
{
|
|
43731
|
+
method: "POST",
|
|
43732
|
+
headers: {
|
|
43733
|
+
"Content-Type": "application/json"
|
|
43734
|
+
},
|
|
43735
|
+
body: JSON.stringify({
|
|
43736
|
+
branch,
|
|
43737
|
+
stringEntries,
|
|
43738
|
+
targetLocales,
|
|
43739
|
+
...options?.force ? {} : { stringsHash },
|
|
43740
|
+
...options?.requestedMode ? { requestedMode: options.requestedMode } : {},
|
|
43741
|
+
...typeof options?.requestedMaxWaitMs === "number" ? { requestedMaxWaitMs: options.requestedMaxWaitMs } : {},
|
|
43742
|
+
...options?.clientRunId ? { clientRunId: options.clientRunId } : {},
|
|
43743
|
+
...repoIdentity?.repoCanonical ? { repoCanonical: repoIdentity.repoCanonical } : {},
|
|
43744
|
+
...repoIdentity?.repoAppDir !== void 0 ? { repoAppDir: repoIdentity.repoAppDir } : {},
|
|
43745
|
+
...repoIdentity?.commitSha ? { commitSha: repoIdentity.commitSha } : {},
|
|
43746
|
+
...options?.appIndustry ? { appIndustry: options.appIndustry } : {}
|
|
43747
|
+
})
|
|
43748
|
+
},
|
|
43749
|
+
"Translation submission failed"
|
|
43750
|
+
);
|
|
43751
|
+
}
|
|
43752
|
+
/**
|
|
43753
|
+
* Check translation status
|
|
43754
|
+
*/
|
|
43755
|
+
async getTranslationStatus(batchId) {
|
|
43756
|
+
return this.request(
|
|
43757
|
+
`/api/cli/sync/status/${batchId}`,
|
|
43758
|
+
{},
|
|
43759
|
+
"Failed to check translation status"
|
|
43760
|
+
);
|
|
43761
|
+
}
|
|
43762
|
+
async getTranslationSnapshot(params) {
|
|
43763
|
+
const search = new URLSearchParams();
|
|
43764
|
+
search.set("branch", params.branch);
|
|
43765
|
+
for (const locale of params.targetLocales) {
|
|
43766
|
+
search.append("targetLocale", locale);
|
|
43767
|
+
}
|
|
43768
|
+
return this.request(
|
|
43769
|
+
`/api/cli/sync/snapshot?${search.toString()}`,
|
|
43770
|
+
{},
|
|
43771
|
+
"Failed to fetch translation snapshot"
|
|
43772
|
+
);
|
|
43773
|
+
}
|
|
43774
|
+
/**
|
|
43775
|
+
* Wait for translation to complete with polling
|
|
43776
|
+
*/
|
|
43777
|
+
async waitForCompletion(batchId, timeout = 6e4, onProgress) {
|
|
43778
|
+
const startTime = Date.now();
|
|
43779
|
+
const pollInterval = 1e3;
|
|
43780
|
+
while (Date.now() - startTime < timeout) {
|
|
43781
|
+
const status = await this.getTranslationStatus(batchId);
|
|
43782
|
+
if (onProgress) {
|
|
43783
|
+
onProgress(status.progress);
|
|
43784
|
+
}
|
|
43785
|
+
if (status.status === "COMPLETED") {
|
|
43786
|
+
if (!status.translations) {
|
|
43787
|
+
throw new Error("Translation completed but no translations returned");
|
|
43788
|
+
}
|
|
43789
|
+
return {
|
|
43790
|
+
translations: status.translations,
|
|
43791
|
+
localeMetadata: status.localeMetadata
|
|
43792
|
+
};
|
|
43793
|
+
}
|
|
43794
|
+
if (status.status === "FAILED") {
|
|
43795
|
+
throw new Error(
|
|
43796
|
+
`Translation failed: ${status.errorMessage || "Unknown error"}`
|
|
43797
|
+
);
|
|
43798
|
+
}
|
|
43799
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
43800
|
+
}
|
|
43801
|
+
throw new Error(`Translation timeout after ${timeout}ms`);
|
|
43802
|
+
}
|
|
43803
|
+
async startInitSession(input) {
|
|
43804
|
+
const response = await fetch(`${this.apiUrl}/api/cli/init/start`, {
|
|
43805
|
+
method: "POST",
|
|
43806
|
+
headers: {
|
|
43807
|
+
"Content-Type": "application/json"
|
|
43808
|
+
},
|
|
43809
|
+
body: JSON.stringify(input)
|
|
43810
|
+
});
|
|
43811
|
+
const payload = await readPayload(response);
|
|
43812
|
+
if (!response.ok) {
|
|
43813
|
+
throw new VocoderAPIError({
|
|
43814
|
+
message: extractErrorMessage(
|
|
43815
|
+
payload,
|
|
43816
|
+
`Failed to start init session (${response.status})`
|
|
43817
|
+
),
|
|
43818
|
+
status: response.status,
|
|
43819
|
+
payload
|
|
43820
|
+
});
|
|
43821
|
+
}
|
|
43822
|
+
return payload;
|
|
43823
|
+
}
|
|
43824
|
+
async getInitSessionStatus(params) {
|
|
43825
|
+
const response = await fetch(
|
|
43826
|
+
`${this.apiUrl}/api/cli/init/status/${params.sessionId}`,
|
|
43827
|
+
{
|
|
43828
|
+
headers: {
|
|
43829
|
+
Authorization: `Bearer ${params.pollToken}`
|
|
43830
|
+
}
|
|
43831
|
+
}
|
|
43832
|
+
);
|
|
43833
|
+
const payload = await readPayload(response);
|
|
43834
|
+
if (!response.ok) {
|
|
43835
|
+
throw new VocoderAPIError({
|
|
43836
|
+
message: extractErrorMessage(
|
|
43837
|
+
payload,
|
|
43838
|
+
`Failed to get init status (${response.status})`
|
|
43839
|
+
),
|
|
43840
|
+
status: response.status,
|
|
43841
|
+
payload
|
|
43842
|
+
});
|
|
43843
|
+
}
|
|
43844
|
+
return payload;
|
|
43845
|
+
}
|
|
43846
|
+
// ── CLI Auth endpoints (no project API key needed) ──────────────────────────
|
|
43847
|
+
/**
|
|
43848
|
+
* Start a CLI auth session. Returns `{ sessionId, verificationUrl, expiresAt }`.
|
|
43849
|
+
* `sessionId` is the raw poll token — keep it secret, used for polling.
|
|
43850
|
+
*/
|
|
43851
|
+
async startCliAuthSession(callbackPort, repoCanonical) {
|
|
43852
|
+
const response = await fetch(`${this.apiUrl}/api/cli/auth/start`, {
|
|
43853
|
+
method: "POST",
|
|
43854
|
+
headers: { "Content-Type": "application/json" },
|
|
43855
|
+
body: JSON.stringify({
|
|
43856
|
+
...callbackPort != null ? { callbackPort } : {},
|
|
43857
|
+
...repoCanonical ? { repoCanonical } : {}
|
|
43858
|
+
})
|
|
43859
|
+
});
|
|
43860
|
+
const payload = await readPayload(response);
|
|
43861
|
+
if (!response.ok) {
|
|
43862
|
+
throw new VocoderAPIError({
|
|
43863
|
+
message: extractErrorMessage(
|
|
43864
|
+
payload,
|
|
43865
|
+
`Failed to start auth session (${response.status})`
|
|
43866
|
+
),
|
|
43867
|
+
status: response.status,
|
|
43868
|
+
payload
|
|
43869
|
+
});
|
|
43870
|
+
}
|
|
43871
|
+
return payload;
|
|
43872
|
+
}
|
|
43873
|
+
/**
|
|
43874
|
+
* Poll for CLI auth session completion.
|
|
43875
|
+
* Returns `{ token }` on success, throws on failure/expiry.
|
|
43876
|
+
* The server returns HTTP 202 while still pending.
|
|
43877
|
+
*/
|
|
43878
|
+
async pollCliAuthSession(pollToken) {
|
|
43879
|
+
const response = await fetch(
|
|
43880
|
+
`${this.apiUrl}/api/cli/auth/session?session=${encodeURIComponent(pollToken)}`
|
|
43881
|
+
);
|
|
43882
|
+
const payload = await readPayload(response);
|
|
43883
|
+
if (response.status === 202) {
|
|
43884
|
+
return { status: "pending" };
|
|
43885
|
+
}
|
|
43886
|
+
if (response.status === 410) {
|
|
43887
|
+
return {
|
|
43888
|
+
status: "failed",
|
|
43889
|
+
reason: extractErrorMessage(payload, "Auth session expired or failed")
|
|
43890
|
+
};
|
|
43891
|
+
}
|
|
43892
|
+
if (!response.ok) {
|
|
43893
|
+
return {
|
|
43894
|
+
status: "failed",
|
|
43895
|
+
reason: extractErrorMessage(
|
|
43896
|
+
payload,
|
|
43897
|
+
`Auth session error (${response.status})`
|
|
43898
|
+
)
|
|
43899
|
+
};
|
|
43900
|
+
}
|
|
43901
|
+
const result = payload;
|
|
43902
|
+
if (!result.token) {
|
|
43903
|
+
return { status: "failed", reason: "No token in response" };
|
|
43904
|
+
}
|
|
43905
|
+
return {
|
|
43906
|
+
status: "complete",
|
|
43907
|
+
token: result.token,
|
|
43908
|
+
...result.organizationId ? { organizationId: result.organizationId } : {}
|
|
43909
|
+
};
|
|
43910
|
+
}
|
|
43911
|
+
/**
|
|
43912
|
+
* Validate a CLI user token and return the authenticated user's info.
|
|
43913
|
+
* Used by the CLI to verify stored credentials on startup.
|
|
43914
|
+
*/
|
|
43915
|
+
async getCliUserInfo(userToken) {
|
|
43916
|
+
const response = await fetch(`${this.apiUrl}/api/cli/auth/me`, {
|
|
43917
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
43918
|
+
});
|
|
43919
|
+
const payload = await readPayload(response);
|
|
43920
|
+
if (!response.ok) {
|
|
43921
|
+
throw new VocoderAPIError({
|
|
43922
|
+
message: extractErrorMessage(
|
|
43923
|
+
payload,
|
|
43924
|
+
`Token validation failed (${response.status})`
|
|
43925
|
+
),
|
|
43926
|
+
status: response.status,
|
|
43927
|
+
payload
|
|
43928
|
+
});
|
|
43929
|
+
}
|
|
43930
|
+
return payload;
|
|
43931
|
+
}
|
|
43932
|
+
/**
|
|
43933
|
+
* Revoke the given CLI user token server-side.
|
|
43934
|
+
*/
|
|
43935
|
+
async revokeCliToken(userToken) {
|
|
43936
|
+
const response = await fetch(`${this.apiUrl}/api/cli/auth/token`, {
|
|
43937
|
+
method: "DELETE",
|
|
43938
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
43939
|
+
});
|
|
43940
|
+
if (!response.ok) {
|
|
43941
|
+
const payload = await readPayload(response);
|
|
43942
|
+
throw new VocoderAPIError({
|
|
43943
|
+
message: extractErrorMessage(
|
|
43944
|
+
payload,
|
|
43945
|
+
`Token revocation failed (${response.status})`
|
|
43946
|
+
),
|
|
43947
|
+
status: response.status,
|
|
43948
|
+
payload
|
|
43949
|
+
});
|
|
43950
|
+
}
|
|
43951
|
+
}
|
|
43952
|
+
// ── Workspaces ────────────────────────────────────────────────────────────────
|
|
43953
|
+
async listWorkspaces(userToken, params) {
|
|
43954
|
+
const url = new URL(`${this.apiUrl}/api/cli/workspaces`);
|
|
43955
|
+
if (params?.repo) url.searchParams.set("repo", params.repo);
|
|
43956
|
+
const response = await fetch(url.toString(), {
|
|
43957
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
43958
|
+
});
|
|
43959
|
+
const payload = await readPayload(response);
|
|
43960
|
+
if (!response.ok) {
|
|
43961
|
+
throw new VocoderAPIError({
|
|
43962
|
+
message: extractErrorMessage(
|
|
43963
|
+
payload,
|
|
43964
|
+
`Failed to list workspaces (${response.status})`
|
|
43965
|
+
),
|
|
43966
|
+
status: response.status,
|
|
43967
|
+
payload
|
|
43968
|
+
});
|
|
43969
|
+
}
|
|
43970
|
+
return payload;
|
|
43971
|
+
}
|
|
43972
|
+
async listProjects(userToken, organizationId) {
|
|
43973
|
+
const url = new URL(`${this.apiUrl}/api/cli/projects`);
|
|
43974
|
+
url.searchParams.set("organizationId", organizationId);
|
|
43975
|
+
const response = await fetch(url.toString(), {
|
|
43976
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
43977
|
+
});
|
|
43978
|
+
const payload = await readPayload(response);
|
|
43979
|
+
if (!response.ok) {
|
|
43980
|
+
throw new VocoderAPIError({
|
|
43981
|
+
message: extractErrorMessage(
|
|
43982
|
+
payload,
|
|
43983
|
+
`Failed to list projects (${response.status})`
|
|
43984
|
+
),
|
|
43985
|
+
status: response.status,
|
|
43986
|
+
payload
|
|
43987
|
+
});
|
|
43988
|
+
}
|
|
43989
|
+
const result = payload;
|
|
43990
|
+
return result.projects;
|
|
43991
|
+
}
|
|
43992
|
+
async regenerateProjectApiKey(userToken, projectId) {
|
|
43993
|
+
const response = await fetch(
|
|
43994
|
+
`${this.apiUrl}/api/cli/project/regenerate-key`,
|
|
43995
|
+
{
|
|
43996
|
+
method: "POST",
|
|
43997
|
+
headers: {
|
|
43998
|
+
"Content-Type": "application/json",
|
|
43999
|
+
Authorization: `Bearer ${userToken}`
|
|
44000
|
+
},
|
|
44001
|
+
body: JSON.stringify({ projectId })
|
|
44002
|
+
}
|
|
44003
|
+
);
|
|
44004
|
+
const payload = await readPayload(response);
|
|
44005
|
+
if (!response.ok) {
|
|
44006
|
+
throw new VocoderAPIError({
|
|
44007
|
+
message: extractErrorMessage(
|
|
44008
|
+
payload,
|
|
44009
|
+
`Failed to regenerate API key (${response.status})`
|
|
44010
|
+
),
|
|
44011
|
+
status: response.status,
|
|
44012
|
+
payload
|
|
44013
|
+
});
|
|
44014
|
+
}
|
|
44015
|
+
return payload;
|
|
44016
|
+
}
|
|
44017
|
+
// ── CLI GitHub endpoints ──────────────────────────────────────────────────────
|
|
44018
|
+
async startCliGitHubInstall(userToken, params) {
|
|
44019
|
+
const response = await fetch(
|
|
44020
|
+
`${this.apiUrl}/api/cli/github/install/start`,
|
|
44021
|
+
{
|
|
44022
|
+
method: "POST",
|
|
44023
|
+
headers: {
|
|
44024
|
+
Authorization: `Bearer ${userToken}`,
|
|
44025
|
+
"Content-Type": "application/json"
|
|
44026
|
+
},
|
|
44027
|
+
body: JSON.stringify(params)
|
|
44028
|
+
}
|
|
44029
|
+
);
|
|
44030
|
+
const payload = await readPayload(response);
|
|
44031
|
+
if (!response.ok) {
|
|
44032
|
+
throw new VocoderAPIError({
|
|
44033
|
+
message: extractErrorMessage(
|
|
44034
|
+
payload,
|
|
44035
|
+
`Failed to start GitHub install (${response.status})`
|
|
44036
|
+
),
|
|
44037
|
+
status: response.status,
|
|
44038
|
+
payload
|
|
44039
|
+
});
|
|
44040
|
+
}
|
|
44041
|
+
return payload;
|
|
44042
|
+
}
|
|
44043
|
+
/**
|
|
44044
|
+
* Start the "link existing installation" discovery flow.
|
|
44045
|
+
* Unlike startCliGitHubOAuth, this requires no bearer token — the Vocoder
|
|
44046
|
+
* account is created from the OAuth code in the callback.
|
|
44047
|
+
*/
|
|
44048
|
+
async startCliGitHubLinkSession(sessionId, callbackPort) {
|
|
44049
|
+
const response = await fetch(
|
|
44050
|
+
`${this.apiUrl}/api/cli/github/oauth/link-start`,
|
|
44051
|
+
{
|
|
44052
|
+
method: "POST",
|
|
44053
|
+
headers: { "Content-Type": "application/json" },
|
|
44054
|
+
body: JSON.stringify({
|
|
44055
|
+
sessionId,
|
|
44056
|
+
...callbackPort != null ? { callbackPort } : {}
|
|
44057
|
+
})
|
|
44058
|
+
}
|
|
44059
|
+
);
|
|
44060
|
+
const payload = await readPayload(response);
|
|
44061
|
+
if (!response.ok) {
|
|
44062
|
+
throw new VocoderAPIError({
|
|
44063
|
+
message: extractErrorMessage(
|
|
44064
|
+
payload,
|
|
44065
|
+
`Failed to start GitHub link session (${response.status})`
|
|
44066
|
+
),
|
|
44067
|
+
status: response.status,
|
|
44068
|
+
payload
|
|
44069
|
+
});
|
|
44070
|
+
}
|
|
44071
|
+
return payload;
|
|
44072
|
+
}
|
|
44073
|
+
async startCliGitHubOAuth(userToken, params) {
|
|
44074
|
+
const response = await fetch(`${this.apiUrl}/api/cli/github/oauth/start`, {
|
|
44075
|
+
method: "POST",
|
|
44076
|
+
headers: {
|
|
44077
|
+
Authorization: `Bearer ${userToken}`,
|
|
44078
|
+
"Content-Type": "application/json"
|
|
44079
|
+
},
|
|
44080
|
+
body: JSON.stringify(params)
|
|
44081
|
+
});
|
|
44082
|
+
const payload = await readPayload(response);
|
|
44083
|
+
if (!response.ok) {
|
|
44084
|
+
throw new VocoderAPIError({
|
|
44085
|
+
message: extractErrorMessage(
|
|
44086
|
+
payload,
|
|
44087
|
+
`Failed to start GitHub OAuth (${response.status})`
|
|
44088
|
+
),
|
|
44089
|
+
status: response.status,
|
|
44090
|
+
payload
|
|
44091
|
+
});
|
|
44092
|
+
}
|
|
44093
|
+
return payload;
|
|
44094
|
+
}
|
|
44095
|
+
async getCliGitHubDiscovery(userToken) {
|
|
44096
|
+
const response = await fetch(`${this.apiUrl}/api/cli/github/discovery`, {
|
|
44097
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
44098
|
+
});
|
|
44099
|
+
const payload = await readPayload(response);
|
|
44100
|
+
if (!response.ok) {
|
|
44101
|
+
throw new VocoderAPIError({
|
|
44102
|
+
message: extractErrorMessage(
|
|
44103
|
+
payload,
|
|
44104
|
+
`Failed to fetch GitHub discovery (${response.status})`
|
|
44105
|
+
),
|
|
44106
|
+
status: response.status,
|
|
44107
|
+
payload
|
|
44108
|
+
});
|
|
44109
|
+
}
|
|
44110
|
+
return payload;
|
|
44111
|
+
}
|
|
44112
|
+
async claimCliGitHubInstallation(userToken, params) {
|
|
44113
|
+
const response = await fetch(`${this.apiUrl}/api/cli/github/claim`, {
|
|
44114
|
+
method: "POST",
|
|
44115
|
+
headers: {
|
|
44116
|
+
Authorization: `Bearer ${userToken}`,
|
|
44117
|
+
"Content-Type": "application/json"
|
|
44118
|
+
},
|
|
44119
|
+
body: JSON.stringify(params)
|
|
44120
|
+
});
|
|
44121
|
+
const payload = await readPayload(response);
|
|
44122
|
+
if (!response.ok) {
|
|
44123
|
+
throw new VocoderAPIError({
|
|
44124
|
+
message: extractErrorMessage(
|
|
44125
|
+
payload,
|
|
44126
|
+
`Failed to claim GitHub installation (${response.status})`
|
|
44127
|
+
),
|
|
44128
|
+
status: response.status,
|
|
44129
|
+
payload
|
|
44130
|
+
});
|
|
44131
|
+
}
|
|
44132
|
+
return payload;
|
|
44133
|
+
}
|
|
44134
|
+
// ── Locales ───────────────────────────────────────────────────────────────────
|
|
44135
|
+
async listLocales(userToken) {
|
|
44136
|
+
const response = await fetch(`${this.apiUrl}/api/cli/locales`, {
|
|
44137
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
44138
|
+
});
|
|
44139
|
+
const payload = await readPayload(response);
|
|
44140
|
+
if (!response.ok) {
|
|
44141
|
+
throw new VocoderAPIError({
|
|
44142
|
+
message: extractErrorMessage(
|
|
44143
|
+
payload,
|
|
44144
|
+
`Failed to list locales (${response.status})`
|
|
44145
|
+
),
|
|
44146
|
+
status: response.status,
|
|
44147
|
+
payload
|
|
44148
|
+
});
|
|
44149
|
+
}
|
|
44150
|
+
const result = payload;
|
|
44151
|
+
return result;
|
|
44152
|
+
}
|
|
44153
|
+
async listCompatibleLocales(userToken, sourceLocale) {
|
|
44154
|
+
const url = `${this.apiUrl}/api/cli/locales/compatible?source=${encodeURIComponent(sourceLocale)}`;
|
|
44155
|
+
const response = await fetch(url, {
|
|
44156
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
44157
|
+
});
|
|
44158
|
+
const payload = await readPayload(response);
|
|
44159
|
+
if (!response.ok) {
|
|
44160
|
+
throw new VocoderAPIError({
|
|
44161
|
+
message: extractErrorMessage(
|
|
44162
|
+
payload,
|
|
44163
|
+
`Failed to list compatible locales (${response.status})`
|
|
44164
|
+
),
|
|
44165
|
+
status: response.status,
|
|
44166
|
+
payload
|
|
44167
|
+
});
|
|
44168
|
+
}
|
|
44169
|
+
const result = payload;
|
|
44170
|
+
return result.locales;
|
|
44171
|
+
}
|
|
44172
|
+
// ── Project creation ──────────────────────────────────────────────────────────
|
|
44173
|
+
async createProject(userToken, params) {
|
|
44174
|
+
const response = await fetch(`${this.apiUrl}/api/cli/projects`, {
|
|
44175
|
+
method: "POST",
|
|
44176
|
+
headers: {
|
|
44177
|
+
"Content-Type": "application/json",
|
|
44178
|
+
Authorization: `Bearer ${userToken}`
|
|
44179
|
+
},
|
|
44180
|
+
body: JSON.stringify(params)
|
|
44181
|
+
});
|
|
44182
|
+
const payload = await readPayload(response);
|
|
44183
|
+
if (!response.ok) {
|
|
44184
|
+
throw new VocoderAPIError({
|
|
44185
|
+
message: extractErrorMessage(
|
|
44186
|
+
payload,
|
|
44187
|
+
`Failed to create project (${response.status})`
|
|
44188
|
+
),
|
|
44189
|
+
status: response.status,
|
|
44190
|
+
payload
|
|
44191
|
+
});
|
|
44192
|
+
}
|
|
44193
|
+
return payload;
|
|
44194
|
+
}
|
|
44195
|
+
// ── Project lookup ────────────────────────────────────────────────────────────
|
|
44196
|
+
/**
|
|
44197
|
+
* Look up all project apps for a given repo. Returns info about exact matches,
|
|
44198
|
+
* existing apps in other scopes, and whether a whole-repo app exists.
|
|
44199
|
+
* No auth required.
|
|
44200
|
+
*/
|
|
44201
|
+
async lookupProjectByRepo(params) {
|
|
44202
|
+
try {
|
|
44203
|
+
const response = await fetch(`${this.apiUrl}/api/cli/init/lookup`, {
|
|
44204
|
+
method: "POST",
|
|
44205
|
+
headers: { "Content-Type": "application/json" },
|
|
44206
|
+
body: JSON.stringify({
|
|
44207
|
+
repo: params.repoCanonical,
|
|
44208
|
+
appDir: params.appDir
|
|
44209
|
+
})
|
|
44210
|
+
});
|
|
44211
|
+
if (!response.ok) {
|
|
44212
|
+
return { exactMatch: null, existingApps: [], hasWholeRepoApp: false };
|
|
44213
|
+
}
|
|
44214
|
+
const data = await response.json();
|
|
44215
|
+
return {
|
|
44216
|
+
exactMatch: data.exactMatch ?? null,
|
|
44217
|
+
existingApps: data.existingApps ?? [],
|
|
44218
|
+
hasWholeRepoApp: data.hasWholeRepoApp ?? false
|
|
44219
|
+
};
|
|
44220
|
+
} catch {
|
|
44221
|
+
return { exactMatch: null, existingApps: [], hasWholeRepoApp: false };
|
|
44222
|
+
}
|
|
44223
|
+
}
|
|
44224
|
+
/**
|
|
44225
|
+
* Add a new ProjectApp to an existing project (monorepo: new app directory).
|
|
44226
|
+
* Does not check plan limits — no new project is created.
|
|
44227
|
+
*/
|
|
44228
|
+
async createProjectApp(userToken, params) {
|
|
44229
|
+
const response = await fetch(`${this.apiUrl}/api/cli/project/apps`, {
|
|
44230
|
+
method: "POST",
|
|
44231
|
+
headers: {
|
|
44232
|
+
"Content-Type": "application/json",
|
|
44233
|
+
Authorization: `Bearer ${userToken}`
|
|
44234
|
+
},
|
|
44235
|
+
body: JSON.stringify(params)
|
|
44236
|
+
});
|
|
44237
|
+
const payload = await readPayload(response);
|
|
44238
|
+
if (!response.ok) {
|
|
44239
|
+
throw new VocoderAPIError({
|
|
44240
|
+
message: extractErrorMessage(
|
|
44241
|
+
payload,
|
|
44242
|
+
`Failed to create project app (${response.status})`
|
|
44243
|
+
),
|
|
44244
|
+
status: response.status,
|
|
44245
|
+
payload
|
|
44246
|
+
});
|
|
44247
|
+
}
|
|
44248
|
+
return payload;
|
|
44249
|
+
}
|
|
44250
|
+
};
|
|
44251
|
+
|
|
43571
44252
|
// src/utils/detect-local.ts
|
|
43572
44253
|
import { existsSync, readFileSync } from "fs";
|
|
43573
44254
|
import { join } from "path";
|
|
@@ -43679,6 +44360,48 @@ function getPackagesToInstall(detection) {
|
|
|
43679
44360
|
return { devPackages, runtimePackages };
|
|
43680
44361
|
}
|
|
43681
44362
|
|
|
44363
|
+
// src/utils/auth-store.ts
|
|
44364
|
+
import { mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
|
|
44365
|
+
import { homedir } from "os";
|
|
44366
|
+
import { dirname, join as join2 } from "path";
|
|
44367
|
+
function getAuthFilePath() {
|
|
44368
|
+
return join2(homedir(), ".config", "vocoder", "auth.json");
|
|
44369
|
+
}
|
|
44370
|
+
function readAuthData() {
|
|
44371
|
+
const filePath = getAuthFilePath();
|
|
44372
|
+
try {
|
|
44373
|
+
const raw = readFileSync2(filePath, "utf8");
|
|
44374
|
+
const parsed = JSON.parse(raw);
|
|
44375
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
44376
|
+
const data = parsed;
|
|
44377
|
+
if (typeof data.token !== "string" || typeof data.userId !== "string" || typeof data.email !== "string" || typeof data.createdAt !== "string") {
|
|
44378
|
+
return null;
|
|
44379
|
+
}
|
|
44380
|
+
return {
|
|
44381
|
+
token: data.token,
|
|
44382
|
+
userId: data.userId,
|
|
44383
|
+
email: data.email,
|
|
44384
|
+
name: typeof data.name === "string" ? data.name : null,
|
|
44385
|
+
createdAt: data.createdAt
|
|
44386
|
+
};
|
|
44387
|
+
} catch {
|
|
44388
|
+
return null;
|
|
44389
|
+
}
|
|
44390
|
+
}
|
|
44391
|
+
function writeAuthData(data) {
|
|
44392
|
+
const filePath = getAuthFilePath();
|
|
44393
|
+
const dir = dirname(filePath);
|
|
44394
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
44395
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2), { mode: 384 });
|
|
44396
|
+
}
|
|
44397
|
+
function clearAuthData() {
|
|
44398
|
+
const filePath = getAuthFilePath();
|
|
44399
|
+
try {
|
|
44400
|
+
unlinkSync(filePath);
|
|
44401
|
+
} catch {
|
|
44402
|
+
}
|
|
44403
|
+
}
|
|
44404
|
+
|
|
43682
44405
|
// src/utils/setup-snippets.ts
|
|
43683
44406
|
function getSetupSnippets(params) {
|
|
43684
44407
|
const { framework, ecosystem, sourceLocale } = params;
|
|
@@ -43840,28 +44563,28 @@ import { T } from '@vocoder/vue';
|
|
|
43840
44563
|
// ../extractor/src/config.ts
|
|
43841
44564
|
var import_parser = __toESM(require_lib());
|
|
43842
44565
|
var import_traverse = __toESM(require_lib8());
|
|
43843
|
-
import { existsSync as existsSync2, readFileSync as
|
|
43844
|
-
import { join as
|
|
44566
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
44567
|
+
import { join as join3 } from "path";
|
|
43845
44568
|
var traverse = import_traverse.default.default || import_traverse.default;
|
|
43846
44569
|
function loadVocoderConfig(cwd) {
|
|
43847
44570
|
const candidates = [
|
|
43848
|
-
|
|
43849
|
-
|
|
43850
|
-
|
|
43851
|
-
|
|
44571
|
+
join3(cwd, "vocoder.config.ts"),
|
|
44572
|
+
join3(cwd, "vocoder.config.js"),
|
|
44573
|
+
join3(cwd, "vocoder.config.mjs"),
|
|
44574
|
+
join3(cwd, "vocoder.config.cjs")
|
|
43852
44575
|
];
|
|
43853
44576
|
for (const configPath of candidates) {
|
|
43854
44577
|
if (!existsSync2(configPath)) continue;
|
|
43855
44578
|
try {
|
|
43856
|
-
const code =
|
|
44579
|
+
const code = readFileSync3(configPath, "utf-8");
|
|
43857
44580
|
return parseConfigFromSource(code);
|
|
43858
44581
|
} catch {
|
|
43859
44582
|
}
|
|
43860
44583
|
}
|
|
43861
|
-
const jsonPath =
|
|
44584
|
+
const jsonPath = join3(cwd, "vocoder.config.json");
|
|
43862
44585
|
if (existsSync2(jsonPath)) {
|
|
43863
44586
|
try {
|
|
43864
|
-
return JSON.parse(
|
|
44587
|
+
return JSON.parse(readFileSync3(jsonPath, "utf-8"));
|
|
43865
44588
|
} catch {
|
|
43866
44589
|
return null;
|
|
43867
44590
|
}
|
|
@@ -43909,6 +44632,12 @@ function extractFromObject(obj) {
|
|
|
43909
44632
|
if (key === "localesPath" && prop.value.type === "StringLiteral") {
|
|
43910
44633
|
config.localesPath = prop.value.value;
|
|
43911
44634
|
}
|
|
44635
|
+
if (key === "appIndustry" && prop.value.type === "StringLiteral") {
|
|
44636
|
+
config.appIndustry = prop.value.value;
|
|
44637
|
+
}
|
|
44638
|
+
if (key === "formality" && prop.value.type === "StringLiteral") {
|
|
44639
|
+
config.formality = prop.value.value;
|
|
44640
|
+
}
|
|
43912
44641
|
}
|
|
43913
44642
|
return config;
|
|
43914
44643
|
}
|
|
@@ -43916,7 +44645,7 @@ function extractFromObject(obj) {
|
|
|
43916
44645
|
// ../extractor/src/index.ts
|
|
43917
44646
|
var import_parser2 = __toESM(require_lib());
|
|
43918
44647
|
var import_traverse2 = __toESM(require_lib8());
|
|
43919
|
-
import { readFileSync as
|
|
44648
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
43920
44649
|
import { relative as pathRelative } from "path";
|
|
43921
44650
|
|
|
43922
44651
|
// ../../node_modules/.pnpm/minimatch@9.0.5/node_modules/minimatch/dist/esm/index.js
|
|
@@ -50393,249 +51122,324 @@ var StringExtractor = class {
|
|
|
50393
51122
|
const sortedFiles = Array.from(allFiles).sort();
|
|
50394
51123
|
for (const file of sortedFiles) {
|
|
50395
51124
|
try {
|
|
50396
|
-
const
|
|
51125
|
+
const code = readFileSync4(file, "utf-8");
|
|
51126
|
+
const relPath = pathRelative(projectRoot, file).split("\\").join("/");
|
|
51127
|
+
const strings = _extractFromContent(relPath, code);
|
|
50397
51128
|
allStrings.push(...strings);
|
|
50398
51129
|
} catch (error) {
|
|
50399
51130
|
console.warn(`Warning: Failed to extract from ${file}:`, error);
|
|
50400
51131
|
}
|
|
50401
51132
|
}
|
|
50402
|
-
return
|
|
51133
|
+
return deduplicateStrings(allStrings);
|
|
50403
51134
|
}
|
|
50404
|
-
|
|
50405
|
-
|
|
50406
|
-
|
|
50407
|
-
|
|
50408
|
-
|
|
50409
|
-
|
|
50410
|
-
|
|
50411
|
-
|
|
50412
|
-
|
|
50413
|
-
|
|
50414
|
-
|
|
50415
|
-
|
|
50416
|
-
|
|
50417
|
-
|
|
50418
|
-
|
|
50419
|
-
|
|
50420
|
-
|
|
50421
|
-
|
|
50422
|
-
|
|
50423
|
-
|
|
50424
|
-
|
|
50425
|
-
|
|
50426
|
-
|
|
50427
|
-
|
|
50428
|
-
|
|
51135
|
+
};
|
|
51136
|
+
function propNameToUiRole(propName) {
|
|
51137
|
+
switch (propName) {
|
|
51138
|
+
case "placeholder":
|
|
51139
|
+
return "input_placeholder";
|
|
51140
|
+
case "aria-label":
|
|
51141
|
+
case "aria-description":
|
|
51142
|
+
case "label":
|
|
51143
|
+
return "input_label";
|
|
51144
|
+
case "alt":
|
|
51145
|
+
return "image_alt";
|
|
51146
|
+
case "title":
|
|
51147
|
+
return "tooltip";
|
|
51148
|
+
default:
|
|
51149
|
+
return "unknown";
|
|
51150
|
+
}
|
|
51151
|
+
}
|
|
51152
|
+
function elementNameToUiRole(name) {
|
|
51153
|
+
if (!name) return "unknown";
|
|
51154
|
+
switch (name.toLowerCase()) {
|
|
51155
|
+
case "button":
|
|
51156
|
+
return "button_label";
|
|
51157
|
+
case "h1":
|
|
51158
|
+
case "h2":
|
|
51159
|
+
case "h3":
|
|
51160
|
+
case "h4":
|
|
51161
|
+
case "h5":
|
|
51162
|
+
case "h6":
|
|
51163
|
+
return "heading";
|
|
51164
|
+
case "label":
|
|
51165
|
+
return "input_label";
|
|
51166
|
+
case "th":
|
|
51167
|
+
return "table_header";
|
|
51168
|
+
case "option":
|
|
51169
|
+
return "option_label";
|
|
51170
|
+
case "title":
|
|
51171
|
+
return "page_title";
|
|
51172
|
+
case "p":
|
|
51173
|
+
case "li":
|
|
51174
|
+
case "dd":
|
|
51175
|
+
return "body_text";
|
|
51176
|
+
// Custom component name heuristics
|
|
51177
|
+
default: {
|
|
51178
|
+
const lower = name.toLowerCase();
|
|
51179
|
+
if (/button|btn|submit|cta/.test(lower)) return "button_label";
|
|
51180
|
+
if (/heading|headline/.test(lower)) return "heading";
|
|
51181
|
+
if (/label/.test(lower)) return "input_label";
|
|
51182
|
+
if (/tooltip|hint|popover/.test(lower)) return "tooltip";
|
|
51183
|
+
if (/badge|chip|tag|pill/.test(lower)) return "badge";
|
|
51184
|
+
if (/toast|snackbar|notification/.test(lower)) return "toast";
|
|
51185
|
+
if (/navitem|menuitem/.test(lower)) return "nav_item";
|
|
51186
|
+
return "unknown";
|
|
51187
|
+
}
|
|
51188
|
+
}
|
|
51189
|
+
}
|
|
51190
|
+
function detectUiRole(path2) {
|
|
51191
|
+
const parent = path2.parent;
|
|
51192
|
+
if (!parent) return "unknown";
|
|
51193
|
+
if (parent.type === "JSXExpressionContainer") {
|
|
51194
|
+
const attrNode = path2.parentPath?.parent;
|
|
51195
|
+
if (attrNode?.type === "JSXAttribute") {
|
|
51196
|
+
const propName = attrNode.name?.type === "JSXNamespacedName" ? `${attrNode.name.namespace.name}-${attrNode.name.name.name}` : attrNode.name?.name ?? "";
|
|
51197
|
+
return propNameToUiRole(propName);
|
|
51198
|
+
}
|
|
51199
|
+
}
|
|
51200
|
+
if (parent.type === "JSXElement") {
|
|
51201
|
+
const opening = parent.openingElement;
|
|
51202
|
+
const tagName = opening?.name?.type === "JSXMemberExpression" ? "unknown" : opening?.name?.name ?? "";
|
|
51203
|
+
return elementNameToUiRole(tagName);
|
|
51204
|
+
}
|
|
51205
|
+
return "unknown";
|
|
51206
|
+
}
|
|
51207
|
+
function _extractFromContent(filePath, content) {
|
|
51208
|
+
const strings = [];
|
|
51209
|
+
try {
|
|
51210
|
+
const ast = (0, import_parser2.parse)(content, {
|
|
51211
|
+
sourceType: "module",
|
|
51212
|
+
plugins: ["jsx", "typescript"]
|
|
51213
|
+
});
|
|
51214
|
+
const vocoderImports = /* @__PURE__ */ new Map();
|
|
51215
|
+
const tFunctionNames = /* @__PURE__ */ new Set();
|
|
51216
|
+
traverse2(ast, {
|
|
51217
|
+
ImportDeclaration: (path2) => {
|
|
51218
|
+
const source = path2.node.source.value;
|
|
51219
|
+
if (source === "@vocoder/react") {
|
|
51220
|
+
path2.node.specifiers.forEach((spec) => {
|
|
51221
|
+
if (spec.type === "ImportSpecifier") {
|
|
51222
|
+
const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
|
|
51223
|
+
const local = spec.local.name;
|
|
51224
|
+
if (imported === "T") {
|
|
51225
|
+
vocoderImports.set(local, "T");
|
|
50429
51226
|
}
|
|
50430
|
-
|
|
50431
|
-
|
|
50432
|
-
},
|
|
50433
|
-
VariableDeclarator: (path2) => {
|
|
50434
|
-
const init = path2.node.init;
|
|
50435
|
-
if (init && init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "useVocoder" && path2.node.id.type === "ObjectPattern") {
|
|
50436
|
-
path2.node.id.properties.forEach((prop) => {
|
|
50437
|
-
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "t") {
|
|
50438
|
-
const localName = prop.value.type === "Identifier" ? prop.value.name : "t";
|
|
50439
|
-
tFunctionNames.add(localName);
|
|
51227
|
+
if (imported === "t") {
|
|
51228
|
+
tFunctionNames.add(local);
|
|
50440
51229
|
}
|
|
50441
|
-
}
|
|
50442
|
-
}
|
|
50443
|
-
}
|
|
50444
|
-
|
|
50445
|
-
|
|
50446
|
-
|
|
50447
|
-
|
|
50448
|
-
|
|
50449
|
-
|
|
50450
|
-
|
|
50451
|
-
|
|
50452
|
-
|
|
50453
|
-
}
|
|
50454
|
-
|
|
50455
|
-
|
|
50456
|
-
|
|
50457
|
-
|
|
50458
|
-
|
|
50459
|
-
|
|
50460
|
-
|
|
50461
|
-
|
|
50462
|
-
|
|
50463
|
-
|
|
50464
|
-
|
|
50465
|
-
|
|
50466
|
-
|
|
50467
|
-
|
|
50468
|
-
|
|
50469
|
-
|
|
50470
|
-
|
|
50471
|
-
|
|
50472
|
-
|
|
51230
|
+
}
|
|
51231
|
+
});
|
|
51232
|
+
}
|
|
51233
|
+
},
|
|
51234
|
+
VariableDeclarator: (path2) => {
|
|
51235
|
+
const init = path2.node.init;
|
|
51236
|
+
if (init && init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "useVocoder" && path2.node.id.type === "ObjectPattern") {
|
|
51237
|
+
path2.node.id.properties.forEach((prop) => {
|
|
51238
|
+
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "t") {
|
|
51239
|
+
const localName = prop.value.type === "Identifier" ? prop.value.name : "t";
|
|
51240
|
+
tFunctionNames.add(localName);
|
|
51241
|
+
}
|
|
51242
|
+
});
|
|
51243
|
+
}
|
|
51244
|
+
},
|
|
51245
|
+
CallExpression: (path2) => {
|
|
51246
|
+
const callee = path2.node.callee;
|
|
51247
|
+
const isTFunction = callee.type === "Identifier" && tFunctionNames.has(callee.name);
|
|
51248
|
+
if (!isTFunction) return;
|
|
51249
|
+
const firstArg = path2.node.arguments[0];
|
|
51250
|
+
if (!firstArg) return;
|
|
51251
|
+
let text = null;
|
|
51252
|
+
if (firstArg.type === "StringLiteral") {
|
|
51253
|
+
text = firstArg.value;
|
|
51254
|
+
} else if (firstArg.type === "TemplateLiteral") {
|
|
51255
|
+
text = extractTemplateText(firstArg);
|
|
51256
|
+
}
|
|
51257
|
+
if (!text || text.trim().length === 0) return;
|
|
51258
|
+
const optionsArg = path2.node.arguments[2];
|
|
51259
|
+
let context;
|
|
51260
|
+
let formality;
|
|
51261
|
+
let explicitKey;
|
|
51262
|
+
if (optionsArg && optionsArg.type === "ObjectExpression") {
|
|
51263
|
+
optionsArg.properties.forEach((prop) => {
|
|
51264
|
+
if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
|
|
51265
|
+
if (prop.key.name === "context" && prop.value.type === "StringLiteral") {
|
|
51266
|
+
context = prop.value.value;
|
|
50473
51267
|
}
|
|
50474
|
-
|
|
50475
|
-
|
|
50476
|
-
|
|
50477
|
-
|
|
50478
|
-
|
|
50479
|
-
|
|
50480
|
-
|
|
50481
|
-
file: filePath,
|
|
50482
|
-
line,
|
|
50483
|
-
context,
|
|
50484
|
-
formality
|
|
51268
|
+
if (prop.key.name === "formality" && prop.value.type === "StringLiteral") {
|
|
51269
|
+
formality = prop.value.value;
|
|
51270
|
+
}
|
|
51271
|
+
if (prop.key.name === "id" && prop.value.type === "StringLiteral") {
|
|
51272
|
+
explicitKey = prop.value.value.trim();
|
|
51273
|
+
}
|
|
51274
|
+
}
|
|
50485
51275
|
});
|
|
50486
|
-
}
|
|
50487
|
-
|
|
50488
|
-
|
|
50489
|
-
|
|
50490
|
-
|
|
50491
|
-
|
|
50492
|
-
|
|
50493
|
-
|
|
50494
|
-
|
|
50495
|
-
|
|
50496
|
-
|
|
51276
|
+
}
|
|
51277
|
+
const line = path2.node.loc?.start.line || 0;
|
|
51278
|
+
const key = explicitKey && explicitKey.length > 0 ? explicitKey : generateMessageHash(text.trim(), context);
|
|
51279
|
+
const uiRole = detectUiRole(path2);
|
|
51280
|
+
strings.push({
|
|
51281
|
+
key,
|
|
51282
|
+
text: text.trim(),
|
|
51283
|
+
file: filePath,
|
|
51284
|
+
line,
|
|
51285
|
+
context,
|
|
51286
|
+
formality,
|
|
51287
|
+
uiRole: uiRole !== "unknown" ? uiRole : void 0
|
|
51288
|
+
});
|
|
51289
|
+
},
|
|
51290
|
+
JSXElement: (path2) => {
|
|
51291
|
+
const opening = path2.node.openingElement;
|
|
51292
|
+
const tagName = opening.name.type === "JSXIdentifier" ? opening.name.name : null;
|
|
51293
|
+
if (!tagName) return;
|
|
51294
|
+
const isTranslationComponent = vocoderImports.has(tagName);
|
|
51295
|
+
if (!isTranslationComponent) return;
|
|
51296
|
+
const msgAttribute = getStringAttribute(opening.attributes, "message");
|
|
51297
|
+
let text = null;
|
|
51298
|
+
if (msgAttribute) {
|
|
51299
|
+
text = msgAttribute;
|
|
51300
|
+
} else {
|
|
51301
|
+
const pluralSelectICU = extractPluralSelectICU(opening.attributes);
|
|
51302
|
+
if (pluralSelectICU) {
|
|
51303
|
+
text = pluralSelectICU;
|
|
50497
51304
|
} else {
|
|
50498
|
-
|
|
50499
|
-
opening.attributes
|
|
50500
|
-
);
|
|
50501
|
-
if (pluralSelectICU) {
|
|
50502
|
-
text = pluralSelectICU;
|
|
50503
|
-
} else {
|
|
50504
|
-
text = extractTextContentFromNodes(path2.node.children, { count: 0 });
|
|
50505
|
-
}
|
|
51305
|
+
text = extractTextContentFromNodes(path2.node.children, { count: 0 });
|
|
50506
51306
|
}
|
|
50507
|
-
if (!text || text.trim().length === 0) return;
|
|
50508
|
-
const id = this.getStringAttribute(opening.attributes, "id");
|
|
50509
|
-
const context = this.getStringAttribute(
|
|
50510
|
-
opening.attributes,
|
|
50511
|
-
"context"
|
|
50512
|
-
);
|
|
50513
|
-
const formality = this.getStringAttribute(
|
|
50514
|
-
opening.attributes,
|
|
50515
|
-
"formality"
|
|
50516
|
-
);
|
|
50517
|
-
const line = path2.node.loc?.start.line || 0;
|
|
50518
|
-
const key = id && id.trim().length > 0 ? id.trim() : generateMessageHash(text.trim(), context);
|
|
50519
|
-
strings.push({
|
|
50520
|
-
key,
|
|
50521
|
-
text: text.trim(),
|
|
50522
|
-
file: filePath,
|
|
50523
|
-
line,
|
|
50524
|
-
context,
|
|
50525
|
-
formality
|
|
50526
|
-
});
|
|
50527
51307
|
}
|
|
50528
|
-
|
|
50529
|
-
|
|
50530
|
-
|
|
50531
|
-
|
|
50532
|
-
|
|
50533
|
-
|
|
50534
|
-
|
|
50535
|
-
|
|
50536
|
-
|
|
50537
|
-
|
|
50538
|
-
|
|
50539
|
-
|
|
50540
|
-
|
|
50541
|
-
|
|
50542
|
-
|
|
50543
|
-
|
|
50544
|
-
|
|
50545
|
-
|
|
50546
|
-
|
|
50547
|
-
if (name === "ordinal") {
|
|
50548
|
-
isOrdinal = true;
|
|
50549
|
-
continue;
|
|
50550
|
-
}
|
|
50551
|
-
if (name === "gender") {
|
|
50552
|
-
hasGender = true;
|
|
50553
|
-
continue;
|
|
50554
|
-
}
|
|
50555
|
-
const value = attr.value?.type === "StringLiteral" ? attr.value.value : null;
|
|
50556
|
-
if (!value) continue;
|
|
50557
|
-
if (PLURAL_CLDR.has(name) || /^_\d+$/.test(name)) {
|
|
50558
|
-
pluralProps[name] = value;
|
|
50559
|
-
hasPlural = true;
|
|
50560
|
-
} else if (name === "other") {
|
|
50561
|
-
otherValue = value;
|
|
50562
|
-
} else if (/^_[a-zA-Z]/.test(name)) {
|
|
50563
|
-
selectProps[name] = value;
|
|
50564
|
-
hasSelect = true;
|
|
51308
|
+
if (!text || text.trim().length === 0) return;
|
|
51309
|
+
const id = getStringAttribute(opening.attributes, "id");
|
|
51310
|
+
const context = getStringAttribute(opening.attributes, "context");
|
|
51311
|
+
const formality = getStringAttribute(
|
|
51312
|
+
opening.attributes,
|
|
51313
|
+
"formality"
|
|
51314
|
+
);
|
|
51315
|
+
const line = path2.node.loc?.start.line || 0;
|
|
51316
|
+
const key = id && id.trim().length > 0 ? id.trim() : generateMessageHash(text.trim(), context);
|
|
51317
|
+
const uiRole = detectUiRole(path2);
|
|
51318
|
+
strings.push({
|
|
51319
|
+
key,
|
|
51320
|
+
text: text.trim(),
|
|
51321
|
+
file: filePath,
|
|
51322
|
+
line,
|
|
51323
|
+
context,
|
|
51324
|
+
formality,
|
|
51325
|
+
uiRole: uiRole !== "unknown" ? uiRole : void 0
|
|
51326
|
+
});
|
|
50565
51327
|
}
|
|
51328
|
+
});
|
|
51329
|
+
} catch (error) {
|
|
51330
|
+
throw new Error(
|
|
51331
|
+
`Failed to parse ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
51332
|
+
);
|
|
51333
|
+
}
|
|
51334
|
+
return strings;
|
|
51335
|
+
}
|
|
51336
|
+
function extractPluralSelectICU(attributes) {
|
|
51337
|
+
const pluralProps = {};
|
|
51338
|
+
const selectProps = {};
|
|
51339
|
+
let otherValue;
|
|
51340
|
+
let hasPlural = false;
|
|
51341
|
+
let hasSelect = false;
|
|
51342
|
+
let isOrdinal = false;
|
|
51343
|
+
let hasGender = false;
|
|
51344
|
+
for (const attr of attributes) {
|
|
51345
|
+
if (attr.type !== "JSXAttribute") continue;
|
|
51346
|
+
const name = attr.name.name;
|
|
51347
|
+
if (name === "ordinal") {
|
|
51348
|
+
isOrdinal = true;
|
|
51349
|
+
continue;
|
|
50566
51350
|
}
|
|
50567
|
-
if (
|
|
50568
|
-
|
|
50569
|
-
|
|
50570
|
-
return `{gender, select, masculine {${ordinalICU}} feminine {${ordinalICU}} other {${ordinalICU}}}`;
|
|
50571
|
-
}
|
|
50572
|
-
return ordinalICU;
|
|
51351
|
+
if (name === "gender") {
|
|
51352
|
+
hasGender = true;
|
|
51353
|
+
continue;
|
|
50573
51354
|
}
|
|
50574
|
-
|
|
50575
|
-
if (
|
|
50576
|
-
|
|
50577
|
-
|
|
51355
|
+
const value = attr.value?.type === "StringLiteral" ? attr.value.value : null;
|
|
51356
|
+
if (!value) continue;
|
|
51357
|
+
if (PLURAL_CLDR.has(name) || /^_\d+$/.test(name)) {
|
|
51358
|
+
pluralProps[name] = value;
|
|
51359
|
+
hasPlural = true;
|
|
51360
|
+
} else if (name === "other") {
|
|
51361
|
+
otherValue = value;
|
|
51362
|
+
} else if (/^_[a-zA-Z]/.test(name)) {
|
|
51363
|
+
selectProps[name] = value;
|
|
51364
|
+
hasSelect = true;
|
|
50578
51365
|
}
|
|
50579
|
-
|
|
50580
|
-
|
|
50581
|
-
|
|
51366
|
+
}
|
|
51367
|
+
if (isOrdinal) {
|
|
51368
|
+
const ordinalICU = "{count, selectordinal, other {#}}";
|
|
51369
|
+
if (hasGender) {
|
|
51370
|
+
return `{gender, select, masculine {${ordinalICU}} feminine {${ordinalICU}} other {${ordinalICU}}}`;
|
|
50582
51371
|
}
|
|
50583
|
-
return
|
|
51372
|
+
return ordinalICU;
|
|
50584
51373
|
}
|
|
50585
|
-
|
|
50586
|
-
|
|
50587
|
-
|
|
50588
|
-
|
|
50589
|
-
|
|
50590
|
-
|
|
50591
|
-
|
|
50592
|
-
|
|
50593
|
-
|
|
50594
|
-
|
|
50595
|
-
|
|
50596
|
-
|
|
51374
|
+
if (!hasPlural && !hasSelect) return null;
|
|
51375
|
+
if (hasPlural) {
|
|
51376
|
+
if (otherValue !== void 0) pluralProps.other = otherValue;
|
|
51377
|
+
return buildPluralICU(pluralProps, false);
|
|
51378
|
+
}
|
|
51379
|
+
if (hasSelect) {
|
|
51380
|
+
if (otherValue !== void 0) selectProps.other = otherValue;
|
|
51381
|
+
return buildSelectICU(selectProps);
|
|
51382
|
+
}
|
|
51383
|
+
return null;
|
|
51384
|
+
}
|
|
51385
|
+
function extractTemplateText(node) {
|
|
51386
|
+
let text = "";
|
|
51387
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
51388
|
+
const quasi = node.quasis[i];
|
|
51389
|
+
text += quasi.value.raw;
|
|
51390
|
+
if (i < node.expressions.length) {
|
|
51391
|
+
const expr = node.expressions[i];
|
|
51392
|
+
if (expr.type === "Identifier") {
|
|
51393
|
+
text += `{${expr.name}}`;
|
|
51394
|
+
} else {
|
|
51395
|
+
text += "{value}";
|
|
50597
51396
|
}
|
|
50598
51397
|
}
|
|
50599
|
-
return text;
|
|
50600
51398
|
}
|
|
50601
|
-
|
|
50602
|
-
|
|
50603
|
-
|
|
50604
|
-
|
|
50605
|
-
|
|
50606
|
-
|
|
50607
|
-
|
|
51399
|
+
return text;
|
|
51400
|
+
}
|
|
51401
|
+
function getStringAttribute(attributes, name) {
|
|
51402
|
+
const attr = attributes.find(
|
|
51403
|
+
(a) => a.type === "JSXAttribute" && a.name.name === name
|
|
51404
|
+
);
|
|
51405
|
+
if (!attr || !attr.value) return void 0;
|
|
51406
|
+
if (attr.value.type === "StringLiteral") {
|
|
51407
|
+
return attr.value.value;
|
|
51408
|
+
}
|
|
51409
|
+
if (attr.value.type === "JSXExpressionContainer") {
|
|
51410
|
+
const expr = attr.value.expression;
|
|
51411
|
+
if (expr.type === "TemplateLiteral") {
|
|
51412
|
+
return extractTemplateText(expr);
|
|
50608
51413
|
}
|
|
50609
|
-
if (
|
|
50610
|
-
|
|
50611
|
-
if (expr.type === "TemplateLiteral") {
|
|
50612
|
-
return this.extractTemplateText(expr);
|
|
50613
|
-
}
|
|
50614
|
-
if (expr.type === "StringLiteral") {
|
|
50615
|
-
return expr.value;
|
|
50616
|
-
}
|
|
51414
|
+
if (expr.type === "StringLiteral") {
|
|
51415
|
+
return expr.value;
|
|
50617
51416
|
}
|
|
50618
|
-
return void 0;
|
|
50619
51417
|
}
|
|
50620
|
-
|
|
50621
|
-
|
|
50622
|
-
|
|
50623
|
-
|
|
50624
|
-
|
|
50625
|
-
|
|
50626
|
-
|
|
50627
|
-
|
|
51418
|
+
return void 0;
|
|
51419
|
+
}
|
|
51420
|
+
function deduplicateStrings(strings) {
|
|
51421
|
+
const seen = /* @__PURE__ */ new Set();
|
|
51422
|
+
const unique = [];
|
|
51423
|
+
for (const str of strings) {
|
|
51424
|
+
if (!seen.has(str.key)) {
|
|
51425
|
+
seen.add(str.key);
|
|
51426
|
+
unique.push(str);
|
|
50628
51427
|
}
|
|
50629
|
-
return unique;
|
|
50630
51428
|
}
|
|
50631
|
-
|
|
51429
|
+
return unique;
|
|
51430
|
+
}
|
|
50632
51431
|
|
|
50633
51432
|
export {
|
|
51433
|
+
VocoderAPIError,
|
|
51434
|
+
VocoderAPI,
|
|
50634
51435
|
detectLocalEcosystem,
|
|
50635
51436
|
buildInstallCommand,
|
|
50636
51437
|
getPackagesToInstall,
|
|
51438
|
+
readAuthData,
|
|
51439
|
+
writeAuthData,
|
|
51440
|
+
clearAuthData,
|
|
50637
51441
|
getSetupSnippets,
|
|
50638
51442
|
loadVocoderConfig,
|
|
50639
51443
|
StringExtractor
|
|
50640
51444
|
};
|
|
50641
|
-
//# sourceMappingURL=chunk-
|
|
51445
|
+
//# sourceMappingURL=chunk-XF3KGGYQ.mjs.map
|