@ownlate/cli 1.0.1 → 1.0.3
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/index.cjs +235 -74
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2,8 +2,196 @@
|
|
|
2
2
|
let commander = require("commander");
|
|
3
3
|
let node_readline_promises = require("node:readline/promises");
|
|
4
4
|
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_os = require("node:os");
|
|
5
6
|
let node_path = require("node:path");
|
|
6
7
|
|
|
8
|
+
//#region src/lib/api-client.ts
|
|
9
|
+
var ApiClient = class {
|
|
10
|
+
constructor(baseUrl, apiKey, workspaceId) {
|
|
11
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
12
|
+
this.apiKey = apiKey;
|
|
13
|
+
this.workspaceId = workspaceId;
|
|
14
|
+
}
|
|
15
|
+
authHeaders() {
|
|
16
|
+
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
17
|
+
if (this.workspaceId) headers["X-Tenant-Id"] = this.workspaceId;
|
|
18
|
+
return headers;
|
|
19
|
+
}
|
|
20
|
+
async request(path, init) {
|
|
21
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
22
|
+
...init,
|
|
23
|
+
headers: {
|
|
24
|
+
...this.authHeaders(),
|
|
25
|
+
...init?.headers ?? {}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
31
|
+
}
|
|
32
|
+
return res.json();
|
|
33
|
+
}
|
|
34
|
+
async listWorkspaces() {
|
|
35
|
+
return this.request("/v1/workspaces");
|
|
36
|
+
}
|
|
37
|
+
async listProjects(workspaceId) {
|
|
38
|
+
return this.request(`/v1/projects?workspaceId=${encodeURIComponent(workspaceId)}`);
|
|
39
|
+
}
|
|
40
|
+
async upload(dto) {
|
|
41
|
+
return this.request("/v1/translation-files/upload", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify(dto)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async exportTranslations(params) {
|
|
48
|
+
const query = new URLSearchParams({
|
|
49
|
+
projectId: params.projectId,
|
|
50
|
+
language: params.language,
|
|
51
|
+
format: params.format
|
|
52
|
+
});
|
|
53
|
+
if (params.fileId) query.set("fileId", params.fileId);
|
|
54
|
+
if (params.sourceLanguage) query.set("sourceLanguage", params.sourceLanguage);
|
|
55
|
+
const res = await fetch(`${this.baseUrl}/v1/segments/export?${query}`, { headers: this.authHeaders() });
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const text = await res.text();
|
|
58
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
59
|
+
}
|
|
60
|
+
return res.text();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/lib/global-config.ts
|
|
66
|
+
const CONFIG_PATH = (0, node_path.join)((0, node_os.homedir)(), ".ownlate");
|
|
67
|
+
const DEFAULT_API_URL = "https://api.ownlate.com";
|
|
68
|
+
async function readGlobalConfig() {
|
|
69
|
+
try {
|
|
70
|
+
const raw = await (0, node_fs_promises.readFile)(CONFIG_PATH, "utf-8");
|
|
71
|
+
return JSON.parse(raw);
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function writeGlobalConfig(config) {
|
|
77
|
+
await (0, node_fs_promises.writeFile)(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
78
|
+
}
|
|
79
|
+
async function requireGlobalConfig() {
|
|
80
|
+
const config = await readGlobalConfig();
|
|
81
|
+
if (!config?.apiKey) throw new Error("Not logged in. Run \"ownlate login\" first.");
|
|
82
|
+
return config;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/commands/login.ts
|
|
87
|
+
async function ask$1(rl, question, fallback = "") {
|
|
88
|
+
return (await rl.question(question)).trim() || fallback;
|
|
89
|
+
}
|
|
90
|
+
async function loginCommand(options) {
|
|
91
|
+
const rl = (0, node_readline_promises.createInterface)({
|
|
92
|
+
input: process.stdin,
|
|
93
|
+
output: process.stdout
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
const apiUrl = options.url ?? await ask$1(rl, `API URL [${DEFAULT_API_URL}]: `, DEFAULT_API_URL);
|
|
97
|
+
const apiKey = await ask$1(rl, "API Key: ");
|
|
98
|
+
if (!apiKey) {
|
|
99
|
+
console.error("API Key is required.");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
process.stdout.write("\nConnecting...");
|
|
103
|
+
const client = new ApiClient(apiUrl, apiKey);
|
|
104
|
+
let workspaces;
|
|
105
|
+
try {
|
|
106
|
+
workspaces = await client.listWorkspaces();
|
|
107
|
+
console.log(" ok");
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.log(" failed");
|
|
110
|
+
console.error(`\n${err.message}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
let currentWorkspaceId;
|
|
114
|
+
if (workspaces.length === 0) console.log("\nNo workspaces found. Create one in the web app first.");
|
|
115
|
+
else if (workspaces.length === 1) {
|
|
116
|
+
currentWorkspaceId = workspaces[0].id;
|
|
117
|
+
console.log(`\nUsing workspace: ${workspaces[0].name}`);
|
|
118
|
+
} else {
|
|
119
|
+
console.log("\nAvailable workspaces:");
|
|
120
|
+
workspaces.forEach((w, i) => console.log(` ${i + 1}. ${w.name}`));
|
|
121
|
+
const choice = await ask$1(rl, "\nSelect workspace [1]: ", "1");
|
|
122
|
+
const idx = parseInt(choice, 10) - 1;
|
|
123
|
+
const picked = workspaces[Math.max(0, Math.min(idx, workspaces.length - 1))];
|
|
124
|
+
currentWorkspaceId = picked.id;
|
|
125
|
+
console.log(`Using: ${picked.name}`);
|
|
126
|
+
}
|
|
127
|
+
await writeGlobalConfig({
|
|
128
|
+
apiUrl,
|
|
129
|
+
apiKey,
|
|
130
|
+
currentWorkspaceId
|
|
131
|
+
});
|
|
132
|
+
console.log("\nLogged in. Run \"ownlate init\" to set up a project.");
|
|
133
|
+
} finally {
|
|
134
|
+
rl.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/commands/logout.ts
|
|
140
|
+
async function logoutCommand() {
|
|
141
|
+
try {
|
|
142
|
+
await (0, node_fs_promises.unlink)((0, node_path.join)((0, node_os.homedir)(), ".ownlate"));
|
|
143
|
+
console.log("Logged out.");
|
|
144
|
+
} catch {
|
|
145
|
+
console.log("Already logged out.");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/commands/workspace.ts
|
|
151
|
+
async function workspaceListCommand() {
|
|
152
|
+
const auth = await requireGlobalConfig();
|
|
153
|
+
const workspaces = await new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId).listWorkspaces();
|
|
154
|
+
if (workspaces.length === 0) {
|
|
155
|
+
console.log("No workspaces found.");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const w of workspaces) {
|
|
159
|
+
const marker = w.id === auth.currentWorkspaceId ? "* " : " ";
|
|
160
|
+
console.log(`${marker}${w.name} ${w.id}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function workspaceUseCommand(idOrName) {
|
|
164
|
+
const auth = await requireGlobalConfig();
|
|
165
|
+
const found = (await new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId).listWorkspaces()).find((w) => w.id === idOrName || w.name === idOrName);
|
|
166
|
+
if (!found) {
|
|
167
|
+
console.error(`Workspace not found: "${idOrName}"`);
|
|
168
|
+
console.log("Run \"ownlate workspace list\" to see available workspaces.");
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
await writeGlobalConfig({
|
|
172
|
+
...auth,
|
|
173
|
+
currentWorkspaceId: found.id
|
|
174
|
+
});
|
|
175
|
+
console.log(`Now using workspace: ${found.name}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/commands/project.ts
|
|
180
|
+
async function projectListCommand() {
|
|
181
|
+
const auth = await requireGlobalConfig();
|
|
182
|
+
if (!auth.currentWorkspaceId) {
|
|
183
|
+
console.error("No workspace selected. Run \"ownlate workspace use <id>\" first.");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const projects = await new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId).listProjects(auth.currentWorkspaceId);
|
|
187
|
+
if (projects.length === 0) {
|
|
188
|
+
console.log("No projects found in current workspace.");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const p of projects) console.log(` ${p.name} ${p.id}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
7
195
|
//#region src/commands/init.ts
|
|
8
196
|
async function ask(rl, question, fallback = "") {
|
|
9
197
|
return (await rl.question(question)).trim() || fallback;
|
|
@@ -15,22 +203,40 @@ async function initCommand(options) {
|
|
|
15
203
|
console.error(`Config already exists: ${configPath}`);
|
|
16
204
|
process.exit(1);
|
|
17
205
|
} catch {}
|
|
206
|
+
const auth = await requireGlobalConfig();
|
|
207
|
+
const client = new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId);
|
|
18
208
|
const rl = (0, node_readline_promises.createInterface)({
|
|
19
209
|
input: process.stdin,
|
|
20
210
|
output: process.stdout
|
|
21
211
|
});
|
|
22
212
|
try {
|
|
23
|
-
console.log("Initializing ownlate config...\n");
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
213
|
+
console.log("Initializing ownlate project config...\n");
|
|
214
|
+
let projectId;
|
|
215
|
+
if (auth.currentWorkspaceId) {
|
|
216
|
+
let projects = [];
|
|
217
|
+
try {
|
|
218
|
+
projects = await client.listProjects(auth.currentWorkspaceId);
|
|
219
|
+
} catch {}
|
|
220
|
+
if (projects.length > 0) {
|
|
221
|
+
console.log("Projects in current workspace:");
|
|
222
|
+
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name} (${p.id})`));
|
|
223
|
+
console.log(` ${projects.length + 1}. Enter ID manually\n`);
|
|
224
|
+
const choice = await ask(rl, "Select project: ");
|
|
225
|
+
const idx = parseInt(choice, 10) - 1;
|
|
226
|
+
if (idx >= 0 && idx < projects.length) {
|
|
227
|
+
projectId = projects[idx].id;
|
|
228
|
+
console.log(`Using: ${projects[idx].name}`);
|
|
229
|
+
} else projectId = await ask(rl, "Project ID: ");
|
|
230
|
+
} else projectId = await ask(rl, "Project ID: ");
|
|
231
|
+
} else {
|
|
232
|
+
console.log("No workspace selected. Run \"ownlate workspace use <id>\" to pick one, or enter the project ID manually.\n");
|
|
233
|
+
projectId = await ask(rl, "Project ID: ");
|
|
234
|
+
}
|
|
27
235
|
const sourceLanguage = await ask(rl, "Source language [en]: ", "en");
|
|
28
236
|
const targetLangsRaw = await ask(rl, "Target languages, comma-separated [fr,de]: ", "fr,de");
|
|
29
237
|
const sourceFile = await ask(rl, "Source file path [locales/en.json]: ", "locales/en.json");
|
|
30
238
|
const outputPattern = await ask(rl, "Output pattern [locales/{{language}}.json]: ", "locales/{{language}}.json");
|
|
31
239
|
const config = {
|
|
32
|
-
apiUrl,
|
|
33
|
-
apiKey,
|
|
34
240
|
projectId,
|
|
35
241
|
sourceLanguage,
|
|
36
242
|
targetLanguages: targetLangsRaw.split(",").map((l) => l.trim()).filter(Boolean),
|
|
@@ -51,25 +257,16 @@ async function initCommand(options) {
|
|
|
51
257
|
|
|
52
258
|
//#endregion
|
|
53
259
|
//#region src/lib/config.ts
|
|
54
|
-
async function
|
|
260
|
+
async function readProjectConfig(configPath) {
|
|
55
261
|
const absPath = (0, node_path.resolve)(configPath);
|
|
56
262
|
let raw;
|
|
57
263
|
try {
|
|
58
264
|
raw = await (0, node_fs_promises.readFile)(absPath, "utf-8");
|
|
59
265
|
} catch {
|
|
60
|
-
throw new Error(`Config
|
|
61
|
-
}
|
|
62
|
-
let config;
|
|
63
|
-
try {
|
|
64
|
-
config = JSON.parse(raw);
|
|
65
|
-
} catch {
|
|
66
|
-
throw new Error(`Config file is not valid JSON: ${absPath}`);
|
|
266
|
+
throw new Error(`Config not found: ${absPath}\nRun "ownlate init" to create one.`);
|
|
67
267
|
}
|
|
68
|
-
config
|
|
69
|
-
config.apiKey = process.env.OWNLATE_API_KEY ?? config.apiKey;
|
|
268
|
+
const config = JSON.parse(raw);
|
|
70
269
|
for (const field of [
|
|
71
|
-
"apiUrl",
|
|
72
|
-
"apiKey",
|
|
73
270
|
"projectId",
|
|
74
271
|
"sourceLanguage",
|
|
75
272
|
"targetLanguages",
|
|
@@ -77,52 +274,10 @@ async function readConfig(configPath) {
|
|
|
77
274
|
]) if (!config[field]) throw new Error(`Missing required config field: "${field}"`);
|
|
78
275
|
return config;
|
|
79
276
|
}
|
|
80
|
-
async function
|
|
277
|
+
async function writeProjectConfig(configPath, config) {
|
|
81
278
|
await (0, node_fs_promises.writeFile)((0, node_path.resolve)(configPath), JSON.stringify(config, null, 2) + "\n");
|
|
82
279
|
}
|
|
83
280
|
|
|
84
|
-
//#endregion
|
|
85
|
-
//#region src/lib/api-client.ts
|
|
86
|
-
var ApiClient = class {
|
|
87
|
-
constructor(baseUrl, apiKey) {
|
|
88
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
89
|
-
this.apiKey = apiKey;
|
|
90
|
-
}
|
|
91
|
-
authHeaders() {
|
|
92
|
-
return { Authorization: `Bearer ${this.apiKey}` };
|
|
93
|
-
}
|
|
94
|
-
async upload(dto) {
|
|
95
|
-
const res = await fetch(`${this.baseUrl}/v1/translation-files/upload`, {
|
|
96
|
-
method: "POST",
|
|
97
|
-
headers: {
|
|
98
|
-
...this.authHeaders(),
|
|
99
|
-
"Content-Type": "application/json"
|
|
100
|
-
},
|
|
101
|
-
body: JSON.stringify(dto)
|
|
102
|
-
});
|
|
103
|
-
if (!res.ok) {
|
|
104
|
-
const text = await res.text();
|
|
105
|
-
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
106
|
-
}
|
|
107
|
-
return res.json();
|
|
108
|
-
}
|
|
109
|
-
async exportTranslations(params) {
|
|
110
|
-
const query = new URLSearchParams({
|
|
111
|
-
projectId: params.projectId,
|
|
112
|
-
language: params.language,
|
|
113
|
-
format: params.format
|
|
114
|
-
});
|
|
115
|
-
if (params.fileId) query.set("fileId", params.fileId);
|
|
116
|
-
if (params.sourceLanguage) query.set("sourceLanguage", params.sourceLanguage);
|
|
117
|
-
const res = await fetch(`${this.baseUrl}/v1/segments/export?${query}`, { headers: this.authHeaders() });
|
|
118
|
-
if (!res.ok) {
|
|
119
|
-
const text = await res.text();
|
|
120
|
-
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
121
|
-
}
|
|
122
|
-
return res.text();
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
281
|
//#endregion
|
|
127
282
|
//#region src/commands/push.ts
|
|
128
283
|
function detectFormat$1(filePath) {
|
|
@@ -133,10 +288,10 @@ function detectFormat$1(filePath) {
|
|
|
133
288
|
throw new Error(`Unsupported extension "${ext}". Use .json, .yaml, .yml or .po`);
|
|
134
289
|
}
|
|
135
290
|
async function pushCommand(options) {
|
|
136
|
-
const config = await
|
|
137
|
-
const client = new ApiClient(
|
|
291
|
+
const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(options.config)]);
|
|
292
|
+
const client = new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId);
|
|
138
293
|
let configUpdated = false;
|
|
139
|
-
console.log(`Pushing to ${
|
|
294
|
+
console.log(`Pushing to ${auth.apiUrl} (project: ${config.projectId})\n`);
|
|
140
295
|
for (const file of config.files) {
|
|
141
296
|
const absPath = (0, node_path.resolve)(file.source);
|
|
142
297
|
let content;
|
|
@@ -149,13 +304,12 @@ async function pushCommand(options) {
|
|
|
149
304
|
const format = detectFormat$1(file.source);
|
|
150
305
|
const name = (0, node_path.basename)(file.source);
|
|
151
306
|
const dir = (0, node_path.dirname)(file.source);
|
|
152
|
-
const filePath = dir === "." ? "" : dir;
|
|
153
307
|
process.stdout.write(` ${file.source} ...`);
|
|
154
308
|
try {
|
|
155
309
|
const result = await client.upload({
|
|
156
310
|
projectId: config.projectId,
|
|
157
311
|
name,
|
|
158
|
-
path:
|
|
312
|
+
path: dir === "." ? "" : dir,
|
|
159
313
|
format,
|
|
160
314
|
content,
|
|
161
315
|
fileId: file.fileId
|
|
@@ -171,7 +325,7 @@ async function pushCommand(options) {
|
|
|
171
325
|
}
|
|
172
326
|
}
|
|
173
327
|
if (configUpdated) {
|
|
174
|
-
await
|
|
328
|
+
await writeProjectConfig(options.config, config);
|
|
175
329
|
console.log(`\nSaved file IDs to ${options.config}`);
|
|
176
330
|
}
|
|
177
331
|
}
|
|
@@ -186,10 +340,10 @@ function detectFormat(pattern) {
|
|
|
186
340
|
throw new Error(`Cannot detect format from output pattern "${pattern}"`);
|
|
187
341
|
}
|
|
188
342
|
async function pullCommand(options) {
|
|
189
|
-
const config = await
|
|
190
|
-
const client = new ApiClient(
|
|
343
|
+
const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(options.config)]);
|
|
344
|
+
const client = new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId);
|
|
191
345
|
const languages = options.lang ? [options.lang] : config.targetLanguages;
|
|
192
|
-
console.log(`Pulling from ${
|
|
346
|
+
console.log(`Pulling from ${auth.apiUrl} (project: ${config.projectId})\n`);
|
|
193
347
|
for (const file of config.files) {
|
|
194
348
|
const format = detectFormat(file.output);
|
|
195
349
|
for (const language of languages) {
|
|
@@ -201,7 +355,8 @@ async function pullCommand(options) {
|
|
|
201
355
|
projectId: config.projectId,
|
|
202
356
|
language,
|
|
203
357
|
format,
|
|
204
|
-
fileId: file.fileId
|
|
358
|
+
fileId: file.fileId,
|
|
359
|
+
sourceLanguage: config.sourceLanguage
|
|
205
360
|
});
|
|
206
361
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(absPath), { recursive: true });
|
|
207
362
|
await (0, node_fs_promises.writeFile)(absPath, content);
|
|
@@ -217,13 +372,19 @@ async function pullCommand(options) {
|
|
|
217
372
|
//#endregion
|
|
218
373
|
//#region src/index.ts
|
|
219
374
|
const program = new commander.Command();
|
|
220
|
-
program.name("ownlate").description("Sync translation files with your ownlate project").version("1.0.
|
|
375
|
+
program.name("ownlate").description("Sync translation files with your ownlate project").version("1.0.1");
|
|
376
|
+
program.command("login").description("Log in with your API key").option("--url <url>", "API URL (default: https://api.ownlate.com)").action(loginCommand);
|
|
377
|
+
program.command("logout").description("Remove stored credentials").action(logoutCommand);
|
|
378
|
+
const workspace = program.command("workspace").description("Manage workspaces");
|
|
379
|
+
workspace.command("list").description("List available workspaces").action(workspaceListCommand);
|
|
380
|
+
workspace.command("use <id>").description("Switch current workspace by ID or name").action(workspaceUseCommand);
|
|
381
|
+
program.command("project").description("Manage projects").command("list").description("List projects in the current workspace").action(projectListCommand);
|
|
221
382
|
const configOpt = [
|
|
222
383
|
"-c, --config <path>",
|
|
223
|
-
"path to config file",
|
|
384
|
+
"path to project config file",
|
|
224
385
|
".ownlate.json"
|
|
225
386
|
];
|
|
226
|
-
program.command("init").description("Create .ownlate.json
|
|
387
|
+
program.command("init").description("Create .ownlate.json for this project").option(...configOpt).action(initCommand);
|
|
227
388
|
program.command("push").description("Upload source files — creates/updates segments in ownlate").option(...configOpt).action(pushCommand);
|
|
228
389
|
program.command("pull").description("Download translated files from ownlate to local filesystem").option(...configOpt).option("-l, --lang <code>", "pull a single language only (e.g. fr)").action(pullCommand);
|
|
229
390
|
program.parseAsync(process.argv).catch((err) => {
|