@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.
@@ -0,0 +1,47 @@
1
+ import {
2
+ ApiClient,
3
+ ApiKeysApi,
4
+ BillingApi,
5
+ ContributorsApi,
6
+ DocumentsApi,
7
+ EmailsApi,
8
+ ImportExportApi,
9
+ PLAN_LIMITS,
10
+ SnapshotsApi,
11
+ TMApi,
12
+ WebhooksApi,
13
+ WorkflowsApi,
14
+ createApiClient
15
+ } from "./chunk-WVCVQFBI.js";
16
+ import {
17
+ ProjectsApi
18
+ } from "./chunk-KCZQUMQP.js";
19
+ import {
20
+ TranslationsApi
21
+ } from "./chunk-WSY27J6N.js";
22
+ import {
23
+ KeysApi
24
+ } from "./chunk-BULIQM4U.js";
25
+ import {
26
+ BaseApiClient
27
+ } from "./chunk-KIDP7N6D.js";
28
+ import "./chunk-IWYURZV2.js";
29
+ export {
30
+ ApiClient,
31
+ ApiKeysApi,
32
+ BaseApiClient,
33
+ BillingApi,
34
+ ContributorsApi,
35
+ DocumentsApi,
36
+ EmailsApi,
37
+ ImportExportApi,
38
+ KeysApi,
39
+ PLAN_LIMITS,
40
+ ProjectsApi,
41
+ SnapshotsApi,
42
+ TMApi,
43
+ TranslationsApi,
44
+ WebhooksApi,
45
+ WorkflowsApi,
46
+ createApiClient
47
+ };
@@ -0,0 +1,73 @@
1
+ import {
2
+ BaseApiClient
3
+ } from "./chunk-KIDP7N6D.js";
4
+
5
+ // src/lib/api/keys.ts
6
+ var KeysApi = class extends BaseApiClient {
7
+ /**
8
+ * List translation keys for a project
9
+ */
10
+ async listKeys(projectId, options = {}) {
11
+ const params = new URLSearchParams();
12
+ if (options.namespace) {
13
+ params.append("namespace", options.namespace);
14
+ }
15
+ if (options.search) {
16
+ params.append("search", options.search);
17
+ }
18
+ if (options.tags && options.tags.length > 0) {
19
+ params.append("tags", options.tags.join(","));
20
+ }
21
+ if (options.limit) {
22
+ params.append("limit", options.limit.toString());
23
+ }
24
+ const query = params.toString();
25
+ const endpoint = `/api/v1/projects/${projectId}/keys${query ? `?${query}` : ""}`;
26
+ return this.request(endpoint);
27
+ }
28
+ /**
29
+ * Get a specific translation key
30
+ */
31
+ async getKey(projectId, keyId) {
32
+ return this.request(`/api/v1/projects/${projectId}/keys/${keyId}`);
33
+ }
34
+ /**
35
+ * Create a new translation key
36
+ */
37
+ async createKey(projectId, data) {
38
+ return this.request(`/api/v1/projects/${projectId}/keys`, {
39
+ method: "POST",
40
+ body: JSON.stringify(data)
41
+ });
42
+ }
43
+ /**
44
+ * Update an existing translation key
45
+ */
46
+ async updateKey(projectId, keyId, data) {
47
+ return this.request(`/api/v1/projects/${projectId}/keys/${keyId}`, {
48
+ method: "PATCH",
49
+ body: JSON.stringify(data)
50
+ });
51
+ }
52
+ /**
53
+ * Delete a translation key
54
+ */
55
+ async deleteKey(projectId, keyId) {
56
+ return this.request(`/api/v1/projects/${projectId}/keys/${keyId}`, {
57
+ method: "DELETE"
58
+ });
59
+ }
60
+ /**
61
+ * Bulk delete translation keys
62
+ */
63
+ async bulkDeleteKeys(projectId, keyIds) {
64
+ return this.request(`/api/v1/projects/${projectId}/keys/bulk-delete`, {
65
+ method: "POST",
66
+ body: JSON.stringify({ key_ids: keyIds })
67
+ });
68
+ }
69
+ };
70
+
71
+ export {
72
+ KeysApi
73
+ };
@@ -1,7 +1,7 @@
1
1
  // src/lib/config.ts
2
2
  import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, chmodSync, renameSync } from "fs";
3
3
  import { homedir } from "os";
