@llm-translate/cli 1.0.0-next.1 → 1.0.0-next.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/.github/workflows/docker-publish.yml +99 -0
- package/Dockerfile +7 -2
- package/dist/cli/index.js +40 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/serve.ts +7 -0
- package/src/core/engine.ts +23 -12
- package/src/server/index.ts +9 -0
- package/src/server/routes/translate.ts +9 -2
- package/src/server/types.ts +2 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: Docker Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- develop
|
|
7
|
+
- main
|
|
8
|
+
tags:
|
|
9
|
+
- "v*"
|
|
10
|
+
paths-ignore:
|
|
11
|
+
- "docs/**"
|
|
12
|
+
- "*.md"
|
|
13
|
+
|
|
14
|
+
env:
|
|
15
|
+
REGISTRY: ghcr.io
|
|
16
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build-and-push:
|
|
23
|
+
name: Build and Push Docker Image
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
packages: write
|
|
28
|
+
attestations: write
|
|
29
|
+
id-token: write
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Set up QEMU
|
|
36
|
+
uses: docker/setup-qemu-action@v3
|
|
37
|
+
|
|
38
|
+
- name: Set up Docker Buildx
|
|
39
|
+
uses: docker/setup-buildx-action@v3
|
|
40
|
+
|
|
41
|
+
- name: Login to GHCR
|
|
42
|
+
uses: docker/login-action@v3
|
|
43
|
+
with:
|
|
44
|
+
registry: ${{ env.REGISTRY }}
|
|
45
|
+
username: ${{ github.actor }}
|
|
46
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
47
|
+
|
|
48
|
+
- name: Extract version from package.json
|
|
49
|
+
id: pkg
|
|
50
|
+
run: |
|
|
51
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
52
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
53
|
+
|
|
54
|
+
- name: Generate Docker tags
|
|
55
|
+
id: tags
|
|
56
|
+
run: |
|
|
57
|
+
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
|
|
58
|
+
IMAGE_LOWER=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')
|
|
59
|
+
VERSION="${{ steps.pkg.outputs.version }}"
|
|
60
|
+
|
|
61
|
+
if [[ "${{ github.ref_type }}" == "tag" ]]; then
|
|
62
|
+
# Tag push (v*) - stable release
|
|
63
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
64
|
+
TAGS="${IMAGE_LOWER}:${TAG_VERSION},${IMAGE_LOWER}:latest"
|
|
65
|
+
elif [[ "${{ github.ref_name }}" == "main" ]]; then
|
|
66
|
+
# Main branch push - stable release
|
|
67
|
+
TAGS="${IMAGE_LOWER}:${VERSION},${IMAGE_LOWER}:latest"
|
|
68
|
+
elif [[ "${{ github.ref_name }}" == "develop" ]]; then
|
|
69
|
+
# Develop branch push - alpha release
|
|
70
|
+
ALPHA_VERSION="${VERSION}-alpha.${{ github.run_number }}"
|
|
71
|
+
TAGS="${IMAGE_LOWER}:${ALPHA_VERSION},${IMAGE_LOWER}:alpha"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
echo "tags=$TAGS" >> $GITHUB_OUTPUT
|
|
75
|
+
echo "Generated tags: $TAGS"
|
|
76
|
+
|
|
77
|
+
- name: Build and push Docker image
|
|
78
|
+
id: push
|
|
79
|
+
uses: docker/build-push-action@v6
|
|
80
|
+
with:
|
|
81
|
+
context: .
|
|
82
|
+
platforms: linux/amd64,linux/arm64
|
|
83
|
+
push: true
|
|
84
|
+
tags: ${{ steps.tags.outputs.tags }}
|
|
85
|
+
cache-from: type=gha
|
|
86
|
+
cache-to: type=gha,mode=max
|
|
87
|
+
labels: |
|
|
88
|
+
org.opencontainers.image.title=llm-translate
|
|
89
|
+
org.opencontainers.image.description=CLI-based document translation tool powered by LLMs
|
|
90
|
+
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
|
91
|
+
org.opencontainers.image.revision=${{ github.sha }}
|
|
92
|
+
org.opencontainers.image.version=${{ steps.pkg.outputs.version }}
|
|
93
|
+
|
|
94
|
+
- name: Generate artifact attestation
|
|
95
|
+
uses: actions/attest-build-provenance@v2
|
|
96
|
+
with:
|
|
97
|
+
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
98
|
+
subject-digest: ${{ steps.push.outputs.digest }}
|
|
99
|
+
push-to-registry: true
|
package/Dockerfile
CHANGED
|
@@ -38,9 +38,13 @@ COPY --from=builder --chown=llmtranslate:nodejs /app/node_modules ./node_modules
|
|
|
38
38
|
COPY --from=builder --chown=llmtranslate:nodejs /app/dist ./dist
|
|
39
39
|
COPY --from=builder --chown=llmtranslate:nodejs /app/package.json ./
|
|
40
40
|
|
|
41
|
+
# Create cache directory with correct ownership
|
|
42
|
+
RUN mkdir -p /app/cache && chown llmtranslate:nodejs /app/cache
|
|
43
|
+
|
|
41
44
|
# Environment
|
|
42
45
|
ENV NODE_ENV=production
|
|
43
46
|
ENV TRANSLATE_PORT=3000
|
|
47
|
+
ENV TRANSLATE_CACHE_DIR=/app/cache
|
|
44
48
|
|
|
45
49
|
# Switch to non-root user
|
|
46
50
|
USER llmtranslate
|
|
@@ -51,5 +55,6 @@ EXPOSE 3000
|
|
|
51
55
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
52
56
|
CMD node -e "fetch('http://localhost:3000/health/live').then(r => r.ok ? process.exit(0) : process.exit(1)).catch(() => process.exit(1))"
|
|
53
57
|
|
|
54
|
-
#
|
|
55
|
-
|
|
58
|
+
# ENTRYPOINT for CLI, CMD for default arguments
|
|
59
|
+
ENTRYPOINT ["node", "dist/cli/index.js"]
|
|
60
|
+
CMD ["serve", "--json", "--cors", "--no-auth", "--cache-dir", "/app/cache"]
|
package/dist/cli/index.js
CHANGED
|
@@ -2973,20 +2973,28 @@ var init_engine = __esm({
|
|
|
2973
2973
|
}
|
|
2974
2974
|
this.provider = getProvider(this.config.provider.default, providerConfig);
|
|
2975
2975
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
this.cache = createNullCacheManager();
|
|
2979
|
-
if (this.verbose && options.noCache) {
|
|
2980
|
-
logger.info("Cache disabled (--no-cache)");
|
|
2981
|
-
}
|
|
2982
|
-
} else {
|
|
2983
|
-
this.cache = createCacheManager({
|
|
2984
|
-
cacheDir: this.config.paths.cache,
|
|
2985
|
-
verbose: this.verbose
|
|
2986
|
-
});
|
|
2976
|
+
if (options.cacheManager) {
|
|
2977
|
+
this.cache = options.cacheManager;
|
|
2987
2978
|
if (this.verbose) {
|
|
2988
2979
|
const stats = this.cache.getStats();
|
|
2989
|
-
logger.info(`
|
|
2980
|
+
logger.info(`Using shared cache: ${stats.entries} entries`);
|
|
2981
|
+
}
|
|
2982
|
+
} else {
|
|
2983
|
+
const cacheDisabled = options.noCache || !this.config.paths?.cache;
|
|
2984
|
+
if (cacheDisabled) {
|
|
2985
|
+
this.cache = createNullCacheManager();
|
|
2986
|
+
if (this.verbose && options.noCache) {
|
|
2987
|
+
logger.info("Cache disabled (--no-cache)");
|
|
2988
|
+
}
|
|
2989
|
+
} else {
|
|
2990
|
+
this.cache = createCacheManager({
|
|
2991
|
+
cacheDir: this.config.paths.cache,
|
|
2992
|
+
verbose: this.verbose
|
|
2993
|
+
});
|
|
2994
|
+
if (this.verbose) {
|
|
2995
|
+
const stats = this.cache.getStats();
|
|
2996
|
+
logger.info(`Cache initialized: ${stats.entries} entries`);
|
|
2997
|
+
}
|
|
2990
2998
|
}
|
|
2991
2999
|
}
|
|
2992
3000
|
}
|
|
@@ -4232,6 +4240,7 @@ translateRouter.post(
|
|
|
4232
4240
|
try {
|
|
4233
4241
|
const baseConfig = await loadConfig();
|
|
4234
4242
|
const modeConfig = MODE_PRESETS2[body.mode ?? "balanced"];
|
|
4243
|
+
const cachePath = c.get("cachePath");
|
|
4235
4244
|
const config2 = {
|
|
4236
4245
|
...baseConfig,
|
|
4237
4246
|
languages: {
|
|
@@ -4248,12 +4257,16 @@ translateRouter.post(
|
|
|
4248
4257
|
...baseConfig.quality,
|
|
4249
4258
|
threshold: body.qualityThreshold ?? modeConfig.qualityThreshold,
|
|
4250
4259
|
maxIterations: body.maxIterations ?? modeConfig.maxIterations
|
|
4260
|
+
},
|
|
4261
|
+
paths: {
|
|
4262
|
+
...baseConfig.paths,
|
|
4263
|
+
cache: cachePath
|
|
4251
4264
|
}
|
|
4252
4265
|
};
|
|
4253
4266
|
const engine = createTranslationEngine({
|
|
4254
4267
|
config: config2,
|
|
4255
4268
|
verbose: false,
|
|
4256
|
-
noCache:
|
|
4269
|
+
noCache: !cachePath
|
|
4257
4270
|
});
|
|
4258
4271
|
if (body.glossary && body.glossary.length > 0) {
|
|
4259
4272
|
convertInlineGlossary(body.glossary, body.sourceLang, body.targetLang);
|
|
@@ -4345,6 +4358,12 @@ function handleTranslationError(c, error, requestId) {
|
|
|
4345
4358
|
// src/server/index.ts
|
|
4346
4359
|
function createApp(options) {
|
|
4347
4360
|
const app = new Hono();
|
|
4361
|
+
if (options.cachePath) {
|
|
4362
|
+
app.use("*", async (c, next) => {
|
|
4363
|
+
c.set("cachePath", options.cachePath);
|
|
4364
|
+
await next();
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4348
4367
|
app.use("*", createLoggerMiddleware({
|
|
4349
4368
|
json: options.jsonLogging ?? false
|
|
4350
4369
|
}));
|
|
@@ -4411,6 +4430,7 @@ llm-translate server started`);
|
|
|
4411
4430
|
console.log(` - Translate: http://${options.host}:${options.port}/translate`);
|
|
4412
4431
|
console.log(` - Auth: ${options.enableAuth ? "enabled" : "disabled"}`);
|
|
4413
4432
|
console.log(` - CORS: ${options.enableCors ? "enabled" : "disabled"}`);
|
|
4433
|
+
console.log(` - Cache: ${options.cachePath ?? "disabled"}`);
|
|
4414
4434
|
console.log("");
|
|
4415
4435
|
const shutdown = (signal) => {
|
|
4416
4436
|
console.log(`
|
|
@@ -4438,7 +4458,11 @@ var serveCommand = new Command("serve").description("Start the translation API s
|
|
|
4438
4458
|
"-p, --port <number>",
|
|
4439
4459
|
"Server port (env: TRANSLATE_PORT)",
|
|
4440
4460
|
process.env["TRANSLATE_PORT"] ?? "3000"
|
|
4441
|
-
).option("-H, --host <string>", "Host to bind", "0.0.0.0").option("--no-auth", "Disable API key authentication").option("--cors", "Enable CORS for browser clients").option("--json", "Use JSON logging format (for containers)").
|
|
4461
|
+
).option("-H, --host <string>", "Host to bind", "0.0.0.0").option("--no-auth", "Disable API key authentication").option("--cors", "Enable CORS for browser clients").option("--json", "Use JSON logging format (for containers)").option(
|
|
4462
|
+
"--cache-dir <path>",
|
|
4463
|
+
"Cache directory path (env: TRANSLATE_CACHE_DIR)",
|
|
4464
|
+
process.env["TRANSLATE_CACHE_DIR"]
|
|
4465
|
+
).action((options) => {
|
|
4442
4466
|
const port = parseInt(options.port ?? "3000", 10);
|
|
4443
4467
|
const host = options.host ?? "0.0.0.0";
|
|
4444
4468
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
@@ -4460,7 +4484,8 @@ var serveCommand = new Command("serve").description("Start the translation API s
|
|
|
4460
4484
|
enableAuth,
|
|
4461
4485
|
enableCors: options.cors ?? false,
|
|
4462
4486
|
apiKey: process.env["TRANSLATE_API_KEY"],
|
|
4463
|
-
jsonLogging: options.json ?? false
|
|
4487
|
+
jsonLogging: options.json ?? false,
|
|
4488
|
+
cachePath: options.cacheDir
|
|
4464
4489
|
});
|
|
4465
4490
|
});
|
|
4466
4491
|
|