Skip to content

Commit 2165cd8

Browse files
committed
feat: libreoffice v25 support
1 parent 0a349bb commit 2165cd8

31 files changed

+863
-229
lines changed

.gitignore

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@ yarn.lock
2626
.idea/.gitignore
2727
!.husky/_/husky.sh
2828

29-
test/cid
30-
test/lib
31-
test/node_modules
32-
test/dist/
33-
test/test-bundled.js
29+
dist/
30+
tests/integration/lambda/__fixtures__/documents/**/*.pdf
3431

3532
.DS_Store

eslint.config.mjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@ import rules from '@shelf/eslint-config/typescript.js';
22

33
export default [
44
...rules,
5-
{files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.json']},
5+
{files: ['**/*.js', '**/*.mjs', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.json']},
66
{
77
ignores: [
88
'**/node_modules/',
99
'**/coverage/',
1010
'**/lib/',
11+
'**/dist/',
1112
'renovate.json',
1213
'tsconfig.json',
1314
'.pnpm-store/',
1415
],
1516
},
17+
{
18+
files: ['tests/**/*.{js,jsx,ts,tsx,mjs}'],
19+
rules: {
20+
'multiline-ternary': 'off',
21+
},
22+
},
1623
];

jest.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
*/
44
const config = {
55
extensionsToTreatAsEsm: ['.ts'],
6+
roots: ['<rootDir>/tests'],
7+
testMatch: ['**/*.test.ts'],
8+
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
69
transform: {
710
'^.+\\.(t|j)sx?$': '@swc/jest',
811
},

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@
2929
"lint": "eslint . --fix",
3030
"lint:ci": "eslint .",
3131
"prepack": "pnpm build",
32-
"test": "jest src",
32+
"test": "pnpm run test:unit",
33+
"test:unit": "jest --runInBand --testPathPatterns tests/unit",
34+
"test:integration": "jest --runInBand --testPathPatterns tests/integration",
35+
"test:e2e": "pnpm run test:integration",
36+
"build:lambda": "node scripts/build-lambda.mjs",
37+
"test:lambda": "node scripts/run-lambda-integration.mjs",
3338
"type-check": "tsc --noEmit",
3439
"type-check:watch": "pnpm run type-check -- --watch"
3540
},

readme.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,31 +93,32 @@ module.exports.handler = async () => {
9393

9494
## Test
9595

96-
Beside unit tests that could be run via `pnpm test`, there are integration tests.
96+
The repository now ships two Jest suites and a standalone Lambda harness.
9797

98-
### Running Tests
98+
### Jest suites
99+
100+
- `pnpm test:unit` (or `pnpm test`) – runs unit tests in `tests/unit`
101+
- `pnpm test:integration` – spins up the Lambda container through Jest; automatically skips if Podman or the base image is unavailable
102+
- `pnpm test:e2e` – alias for the current integration suite
103+
104+
### Lambda harness
105+
106+
Use `pnpm test:lambda` to exercise the runtime end-to-end. The script bundles the handler, builds a disposable image on top of `libreoffice-lambda-base:local`, starts the Lambda runtime with Podman, invokes it once, and streams the conversion summary plus the temp output directory.
107+
108+
Requirements:
109+
110+
- `podman` installed locally
111+
- A base image tagged `libreoffice-lambda-base:local` (override with `LIBREOFFICE_LAMBDA_BASE_IMAGE`)
112+
- `pnpm build` completed beforehand (generates `lib/index.js` consumed by the bundler)
113+
114+
Handy workflow:
99115

100116
```sh
101-
# Unit tests
102-
pnpm test
103-
104-
# Integration test with Docker/Podman
105-
cd test
106-
./test.sh
107-
108-
# The test will:
109-
# 1. Build the ESM code and transpile to CommonJS for Lambda compatibility
110-
# 2. Process all files in test-data/ directory
111-
# 3. Generate PDFs in the same test-data/ directory
112-
# 4. Show conversion summary
117+
pnpm build
118+
pnpm test:lambda -- --cleanup
113119
```
114120

115-
The test setup includes:
116-
117-
- Automatic ESM to CommonJS transpilation using esbuild
118-
- Batch conversion of multiple file types (DOCX, HTML, etc.)
119-
- Volume mounting for easy PDF retrieval
120-
- Sample test files in `test/test-data/`
121+
`--cleanup` deletes the generated fixture output once you are done inspecting it. Additional flags like `--bundle`, `--fixtures`, and `--port` are available for custom runs; see `scripts/run-lambda-integration.mjs` for the full list.
121122

122123
## Publish
123124

scripts/build-lambda.mjs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env node
2+
import {fileURLToPath} from 'node:url';
3+
import path from 'node:path';
4+
import {buildLambdaBundle} from './utils/lambda.js';
5+
6+
const parseArgs = argv => {
7+
const args = {};
8+
9+
for (let index = 0; index < argv.length; index += 1) {
10+
const value = argv[index];
11+
12+
if (value === '--outfile' || value === '-o') {
13+
args.outfile = argv[index + 1];
14+
index += 1;
15+
continue;
16+
}
17+
18+
if (value === '--entry') {
19+
args.entry = argv[index + 1];
20+
index += 1;
21+
continue;
22+
}
23+
}
24+
25+
return args;
26+
};
27+
28+
const main = async () => {
29+
const args = parseArgs(process.argv.slice(2));
30+
31+
try {
32+
const outfile = await buildLambdaBundle(args);
33+
console.log(`Lambda bundle generated at ${outfile}`);
34+
} catch (error) {
35+
console.error(error instanceof Error ? error.message : error);
36+
process.exitCode = 1;
37+
}
38+
};
39+
40+
const executedFile = fileURLToPath(import.meta.url);
41+
42+
if (process.argv[1] && path.resolve(process.argv[1]) === executedFile) {
43+
await main();
44+
}

scripts/run-lambda-integration.mjs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env node
2+
import {fileURLToPath} from 'node:url';
3+
import path from 'node:path';
4+
import {isPodmanAvailable, removeTempDir, runLambdaContainer} from './utils/lambda.js';
5+
6+
const parseArgs = argv => {
7+
const args = {};
8+
9+
for (let index = 0; index < argv.length; index += 1) {
10+
const value = argv[index];
11+
12+
if (value === '--image-tag') {
13+
args.imageTag = argv[index + 1];
14+
index += 1;
15+
continue;
16+
}
17+
18+
if (value === '--base-image') {
19+
args.baseImage = argv[index + 1];
20+
index += 1;
21+
continue;
22+
}
23+
24+
if (value === '--bundle') {
25+
args.bundlePath = argv[index + 1];
26+
index += 1;
27+
continue;
28+
}
29+
30+
if (value === '--fixtures') {
31+
args.documentsDir = argv[index + 1];
32+
index += 1;
33+
continue;
34+
}
35+
36+
if (value === '--port') {
37+
args.port = Number(argv[index + 1]);
38+
index += 1;
39+
continue;
40+
}
41+
42+
if (value === '--cleanup') {
43+
args.cleanup = true;
44+
continue;
45+
}
46+
}
47+
48+
return args;
49+
};
50+
51+
const main = async () => {
52+
const args = parseArgs(process.argv.slice(2));
53+
54+
if (!(await isPodmanAvailable())) {
55+
console.error('podman is not available. Install it to run the Lambda integration harness.');
56+
process.exitCode = 1;
57+
58+
return;
59+
}
60+
61+
let cleanupDir;
62+
63+
try {
64+
const result = await runLambdaContainer(args);
65+
cleanupDir = result.outputDir;
66+
67+
console.log('Lambda invocation response:', result.response);
68+
69+
if (result.results) {
70+
console.log('Conversion results parsed from logs:', result.results);
71+
} else {
72+
console.log('Raw container logs:\n', result.logs);
73+
}
74+
75+
console.log('Generated files:', result.outputFiles);
76+
console.log(`Output directory: ${result.outputDir}`);
77+
} catch (error) {
78+
console.error(error instanceof Error ? error.message : error);
79+
process.exitCode = 1;
80+
} finally {
81+
if (args.cleanup && cleanupDir) {
82+
await removeTempDir(cleanupDir);
83+
}
84+
}
85+
};
86+
87+
const executedFile = fileURLToPath(import.meta.url);
88+
89+
if (process.argv[1] && path.resolve(process.argv[1]) === executedFile) {
90+
await main();
91+
}

scripts/utils/bundler.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {existsSync} from 'node:fs';
2+
import path from 'node:path';
3+
import esbuild from 'esbuild';
4+
import {ensureDirectory} from './io.js';
5+
import {lambdaBundlePath, lambdaSourceEntry, projectRoot} from './paths.js';
6+
7+
export const buildLambdaBundle = async ({
8+
entry = lambdaSourceEntry,
9+
outfile = lambdaBundlePath,
10+
} = {}) => {
11+
const buildOutputDir = path.dirname(outfile);
12+
13+
if (!existsSync(path.join(projectRoot, 'lib/index.js'))) {
14+
throw new Error('Missing lib/index.js. Run `pnpm build` before building the Lambda bundle.');
15+
}
16+
17+
ensureDirectory(buildOutputDir);
18+
19+
await esbuild.build({
20+
entryPoints: [entry],
21+
bundle: true,
22+
platform: 'node',
23+
target: 'node20',
24+
format: 'cjs',
25+
outfile,
26+
logLevel: 'info',
27+
external: ['child_process', 'fs', 'fs/promises', 'path', 'util', 'node:path'],
28+
});
29+
30+
return outfile;
31+
};

scripts/utils/io.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {existsSync, mkdirSync, mkdtempSync} from 'node:fs';
2+
import {promises as fsPromises} from 'node:fs';
3+
import os from 'node:os';
4+
import path from 'node:path';
5+
6+
export const ensureDirectory = directory => {
7+
if (!existsSync(directory)) {
8+
mkdirSync(directory, {recursive: true});
9+
}
10+
};
11+
12+
export const createTempDir = (prefix = 'lambda-fixtures-') => {
13+
const base = path.join(os.tmpdir(), prefix);
14+
const directory = mkdtempSync(base);
15+
ensureDirectory(directory);
16+
17+
return directory;
18+
};
19+
20+
export const removeDir = async directory => {
21+
await fsPromises.rm(directory, {recursive: true, force: true});
22+
};

0 commit comments

Comments
 (0)