4
- import { join, dirname } from "path";
4
+ import { join, dirname, resolve, relative } from "path";
5
5
  function writeFileAtomic(filePath, content, options) {
6
6
  const dir = dirname(filePath);
7
7
  if (options?.createDir && !existsSync(dir)) {
@@ -331,7 +331,16 @@ function resolveOutputDir(outputDir, projectRoot) {
331
331
  if (outputDir.startsWith("/") || outputDir.match(/^[A-Z]:\\/i)) {
332
332
  return outputDir;
333
333
  }
334
- return join(projectRoot, outputDir);
334
+ const resolvedPath = resolve(projectRoot, outputDir);
335
+ const relativePath = relative(projectRoot, resolvedPath);
336
+ if (relativePath.startsWith("..") || relativePath.includes("/../")) {
337
+ throw new Error(
338
+ `Invalid output directory: path traversal detected. Output directory must be within the project directory.
339
+ Project root: ${projectRoot}
340
+ Attempted path: ${outputDir}`
341
+ );
342
+ }
343
+ return resolvedPath;
335
344
  }
336
345
  function detectGitBranch(dir = process.cwd()) {
337
346
  try {
@@ -0,0 +1,64 @@
1
+ import {
2
+ BaseApiClient
3
+ } from "./chunk-KIDP7N6D.js";
4
+
5
+ // src/lib/api/projects.ts
6
+ var ProjectsApi = class extends BaseApiClient {
7
+ async listProjects() {
8
+ return this.request("/api/v1/projects");
9
+ }
10
+ async getProject(projectId) {
11
+ return this.request(`/api/v1/projects/${projectId}`);
12
+ }
13
+ async createProject(data) {
14
+ return this.request("/api/v1/projects", {
15
+ method: "POST",
16
+ body: JSON.stringify(data)
17
+ });
18
+ }
19
+ /**
20
+ * Find a project by name (searches through all projects)
21
+ */
22
+ async findProjectByName(name) {
23
+ const { projects } = await this.listProjects();
24
+ const normalizedName = name.toLowerCase().trim();
25
+ return projects.find(
26
+ (p) => p.name.toLowerCase() === normalizedName || p.slug.toLowerCase() === normalizedName
27
+ ) || null;
28
+ }
29
+ /**
30
+ * Get project languages/settings
31
+ */
32
+ async getProjectLanguages(projectId) {
33
+ return this.request(`/api/v1/projects/${projectId}/language-settings`);
34
+ }
35
+ /**
36
+ * Add multiple languages to a project with optional skip_translation flag
37
+ */
38
+ async addLanguagesBulk(projectId, languages, skipTranslation = false) {
39
+ return this.request(`/api/v1/projects/${projectId}/language-settings/bulk`, {
40
+ method: "POST",
41
+ body: JSON.stringify({
42
+ languages,
43
+ skip_translation: skipTranslation
44
+ })
45
+ });
46
+ }
47
+ // Versions
48
+ async listVersions(projectId) {
49
+ return this.request(`/api/v1/projects/${projectId}/versions`);
50
+ }
51
+ async createVersion(projectId, data) {
52
+ return this.request(`/api/v1/projects/${projectId}/versions`, {
53
+ method: "POST",
54
+ body: JSON.stringify(data)
55
+ });
56
+ }
57
+ async downloadVersion(projectId, version) {
58
+ return this.requestBlob(`/api/v1/projects/${projectId}/versions/${version}/download`);
59
+ }
60
+ };
61
+
62
+ export {
63
+ ProjectsApi
64
+ };
@@ -0,0 +1,149 @@
1
+ import {
2
+ getAuthErrorMessage,
3
+ getGlobalConfig,
4
+ getResolvedApiKey
5
+ } from "./chunk-IWYURZV2.js";
6
+
7
+ // src/lib/api/base.ts
8
+ var DEFAULT_API_URL = process.env.INTLPULL_API_URL || "https://api.intlpull.com";
9
+ var DEFAULT_TIMEOUT_MS = 3e4;
10
+ var BaseApiClient = class {
11
+ baseUrl;
12
+ apiKey;
13
+ timeout;
14
+ constructor() {
15
+ const globalConfig = getGlobalConfig();
16
+ this.baseUrl = globalConfig.apiUrl || DEFAULT_API_URL;
17
+ const resolved = getResolvedApiKey();
18
+ this.apiKey = resolved?.key || null;
19
+ this.timeout = DEFAULT_TIMEOUT_MS;
20
+ }
21
+ async request(endpoint, options = {}) {
22
+ if (!this.apiKey) {
23
+ throw new Error(getAuthErrorMessage());
24
+ }
25
+ const url = `${this.baseUrl}${endpoint}`;
26
+ const headers = {
27
+ "Content-Type": "application/json",
28
+ "X-API-Key": this.apiKey,
29
+ ...options.headers || {}
30
+ };
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
33
+ let response;
34
+ try {
35
+ response = await fetch(url, {
36
+ ...options,
37
+ headers,
38
+ signal: controller.signal
39
+ });
40
+ } catch (err) {
41
+ clearTimeout(timeoutId);
42
+ if (err instanceof Error && err.name === "AbortError") {
43
+ throw new Error(`Request timeout: Server did not respond within ${this.timeout / 1e3}s`);
44
+ }
45
+ if (err instanceof TypeError) {
46
+ throw new Error(`Network error: Unable to connect to ${this.baseUrl}. Check your internet connection.`);
47
+ }
48
+ throw new Error(`Request failed: ${err instanceof Error ? err.message : "Unknown network error"}`);
49
+ } finally {
50
+ clearTimeout(timeoutId);
51
+ }
52
+ if (!response.ok) {
53
+ const error = await response.json().catch(() => ({ error: `Request failed: ${response.status}` }));
54
+ throw new Error(error.error || `Request failed: ${response.status}`);
55
+ }
56
+ const text = await response.text();
57
+ if (!text) {
58
+ return {};
59
+ }
60
+ try {
61
+ return JSON.parse(text);
62
+ } catch {
63
+ throw new Error(`Invalid JSON response from API`);
64
+ }
65
+ }
66
+ async requestBlob(endpoint) {
67
+ if (!this.apiKey) {
68
+ throw new Error(getAuthErrorMessage());
69
+ }
70
+ const url = `${this.baseUrl}${endpoint}`;
71
+ const controller = new AbortController();
72
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
73
+ let response;
74
+ try {
75
+ response = await fetch(url, {
76
+ headers: {
77
+ "X-API-Key": this.apiKey
78
+ },
79
+ signal: controller.signal
80
+ });
81
+ } catch (err) {
82
+ clearTimeout(timeoutId);
83
+ if (err instanceof Error && err.name === "AbortError") {
84
+ throw new Error(`Request timeout: Server did not respond within ${this.timeout / 1e3}s`);
85
+ }
86
+ if (err instanceof TypeError) {
87
+ throw new Error(`Network error: Unable to connect to ${this.baseUrl}. Check your internet connection.`);
88
+ }
89
+ throw new Error(`Request failed: ${err instanceof Error ? err.message : "Unknown network error"}`);
90
+ } finally {
91
+ clearTimeout(timeoutId);
92
+ }
93
+ if (!response.ok) {
94
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
95
+ throw new Error(error.error || "Request failed");
96
+ }
97
+ return response.blob();
98
+ }
99
+ async requestBlobWithJsonFallback(endpoint) {
100
+ if (!this.apiKey) {
101
+ throw new Error(getAuthErrorMessage());
102
+ }
103
+ const url = `${this.baseUrl}${endpoint}`;
104
+ const controller = new AbortController();
105
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
106
+ let response;
107
+ try {
108
+ response = await fetch(url, {
109
+ headers: {
110
+ "X-API-Key": this.apiKey
111
+ },
112
+ signal: controller.signal
113
+ });
114
+ } catch (err) {
115
+ clearTimeout(timeoutId);
116
+ if (err instanceof Error && err.name === "AbortError") {
117
+ throw new Error(`Request timeout: Server did not respond within ${this.timeout / 1e3}s`);
118
+ }
119
+ if (err instanceof TypeError) {
120
+ throw new Error(`Network error: Unable to connect to ${this.baseUrl}. Check your internet connection.`);
121
+ }
122
+ throw new Error(`Request failed: ${err instanceof Error ? err.message : "Unknown network error"}`);
123
+ } finally {
124
+ clearTimeout(timeoutId);
125
+ }
126
+ if (!response.ok) {
127
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
128
+ throw new Error(error.error || "Request failed");
129
+ }
130
+ const contentType = response.headers.get("content-type") || "";
131
+ if (contentType.includes("application/json")) {
132
+ const jsonData = await response.json();
133
+ if (jsonData.url && typeof jsonData.url === "string") {
134
+ const fileResponse = await fetch(jsonData.url);
135
+ if (!fileResponse.ok) {
136
+ throw new Error(`Failed to download file: ${fileResponse.status}`);
137
+ }
138
+ return fileResponse.blob();
139
+ }
140
+ const jsonString = JSON.stringify(jsonData, null, 2);
141
+ return new Blob([jsonString], { type: "application/json" });
142
+ }
143
+ return response.blob();
144
+ }
145
+ };
146
+
147
+ export {
148
+ BaseApiClient
149
+ };
@@ -0,0 +1,116 @@
1
+ import {
2
+ BaseApiClient
3
+ } from "./chunk-KIDP7N6D.js";
4
+ import {
5
+ getProjectConfig
6
+ } from "./chunk-IWYURZV2.js";
7
+
8
+ // src/lib/api/translations.ts
9
+ var TranslationsApi = class extends BaseApiClient {
10
+ // Keys - uses import endpoint to create/update keys
11
+ async pushKeys(projectId, keys, language = "en", namespace, options) {
12
+ const content = {};
13
+ for (const k of keys) {
14
+ content[k.key] = k.value;
15
+ }
16
+ const fileName = namespace ? `${namespace}.json` : "push.json";
17
+ const result = await this.request(`/api/v1/projects/${projectId}/import`, {
18
+ method: "POST",
19
+ body: JSON.stringify({
20
+ content: JSON.stringify(content),
21
+ file_name: fileName,
22
+ language,
23
+ options: {
24
+ namespace_name: namespace || "common",
25
+ update_existing: true,
26
+ branch_id: options?.branchId,
27
+ platforms: options?.platforms
28
+ }
29
+ })
30
+ });
31
+ return {
32
+ keys_inserted: result.keys_inserted,
33
+ keys_updated: result.keys_updated,
34
+ keys_skipped: result.keys_skipped,
35
+ translations_inserted: result.translations_inserted,
36
+ translations_updated: result.translations_updated
37
+ };
38
+ }
39
+ async getKeys(projectId, namespace) {
40
+ const params = namespace ? `?namespace=${namespace}` : "";
41
+ return this.request(`/api/v1/projects/${projectId}/keys${params}`);
42
+ }
43
+ // Translations - uses export/download endpoint
44
+ async pullTranslations(projectId, options) {
45
+ const params = new URLSearchParams();
46
+ params.set("format", "json");
47
+ if (options.languages?.length) {
48
+ options.languages.forEach((lang) => params.append("languages", lang));
49
+ }
50
+ if (options.branch) {
51
+ params.set("branch", options.branch);
52
+ }
53
+ if (options.platform) {
54
+ params.set("platform", options.platform);
55
+ }
56
+ return this.request(`/api/v1/projects/${projectId}/export/download?${params.toString()}`);
57
+ }
58
+ // Glossary
59
+ async searchGlossary(query, options) {
60
+ const params = new URLSearchParams();
61
+ params.set("q", query);
62
+ if (options?.limit) params.set("limit", String(options.limit));
63
+ if (options?.language) params.set("language", options.language);
64
+ return this.request(`/api/v1/glossary/search?${params.toString()}`);
65
+ }
66
+ async addGlossaryTerm(data) {
67
+ const config = getProjectConfig();
68
+ if (!config?.projectId) {
69
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
70
+ }
71
+ return this.request(`/api/v1/projects/${config.projectId}/glossary`, {
72
+ method: "POST",
73
+ body: JSON.stringify(data)
74
+ });
75
+ }
76
+ async exportGlossary(glossaryId) {
77
+ const config = getProjectConfig();
78
+ if (!config?.projectId) {
79
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
80
+ }
81
+ const params = glossaryId ? `?glossary_id=${glossaryId}` : "";
82
+ return this.request(`/api/v1/projects/${config.projectId}/glossary/export${params}`);
83
+ }
84
+ // Translation Memory
85
+ async searchTM(data) {
86
+ const config = getProjectConfig();
87
+ if (!config?.projectId) {
88
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
89
+ }
90
+ return this.request(`/api/v1/projects/${config.projectId}/memory/search`, {
91
+ method: "POST",
92
+ body: JSON.stringify(data)
93
+ });
94
+ }
95
+ async addTMEntry(data) {
96
+ const config = getProjectConfig();
97
+ if (!config?.projectId) {
98
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
99
+ }
100
+ return this.request(`/api/v1/projects/${config.projectId}/memory`, {
101
+ method: "POST",
102
+ body: JSON.stringify(data)
103
+ });
104
+ }
105
+ async getTMStats() {
106
+ const config = getProjectConfig();
107
+ if (!config?.projectId) {
108
+ throw new Error("No project configured. Run `npx @intlpullhq/cli init` first.");
109
+ }
110
+ return this.request(`/api/v1/projects/${config.projectId}/memory/stats`);
111
+ }
112
+ };
113
+
114
+ export {
115
+ TranslationsApi
116
+ };