@transi-store/cli 1.5.0 → 1.5.2
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/CHANGELOG.md +38 -0
- package/README.md +1 -2
- package/dist/cli.js +265 -66
- package/dist/fetchTranslations.d.ts.map +1 -1
- package/package.json +5 -4
- package/dist/cli.js.map +0 -1
- package/dist/fetchTranslations.js +0 -85
- package/dist/fetchTranslations.js.map +0 -1
- package/dist/git.js +0 -74
- package/dist/git.js.map +0 -1
- package/dist/uploadTranslations.js +0 -118
- package/dist/uploadTranslations.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## 1.5.2
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Limit http calls with cli #108
|
|
8
|
+
|
|
9
|
+
## 1.5.1
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
Fix broken link on common package.
|
|
14
|
+
|
|
15
|
+
## 1.5.0
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Accept `--branch=@all` in CLI to download translations from all branches.
|
|
20
|
+
|
|
21
|
+
## 1.4.0
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Skip unchanged files in `upload:config` when running on a git feature branch (compares to main branch).
|
|
26
|
+
|
|
27
|
+
## 1.3.0
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Added `upload:config` command to CLI to upload translations based on a config file.
|
|
32
|
+
- Added `--branch` option to CLI in "download:config" mode.
|
|
33
|
+
|
|
34
|
+
## 1.2.0
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Added `--branch` option to CLI to specify branch slug for fetching translations. ([#72](https://github.com/transi-store/transi-store/pull/72))
|
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,74 +1,273 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, Option } from "@commander-js/extra-typings";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import z from "zod";
|
|
7
|
+
import { simpleGit } from "simple-git";
|
|
8
|
+
//#region ../common/dist/import-strategy.js
|
|
9
|
+
var ImportStrategy;
|
|
10
|
+
(function(ImportStrategy) {
|
|
11
|
+
ImportStrategy["OVERWRITE"] = "overwrite";
|
|
12
|
+
ImportStrategy["SKIP"] = "skip";
|
|
13
|
+
})(ImportStrategy || (ImportStrategy = {}));
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region ../common/dist/constants.js
|
|
16
|
+
const DEFAULT_DOMAIN_ROOT = "https://transi-store.com";
|
|
17
|
+
const ALL_BRANCHES_VALUE = "@all";
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region ../common/dist/config-schema.js
|
|
20
|
+
const configItemSchema = z.object({
|
|
21
|
+
project: z.string().nonempty(),
|
|
22
|
+
langs: z.array(z.string().regex(/^[a-z]{2}(?:-[A-Za-z]{2,})?$/)).min(1).refine((arr) => new Set(arr).size === arr.length, { message: "langs must contain unique items" }),
|
|
23
|
+
format: z.enum([
|
|
24
|
+
"json",
|
|
25
|
+
"yaml",
|
|
26
|
+
"po",
|
|
27
|
+
"xlf",
|
|
28
|
+
"xliff",
|
|
29
|
+
"csv"
|
|
30
|
+
]),
|
|
31
|
+
output: z.string().nonempty().refine((s) => s.includes("<lang>"), { message: "output must contain '<lang>' placeholder" })
|
|
32
|
+
});
|
|
33
|
+
const schema = z.object({
|
|
34
|
+
$schema: z.url().optional(),
|
|
35
|
+
domainRoot: z.url().optional(),
|
|
36
|
+
org: z.string().nonempty(),
|
|
37
|
+
projects: z.array(configItemSchema).min(1)
|
|
38
|
+
});
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/fetchTranslations.ts
|
|
41
|
+
const CONCURRENCY_CALLS = 5;
|
|
42
|
+
async function fetchTranslations({ domainRoot, apiKey, org, project, format, locale, output, branch }) {
|
|
43
|
+
const params = new URLSearchParams({
|
|
44
|
+
format,
|
|
45
|
+
locale
|
|
46
|
+
});
|
|
47
|
+
if (branch) params.set("branch", branch);
|
|
48
|
+
const url = `${domainRoot}/api/orgs/${org}/projects/${project}/translations?${params.toString()}`;
|
|
49
|
+
try {
|
|
50
|
+
const content = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
51
|
+
if (!content.ok) {
|
|
52
|
+
const errorData = await content.text();
|
|
53
|
+
console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, errorData);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const data = await content.json();
|
|
57
|
+
if (!content.ok) {
|
|
58
|
+
console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, data.error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const dir = path.dirname(output);
|
|
62
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
63
|
+
fs.writeFileSync(output, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
64
|
+
console.log(`Translations for project "${project}" and locale "${locale}" "saved to "${output}"`);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("Error exporting translations:", error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function fetchForConfig(configPath, apiKey, branch) {
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
const fullPath = path.resolve(cwd, configPath);
|
|
73
|
+
if (!fs.existsSync(fullPath)) {
|
|
74
|
+
console.error(`Config file not found: ${configPath}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const config = (await import(pathToFileURL(fullPath).href, { with: { type: "json" } })).default;
|
|
78
|
+
const result = schema.safeParse(config);
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
const pretty = z.prettifyError(result.error);
|
|
81
|
+
console.error("Config validation error:", pretty);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const domainRoot = result.data.domainRoot ?? "https://transi-store.com";
|
|
85
|
+
console.log(`Fetching translations from domain "${domainRoot}" for org "${result.data.org}"...`);
|
|
86
|
+
const tasks = [];
|
|
87
|
+
for (const configItem of result.data.projects) for (const locale of configItem.langs) {
|
|
88
|
+
const options = {
|
|
89
|
+
domainRoot,
|
|
90
|
+
apiKey,
|
|
91
|
+
org: result.data.org,
|
|
92
|
+
project: configItem.project,
|
|
93
|
+
format: configItem.format,
|
|
94
|
+
locale,
|
|
95
|
+
output: configItem.output.replace("<lang>", locale).replace("<project>", configItem.project).replace("<format>", configItem.format),
|
|
96
|
+
branch
|
|
97
|
+
};
|
|
98
|
+
tasks.push(options);
|
|
99
|
+
}
|
|
100
|
+
for (let i = 0; i < tasks.length; i += CONCURRENCY_CALLS) await Promise.all(tasks.slice(i, i + CONCURRENCY_CALLS).map(fetchTranslations));
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/git.ts
|
|
104
|
+
let gitInstance;
|
|
105
|
+
function getGit() {
|
|
106
|
+
if (!gitInstance) gitInstance = simpleGit();
|
|
107
|
+
return gitInstance;
|
|
108
|
+
}
|
|
109
|
+
async function isGitRepository() {
|
|
110
|
+
try {
|
|
111
|
+
return await getGit().checkIsRepo();
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Detect the default branch (main/master) with remote or local ref.
|
|
118
|
+
* Returns null if not found.
|
|
119
|
+
*/
|
|
120
|
+
async function getDefaultBranch() {
|
|
121
|
+
const git = getGit();
|
|
122
|
+
try {
|
|
123
|
+
const branches = await git.branch(["-r"]);
|
|
124
|
+
if (branches.all.includes("origin/main")) return "origin/main";
|
|
125
|
+
if (branches.all.includes("origin/master")) return "origin/master";
|
|
126
|
+
} catch {}
|
|
127
|
+
try {
|
|
128
|
+
const branches = await git.branchLocal();
|
|
129
|
+
if (branches.all.includes("main")) return "main";
|
|
130
|
+
if (branches.all.includes("master")) return "master";
|
|
131
|
+
} catch {}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
async function getCurrentBranch() {
|
|
135
|
+
try {
|
|
136
|
+
const { current } = await getGit().branchLocal();
|
|
137
|
+
return current || null;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Returns the set of absolute paths of files that have been modified
|
|
144
|
+
* compared to the given base branch, including untracked files.
|
|
145
|
+
*/
|
|
146
|
+
async function getModifiedFiles(baseBranch) {
|
|
147
|
+
const git = getGit();
|
|
148
|
+
const repoRoot = (await git.revparse(["--show-toplevel"])).trim();
|
|
149
|
+
const diffFiles = (await git.diff(["--name-only", baseBranch])).trim().split("\n").filter(Boolean);
|
|
150
|
+
const status = await git.status();
|
|
151
|
+
const modified = /* @__PURE__ */ new Set();
|
|
152
|
+
for (const f of [...diffFiles, ...status.not_added]) modified.add(`${repoRoot}/${f}`);
|
|
153
|
+
return modified;
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/uploadTranslations.ts
|
|
157
|
+
async function uploadTranslations({ domainRoot, apiKey, org, project, locale, input, strategy, format, branch }) {
|
|
158
|
+
const url = `${domainRoot}/api/orgs/${org}/projects/${project}/translations`;
|
|
159
|
+
const filePath = path.resolve(input);
|
|
160
|
+
if (!fs.existsSync(filePath)) {
|
|
161
|
+
console.error(`File not found: ${input}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
const fileContent = fs.readFileSync(filePath);
|
|
165
|
+
const fileName = path.basename(filePath);
|
|
166
|
+
const formData = new FormData();
|
|
167
|
+
formData.append("file", new Blob([fileContent]), fileName);
|
|
168
|
+
formData.append("locale", locale);
|
|
169
|
+
formData.append("strategy", strategy);
|
|
170
|
+
if (format) formData.append("format", format);
|
|
171
|
+
if (branch) formData.append("branch", branch);
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(url, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
176
|
+
body: formData
|
|
177
|
+
});
|
|
178
|
+
const data = await response.json();
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
console.error(`Failed to import translations: ${response.status} ${response.statusText}\n`, data.error, data.details ? `\nDetails: ${data.details}` : "");
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
console.log(`Translations imported for project "${project}" locale "${locale}":`);
|
|
184
|
+
console.log(` Total keys: ${data.stats.total}`);
|
|
185
|
+
console.log(` Keys created: ${data.stats.keysCreated}`);
|
|
186
|
+
console.log(` Translations created: ${data.stats.translationsCreated}`);
|
|
187
|
+
console.log(` Translations updated: ${data.stats.translationsUpdated}`);
|
|
188
|
+
console.log(` Translations skipped: ${data.stats.translationsSkipped}`);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error("Error importing translations:", error);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function uploadForConfig(configPath, apiKey, strategy, branch) {
|
|
195
|
+
const cwd = process.cwd();
|
|
196
|
+
const fullPath = path.resolve(cwd, configPath);
|
|
197
|
+
if (!fs.existsSync(fullPath)) {
|
|
198
|
+
console.error(`Config file not found: ${configPath}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const config = (await import(pathToFileURL(fullPath).href, { with: { type: "json" } })).default;
|
|
202
|
+
const result = schema.safeParse(config);
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
const pretty = z.prettifyError(result.error);
|
|
205
|
+
console.error("Config validation error:", pretty);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const domainRoot = result.data.domainRoot ?? "https://transi-store.com";
|
|
209
|
+
console.log(`Uploading translations to domain "${domainRoot}" for org "${result.data.org}"...`);
|
|
210
|
+
let modifiedFiles = null;
|
|
211
|
+
if (await isGitRepository()) {
|
|
212
|
+
const defaultBranch = await getDefaultBranch();
|
|
213
|
+
const currentBranch = await getCurrentBranch();
|
|
214
|
+
if (defaultBranch && currentBranch && currentBranch !== defaultBranch.replace(/^origin\//, "")) {
|
|
215
|
+
modifiedFiles = await getModifiedFiles(defaultBranch);
|
|
216
|
+
console.log(`Git optimization enabled: only uploading files modified compared to "${defaultBranch}"`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (const configItem of result.data.projects) for (const locale of configItem.langs) {
|
|
220
|
+
const input = configItem.output.replace("<lang>", locale).replace("<project>", configItem.project).replace("<format>", configItem.format);
|
|
221
|
+
const resolvedInput = path.resolve(cwd, input);
|
|
222
|
+
if (!fs.existsSync(resolvedInput)) {
|
|
223
|
+
console.log(`Skipping project "${configItem.project}" locale "${locale}": file not found "${input}"`);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (modifiedFiles && !modifiedFiles.has(resolvedInput)) {
|
|
227
|
+
console.log(`Skipping project "${configItem.project}" locale "${locale}": file not modified`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
await uploadTranslations({
|
|
231
|
+
domainRoot,
|
|
232
|
+
apiKey,
|
|
233
|
+
org: result.data.org,
|
|
234
|
+
project: configItem.project,
|
|
235
|
+
format: configItem.format,
|
|
236
|
+
locale,
|
|
237
|
+
input,
|
|
238
|
+
strategy,
|
|
239
|
+
branch
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/cli.ts
|
|
6
245
|
const program = new Command();
|
|
7
|
-
const apiKeyOption = new Option("-k, --api-key <apiKey>", "API key for authentication")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
program
|
|
11
|
-
.command("download")
|
|
12
|
-
.description("Download translations for a project")
|
|
13
|
-
.addOption(apiKeyOption)
|
|
14
|
-
.requiredOption("-d, --domain-root <domainRoot>", "Root domain to download translations from (default is https://transi-store.com)", DEFAULT_DOMAIN_ROOT)
|
|
15
|
-
.requiredOption("-o, --org <org>", "Organization slug")
|
|
16
|
-
.requiredOption("-p, --project <project>", "Project slug")
|
|
17
|
-
.requiredOption("-l, --locale <locale>", "Locale to export")
|
|
18
|
-
.requiredOption("-O, --output <output>", "Output file path")
|
|
19
|
-
.option("-f, --format <format>", "Export format (json, csv, etc.)", "json")
|
|
20
|
-
.option("-b, --branch <branch>", `Branch slug (exports main + branch keys). Use "${ALL_BRANCHES_VALUE}" to export all branches`)
|
|
21
|
-
.action((options) => {
|
|
22
|
-
fetchTranslations(options);
|
|
246
|
+
const apiKeyOption = new Option("-k, --api-key <apiKey>", "API key for authentication").env("TRANSI_STORE_API_KEY").makeOptionMandatory();
|
|
247
|
+
program.command("download").description("Download translations for a project").addOption(apiKeyOption).requiredOption("-d, --domain-root <domainRoot>", "Root domain to download translations from (default is https://transi-store.com)", DEFAULT_DOMAIN_ROOT).requiredOption("-o, --org <org>", "Organization slug").requiredOption("-p, --project <project>", "Project slug").requiredOption("-l, --locale <locale>", "Locale to export").requiredOption("-O, --output <output>", "Output file path").option("-f, --format <format>", "Export format (json, csv, etc.)", "json").option("-b, --branch <branch>", `Branch slug (exports main + branch keys). Use "${ALL_BRANCHES_VALUE}" to export all branches`).action((options) => {
|
|
248
|
+
fetchTranslations(options);
|
|
23
249
|
});
|
|
24
|
-
program
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.option("-f, --format <format>", "File format (json or xliff). Auto-detected from extension if omitted")
|
|
35
|
-
.option("-b, --branch <branch>", "Branch slug (new keys will be created on this branch)")
|
|
36
|
-
.action((options) => {
|
|
37
|
-
const strategy = options.strategy;
|
|
38
|
-
if (strategy !== ImportStrategy.OVERWRITE &&
|
|
39
|
-
strategy !== ImportStrategy.SKIP) {
|
|
40
|
-
console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
uploadTranslations({
|
|
44
|
-
...options,
|
|
45
|
-
strategy,
|
|
46
|
-
});
|
|
250
|
+
program.command("upload").description("Upload translations for a project").addOption(apiKeyOption).requiredOption("-d, --domain-root <domainRoot>", "Root domain to upload translations to (default is https://transi-store.com)", DEFAULT_DOMAIN_ROOT).requiredOption("-o, --org <org>", "Organization slug").requiredOption("-p, --project <project>", "Project slug").requiredOption("-l, --locale <locale>", "Target locale").requiredOption("-I, --input <input>", "Input file path (JSON or XLIFF)").option("-s, --strategy <strategy>", `Import strategy: '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}' existing translations`, ImportStrategy.SKIP).option("-f, --format <format>", "File format (json or xliff). Auto-detected from extension if omitted").option("-b, --branch <branch>", "Branch slug (new keys will be created on this branch)").action((options) => {
|
|
251
|
+
const strategy = options.strategy;
|
|
252
|
+
if (strategy !== ImportStrategy.OVERWRITE && strategy !== ImportStrategy.SKIP) {
|
|
253
|
+
console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
uploadTranslations({
|
|
257
|
+
...options,
|
|
258
|
+
strategy
|
|
259
|
+
});
|
|
47
260
|
});
|
|
48
|
-
program
|
|
49
|
-
|
|
50
|
-
.description("Use configuration from config file")
|
|
51
|
-
.option("-c, --config <config>", "Path to config file", "transi-store.config.json")
|
|
52
|
-
.option("-b, --branch <branch>", `Branch slug (exports main + branch keys). Use "${ALL_BRANCHES_VALUE}" to export all branches`)
|
|
53
|
-
.addOption(apiKeyOption)
|
|
54
|
-
.action((options) => {
|
|
55
|
-
fetchForConfig(options.config, options.apiKey, options.branch);
|
|
261
|
+
program.command("download:config", { isDefault: true }).description("Use configuration from config file").option("-c, --config <config>", "Path to config file", "transi-store.config.json").option("-b, --branch <branch>", `Branch slug (exports main + branch keys). Use "${ALL_BRANCHES_VALUE}" to export all branches`).addOption(apiKeyOption).action((options) => {
|
|
262
|
+
fetchForConfig(options.config, options.apiKey, options.branch);
|
|
56
263
|
});
|
|
57
|
-
program
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.action((options) => {
|
|
65
|
-
const strategy = options.strategy;
|
|
66
|
-
if (strategy !== ImportStrategy.OVERWRITE &&
|
|
67
|
-
strategy !== ImportStrategy.SKIP) {
|
|
68
|
-
console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
uploadForConfig(options.config, options.apiKey, strategy, options.branch);
|
|
264
|
+
program.command("upload:config").description("Upload translations using configuration from config file").option("-c, --config <config>", "Path to config file", "transi-store.config.json").option("-b, --branch <branch>", "Branch slug (new keys will be created on this branch)").option("-s, --strategy <strategy>", `Import strategy: '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}' existing translations`, ImportStrategy.SKIP).addOption(apiKeyOption).action((options) => {
|
|
265
|
+
const strategy = options.strategy;
|
|
266
|
+
if (strategy !== ImportStrategy.OVERWRITE && strategy !== ImportStrategy.SKIP) {
|
|
267
|
+
console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
uploadForConfig(options.config, options.apiKey, strategy, options.branch);
|
|
72
271
|
});
|
|
73
272
|
program.parse();
|
|
74
|
-
//#
|
|
273
|
+
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetchTranslations.d.ts","sourceRoot":"","sources":["../src/fetchTranslations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fetchTranslations.d.ts","sourceRoot":"","sources":["../src/fetchTranslations.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,MAAM,GAAG;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,wBAAsB,iBAAiB,CAAC,EACtC,UAAU,EACV,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,MAAM,iBA+CR;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAsDf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transi-store/cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/cli.js",
|
|
@@ -11,21 +11,22 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@commander-js/extra-typings": "^14.0.0",
|
|
14
|
-
"@transi-store/common": "workspace:^",
|
|
15
14
|
"commander": "^14.0.3",
|
|
16
15
|
"simple-git": "^3.33.0",
|
|
17
16
|
"zod": "^4.3.6"
|
|
18
17
|
},
|
|
19
18
|
"devDependencies": {
|
|
19
|
+
"@transi-store/common": "^1.0.0",
|
|
20
|
+
"rolldown": "^1.0.0-rc.12",
|
|
20
21
|
"typescript": "^5.7.3"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
24
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
24
|
-
"build": "tsc && yarn build:schema",
|
|
25
|
+
"build": "rolldown -c && tsc --emitDeclarationOnly && yarn build:schema",
|
|
25
26
|
"build:schema": "node ./scripts/create-json-schema.mjs",
|
|
26
27
|
"lint:types": "tsc --noEmit"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"dist"
|
|
30
31
|
]
|
|
31
|
-
}
|
|
32
|
+
}
|
package/dist/cli.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
|
-
import fs from "node:fs";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { pathToFileURL } from "node:url";
|
|
12
|
-
import { DEFAULT_DOMAIN_ROOT } from "@transi-store/common";
|
|
13
|
-
import z from "zod";
|
|
14
|
-
import { configSchema } from "@transi-store/common";
|
|
15
|
-
export async function fetchTranslations({ domainRoot, apiKey, org, project, format, locale, output, branch, }) {
|
|
16
|
-
const params = new URLSearchParams({ format, locale });
|
|
17
|
-
if (branch) {
|
|
18
|
-
params.set("branch", branch);
|
|
19
|
-
}
|
|
20
|
-
const url = `${domainRoot}/api/orgs/${org}/projects/${project}/translations?${params.toString()}`;
|
|
21
|
-
try {
|
|
22
|
-
const content = await fetch(url, {
|
|
23
|
-
headers: {
|
|
24
|
-
Authorization: `Bearer ${apiKey}`,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
if (!content.ok) {
|
|
28
|
-
const errorData = await content.text();
|
|
29
|
-
console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, errorData);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
const data = await content.json();
|
|
33
|
-
if (!content.ok) {
|
|
34
|
-
console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, data.error);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
// create directory if not exists
|
|
38
|
-
const dir = path.dirname(output);
|
|
39
|
-
if (!fs.existsSync(dir)) {
|
|
40
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
-
}
|
|
42
|
-
fs.writeFileSync(output, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
43
|
-
console.log(`Translations for project "${project}" and locale "${locale}" "saved to "${output}"`);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
console.error("Error exporting translations:", error);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
export async function fetchForConfig(configPath, apiKey, branch) {
|
|
51
|
-
const cwd = process.cwd();
|
|
52
|
-
const fullPath = path.resolve(cwd, configPath);
|
|
53
|
-
if (!fs.existsSync(fullPath)) {
|
|
54
|
-
console.error(`Config file not found: ${configPath}`);
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
const config = (await import(__rewriteRelativeImportExtension(pathToFileURL(fullPath).href), { with: { type: "json" } })).default;
|
|
58
|
-
const result = configSchema.safeParse(config);
|
|
59
|
-
if (!result.success) {
|
|
60
|
-
const pretty = z.prettifyError(result.error);
|
|
61
|
-
console.error("Config validation error:", pretty);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
const domainRoot = result.data.domainRoot ?? DEFAULT_DOMAIN_ROOT;
|
|
65
|
-
console.log(`Fetching translations from domain "${domainRoot}" for org "${result.data.org}"...`);
|
|
66
|
-
for (const configItem of result.data.projects) {
|
|
67
|
-
for (const locale of configItem.langs) {
|
|
68
|
-
const options = {
|
|
69
|
-
domainRoot,
|
|
70
|
-
apiKey,
|
|
71
|
-
org: result.data.org,
|
|
72
|
-
project: configItem.project,
|
|
73
|
-
format: configItem.format,
|
|
74
|
-
locale,
|
|
75
|
-
output: configItem.output
|
|
76
|
-
.replace("<lang>", locale)
|
|
77
|
-
.replace("<project>", configItem.project)
|
|
78
|
-
.replace("<format>", configItem.format),
|
|
79
|
-
branch,
|
|
80
|
-
};
|
|
81
|
-
fetchTranslations(options);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
//# sourceMappingURL=fetchTranslations.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetchTranslations.js","sourceRoot":"","sources":["../src/fetchTranslations.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,CAAC,MAAM,KAAK,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAapD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACtC,UAAU,EACV,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GACC;IACP,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvD,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,UAAU,aAAa,GAAG,aAAa,OAAO,iBAAiB,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IAElG,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC/B,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,MAAM,EAAE;aAClC;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACvC,OAAO,CAAC,KAAK,CACX,iCAAiC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,UAAU,IAAI,EACzE,SAAS,CACV,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QAElC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CACX,iCAAiC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,UAAU,IAAI,EACzE,IAAI,CAAC,KAAK,CACX,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,iCAAiC;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;QAED,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CACT,6BAA6B,OAAO,iBAAiB,MAAM,gBAAgB,MAAM,GAAG,CACrF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAAkB,EAClB,MAAc,EACd,MAAe;IAEf,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAE/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,0BAA0B,UAAU,EAAE,CAAC,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,CACb,MAAM,MAAM,kCAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,GAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CACvE,CAAC,OAAO,CAAC;IACV,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAE9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAEjE,OAAO,CAAC,GAAG,CACT,sCAAsC,UAAU,cAAc,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CACpF,CAAC;IAEF,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9C,KAAK,MAAM,MAAM,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;YACtC,MAAM,OAAO,GAAG;gBACd,UAAU;gBACV,MAAM;gBACN,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG;gBACpB,OAAO,EAAE,UAAU,CAAC,OAAO;gBAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,MAAM;gBACN,MAAM,EAAE,UAAU,CAAC,MAAM;qBACtB,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC;qBACzB,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC,OAAO,CAAC;qBACxC,OAAO,CAAC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC;gBACzC,MAAM;aACU,CAAC;YAEnB,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/git.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { simpleGit } from "simple-git";
|
|
2
|
-
let gitInstance;
|
|
3
|
-
function getGit() {
|
|
4
|
-
if (!gitInstance) {
|
|
5
|
-
gitInstance = simpleGit();
|
|
6
|
-
}
|
|
7
|
-
return gitInstance;
|
|
8
|
-
}
|
|
9
|
-
export async function isGitRepository() {
|
|
10
|
-
try {
|
|
11
|
-
return await getGit().checkIsRepo();
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Detect the default branch (main/master) with remote or local ref.
|
|
19
|
-
* Returns null if not found.
|
|
20
|
-
*/
|
|
21
|
-
export async function getDefaultBranch() {
|
|
22
|
-
const git = getGit();
|
|
23
|
-
// Check remote branches
|
|
24
|
-
try {
|
|
25
|
-
const branches = await git.branch(["-r"]);
|
|
26
|
-
if (branches.all.includes("origin/main"))
|
|
27
|
-
return "origin/main";
|
|
28
|
-
if (branches.all.includes("origin/master"))
|
|
29
|
-
return "origin/master";
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// ignore
|
|
33
|
-
}
|
|
34
|
-
// Fallback to local branches
|
|
35
|
-
try {
|
|
36
|
-
const branches = await git.branchLocal();
|
|
37
|
-
if (branches.all.includes("main"))
|
|
38
|
-
return "main";
|
|
39
|
-
if (branches.all.includes("master"))
|
|
40
|
-
return "master";
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// ignore
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
export async function getCurrentBranch() {
|
|
48
|
-
try {
|
|
49
|
-
const { current } = await getGit().branchLocal();
|
|
50
|
-
return current || null;
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Returns the set of absolute paths of files that have been modified
|
|
58
|
-
* compared to the given base branch, including untracked files.
|
|
59
|
-
*/
|
|
60
|
-
export async function getModifiedFiles(baseBranch) {
|
|
61
|
-
const git = getGit();
|
|
62
|
-
const repoRoot = (await git.revparse(["--show-toplevel"])).trim();
|
|
63
|
-
// Files that differ from base branch (committed + staged + unstaged changes)
|
|
64
|
-
const diff = await git.diff(["--name-only", baseBranch]);
|
|
65
|
-
const diffFiles = diff.trim().split("\n").filter(Boolean);
|
|
66
|
-
// Untracked files (not in git at all)
|
|
67
|
-
const status = await git.status();
|
|
68
|
-
const modified = new Set();
|
|
69
|
-
for (const f of [...diffFiles, ...status.not_added]) {
|
|
70
|
-
modified.add(`${repoRoot}/${f}`);
|
|
71
|
-
}
|
|
72
|
-
return modified;
|
|
73
|
-
}
|
|
74
|
-
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AAEvD,IAAI,WAAkC,CAAC;AAEvC,SAAS,MAAM;IACb,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,SAAS,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IAErB,wBAAwB;IACxB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC;YAAE,OAAO,aAAa,CAAC;QAC/D,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC;YAAE,OAAO,eAAe,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IAED,6BAA6B;IAC7B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;QACjD,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,OAAO,IAAI,IAAI,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAkB;IAElB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IAErB,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAElE,6EAA6E;IAC7E,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAE1D,sCAAsC;IACtC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;IAElC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACpD,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
|
-
import fs from "node:fs";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { pathToFileURL } from "node:url";
|
|
12
|
-
import { DEFAULT_DOMAIN_ROOT } from "@transi-store/common";
|
|
13
|
-
import { ImportStrategy } from "@transi-store/common";
|
|
14
|
-
import z from "zod";
|
|
15
|
-
import { getCurrentBranch, getDefaultBranch, getModifiedFiles, isGitRepository, } from "./git.js";
|
|
16
|
-
import { configSchema } from "@transi-store/common";
|
|
17
|
-
export async function uploadTranslations({ domainRoot, apiKey, org, project, locale, input, strategy, format, branch, }) {
|
|
18
|
-
const url = `${domainRoot}/api/orgs/${org}/projects/${project}/translations`;
|
|
19
|
-
const filePath = path.resolve(input);
|
|
20
|
-
if (!fs.existsSync(filePath)) {
|
|
21
|
-
console.error(`File not found: ${input}`);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
const fileContent = fs.readFileSync(filePath);
|
|
25
|
-
const fileName = path.basename(filePath);
|
|
26
|
-
const formData = new FormData();
|
|
27
|
-
formData.append("file", new Blob([fileContent]), fileName);
|
|
28
|
-
formData.append("locale", locale);
|
|
29
|
-
formData.append("strategy", strategy);
|
|
30
|
-
if (format) {
|
|
31
|
-
formData.append("format", format);
|
|
32
|
-
}
|
|
33
|
-
if (branch) {
|
|
34
|
-
formData.append("branch", branch);
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const response = await fetch(url, {
|
|
38
|
-
method: "POST",
|
|
39
|
-
headers: {
|
|
40
|
-
Authorization: `Bearer ${apiKey}`,
|
|
41
|
-
},
|
|
42
|
-
body: formData,
|
|
43
|
-
});
|
|
44
|
-
const data = await response.json();
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
console.error(`Failed to import translations: ${response.status} ${response.statusText}\n`, data.error, data.details ? `\nDetails: ${data.details}` : "");
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
console.log(`Translations imported for project "${project}" locale "${locale}":`);
|
|
50
|
-
console.log(` Total keys: ${data.stats.total}`);
|
|
51
|
-
console.log(` Keys created: ${data.stats.keysCreated}`);
|
|
52
|
-
console.log(` Translations created: ${data.stats.translationsCreated}`);
|
|
53
|
-
console.log(` Translations updated: ${data.stats.translationsUpdated}`);
|
|
54
|
-
console.log(` Translations skipped: ${data.stats.translationsSkipped}`);
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
console.error("Error importing translations:", error);
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export async function uploadForConfig(configPath, apiKey, strategy, branch) {
|
|
62
|
-
const cwd = process.cwd();
|
|
63
|
-
const fullPath = path.resolve(cwd, configPath);
|
|
64
|
-
if (!fs.existsSync(fullPath)) {
|
|
65
|
-
console.error(`Config file not found: ${configPath}`);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
const config = (await import(__rewriteRelativeImportExtension(pathToFileURL(fullPath).href), { with: { type: "json" } })).default;
|
|
69
|
-
const result = configSchema.safeParse(config);
|
|
70
|
-
if (!result.success) {
|
|
71
|
-
const pretty = z.prettifyError(result.error);
|
|
72
|
-
console.error("Config validation error:", pretty);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
const domainRoot = result.data.domainRoot ?? DEFAULT_DOMAIN_ROOT;
|
|
76
|
-
console.log(`Uploading translations to domain "${domainRoot}" for org "${result.data.org}"...`);
|
|
77
|
-
// Determine if we can use git to skip unchanged files
|
|
78
|
-
let modifiedFiles = null;
|
|
79
|
-
if (await isGitRepository()) {
|
|
80
|
-
const defaultBranch = await getDefaultBranch();
|
|
81
|
-
const currentBranch = await getCurrentBranch();
|
|
82
|
-
if (defaultBranch &&
|
|
83
|
-
currentBranch &&
|
|
84
|
-
currentBranch !== defaultBranch.replace(/^origin\//, "")) {
|
|
85
|
-
modifiedFiles = await getModifiedFiles(defaultBranch);
|
|
86
|
-
console.log(`Git optimization enabled: only uploading files modified compared to "${defaultBranch}"`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
for (const configItem of result.data.projects) {
|
|
90
|
-
for (const locale of configItem.langs) {
|
|
91
|
-
const input = configItem.output
|
|
92
|
-
.replace("<lang>", locale)
|
|
93
|
-
.replace("<project>", configItem.project)
|
|
94
|
-
.replace("<format>", configItem.format);
|
|
95
|
-
const resolvedInput = path.resolve(cwd, input);
|
|
96
|
-
if (!fs.existsSync(resolvedInput)) {
|
|
97
|
-
console.log(`Skipping project "${configItem.project}" locale "${locale}": file not found "${input}"`);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (modifiedFiles && !modifiedFiles.has(resolvedInput)) {
|
|
101
|
-
console.log(`Skipping project "${configItem.project}" locale "${locale}": file not modified`);
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
await uploadTranslations({
|
|
105
|
-
domainRoot,
|
|
106
|
-
apiKey,
|
|
107
|
-
org: result.data.org,
|
|
108
|
-
project: configItem.project,
|
|
109
|
-
format: configItem.format,
|
|
110
|
-
locale,
|
|
111
|
-
input,
|
|
112
|
-
strategy,
|
|
113
|
-
branch,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
//# sourceMappingURL=uploadTranslations.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"uploadTranslations.js","sourceRoot":"","sources":["../src/uploadTranslations.ts"],"names":[],"mappings":"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"}
|