@skillful-ai/piece-latex-to-pdf 0.0.1

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/CHECKLIST.md ADDED
@@ -0,0 +1,817 @@
1
+ # LaTeX to PDF Piece - Checklist de Implementación
2
+
3
+ ## 📋 Contexto
4
+
5
+ Esta pieza permite compilar documentos LaTeX a PDF usando un servicio externo desplegado en AWS Lambda.
6
+
7
+ **Servicio Backend:**
8
+ - URL: `https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev`
9
+ - Documentación: `https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev/docs`
10
+
11
+ **API del servicio:**
12
+ | Método | Endpoint | Descripción |
13
+ |--------|----------|-------------|
14
+ | `POST` | `/compile` | Envía LaTeX para compilar (async, retorna job_id) |
15
+ | `GET` | `/compile/{job_id}` | Obtiene estado del job (polling) |
16
+ | `GET` | `/health` | Health check |
17
+
18
+ **Flujo de compilación:**
19
+ 1. `POST /compile` con `file_content` (base64), `filename`, `compiler`, `output_format`
20
+ 2. Respuesta: `{ job_id, status: "pending" }`
21
+ 3. Polling `GET /compile/{job_id}` hasta `status: "completed"`
22
+ 4. Resultado: `{ pdf_url, log_url, duration_ms, ... }`
23
+
24
+ ---
25
+
26
+ ## 📁 Estructura de Directorios
27
+
28
+ ```
29
+ packages/pieces/custom/latex-to-pdf/
30
+ ├── .eslintrc.json # [1] Config ESLint
31
+ ├── package.json # [2] Dependencias npm
32
+ ├── project.json # [3] Config Nx (build, test, lint)
33
+ ├── tsconfig.json # [4] TypeScript base
34
+ ├── tsconfig.lib.json # [5] TypeScript para build
35
+ ├── tsconfig.spec.json # [6] TypeScript para tests
36
+ ├── jest.config.ts # [7] Config Jest
37
+ ├── README.md # [8] Documentación de la pieza
38
+ ├── src/
39
+ │ ├── index.ts # [9] Entry point - exporta la pieza
40
+ │ └── lib/
41
+ │ ├── auth.ts # [10] Autenticación (URL + API key)
42
+ │ ├── common.ts # [11] Helpers (polling, API calls)
43
+ │ └── actions/
44
+ │ └── compile-latex.ts # [12] Acción principal
45
+ └── test/
46
+ └── compile-latex.test.ts # [13] Tests unitarios
47
+ ```
48
+
49
+ ---
50
+
51
+ ## ✅ Checklist (Orden de Implementación)
52
+
53
+ ### Fase 1: Configuración del Proyecto
54
+
55
+ #### [1] `.eslintrc.json`
56
+ **Qué hace:** Configura ESLint para el linting del código.
57
+ **Copiar de:** `packages/pieces/custom/web/.eslintrc.json`
58
+ **Cambios:** Ninguno, es genérico.
59
+
60
+ ```jsonc
61
+ {
62
+ "extends": ["../../../../.eslintrc.base.json"],
63
+ "ignorePatterns": ["!**/*"],
64
+ "overrides": [
65
+ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": {} },
66
+ { "files": ["*.ts", "*.tsx"], "rules": {} },
67
+ { "files": ["*.js", "*.jsx"], "rules": {} }
68
+ ]
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ #### [2] `package.json`
75
+ **Qué hace:** Define el paquete npm, nombre, versión y dependencias.
76
+ **Importante:** El nombre debe seguir el patrón `@skillful-ai/piece-<nombre>`.
77
+
78
+ ```json
79
+ {
80
+ "name": "@skillful-ai/piece-latex-to-pdf",
81
+ "version": "0.0.1",
82
+ "type": "commonjs",
83
+ "main": "./src/index.js",
84
+ "types": "./src/index.d.ts",
85
+ "dependencies": {
86
+ "@activepieces/pieces-common": "*",
87
+ "@activepieces/pieces-framework": "*",
88
+ "@activepieces/shared": "*",
89
+ "tslib": "^2.3.0"
90
+ }
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ #### [3] `project.json`
97
+ **Qué hace:** Configura Nx para build, test y lint.
98
+ **Importante:**
99
+ - `name` debe ser `pieces-latex-to-pdf`
100
+ - Actualizar todos los paths a `latex-to-pdf`
101
+
102
+ ```json
103
+ {
104
+ "name": "pieces-latex-to-pdf",
105
+ "$schema": "../../../../node_modules/nx/schemas/project-schema.json",
106
+ "sourceRoot": "packages/pieces/custom/latex-to-pdf/src",
107
+ "projectType": "library",
108
+ "release": {
109
+ "version": {
110
+ "manifestRootsToUpdate": ["dist/{projectRoot}"],
111
+ "currentVersionResolver": "git-tag",
112
+ "fallbackCurrentVersionResolver": "disk"
113
+ }
114
+ },
115
+ "tags": [],
116
+ "targets": {
117
+ "build": {
118
+ "executor": "@nx/js:tsc",
119
+ "outputs": ["{options.outputPath}"],
120
+ "options": {
121
+ "outputPath": "dist/packages/pieces/custom/latex-to-pdf",
122
+ "tsConfig": "packages/pieces/custom/latex-to-pdf/tsconfig.lib.json",
123
+ "packageJson": "packages/pieces/custom/latex-to-pdf/package.json",
124
+ "main": "packages/pieces/custom/latex-to-pdf/src/index.ts",
125
+ "assets": ["packages/pieces/custom/latex-to-pdf/*.md"],
126
+ "buildableProjectDepsInPackageJsonType": "dependencies",
127
+ "updateBuildableProjectDepsInPackageJson": true
128
+ }
129
+ },
130
+ "test": {
131
+ "executor": "@nx/jest:jest",
132
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
133
+ "options": {
134
+ "jestConfig": "packages/pieces/custom/latex-to-pdf/jest.config.ts"
135
+ }
136
+ },
137
+ "nx-release-publish": {
138
+ "options": {
139
+ "packageRoot": "dist/{projectRoot}"
140
+ }
141
+ },
142
+ "lint": {
143
+ "executor": "@nx/eslint:lint",
144
+ "outputs": ["{options.outputFile}"]
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ ---
151
+
152
+ #### [4] `tsconfig.json`
153
+ **Qué hace:** Config base de TypeScript que extiende del monorepo.
154
+
155
+ ```json
156
+ {
157
+ "extends": "../../../../tsconfig.base.json",
158
+ "compilerOptions": {
159
+ "module": "commonjs",
160
+ "forceConsistentCasingInFileNames": true,
161
+ "strict": true,
162
+ "importHelpers": true,
163
+ "noImplicitOverride": true,
164
+ "noImplicitReturns": true,
165
+ "noFallthroughCasesInSwitch": true,
166
+ "noPropertyAccessFromIndexSignature": true
167
+ },
168
+ "files": [],
169
+ "include": [],
170
+ "references": [
171
+ { "path": "./tsconfig.lib.json" }
172
+ ]
173
+ }
174
+ ```
175
+
176
+ ---
177
+
178
+ #### [5] `tsconfig.lib.json`
179
+ **Qué hace:** Config TypeScript para el build de producción.
180
+
181
+ ```json
182
+ {
183
+ "extends": "./tsconfig.json",
184
+ "compilerOptions": {
185
+ "outDir": "../../../../dist/out-tsc",
186
+ "declaration": true,
187
+ "types": ["node"]
188
+ },
189
+ "include": ["src/**/*.ts"],
190
+ "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ #### [6] `tsconfig.spec.json`
197
+ **Qué hace:** Config TypeScript para tests.
198
+
199
+ ```json
200
+ {
201
+ "extends": "./tsconfig.json",
202
+ "compilerOptions": {
203
+ "outDir": "../../../../dist/out-tsc",
204
+ "module": "commonjs",
205
+ "moduleResolution": "node10",
206
+ "types": ["jest", "node"]
207
+ },
208
+ "include": [
209
+ "jest.config.ts",
210
+ "src/**/*.test.ts",
211
+ "src/**/*.spec.ts",
212
+ "test/**/*.test.ts",
213
+ "test/**/*.spec.ts"
214
+ ]
215
+ }
216
+ ```
217
+
218
+ ---
219
+
220
+ #### [7] `jest.config.ts`
221
+ **Qué hace:** Configura Jest para tests unitarios.
222
+
223
+ ```typescript
224
+ export default {
225
+ displayName: 'pieces-latex-to-pdf',
226
+ preset: '../../../../jest.preset.js',
227
+ testEnvironment: 'node',
228
+ transform: {
229
+ '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
230
+ },
231
+ moduleFileExtensions: ['ts', 'js', 'html'],
232
+ coverageDirectory: '../../../../coverage/packages/pieces/custom/latex-to-pdf',
233
+ passWithNoTests: true,
234
+ };
235
+ ```
236
+
237
+ ---
238
+
239
+ #### [8] `README.md`
240
+ **Qué hace:** Documentación de la pieza para desarrolladores.
241
+
242
+ ```markdown
243
+ # LaTeX to PDF Piece
244
+
245
+ Compile LaTeX documents to PDF using the Skillful LaTeX Compiler Service.
246
+
247
+ ## Features
248
+
249
+ - Compile `.tex` files or `.zip` archives to PDF
250
+ - Support for `pdflatex` and `xelatex` compilers
251
+ - Multiple output formats: URL, base64, or raw bytes
252
+ - Async compilation with automatic polling
253
+
254
+ ## Authentication
255
+
256
+ Configure the piece with:
257
+ - **Service URL** (optional): Custom LaTeX service URL. Defaults to Skillful's service.
258
+ - **API Key** (optional): For authenticated requests.
259
+
260
+ ## Actions
261
+
262
+ ### Compile LaTeX
263
+
264
+ Compiles a LaTeX document and returns the PDF.
265
+
266
+ **Inputs:**
267
+ - `latexContent`: The LaTeX source code
268
+ - `filename`: Filename (e.g., `main.tex`)
269
+ - `compiler`: `pdflatex` or `xelatex`
270
+ - `outputFormat`: `url`, `base64`, or `bytes`
271
+
272
+ **Outputs:**
273
+ - `success`: boolean
274
+ - `pdfUrl`: Presigned URL to download PDF
275
+ - `logUrl`: Presigned URL to download compilation log
276
+ - `durationMs`: Compilation time in milliseconds
277
+
278
+ ## Building
279
+
280
+ \`\`\`bash
281
+ npx nx build pieces-latex-to-pdf
282
+ \`\`\`
283
+
284
+ ## Testing
285
+
286
+ \`\`\`bash
287
+ npx nx test pieces-latex-to-pdf
288
+ \`\`\`
289
+ ```
290
+
291
+ ---
292
+
293
+ ### Fase 2: Código de la Pieza
294
+
295
+ #### [9] `src/index.ts`
296
+ **Qué hace:** Entry point que exporta la pieza con `createPiece()`.
297
+ **Importa:** auth, actions
298
+ **Registra:** La pieza en el sistema de Activepieces.
299
+
300
+ ```typescript
301
+ import { createPiece } from "@activepieces/pieces-framework";
302
+ import { PieceCategory } from "@activepieces/shared";
303
+ import { latexToPdfAuth } from "./lib/auth";
304
+ import { compileLatex } from "./lib/actions/compile-latex";
305
+
306
+ export const latexToPdf = createPiece({
307
+ displayName: "LaTeX to PDF",
308
+ description: "Compile LaTeX documents to PDF using a cloud compilation service.",
309
+ auth: latexToPdfAuth,
310
+ minimumSupportedRelease: "0.36.1",
311
+ logoUrl: "https://cdn.activepieces.com/pieces/latex.png", // TODO: Actualizar con logo real
312
+ categories: [PieceCategory.CORE],
313
+ authors: ["skillful-ai"],
314
+ actions: [compileLatex],
315
+ triggers: [],
316
+ });
317
+ ```
318
+
319
+ ---
320
+
321
+ #### [10] `src/lib/auth.ts`
322
+ **Qué hace:** Define la autenticación de la pieza.
323
+ **Campos:**
324
+ - `serviceUrl` (opcional): URL del servicio LaTeX (default: nuestro servicio)
325
+ - `apiKey` (opcional): API key para autenticación
326
+
327
+ ```typescript
328
+ import { PieceAuth, Property } from "@activepieces/pieces-framework";
329
+
330
+ export const latexToPdfAuth = PieceAuth.CustomAuth({
331
+ description: "Configure the LaTeX compilation service connection",
332
+ required: false, // Opcional porque tenemos un default
333
+ props: {
334
+ serviceUrl: Property.ShortText({
335
+ displayName: "Service URL",
336
+ description: "LaTeX compiler service URL. Leave empty to use default Skillful service.",
337
+ required: false,
338
+ defaultValue: "https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev",
339
+ }),
340
+ apiKey: PieceAuth.SecretText({
341
+ displayName: "API Key",
342
+ description: "API key for authenticated requests (optional)",
343
+ required: false,
344
+ }),
345
+ },
346
+ });
347
+
348
+ // Tipo para usar en acciones
349
+ export type LatexToPdfAuth = {
350
+ serviceUrl?: string;
351
+ apiKey?: string;
352
+ };
353
+ ```
354
+
355
+ ---
356
+
357
+ #### [11] `src/lib/common.ts`
358
+ **Qué hace:** Funciones helper compartidas.
359
+ **Contiene:**
360
+ - `getServiceUrl()`: Obtiene la URL del servicio (con default)
361
+ - `pollForCompletion()`: Hace polling hasta que el job termine
362
+ - Constantes de configuración
363
+
364
+ ```typescript
365
+ import { httpClient, HttpMethod } from "@activepieces/pieces-common";
366
+ import { LatexToPdfAuth } from "./auth";
367
+
368
+ // Constantes
369
+ export const DEFAULT_SERVICE_URL = "https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev";
370
+ export const POLLING_INTERVAL_MS = 2000; // 2 segundos
371
+ export const MAX_POLLING_ATTEMPTS = 150; // 5 minutos máximo (150 * 2s)
372
+
373
+ // Tipos basados en el OpenAPI del servicio
374
+ export type CompilerType = "pdflatex" | "xelatex";
375
+ export type OutputFormat = "url" | "base64" | "bytes";
376
+ export type JobStatus = "pending" | "processing" | "completed" | "failed";
377
+
378
+ export interface CompileRequest {
379
+ file_content: string; // Base64
380
+ filename: string;
381
+ main_file?: string;
382
+ compiler?: CompilerType;
383
+ output_format?: OutputFormat;
384
+ compiler_options?: string[];
385
+ }
386
+
387
+ export interface CompileResponse {
388
+ job_id: string;
389
+ status: JobStatus;
390
+ message: string;
391
+ }
392
+
393
+ export interface CompilationResult {
394
+ success: boolean;
395
+ output_format: OutputFormat;
396
+ pdf_url?: string;
397
+ pdf_base64?: string;
398
+ log_url?: string;
399
+ error_message?: string;
400
+ exit_code: number;
401
+ duration_ms: number;
402
+ pdf_size_bytes?: number;
403
+ }
404
+
405
+ export interface JobStatusResponse {
406
+ job_id: string;
407
+ status: JobStatus;
408
+ message: string;
409
+ error_message?: string;
410
+ result?: CompilationResult;
411
+ }
412
+
413
+ /**
414
+ * Obtiene la URL del servicio, usando el default si no se configura
415
+ */
416
+ export function getServiceUrl(auth: LatexToPdfAuth | undefined): string {
417
+ return auth?.serviceUrl?.trim() || DEFAULT_SERVICE_URL;
418
+ }
419
+
420
+ /**
421
+ * Construye headers para las peticiones
422
+ */
423
+ export function buildHeaders(auth: LatexToPdfAuth | undefined): Record<string, string> {
424
+ const headers: Record<string, string> = {
425
+ "Content-Type": "application/json",
426
+ };
427
+
428
+ if (auth?.apiKey?.trim()) {
429
+ headers["X-API-Key"] = auth.apiKey.trim();
430
+ }
431
+
432
+ return headers;
433
+ }
434
+
435
+ /**
436
+ * Envía el LaTeX para compilar
437
+ */
438
+ export async function submitCompilation(
439
+ serviceUrl: string,
440
+ request: CompileRequest,
441
+ headers: Record<string, string>
442
+ ): Promise<CompileResponse> {
443
+ const response = await httpClient.sendRequest<CompileResponse>({
444
+ method: HttpMethod.POST,
445
+ url: `${serviceUrl}/compile`,
446
+ headers,
447
+ body: request,
448
+ });
449
+
450
+ return response.body;
451
+ }
452
+
453
+ /**
454
+ * Obtiene el estado de un job
455
+ */
456
+ export async function getJobStatus(
457
+ serviceUrl: string,
458
+ jobId: string,
459
+ headers: Record<string, string>
460
+ ): Promise<JobStatusResponse> {
461
+ const response = await httpClient.sendRequest<JobStatusResponse>({
462
+ method: HttpMethod.GET,
463
+ url: `${serviceUrl}/compile/${jobId}`,
464
+ headers,
465
+ });
466
+
467
+ return response.body;
468
+ }
469
+
470
+ /**
471
+ * Hace polling hasta que el job termine o falle
472
+ */
473
+ export async function pollForCompletion(
474
+ serviceUrl: string,
475
+ jobId: string,
476
+ headers: Record<string, string>
477
+ ): Promise<JobStatusResponse> {
478
+ let attempts = 0;
479
+
480
+ while (attempts < MAX_POLLING_ATTEMPTS) {
481
+ const status = await getJobStatus(serviceUrl, jobId, headers);
482
+
483
+ if (status.status === "completed" || status.status === "failed") {
484
+ return status;
485
+ }
486
+
487
+ // Esperar antes del siguiente intento
488
+ await new Promise(resolve => setTimeout(resolve, POLLING_INTERVAL_MS));
489
+ attempts++;
490
+ }
491
+
492
+ throw new Error(`Compilation timed out after ${MAX_POLLING_ATTEMPTS * POLLING_INTERVAL_MS / 1000} seconds`);
493
+ }
494
+
495
+ /**
496
+ * Convierte string a Base64
497
+ */
498
+ export function toBase64(content: string): string {
499
+ return Buffer.from(content, "utf-8").toString("base64");
500
+ }
501
+ ```
502
+
503
+ ---
504
+
505
+ #### [12] `src/lib/actions/compile-latex.ts`
506
+ **Qué hace:** Acción principal que compila LaTeX a PDF.
507
+ **Props:**
508
+ - `latexContent`: Código LaTeX (LongText)
509
+ - `filename`: Nombre del archivo
510
+ - `compiler`: pdflatex o xelatex
511
+ - `outputFormat`: url, base64, bytes
512
+
513
+ **Lógica:**
514
+ 1. Validar inputs
515
+ 2. Convertir contenido a Base64
516
+ 3. Enviar a `/compile`
517
+ 4. Hacer polling hasta completar
518
+ 5. Retornar resultado
519
+
520
+ ```typescript
521
+ import { createAction, Property } from "@activepieces/pieces-framework";
522
+ import { latexToPdfAuth, LatexToPdfAuth } from "../auth";
523
+ import {
524
+ getServiceUrl,
525
+ buildHeaders,
526
+ submitCompilation,
527
+ pollForCompletion,
528
+ toBase64,
529
+ CompilerType,
530
+ OutputFormat,
531
+ } from "../common";
532
+
533
+ export const compileLatex = createAction({
534
+ name: "compile_latex",
535
+ displayName: "Compile LaTeX",
536
+ description: "Compile a LaTeX document to PDF",
537
+ auth: latexToPdfAuth,
538
+ props: {
539
+ latexContent: Property.LongText({
540
+ displayName: "LaTeX Content",
541
+ description: "The LaTeX source code to compile",
542
+ required: true,
543
+ }),
544
+ filename: Property.ShortText({
545
+ displayName: "Filename",
546
+ description: "Name of the .tex file (e.g., main.tex)",
547
+ required: true,
548
+ defaultValue: "main.tex",
549
+ }),
550
+ compiler: Property.StaticDropdown({
551
+ displayName: "Compiler",
552
+ description: "LaTeX compiler to use",
553
+ required: true,
554
+ defaultValue: "xelatex",
555
+ options: {
556
+ options: [
557
+ { label: "XeLaTeX (recommended for Unicode)", value: "xelatex" },
558
+ { label: "pdfLaTeX (traditional)", value: "pdflatex" },
559
+ ],
560
+ },
561
+ }),
562
+ outputFormat: Property.StaticDropdown({
563
+ displayName: "Output Format",
564
+ description: "How to return the compiled PDF",
565
+ required: true,
566
+ defaultValue: "url",
567
+ options: {
568
+ options: [
569
+ { label: "URL (presigned download link)", value: "url" },
570
+ { label: "Base64 (embedded in response)", value: "base64" },
571
+ ],
572
+ },
573
+ }),
574
+ },
575
+ async run(context) {
576
+ const { latexContent, filename, compiler, outputFormat } = context.propsValue;
577
+ const auth = context.auth as LatexToPdfAuth | undefined;
578
+
579
+ // Validaciones
580
+ if (!latexContent?.trim()) {
581
+ return {
582
+ success: false,
583
+ error: "LaTeX content cannot be empty",
584
+ };
585
+ }
586
+
587
+ if (!filename?.trim()) {
588
+ return {
589
+ success: false,
590
+ error: "Filename is required",
591
+ };
592
+ }
593
+
594
+ // Asegurar extensión .tex
595
+ const normalizedFilename = filename.endsWith(".tex") ? filename : `${filename}.tex`;
596
+
597
+ try {
598
+ const serviceUrl = getServiceUrl(auth);
599
+ const headers = buildHeaders(auth);
600
+
601
+ // 1. Enviar para compilar
602
+ const submitResponse = await submitCompilation(serviceUrl, {
603
+ file_content: toBase64(latexContent),
604
+ filename: normalizedFilename,
605
+ compiler: compiler as CompilerType,
606
+ output_format: outputFormat as OutputFormat,
607
+ }, headers);
608
+
609
+ // 2. Polling hasta completar
610
+ const result = await pollForCompletion(serviceUrl, submitResponse.job_id, headers);
611
+
612
+ // 3. Procesar resultado
613
+ if (result.status === "failed") {
614
+ return {
615
+ success: false,
616
+ error: result.error_message || result.message || "Compilation failed",
617
+ jobId: result.job_id,
618
+ logUrl: result.result?.log_url,
619
+ };
620
+ }
621
+
622
+ // Éxito
623
+ return {
624
+ success: true,
625
+ jobId: result.job_id,
626
+ pdfUrl: result.result?.pdf_url,
627
+ pdfBase64: result.result?.pdf_base64,
628
+ logUrl: result.result?.log_url,
629
+ durationMs: result.result?.duration_ms,
630
+ pdfSizeBytes: result.result?.pdf_size_bytes,
631
+ compiler,
632
+ filename: normalizedFilename,
633
+ };
634
+ } catch (error) {
635
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
636
+ return {
637
+ success: false,
638
+ error: errorMessage,
639
+ };
640
+ }
641
+ },
642
+ });
643
+ ```
644
+
645
+ ---
646
+
647
+ ### Fase 3: Tests
648
+
649
+ #### [13] `test/compile-latex.test.ts`
650
+ **Qué hace:** Tests unitarios para la acción.
651
+ **Mockea:** httpClient para no hacer llamadas reales.
652
+
653
+ ```typescript
654
+ import { compileLatex } from "../src/lib/actions/compile-latex";
655
+
656
+ // Mock httpClient
657
+ jest.mock("@activepieces/pieces-common", () => ({
658
+ httpClient: {
659
+ sendRequest: jest.fn(),
660
+ },
661
+ HttpMethod: {
662
+ GET: "GET",
663
+ POST: "POST",
664
+ },
665
+ }));
666
+
667
+ const { httpClient } = jest.requireMock("@activepieces/pieces-common") as {
668
+ httpClient: { sendRequest: jest.Mock };
669
+ };
670
+
671
+ describe("compileLatex action", () => {
672
+ beforeEach(() => {
673
+ jest.clearAllMocks();
674
+ });
675
+
676
+ it("returns error when latexContent is empty", async () => {
677
+ const result = await compileLatex.run({
678
+ auth: undefined,
679
+ propsValue: {
680
+ latexContent: "",
681
+ filename: "main.tex",
682
+ compiler: "xelatex",
683
+ outputFormat: "url",
684
+ },
685
+ } as any);
686
+
687
+ expect(result.success).toBe(false);
688
+ expect(result.error).toMatch(/empty/i);
689
+ });
690
+
691
+ it("returns error when filename is empty", async () => {
692
+ const result = await compileLatex.run({
693
+ auth: undefined,
694
+ propsValue: {
695
+ latexContent: "\\documentclass{article}",
696
+ filename: "",
697
+ compiler: "xelatex",
698
+ outputFormat: "url",
699
+ },
700
+ } as any);
701
+
702
+ expect(result.success).toBe(false);
703
+ expect(result.error).toMatch(/required/i);
704
+ });
705
+
706
+ it("successfully compiles LaTeX and returns PDF URL", async () => {
707
+ // Mock submit response
708
+ httpClient.sendRequest
709
+ .mockResolvedValueOnce({
710
+ body: { job_id: "test-job-123", status: "pending", message: "Job created" },
711
+ })
712
+ // Mock poll response - completed
713
+ .mockResolvedValueOnce({
714
+ body: {
715
+ job_id: "test-job-123",
716
+ status: "completed",
717
+ message: "Compilation successful",
718
+ result: {
719
+ success: true,
720
+ pdf_url: "https://s3.amazonaws.com/bucket/test.pdf",
721
+ log_url: "https://s3.amazonaws.com/bucket/test.log",
722
+ duration_ms: 3500,
723
+ pdf_size_bytes: 125000,
724
+ },
725
+ },
726
+ });
727
+
728
+ const result = await compileLatex.run({
729
+ auth: undefined,
730
+ propsValue: {
731
+ latexContent: "\\documentclass{article}\\begin{document}Hello\\end{document}",
732
+ filename: "main.tex",
733
+ compiler: "xelatex",
734
+ outputFormat: "url",
735
+ },
736
+ } as any);
737
+
738
+ expect(result.success).toBe(true);
739
+ expect(result.pdfUrl).toBe("https://s3.amazonaws.com/bucket/test.pdf");
740
+ expect(result.durationMs).toBe(3500);
741
+ });
742
+
743
+ it("handles compilation failure", async () => {
744
+ httpClient.sendRequest
745
+ .mockResolvedValueOnce({
746
+ body: { job_id: "test-job-456", status: "pending", message: "Job created" },
747
+ })
748
+ .mockResolvedValueOnce({
749
+ body: {
750
+ job_id: "test-job-456",
751
+ status: "failed",
752
+ message: "Compilation failed",
753
+ error_message: "Undefined control sequence",
754
+ result: {
755
+ success: false,
756
+ log_url: "https://s3.amazonaws.com/bucket/error.log",
757
+ },
758
+ },
759
+ });
760
+
761
+ const result = await compileLatex.run({
762
+ auth: undefined,
763
+ propsValue: {
764
+ latexContent: "\\invalid{command}",
765
+ filename: "main.tex",
766
+ compiler: "pdflatex",
767
+ outputFormat: "url",
768
+ },
769
+ } as any);
770
+
771
+ expect(result.success).toBe(false);
772
+ expect(result.error).toMatch(/Undefined control sequence/);
773
+ expect(result.logUrl).toBeDefined();
774
+ });
775
+ });
776
+ ```
777
+
778
+ ---
779
+
780
+ ## 🚀 Pasos para Probar Localmente
781
+
782
+ 1. **Agregar a AP_DEV_PIECES** en `packages/server/api/.env`:
783
+ ```
784
+ AP_DEV_PIECES="...,latex-to-pdf"
785
+ ```
786
+
787
+ 2. **Build de la pieza:**
788
+ ```bash
789
+ npx nx build pieces-latex-to-pdf
790
+ ```
791
+
792
+ 3. **Correr tests:**
793
+ ```bash
794
+ npx nx test pieces-latex-to-pdf
795
+ ```
796
+
797
+ 4. **Reiniciar el backend** y refrescar el frontend.
798
+
799
+ ---
800
+
801
+ ## 📝 Orden de Creación Recomendado
802
+
803
+ 1. ✅ `.eslintrc.json` - Config ESLint
804
+ 2. ✅ `package.json` - Dependencias
805
+ 3. ✅ `project.json` - Config Nx
806
+ 4. ✅ `tsconfig.json` - TS base
807
+ 5. ✅ `tsconfig.lib.json` - TS build
808
+ 6. ✅ `tsconfig.spec.json` - TS tests
809
+ 7. ✅ `jest.config.ts` - Config Jest
810
+ 8. ✅ `src/lib/auth.ts` - Autenticación
811
+ 9. ✅ `src/lib/common.ts` - Helpers
812
+ 10. ✅ `src/lib/actions/compile-latex.ts` - Acción principal
813
+ 11. ✅ `src/index.ts` - Entry point
814
+ 12. ✅ `test/compile-latex.test.ts` - Tests (8/8 passing)
815
+ 13. ✅ `README.md` - Documentación
816
+ 14. ✅ Build y test local - PASSED
817
+ 15. ⬜ Commit y push
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # LaTeX to PDF Piece
2
+
3
+ Compile LaTeX documents to PDF using the Skillful LaTeX Compiler Service.
4
+
5
+ ## Features
6
+
7
+ - ✅ Compile `.tex` files to PDF
8
+ - ✅ Support for `pdflatex` and `xelatex` compilers
9
+ - ✅ Multiple output formats: URL (presigned) or Base64
10
+ - ✅ Async compilation with automatic polling
11
+ - ✅ Detailed error messages with compilation logs
12
+
13
+ ## Authentication
14
+
15
+ Configure the piece with (all optional):
16
+
17
+ | Field | Description |
18
+ |-------|-------------|
19
+ | **Service URL** | Custom LaTeX service URL. Defaults to Skillful's service. |
20
+ | **API Key** | API key for authenticated requests. |
21
+
22
+ ## Actions
23
+
24
+ ### Compile LaTeX
25
+
26
+ Compiles a LaTeX document and returns the PDF.
27
+
28
+ **Inputs:**
29
+
30
+ | Property | Type | Required | Default | Description |
31
+ |----------|------|----------|---------|-------------|
32
+ | `latexContent` | Long Text | ✅ | - | The LaTeX source code to compile |
33
+ | `filename` | Short Text | ✅ | `main.tex` | Name of the .tex file |
34
+ | `compiler` | Dropdown | ✅ | `xelatex` | Compiler: `xelatex` or `pdflatex` |
35
+ | `outputFormat` | Dropdown | ✅ | `url` | Output: `url` or `base64` |
36
+
37
+ **Outputs (Success):**
38
+
39
+ ```json
40
+ {
41
+ "success": true,
42
+ "jobId": "550e8400-e29b-41d4-a716-446655440000",
43
+ "pdfUrl": "https://s3.amazonaws.com/.../output.pdf",
44
+ "logUrl": "https://s3.amazonaws.com/.../output.log",
45
+ "durationMs": 3500,
46
+ "pdfSizeBytes": 125000,
47
+ "compiler": "xelatex",
48
+ "filename": "main.tex"
49
+ }
50
+ ```
51
+
52
+ **Outputs (Failure):**
53
+
54
+ ```json
55
+ {
56
+ "success": false,
57
+ "error": "Undefined control sequence \\invalid",
58
+ "jobId": "550e8400-e29b-41d4-a716-446655440000",
59
+ "logUrl": "https://s3.amazonaws.com/.../error.log"
60
+ }
61
+ ```
62
+
63
+ ## Example Usage
64
+
65
+ ### Simple Document
66
+
67
+ ```latex
68
+ \documentclass{article}
69
+ \begin{document}
70
+ Hello, World!
71
+ \end{document}
72
+ ```
73
+
74
+ ### Document with Unicode (use XeLaTeX)
75
+
76
+ ```latex
77
+ \documentclass{article}
78
+ \usepackage{fontspec}
79
+ \begin{document}
80
+ Hello, 世界! مرحبا
81
+ \end{document}
82
+ ```
83
+
84
+ ## Building
85
+
86
+ ```bash
87
+ npx nx build pieces-latex-to-pdf
88
+ ```
89
+
90
+ ## Testing
91
+
92
+ ```bash
93
+ npx nx test pieces-latex-to-pdf
94
+ ```
95
+
96
+ ## Development
97
+
98
+ Add to `AP_DEV_PIECES` in `packages/server/api/.env`:
99
+
100
+ ```
101
+ AP_DEV_PIECES="...,latex-to-pdf"
102
+ ```
103
+
104
+ Then restart the backend and refresh the frontend.
105
+
106
+ ## Service API
107
+
108
+ The piece uses the Skillful LaTeX Compiler Service:
109
+
110
+ - **Base URL**: `https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev`
111
+ - **Docs**: `https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev/docs`
112
+
113
+ | Method | Endpoint | Description |
114
+ |--------|----------|-------------|
115
+ | `POST` | `/compile` | Submit LaTeX for compilation |
116
+ | `GET` | `/compile/{job_id}` | Get compilation status |
117
+ | `GET` | `/health` | Health check |
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@skillful-ai/piece-latex-to-pdf",
3
+ "version": "0.0.1",
4
+ "type": "commonjs",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "dependencies": {
8
+ "@activepieces/pieces-common": "^0.7.0",
9
+ "@activepieces/pieces-framework": "^0.20.1",
10
+ "tslib": "^2.3.0",
11
+ "@activepieces/shared": "0.22.0"
12
+ },
13
+ "overrides": {
14
+ "@tryfabric/martian": {
15
+ "@notionhq/client": "$@notionhq/client"
16
+ },
17
+ "vite": {
18
+ "rollup": "npm:@rollup/wasm-node"
19
+ },
20
+ "undici": "6.19.8"
21
+ },
22
+ "resolutions": {
23
+ "rollup": "npm:@rollup/wasm-node",
24
+ "undici": "6.19.8"
25
+ }
26
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export declare const latexToPdf: import("@activepieces/pieces-framework").Piece<import("@activepieces/pieces-framework").CustomAuthProperty<{
2
+ serviceUrl: import("@activepieces/pieces-framework").ShortTextProperty<false>;
3
+ apiKey: import("@activepieces/pieces-framework").SecretTextProperty<false>;
4
+ }>>;
package/src/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.latexToPdf = void 0;
4
+ const pieces_framework_1 = require("@activepieces/pieces-framework");
5
+ const shared_1 = require("@activepieces/shared");
6
+ const auth_1 = require("./lib/auth");
7
+ const compile_latex_1 = require("./lib/actions/compile-latex");
8
+ exports.latexToPdf = (0, pieces_framework_1.createPiece)({
9
+ displayName: "LaTeX to PDF",
10
+ description: "Compile LaTeX documents to PDF using a cloud compilation service. Supports pdflatex and xelatex compilers with multiple output formats.",
11
+ auth: auth_1.latexToPdfAuth,
12
+ minimumSupportedRelease: "0.36.1",
13
+ logoUrl: "https://cdn.activepieces.com/pieces/latex.png",
14
+ authors: ["skillful-ai"],
15
+ categories: [shared_1.PieceCategory.CORE],
16
+ actions: [compile_latex_1.compileLatex],
17
+ triggers: [],
18
+ });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/pieces/custom/latex-to-pdf/src/index.ts"],"names":[],"mappings":";;;AAAA,qEAA6D;AAC7D,iDAAqD;AACrD,qCAA4C;AAC5C,+DAA2D;AAE9C,QAAA,UAAU,GAAG,IAAA,8BAAW,EAAC;IACpC,WAAW,EAAE,cAAc;IAC3B,WAAW,EAAE,yIAAyI;IACtJ,IAAI,EAAE,qBAAc;IACpB,uBAAuB,EAAE,QAAQ;IACjC,OAAO,EAAE,+CAA+C;IACxD,OAAO,EAAE,CAAC,aAAa,CAAC;IACxB,UAAU,EAAE,CAAC,sBAAa,CAAC,IAAI,CAAC;IAChC,OAAO,EAAE,CAAC,4BAAY,CAAC;IACvB,QAAQ,EAAE,EAAE;CACb,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare const compileLatex: import("@activepieces/pieces-framework").IAction<import("@activepieces/pieces-framework").CustomAuthProperty<{
2
+ serviceUrl: import("@activepieces/pieces-framework").ShortTextProperty<false>;
3
+ apiKey: import("@activepieces/pieces-framework").SecretTextProperty<false>;
4
+ }>, {
5
+ latexContent: import("@activepieces/pieces-framework").LongTextProperty<true>;
6
+ filename: import("@activepieces/pieces-framework").ShortTextProperty<true>;
7
+ compiler: import("@activepieces/pieces-framework").StaticDropdownProperty<string, true>;
8
+ outputFormat: import("@activepieces/pieces-framework").StaticDropdownProperty<string, true>;
9
+ }>;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileLatex = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const pieces_framework_1 = require("@activepieces/pieces-framework");
6
+ const auth_1 = require("../auth");
7
+ const common_1 = require("../common");
8
+ exports.compileLatex = (0, pieces_framework_1.createAction)({
9
+ name: "compile_latex",
10
+ displayName: "Compile LaTeX",
11
+ description: "Compile a LaTeX document to PDF using a cloud compilation service. Supports pdflatex and xelatex compilers.",
12
+ auth: auth_1.latexToPdfAuth,
13
+ props: {
14
+ latexContent: pieces_framework_1.Property.LongText({
15
+ displayName: "LaTeX Content",
16
+ description: "The LaTeX source code to compile. Must be valid LaTeX syntax.",
17
+ required: true,
18
+ }),
19
+ filename: pieces_framework_1.Property.ShortText({
20
+ displayName: "Filename",
21
+ description: "Name of the .tex file (e.g., main.tex, document.tex)",
22
+ required: true,
23
+ defaultValue: "main.tex",
24
+ }),
25
+ compiler: pieces_framework_1.Property.StaticDropdown({
26
+ displayName: "Compiler",
27
+ description: "LaTeX compiler to use for compilation",
28
+ required: true,
29
+ defaultValue: "xelatex",
30
+ options: {
31
+ options: [
32
+ { label: "XeLaTeX (recommended for Unicode/fonts)", value: "xelatex" },
33
+ { label: "pdfLaTeX (traditional)", value: "pdflatex" },
34
+ ],
35
+ },
36
+ }),
37
+ outputFormat: pieces_framework_1.Property.StaticDropdown({
38
+ displayName: "Output Format",
39
+ description: "How to return the compiled PDF",
40
+ required: true,
41
+ defaultValue: "url",
42
+ options: {
43
+ options: [
44
+ { label: "URL (presigned download link)", value: "url" },
45
+ { label: "Base64 (embedded in response)", value: "base64" },
46
+ ],
47
+ },
48
+ }),
49
+ },
50
+ run(context) {
51
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
52
+ var _a, _b, _c, _d, _e, _f;
53
+ const { latexContent, filename, compiler, outputFormat } = context.propsValue;
54
+ const auth = context.auth;
55
+ // Validate inputs
56
+ if (!(latexContent === null || latexContent === void 0 ? void 0 : latexContent.trim())) {
57
+ return {
58
+ success: false,
59
+ error: "LaTeX content cannot be empty",
60
+ };
61
+ }
62
+ if (!(filename === null || filename === void 0 ? void 0 : filename.trim())) {
63
+ return {
64
+ success: false,
65
+ error: "Filename is required",
66
+ };
67
+ }
68
+ // Ensure .tex extension
69
+ const normalizedFilename = filename.trim().endsWith(".tex")
70
+ ? filename.trim()
71
+ : `${filename.trim()}.tex`;
72
+ try {
73
+ const serviceUrl = (0, common_1.getServiceUrl)(auth);
74
+ const headers = (0, common_1.buildHeaders)(auth);
75
+ // 1. Submit for compilation
76
+ const submitResponse = yield (0, common_1.submitCompilation)(serviceUrl, {
77
+ file_content: (0, common_1.toBase64)(latexContent),
78
+ filename: normalizedFilename,
79
+ compiler: compiler,
80
+ output_format: outputFormat,
81
+ }, headers);
82
+ // 2. Poll until completion
83
+ const result = yield (0, common_1.pollForCompletion)(serviceUrl, submitResponse.job_id, headers);
84
+ // 3. Process result
85
+ if (result.status === "failed") {
86
+ return {
87
+ success: false,
88
+ error: result.error_message || result.message || "Compilation failed",
89
+ jobId: result.job_id,
90
+ logUrl: (_a = result.result) === null || _a === void 0 ? void 0 : _a.log_url,
91
+ };
92
+ }
93
+ // Success
94
+ return {
95
+ success: true,
96
+ jobId: result.job_id,
97
+ pdfUrl: (_b = result.result) === null || _b === void 0 ? void 0 : _b.pdf_url,
98
+ pdfBase64: (_c = result.result) === null || _c === void 0 ? void 0 : _c.pdf_base64,
99
+ logUrl: (_d = result.result) === null || _d === void 0 ? void 0 : _d.log_url,
100
+ durationMs: (_e = result.result) === null || _e === void 0 ? void 0 : _e.duration_ms,
101
+ pdfSizeBytes: (_f = result.result) === null || _f === void 0 ? void 0 : _f.pdf_size_bytes,
102
+ compiler,
103
+ filename: normalizedFilename,
104
+ };
105
+ }
106
+ catch (error) {
107
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
108
+ return {
109
+ success: false,
110
+ error: errorMessage,
111
+ };
112
+ }
113
+ });
114
+ },
115
+ });
116
+ //# sourceMappingURL=compile-latex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-latex.js","sourceRoot":"","sources":["../../../../../../../../packages/pieces/custom/latex-to-pdf/src/lib/actions/compile-latex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare const latexToPdfAuth: import("@activepieces/pieces-framework").CustomAuthProperty<{
2
+ serviceUrl: import("@activepieces/pieces-framework").ShortTextProperty<false>;
3
+ apiKey: import("@activepieces/pieces-framework").SecretTextProperty<false>;
4
+ }>;
5
+ export type LatexToPdfAuth = {
6
+ serviceUrl?: string;
7
+ apiKey?: string;
8
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.latexToPdfAuth = void 0;
4
+ const pieces_framework_1 = require("@activepieces/pieces-framework");
5
+ exports.latexToPdfAuth = pieces_framework_1.PieceAuth.CustomAuth({
6
+ description: 'Configure the LaTeX compilation service connection (optional - defaults are provided)',
7
+ required: true,
8
+ props: {
9
+ serviceUrl: pieces_framework_1.Property.ShortText({
10
+ displayName: 'Service URL',
11
+ description: 'LaTeX compiler service URL. Leave empty to use default Skillful service.',
12
+ required: false,
13
+ }),
14
+ apiKey: pieces_framework_1.PieceAuth.SecretText({
15
+ displayName: 'API Key',
16
+ description: 'API key for authenticated requests (optional)',
17
+ required: false,
18
+ }),
19
+ },
20
+ });
21
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../../../../../packages/pieces/custom/latex-to-pdf/src/lib/auth.ts"],"names":[],"mappings":";;;AAAA,qEAAqE;AAExD,QAAA,cAAc,GAAG,4BAAS,CAAC,UAAU,CAAC;IACjD,WAAW,EAAE,uFAAuF;IACpG,QAAQ,EAAE,IAAI;IACd,KAAK,EAAE;QACL,UAAU,EAAE,2BAAQ,CAAC,SAAS,CAAC;YAC7B,WAAW,EAAE,aAAa;YAC1B,WAAW,EAAE,0EAA0E;YACvF,QAAQ,EAAE,KAAK;SAChB,CAAC;QACF,MAAM,EAAE,4BAAS,CAAC,UAAU,CAAC;YAC3B,WAAW,EAAE,SAAS;YACtB,WAAW,EAAE,+CAA+C;YAC5D,QAAQ,EAAE,KAAK;SAChB,CAAC;KACH;CACF,CAAC,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { LatexToPdfAuth } from "./auth";
2
+ export declare const DEFAULT_SERVICE_URL = "https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev";
3
+ export declare const POLLING_INTERVAL_MS = 2000;
4
+ export declare const MAX_POLLING_ATTEMPTS = 150;
5
+ export type CompilerType = "pdflatex" | "xelatex";
6
+ export type OutputFormat = "url" | "base64" | "bytes";
7
+ export type JobStatus = "pending" | "processing" | "completed" | "failed";
8
+ export interface CompileRequest {
9
+ file_content: string;
10
+ filename: string;
11
+ main_file?: string;
12
+ compiler?: CompilerType;
13
+ output_format?: OutputFormat;
14
+ compiler_options?: string[];
15
+ }
16
+ export interface CompileResponse {
17
+ job_id: string;
18
+ status: JobStatus;
19
+ message: string;
20
+ }
21
+ export interface CompilationResult {
22
+ success: boolean;
23
+ output_format: OutputFormat;
24
+ pdf_url?: string;
25
+ pdf_base64?: string;
26
+ log_url?: string;
27
+ error_message?: string;
28
+ exit_code: number;
29
+ duration_ms: number;
30
+ pdf_size_bytes?: number;
31
+ }
32
+ export interface JobStatusResponse {
33
+ job_id: string;
34
+ status: JobStatus;
35
+ message: string;
36
+ error_message?: string;
37
+ result?: CompilationResult;
38
+ }
39
+ /**
40
+ * Get the service URL, using default if not configured
41
+ */
42
+ export declare function getServiceUrl(auth: LatexToPdfAuth | undefined): string;
43
+ /**
44
+ * Build request headers
45
+ */
46
+ export declare function buildHeaders(auth: LatexToPdfAuth | undefined): Record<string, string>;
47
+ /**
48
+ * Convert string to Base64
49
+ */
50
+ export declare function toBase64(content: string): string;
51
+ /**
52
+ * Submit LaTeX for compilation
53
+ */
54
+ export declare function submitCompilation(serviceUrl: string, request: CompileRequest, headers: Record<string, string>): Promise<CompileResponse>;
55
+ /**
56
+ * Get job status
57
+ */
58
+ export declare function getJobStatus(serviceUrl: string, jobId: string, headers: Record<string, string>): Promise<JobStatusResponse>;
59
+ /**
60
+ * Poll for compilation completion
61
+ */
62
+ export declare function pollForCompletion(serviceUrl: string, jobId: string, headers: Record<string, string>): Promise<JobStatusResponse>;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_POLLING_ATTEMPTS = exports.POLLING_INTERVAL_MS = exports.DEFAULT_SERVICE_URL = void 0;
4
+ exports.getServiceUrl = getServiceUrl;
5
+ exports.buildHeaders = buildHeaders;
6
+ exports.toBase64 = toBase64;
7
+ exports.submitCompilation = submitCompilation;
8
+ exports.getJobStatus = getJobStatus;
9
+ exports.pollForCompletion = pollForCompletion;
10
+ const tslib_1 = require("tslib");
11
+ const pieces_common_1 = require("@activepieces/pieces-common");
12
+ // =============================================================================
13
+ // Constants
14
+ // =============================================================================
15
+ exports.DEFAULT_SERVICE_URL = "https://m9tbwb9jhi.execute-api.us-west-2.amazonaws.com/dev";
16
+ exports.POLLING_INTERVAL_MS = 2000; // 2 seconds
17
+ exports.MAX_POLLING_ATTEMPTS = 150; // 5 minutes max (150 * 2s)
18
+ // =============================================================================
19
+ // Helper Functions
20
+ // =============================================================================
21
+ /**
22
+ * Get the service URL, using default if not configured
23
+ */
24
+ function getServiceUrl(auth) {
25
+ var _a;
26
+ const url = (_a = auth === null || auth === void 0 ? void 0 : auth.serviceUrl) === null || _a === void 0 ? void 0 : _a.trim();
27
+ if (url) {
28
+ // Remove trailing slash if present
29
+ return url.endsWith('/') ? url.slice(0, -1) : url;
30
+ }
31
+ return exports.DEFAULT_SERVICE_URL;
32
+ }
33
+ /**
34
+ * Build request headers
35
+ */
36
+ function buildHeaders(auth) {
37
+ var _a;
38
+ const headers = {
39
+ "Content-Type": "application/json",
40
+ };
41
+ if ((_a = auth === null || auth === void 0 ? void 0 : auth.apiKey) === null || _a === void 0 ? void 0 : _a.trim()) {
42
+ headers["X-API-Key"] = auth.apiKey.trim();
43
+ }
44
+ return headers;
45
+ }
46
+ /**
47
+ * Convert string to Base64
48
+ */
49
+ function toBase64(content) {
50
+ return Buffer.from(content, "utf-8").toString("base64");
51
+ }
52
+ /**
53
+ * Submit LaTeX for compilation
54
+ */
55
+ function submitCompilation(serviceUrl, request, headers) {
56
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
57
+ const response = yield pieces_common_1.httpClient.sendRequest({
58
+ method: pieces_common_1.HttpMethod.POST,
59
+ url: `${serviceUrl}/compile`,
60
+ headers,
61
+ body: request,
62
+ });
63
+ return response.body;
64
+ });
65
+ }
66
+ /**
67
+ * Get job status
68
+ */
69
+ function getJobStatus(serviceUrl, jobId, headers) {
70
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
71
+ const response = yield pieces_common_1.httpClient.sendRequest({
72
+ method: pieces_common_1.HttpMethod.GET,
73
+ url: `${serviceUrl}/compile/${jobId}`,
74
+ headers,
75
+ });
76
+ return response.body;
77
+ });
78
+ }
79
+ /**
80
+ * Sleep helper for polling
81
+ */
82
+ function sleep(ms) {
83
+ return new Promise(resolve => setTimeout(resolve, ms));
84
+ }
85
+ /**
86
+ * Poll for compilation completion
87
+ */
88
+ function pollForCompletion(serviceUrl, jobId, headers) {
89
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
90
+ let attempts = 0;
91
+ while (attempts < exports.MAX_POLLING_ATTEMPTS) {
92
+ const status = yield getJobStatus(serviceUrl, jobId, headers);
93
+ if (status.status === "completed" || status.status === "failed") {
94
+ return status;
95
+ }
96
+ // Wait before next attempt
97
+ yield sleep(exports.POLLING_INTERVAL_MS);
98
+ attempts++;
99
+ }
100
+ throw new Error(`Compilation timed out after ${(exports.MAX_POLLING_ATTEMPTS * exports.POLLING_INTERVAL_MS) / 1000} seconds`);
101
+ });
102
+ }
103
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../../../../../../packages/pieces/custom/latex-to-pdf/src/lib/common.ts"],"names":[],"mappings":"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"}