@fdm-monster/server 2.0.11 → 2.1.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/.yarn/install-state.gz +0 -0
- package/.yarn/releases/{yarn-4.13.0.cjs → yarn-4.14.1.cjs} +288 -288
- package/.yarnrc.yml +5 -1
- package/README.md +2 -1
- package/RELEASE_NOTES.MD +153 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorateMetadata.js +1 -1
- package/dist/_virtual/_virtual_controllers.js +2 -0
- package/dist/consoles/typeorm-create.js.map +1 -1
- package/dist/consoles/typeorm-generate.js.map +1 -1
- package/dist/consoles/typeorm-migrate.js.map +1 -1
- package/dist/constants/authorization.constants.js.map +1 -1
- package/dist/container.js +2 -0
- package/dist/container.js.map +1 -1
- package/dist/container.tokens.js +1 -0
- package/dist/container.tokens.js.map +1 -1
- package/dist/controllers/api-key.controller.js +72 -0
- package/dist/controllers/api-key.controller.js.map +1 -0
- package/dist/controllers/auth.controller.js +6 -4
- package/dist/controllers/auth.controller.js.map +1 -1
- package/dist/controllers/batch-call.controller.js +2 -2
- package/dist/controllers/batch-call.controller.js.map +1 -1
- package/dist/controllers/camera-stream.controller.js +2 -2
- package/dist/controllers/camera-stream.controller.js.map +1 -1
- package/dist/controllers/file-storage.controller.js +2 -2
- package/dist/controllers/file-storage.controller.js.map +1 -1
- package/dist/controllers/first-time-setup.controller.js +2 -2
- package/dist/controllers/first-time-setup.controller.js.map +1 -1
- package/dist/controllers/floor.controller.js +2 -2
- package/dist/controllers/floor.controller.js.map +1 -1
- package/dist/controllers/metrics.controller.js +2 -2
- package/dist/controllers/metrics.controller.js.map +1 -1
- package/dist/controllers/print-job.controller.js +2 -2
- package/dist/controllers/print-job.controller.js.map +1 -1
- package/dist/controllers/print-queue.controller.js +2 -2
- package/dist/controllers/print-queue.controller.js.map +1 -1
- package/dist/controllers/printer-files.controller.js +2 -2
- package/dist/controllers/printer-files.controller.js.map +1 -1
- package/dist/controllers/printer-maintenance-log.controller.js +2 -2
- package/dist/controllers/printer-maintenance-log.controller.js.map +1 -1
- package/dist/controllers/printer-settings.controller.js +2 -2
- package/dist/controllers/printer-settings.controller.js.map +1 -1
- package/dist/controllers/printer-tag.controller.js +2 -2
- package/dist/controllers/printer-tag.controller.js.map +1 -1
- package/dist/controllers/printer.controller.js +2 -2
- package/dist/controllers/printer.controller.js.map +1 -1
- package/dist/controllers/server-private.controller.js +2 -2
- package/dist/controllers/server-private.controller.js.map +1 -1
- package/dist/controllers/server-public.controller.js +2 -2
- package/dist/controllers/server-public.controller.js.map +1 -1
- package/dist/controllers/settings.controller.js +2 -2
- package/dist/controllers/settings.controller.js.map +1 -1
- package/dist/controllers/slicer-compat.controller.js +2 -2
- package/dist/controllers/slicer-compat.controller.js.map +1 -1
- package/dist/controllers/user.controller.js +2 -2
- package/dist/controllers/user.controller.js.map +1 -1
- package/dist/controllers/validation/api-key-controller.validation.js +11 -0
- package/dist/controllers/validation/api-key-controller.validation.js.map +1 -0
- package/dist/data-source.js +6 -2
- package/dist/data-source.js.map +1 -1
- package/dist/entities/api-key.entity.js +60 -0
- package/dist/entities/api-key.entity.js.map +1 -0
- package/dist/entities/camera-stream.entity.js +2 -2
- package/dist/entities/floor-position.entity.js +2 -2
- package/dist/entities/floor.entity.js +2 -2
- package/dist/entities/index.js +2 -1
- package/dist/entities/print-job.entity.js +2 -2
- package/dist/entities/printer-maintenance-log.entity.js +2 -2
- package/dist/entities/printer-tag.entity.js +2 -2
- package/dist/entities/printer.entity.js +2 -2
- package/dist/entities/refresh-token.entity.js +2 -2
- package/dist/entities/role.entity.js +2 -2
- package/dist/entities/settings.entity.js +2 -2
- package/dist/entities/tag.entity.js +2 -2
- package/dist/entities/user-role.entity.js +2 -2
- package/dist/entities/user.entity.js +2 -2
- package/dist/exceptions/failed-dependency.exception.js.map +1 -1
- package/dist/exceptions/job.exceptions.js.map +1 -1
- package/dist/exceptions/runtime.exceptions.js.map +1 -1
- package/dist/handlers/event-emitter.js.map +1 -1
- package/dist/handlers/logger-factory.js.map +1 -1
- package/dist/handlers/logger.js.map +1 -1
- package/dist/handlers/logging/file-logging.transport.js +1 -2
- package/dist/handlers/logging/file-logging.transport.js.map +1 -1
- package/dist/handlers/logging/loki-logging.transport.js.map +1 -1
- package/dist/handlers/logging/static.logger.js.map +1 -1
- package/dist/handlers/validators.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/api-key.strategy.js +45 -0
- package/dist/middleware/api-key.strategy.js.map +1 -0
- package/dist/middleware/authenticate.js.map +1 -1
- package/dist/middleware/database.js.map +1 -1
- package/dist/middleware/demo.middleware.js.map +1 -1
- package/dist/middleware/exception.filter.js.map +1 -1
- package/dist/middleware/global.middleware.js.map +1 -1
- package/dist/middleware/param-converter.middleware.js.map +1 -1
- package/dist/middleware/passport.js +3 -0
- package/dist/middleware/passport.js.map +1 -1
- package/dist/middleware/printer-resolver.js.map +1 -1
- package/dist/middleware/printer.js.map +1 -1
- package/dist/middleware/slicer-api-key.middleware.js.map +1 -1
- package/dist/middleware/socketio.middleware.js.map +1 -1
- package/dist/migrations/1706829146617-InitSqlite.js.map +1 -1
- package/dist/migrations/1707494762198-PrinterGroup.js.map +1 -1
- package/dist/migrations/1708465930665-ChangePrintCompletionDeletePrinterCascade.js.map +1 -1
- package/dist/migrations/1713300747465-ChangeRoleNameUnique.js.map +1 -1
- package/dist/migrations/1713897879622-AddPrinterType.js.map +1 -1
- package/dist/migrations/1720338804844-RemovePrinterFile.js.map +1 -1
- package/dist/migrations/1745141688926-AddPrinterUsernamePassword.js.map +1 -1
- package/dist/migrations/1766576698569-DropPermissions.js.map +1 -1
- package/dist/migrations/1767278216516-ChangeCameraPrinterOnDeleteSetNull.js.map +1 -1
- package/dist/migrations/1767279607392-DropCustomGcode.js.map +1 -1
- package/dist/migrations/1767291804417-DropPrintCompletions.js.map +1 -1
- package/dist/migrations/1767352862576-DropSettingsFileClean.js.map +1 -1
- package/dist/migrations/1767355639023-ChangeFloorLevelToOrder.js.map +1 -1
- package/dist/migrations/1767370191762-ChangeFloorNonUniqueOrder.js.map +1 -1
- package/dist/migrations/1767432108916-RenameGroupToTag.js.map +1 -1
- package/dist/migrations/1767451444137-AddPrintJob.js.map +1 -1
- package/dist/migrations/1767909428129-AddPrinterMaintenanceLog.js.map +1 -1
- package/dist/migrations/1778446203015-AddApiKey.js +49 -0
- package/dist/migrations/1778446203015-AddApiKey.js.map +1 -0
- package/dist/plugins/controllers-plugin.js.map +1 -1
- package/dist/server.constants.js +2 -1
- package/dist/server.constants.js.map +1 -1
- package/dist/server.core.js +6 -2
- package/dist/server.core.js.map +1 -1
- package/dist/server.env.js.map +1 -1
- package/dist/server.host.js.map +1 -1
- package/dist/services/authentication/auth.service.js.map +1 -1
- package/dist/services/authentication/jwt.service.js.map +1 -1
- package/dist/services/bambu/bambu-ftp.adapter.js.map +1 -1
- package/dist/services/bambu/bambu-mqtt.adapter.js.map +1 -1
- package/dist/services/bambu/bambu.client.js.map +1 -1
- package/dist/services/bambu.api.js.map +1 -1
- package/dist/services/core/batch-call.service.js.map +1 -1
- package/dist/services/core/client-bundle.service.js.map +1 -1
- package/dist/services/core/config.service.js +4 -0
- package/dist/services/core/config.service.js.map +1 -1
- package/dist/services/core/cradle.service.js.map +1 -1
- package/dist/services/core/github.service.js.map +1 -1
- package/dist/services/core/http-client.factory.js.map +1 -1
- package/dist/services/core/logs-manager.service.js.map +1 -1
- package/dist/services/core/monsterpi.service.js.map +1 -1
- package/dist/services/core/multer.service.js.map +1 -1
- package/dist/services/core/server-release.service.js.map +1 -1
- package/dist/services/core/yaml.service.js.map +1 -1
- package/dist/services/file-analysis.service.js.map +1 -1
- package/dist/services/file-storage.service.js.map +1 -1
- package/dist/services/interfaces/api-key.dto.js +19 -0
- package/dist/services/interfaces/api-key.dto.js.map +1 -0
- package/dist/services/interfaces/api-key.service.interface.js +1 -0
- package/dist/services/interfaces/user.dto.js +2 -0
- package/dist/services/interfaces/user.dto.js.map +1 -1
- package/dist/services/moonraker/moonraker-websocket.adapter.js.map +1 -1
- package/dist/services/moonraker/moonraker.client.js.map +1 -1
- package/dist/services/moonraker.api.js.map +1 -1
- package/dist/services/octoprint/octoprint-api.routes.js.map +1 -1
- package/dist/services/octoprint/octoprint-websocket.adapter.js.map +1 -1
- package/dist/services/octoprint/octoprint.client.js.map +1 -1
- package/dist/services/octoprint/utils/api.utils.js.map +1 -1
- package/dist/services/octoprint/utils/file.utils.js.map +1 -1
- package/dist/services/octoprint/utils/octoprint-http-client.builder.js.map +1 -1
- package/dist/services/octoprint.api.js.map +1 -1
- package/dist/services/orm/api-key.service.js +90 -0
- package/dist/services/orm/api-key.service.js.map +1 -0
- package/dist/services/orm/base.service.js.map +1 -1
- package/dist/services/orm/camera-stream.service.js.map +1 -1
- package/dist/services/orm/floor-position.service.js.map +1 -1
- package/dist/services/orm/floor.service.js.map +1 -1
- package/dist/services/orm/permission.service.js.map +1 -1
- package/dist/services/orm/print-job.service.js.map +1 -1
- package/dist/services/orm/printer-maintenance-log.service.js.map +1 -1
- package/dist/services/orm/printer-tag.service.js.map +1 -1
- package/dist/services/orm/printer.service.js.map +1 -1
- package/dist/services/orm/refresh-token.service.js.map +1 -1
- package/dist/services/orm/role.service.js.map +1 -1
- package/dist/services/orm/settings.service.js.map +1 -1
- package/dist/services/orm/user-role.service.js.map +1 -1
- package/dist/services/orm/user.service.js.map +1 -1
- package/dist/services/print-file-downloader.service.js.map +1 -1
- package/dist/services/print-queue.service.js.map +1 -1
- package/dist/services/printer-api.factory.js.map +1 -1
- package/dist/services/printer-api.interface.js.map +1 -1
- package/dist/services/prusa-link/prusa-link-http-polling.adapter.js.map +1 -1
- package/dist/services/prusa-link/prusa-link.api.js.map +1 -1
- package/dist/services/prusa-link/utils/digest-auth.util.js +19 -12
- package/dist/services/prusa-link/utils/digest-auth.util.js.map +1 -1
- package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js +45 -11
- package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js.map +1 -1
- package/dist/services/socket.factory.js.map +1 -1
- package/dist/services/task-manager.service.js.map +1 -1
- package/dist/services/typeorm/typeorm.service.js.map +1 -1
- package/dist/services/validators/printer-service.validation.js.map +1 -1
- package/dist/shared/default-http-client.builder.js.map +1 -1
- package/dist/shared/load-controllers.js.map +1 -1
- package/dist/shared/runtime-settings.migration.js.map +1 -1
- package/dist/shared/websocket-rpc-extended.adapter.js.map +1 -1
- package/dist/shared/websocket.adapter.js.map +1 -1
- package/dist/state/file-upload-tracker.cache.js.map +1 -1
- package/dist/state/floor.store.js.map +1 -1
- package/dist/state/printer-events.cache.js.map +1 -1
- package/dist/state/printer-socket.store.js.map +1 -1
- package/dist/state/printer-thumbnail.cache.js.map +1 -1
- package/dist/state/printer.cache.js.map +1 -1
- package/dist/state/settings.store.js.map +1 -1
- package/dist/state/socket-io.gateway.js.map +1 -1
- package/dist/state/test-printer-socket.store.js.map +1 -1
- package/dist/tasks/boot.task.js.map +1 -1
- package/dist/tasks/client-bundle.task.js.map +1 -1
- package/dist/tasks/print-job-analysis.task.js.map +1 -1
- package/dist/tasks/printer-websocket-restore.task.js.map +1 -1
- package/dist/tasks/printer-websocket.task.js.map +1 -1
- package/dist/tasks/socketio.task.js.map +1 -1
- package/dist/tasks/software-update.task.js.map +1 -1
- package/dist/tasks.js.map +1 -1
- package/dist/utils/array.util.js.map +1 -1
- package/dist/utils/bgcode/bgcode-thumbnail.parser.js.map +1 -1
- package/dist/utils/bgcode/bgcode.utils.js.map +1 -1
- package/dist/utils/bgcode/heatshrink-decoder.js.map +1 -1
- package/dist/utils/bgcode/png-encoder.js.map +1 -1
- package/dist/utils/bgcode/qoi-decoder.js.map +1 -1
- package/dist/utils/cache/key-diff.cache.js.map +1 -1
- package/dist/utils/correlation-token.util.js.map +1 -1
- package/dist/utils/crypto.utils.js.map +1 -1
- package/dist/utils/env.utils.js.map +1 -1
- package/dist/utils/error.utils.js.map +1 -1
- package/dist/utils/fs.utils.js.map +1 -1
- package/dist/utils/gcode.utils.js.map +1 -1
- package/dist/utils/image-dimensions.js.map +1 -1
- package/dist/utils/job-stats.util.js.map +1 -1
- package/dist/utils/normalize-url.js.map +1 -1
- package/dist/utils/parsers/3mf.parser.js.map +1 -1
- package/dist/utils/parsers/bgcode.parser.js.map +1 -1
- package/dist/utils/parsers/gcode.parser.js.map +1 -1
- package/dist/utils/pretty-print.utils.js.map +1 -1
- package/dist/utils/semver.utils.js.map +1 -1
- package/dist/utils/swagger/decorators.js.map +1 -1
- package/dist/utils/swagger/generator.js.map +1 -1
- package/dist/utils/swagger/swagger.js.map +1 -1
- package/dist/utils/thumbnail.util.js.map +1 -1
- package/dist/utils/time.utils.js.map +1 -1
- package/dist/utils/url.utils.js.map +1 -1
- package/package.json +17 -14
- package/packages/consoles/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gcode.parser.js","names":["fs"],"sources":["../../../src/utils/parsers/gcode.parser.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as readline from \"node:readline\";\nimport { createReadStream } from \"node:fs\";\nimport { GCodeMetadata } from \"@/entities/print-job.entity\";\nimport { convertQoiToPng } from \"../bgcode/bgcode-thumbnail.parser\";\nimport { ParsedThumbnail } from \"./parser.types\";\n\ninterface GCodeParseResult {\n raw: {\n _thumbnails?: ParsedThumbnail[];\n metadata: Record<string, string>;\n };\n normalized: GCodeMetadata;\n}\n\n/**\n * G-code parser for extracting metadata from .gcode files\n * Reads first and last N lines to extract slicer metadata\n */\nexport class GCodeParser {\n private readonly maxHeaderLinesToRead = 500; // Read from start\n private readonly maxFooterLinesToRead = 500; // Read from end\n\n async parse(filePath: string): Promise<GCodeParseResult> {\n const stats = await fs.stat(filePath);\n const fileName = filePath.split(/[/\\\\]/).pop() || filePath;\n\n const metadata = await this.extractMetadata(filePath);\n const thumbnails = await this.extractThumbnails(filePath);\n\n const normalized: GCodeMetadata = {\n fileName,\n fileFormat: \"gcode\",\n fileSize: stats.size,\n gcodePrintTimeSeconds: this.parseTime(\n metadata.estimated_printing_time_normal_mode || metadata.estimated_printing_time || metadata.print_time,\n ),\n nozzleDiameterMm: this.parseFloat(metadata.nozzle_diameter),\n filamentDiameterMm: this.parseFloat(metadata.filament_diameter),\n filamentDensityGramsCm3: this.parseFloat(metadata.filament_density),\n filamentUsedMm: this.parseFloat(metadata.filament_used_mm),\n filamentUsedCm3: this.parseFloat(metadata.filament_used_cm3),\n filamentUsedGrams: this.parseFloat(metadata.filament_used_g),\n totalFilamentUsedGrams: this.parseFloat(metadata.total_filament_used_g || metadata.filament_used_g),\n layerHeight: this.parseFloat(metadata.layer_height),\n firstLayerHeight: this.parseFloat(metadata.first_layer_height || metadata.initial_layer_height),\n bedTemperature: this.parseFloat(metadata.bed_temperature || metadata.first_layer_bed_temperature),\n nozzleTemperature: this.parseFloat(metadata.temperature || metadata.first_layer_temperature),\n fillDensity: metadata.fill_density || null,\n filamentType: metadata.filament_type || null,\n printerModel: metadata.printer_model || metadata.printer_name || null,\n slicerVersion: metadata.generated_by || metadata.slicer_version || null,\n maxLayerZ: this.parseFloat(metadata.max_layer_z),\n totalLayers: this.parseInt(metadata.total_layers) || this.parseInt(metadata.layer_count),\n generatedBy: metadata.generated_by,\n thumbnails:\n thumbnails.length > 0\n ? thumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n };\n\n return {\n raw: {\n _thumbnails: thumbnails,\n metadata,\n },\n normalized,\n };\n }\n\n private async extractMetadata(filePath: string): Promise<Record<string, string>> {\n const metadata: Record<string, string> = {};\n\n // Read from start of file (header often has thumbnails and basic info)\n await this.extractMetadataFromStart(filePath, metadata);\n\n // Read from end of file (footer often has summary metadata - filament, time, etc.)\n await this.extractMetadataFromEnd(filePath, metadata);\n\n return metadata;\n }\n\n private async extractMetadataFromStart(filePath: string, metadata: Record<string, string>): Promise<void> {\n let linesRead = 0;\n\n const fileStream = createReadStream(filePath);\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (linesRead >= this.maxHeaderLinesToRead) break;\n linesRead++;\n\n this.parseMetadataLine(line, metadata);\n }\n\n rl.close();\n fileStream.close();\n }\n\n private async extractMetadataFromEnd(filePath: string, metadata: Record<string, string>): Promise<void> {\n // Read last N lines efficiently\n const stats = await fs.stat(filePath);\n const fileSize = stats.size;\n\n // Estimate bytes to read (assume ~50 bytes per line avg)\n const estimatedBytes = this.maxFooterLinesToRead * 50;\n const startPosition = Math.max(0, fileSize - estimatedBytes);\n\n const fileHandle = await fs.open(filePath, \"r\");\n try {\n const buffer = Buffer.alloc(estimatedBytes);\n const { bytesRead } = await fileHandle.read(buffer, 0, estimatedBytes, startPosition);\n const text = buffer.toString(\"utf8\", 0, bytesRead);\n const lines = text.split(\"\\n\");\n\n // Process footer lines (summary metadata often here)\n for (const line of lines) {\n this.parseMetadataLine(line, metadata);\n }\n } finally {\n await fileHandle.close();\n }\n }\n\n private parseMetadataLine(line: string, metadata: Record<string, string>): void {\n // Skip non-comment lines\n if (!line.startsWith(\";\")) return;\n\n // Special case: \"; generated by PrusaSlicer X.X.X on ...\"\n const generatedByMatch = line.match(/^;\\s*generated by\\s+([^\\s]+)/i);\n if (generatedByMatch && !metadata.generated_by) {\n metadata.generated_by = generatedByMatch[1];\n return;\n }\n\n // Parse PrusaSlicer/SuperSlicer format: \"; key = value\"\n const prusaMatch = line.match(/^;\\s*([^=]+?)\\s*=\\s*(.+)$/);\n if (prusaMatch) {\n let key = prusaMatch[1].trim().toLowerCase().replace(/\\s+/g, \"_\");\n let value = prusaMatch[2].trim();\n\n // Normalize bracketed units: \"filament used [mm]\" -> \"filament_used_mm\"\n key = key.replace(/\\[([^\\]]+)\\]/g, (_, unit) => \"_\" + unit.replace(/\\^/g, \"\"));\n // Normalize parentheses: \"estimated printing time (normal mode)\" -> \"estimated_printing_time_normal_mode\"\n key = key.replace(/\\(([^)]+)\\)/g, (_, content) => \"_\" + content.replace(/\\s+/g, \"_\"));\n // Normalize multiple underscores\n key = key.replace(/_+/g, \"_\");\n\n // Don't overwrite if already set (header takes precedence)\n if (!metadata[key]) {\n metadata[key] = value.trim();\n }\n return;\n }\n\n // Parse Cura format: \";KEY:value\"\n const curaMatch = line.match(/^;([A-Z_]+):(.+)$/);\n if (curaMatch) {\n const [, key, value] = curaMatch;\n const normalizedKey = key.toLowerCase();\n if (!metadata[normalizedKey]) {\n metadata[normalizedKey] = value.trim();\n }\n return;\n }\n\n // Parse Simplify3D format: \"; key: value\"\n const s3dMatch = line.match(/^;\\s*([^:]+?):\\s*(.+)$/);\n if (s3dMatch) {\n const [, key, value] = s3dMatch;\n const normalizedKey = key.trim().toLowerCase().replace(/\\s+/g, \"_\");\n if (!metadata[normalizedKey]) {\n metadata[normalizedKey] = value.trim();\n }\n }\n }\n\n private async extractThumbnails(filePath: string): Promise<ParsedThumbnail[]> {\n const thumbnails: ParsedThumbnail[] = [];\n let linesRead = 0;\n let inThumbnail = false;\n let thumbnailData: string[] = [];\n let currentWidth = 0;\n let currentHeight = 0;\n let currentFormat = \"PNG\";\n\n const fileStream = createReadStream(filePath);\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (linesRead >= this.maxHeaderLinesToRead && !inThumbnail) break;\n linesRead++;\n\n // PrusaSlicer thumbnail format\n // Format 1: ; thumbnail begin 313x173 57100 (width x height dataLength)\n // Format 2: ; thumbnail begin 313x173 PNG (width x height format)\n const thumbnailStart = line.match(/;\\s*thumbnail begin (\\d+)x(\\d+)\\s*(\\w+)?/i);\n if (thumbnailStart) {\n inThumbnail = true;\n currentWidth = parseInt(thumbnailStart[1]);\n currentHeight = parseInt(thumbnailStart[2]);\n\n // Third parameter could be format (PNG/JPG/QOI) or data length (number)\n const thirdParam = thumbnailStart[3];\n if (thirdParam && /^(PNG|JPG|JPEG|QOI)$/i.test(thirdParam)) {\n currentFormat = thirdParam.toUpperCase();\n } else {\n // If it's a number or not specified, default to PNG\n currentFormat = \"PNG\";\n }\n\n thumbnailData = [];\n continue;\n }\n\n if (inThumbnail) {\n if (line.match(/;\\s*thumbnail end/i)) {\n let base64Data = thumbnailData.join(\"\");\n let format = currentFormat.toUpperCase();\n\n if (format === \"QOI\") {\n try {\n const qoiBuffer = Buffer.from(base64Data, \"base64\");\n const pngBuffer = convertQoiToPng(qoiBuffer);\n base64Data = pngBuffer.toString(\"base64\");\n format = \"PNG\";\n } catch {\n // Keep original QOI if conversion fails\n }\n }\n\n thumbnails.push({\n width: currentWidth,\n height: currentHeight,\n format,\n data: base64Data,\n });\n inThumbnail = false;\n thumbnailData = [];\n } else if (line.startsWith(\";\")) {\n const data = line.substring(1).trim();\n if (data) {\n thumbnailData.push(data);\n }\n }\n }\n }\n\n rl.close();\n fileStream.close();\n\n return thumbnails;\n }\n\n private parseFloat(value: string | undefined): number | null {\n if (!value) return null;\n const num = parseFloat(value);\n return isNaN(num) ? null : num;\n }\n\n private parseInt(value: string | undefined): number | null {\n if (!value) return null;\n const num = parseInt(value, 10);\n return isNaN(num) ? null : num;\n }\n\n private parseTime(value: string | undefined): number | null {\n if (!value) return null;\n\n // Try parsing as duration string FIRST (e.g., \"1h 31m 17s\" or \"19m 58s\")\n const match = value.match(/(?:(\\d+)h)?(?:\\s*(\\d+)m)?(?:\\s*(\\d+)s)?/);\n if (match && (match[1] || match[2] || match[3])) {\n const hours = parseInt(match[1] || \"0\");\n const minutes = parseInt(match[2] || \"0\");\n const secs = parseInt(match[3] || \"0\");\n return hours * 3600 + minutes * 60 + secs;\n }\n\n // Fallback to parsing as plain seconds\n const seconds = parseFloat(value);\n if (!isNaN(seconds)) return seconds;\n\n return null;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gcode.parser.js","names":["fs"],"sources":["../../../src/utils/parsers/gcode.parser.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as readline from \"node:readline\";\nimport { createReadStream } from \"node:fs\";\nimport { GCodeMetadata } from \"@/entities/print-job.entity\";\nimport { convertQoiToPng } from \"../bgcode/bgcode-thumbnail.parser\";\nimport { ParsedThumbnail } from \"./parser.types\";\n\ninterface GCodeParseResult {\n raw: {\n _thumbnails?: ParsedThumbnail[];\n metadata: Record<string, string>;\n };\n normalized: GCodeMetadata;\n}\n\n/**\n * G-code parser for extracting metadata from .gcode files\n * Reads first and last N lines to extract slicer metadata\n */\nexport class GCodeParser {\n private readonly maxHeaderLinesToRead = 500; // Read from start\n private readonly maxFooterLinesToRead = 500; // Read from end\n\n async parse(filePath: string): Promise<GCodeParseResult> {\n const stats = await fs.stat(filePath);\n const fileName = filePath.split(/[/\\\\]/).pop() || filePath;\n\n const metadata = await this.extractMetadata(filePath);\n const thumbnails = await this.extractThumbnails(filePath);\n\n const normalized: GCodeMetadata = {\n fileName,\n fileFormat: \"gcode\",\n fileSize: stats.size,\n gcodePrintTimeSeconds: this.parseTime(\n metadata.estimated_printing_time_normal_mode || metadata.estimated_printing_time || metadata.print_time,\n ),\n nozzleDiameterMm: this.parseFloat(metadata.nozzle_diameter),\n filamentDiameterMm: this.parseFloat(metadata.filament_diameter),\n filamentDensityGramsCm3: this.parseFloat(metadata.filament_density),\n filamentUsedMm: this.parseFloat(metadata.filament_used_mm),\n filamentUsedCm3: this.parseFloat(metadata.filament_used_cm3),\n filamentUsedGrams: this.parseFloat(metadata.filament_used_g),\n totalFilamentUsedGrams: this.parseFloat(metadata.total_filament_used_g || metadata.filament_used_g),\n layerHeight: this.parseFloat(metadata.layer_height),\n firstLayerHeight: this.parseFloat(metadata.first_layer_height || metadata.initial_layer_height),\n bedTemperature: this.parseFloat(metadata.bed_temperature || metadata.first_layer_bed_temperature),\n nozzleTemperature: this.parseFloat(metadata.temperature || metadata.first_layer_temperature),\n fillDensity: metadata.fill_density || null,\n filamentType: metadata.filament_type || null,\n printerModel: metadata.printer_model || metadata.printer_name || null,\n slicerVersion: metadata.generated_by || metadata.slicer_version || null,\n maxLayerZ: this.parseFloat(metadata.max_layer_z),\n totalLayers: this.parseInt(metadata.total_layers) || this.parseInt(metadata.layer_count),\n generatedBy: metadata.generated_by,\n thumbnails:\n thumbnails.length > 0\n ? thumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n };\n\n return {\n raw: {\n _thumbnails: thumbnails,\n metadata,\n },\n normalized,\n };\n }\n\n private async extractMetadata(filePath: string): Promise<Record<string, string>> {\n const metadata: Record<string, string> = {};\n\n // Read from start of file (header often has thumbnails and basic info)\n await this.extractMetadataFromStart(filePath, metadata);\n\n // Read from end of file (footer often has summary metadata - filament, time, etc.)\n await this.extractMetadataFromEnd(filePath, metadata);\n\n return metadata;\n }\n\n private async extractMetadataFromStart(filePath: string, metadata: Record<string, string>): Promise<void> {\n let linesRead = 0;\n\n const fileStream = createReadStream(filePath);\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (linesRead >= this.maxHeaderLinesToRead) break;\n linesRead++;\n\n this.parseMetadataLine(line, metadata);\n }\n\n rl.close();\n fileStream.close();\n }\n\n private async extractMetadataFromEnd(filePath: string, metadata: Record<string, string>): Promise<void> {\n // Read last N lines efficiently\n const stats = await fs.stat(filePath);\n const fileSize = stats.size;\n\n // Estimate bytes to read (assume ~50 bytes per line avg)\n const estimatedBytes = this.maxFooterLinesToRead * 50;\n const startPosition = Math.max(0, fileSize - estimatedBytes);\n\n const fileHandle = await fs.open(filePath, \"r\");\n try {\n const buffer = Buffer.alloc(estimatedBytes);\n const { bytesRead } = await fileHandle.read(buffer, 0, estimatedBytes, startPosition);\n const text = buffer.toString(\"utf8\", 0, bytesRead);\n const lines = text.split(\"\\n\");\n\n // Process footer lines (summary metadata often here)\n for (const line of lines) {\n this.parseMetadataLine(line, metadata);\n }\n } finally {\n await fileHandle.close();\n }\n }\n\n private parseMetadataLine(line: string, metadata: Record<string, string>): void {\n // Skip non-comment lines\n if (!line.startsWith(\";\")) return;\n\n // Special case: \"; generated by PrusaSlicer X.X.X on ...\"\n const generatedByMatch = line.match(/^;\\s*generated by\\s+([^\\s]+)/i);\n if (generatedByMatch && !metadata.generated_by) {\n metadata.generated_by = generatedByMatch[1];\n return;\n }\n\n // Parse PrusaSlicer/SuperSlicer format: \"; key = value\"\n const prusaMatch = line.match(/^;\\s*([^=]+?)\\s*=\\s*(.+)$/);\n if (prusaMatch) {\n let key = prusaMatch[1].trim().toLowerCase().replace(/\\s+/g, \"_\");\n let value = prusaMatch[2].trim();\n\n // Normalize bracketed units: \"filament used [mm]\" -> \"filament_used_mm\"\n key = key.replace(/\\[([^\\]]+)\\]/g, (_, unit) => \"_\" + unit.replace(/\\^/g, \"\"));\n // Normalize parentheses: \"estimated printing time (normal mode)\" -> \"estimated_printing_time_normal_mode\"\n key = key.replace(/\\(([^)]+)\\)/g, (_, content) => \"_\" + content.replace(/\\s+/g, \"_\"));\n // Normalize multiple underscores\n key = key.replace(/_+/g, \"_\");\n\n // Don't overwrite if already set (header takes precedence)\n if (!metadata[key]) {\n metadata[key] = value.trim();\n }\n return;\n }\n\n // Parse Cura format: \";KEY:value\"\n const curaMatch = line.match(/^;([A-Z_]+):(.+)$/);\n if (curaMatch) {\n const [, key, value] = curaMatch;\n const normalizedKey = key.toLowerCase();\n if (!metadata[normalizedKey]) {\n metadata[normalizedKey] = value.trim();\n }\n return;\n }\n\n // Parse Simplify3D format: \"; key: value\"\n const s3dMatch = line.match(/^;\\s*([^:]+?):\\s*(.+)$/);\n if (s3dMatch) {\n const [, key, value] = s3dMatch;\n const normalizedKey = key.trim().toLowerCase().replace(/\\s+/g, \"_\");\n if (!metadata[normalizedKey]) {\n metadata[normalizedKey] = value.trim();\n }\n }\n }\n\n private async extractThumbnails(filePath: string): Promise<ParsedThumbnail[]> {\n const thumbnails: ParsedThumbnail[] = [];\n let linesRead = 0;\n let inThumbnail = false;\n let thumbnailData: string[] = [];\n let currentWidth = 0;\n let currentHeight = 0;\n let currentFormat = \"PNG\";\n\n const fileStream = createReadStream(filePath);\n const rl = readline.createInterface({\n input: fileStream,\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (linesRead >= this.maxHeaderLinesToRead && !inThumbnail) break;\n linesRead++;\n\n // PrusaSlicer thumbnail format\n // Format 1: ; thumbnail begin 313x173 57100 (width x height dataLength)\n // Format 2: ; thumbnail begin 313x173 PNG (width x height format)\n const thumbnailStart = line.match(/;\\s*thumbnail begin (\\d+)x(\\d+)\\s*(\\w+)?/i);\n if (thumbnailStart) {\n inThumbnail = true;\n currentWidth = parseInt(thumbnailStart[1]);\n currentHeight = parseInt(thumbnailStart[2]);\n\n // Third parameter could be format (PNG/JPG/QOI) or data length (number)\n const thirdParam = thumbnailStart[3];\n if (thirdParam && /^(PNG|JPG|JPEG|QOI)$/i.test(thirdParam)) {\n currentFormat = thirdParam.toUpperCase();\n } else {\n // If it's a number or not specified, default to PNG\n currentFormat = \"PNG\";\n }\n\n thumbnailData = [];\n continue;\n }\n\n if (inThumbnail) {\n if (line.match(/;\\s*thumbnail end/i)) {\n let base64Data = thumbnailData.join(\"\");\n let format = currentFormat.toUpperCase();\n\n if (format === \"QOI\") {\n try {\n const qoiBuffer = Buffer.from(base64Data, \"base64\");\n const pngBuffer = convertQoiToPng(qoiBuffer);\n base64Data = pngBuffer.toString(\"base64\");\n format = \"PNG\";\n } catch {\n // Keep original QOI if conversion fails\n }\n }\n\n thumbnails.push({\n width: currentWidth,\n height: currentHeight,\n format,\n data: base64Data,\n });\n inThumbnail = false;\n thumbnailData = [];\n } else if (line.startsWith(\";\")) {\n const data = line.substring(1).trim();\n if (data) {\n thumbnailData.push(data);\n }\n }\n }\n }\n\n rl.close();\n fileStream.close();\n\n return thumbnails;\n }\n\n private parseFloat(value: string | undefined): number | null {\n if (!value) return null;\n const num = parseFloat(value);\n return isNaN(num) ? null : num;\n }\n\n private parseInt(value: string | undefined): number | null {\n if (!value) return null;\n const num = parseInt(value, 10);\n return isNaN(num) ? null : num;\n }\n\n private parseTime(value: string | undefined): number | null {\n if (!value) return null;\n\n // Try parsing as duration string FIRST (e.g., \"1h 31m 17s\" or \"19m 58s\")\n const match = value.match(/(?:(\\d+)h)?(?:\\s*(\\d+)m)?(?:\\s*(\\d+)s)?/);\n if (match && (match[1] || match[2] || match[3])) {\n const hours = parseInt(match[1] || \"0\");\n const minutes = parseInt(match[2] || \"0\");\n const secs = parseInt(match[3] || \"0\");\n return hours * 3600 + minutes * 60 + secs;\n }\n\n // Fallback to parsing as plain seconds\n const seconds = parseFloat(value);\n if (!isNaN(seconds)) return seconds;\n\n return null;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pretty-print.utils.js","names":[],"sources":["../../src/utils/pretty-print.utils.ts"],"sourcesContent":["export function PP(input: any) {\n return JSON.stringify(input, null, 2);\n}\n\nexport function PL(input: any) {\n console.log(PP(input));\n}\n"],"mappings":";AAAA,SAAgB,GAAG,OAAY;
|
|
1
|
+
{"version":3,"file":"pretty-print.utils.js","names":[],"sources":["../../src/utils/pretty-print.utils.ts"],"sourcesContent":["export function PP(input: any) {\n return JSON.stringify(input, null, 2);\n}\n\nexport function PL(input: any) {\n console.log(PP(input));\n}\n"],"mappings":";AAAA,SAAgB,GAAG,OAAY;CAC7B,OAAO,KAAK,UAAU,OAAO,MAAM,EAAE;;AAGvC,SAAgB,GAAG,OAAY;CAC7B,QAAQ,IAAI,GAAG,MAAM,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semver.utils.js","names":[],"sources":["../../src/utils/semver.utils.ts"],"sourcesContent":["import { compare } from \"semver\";\n\nexport function checkVersionSatisfiesMinimum(newVersion: string, minimumVersion: string) {\n const comparison = compare(newVersion, minimumVersion);\n // -1 => newVersion less than\n // 0 => newVersion equal to\n // 1 => newVersion greater than\n return comparison !== -1;\n}\n\nexport function getMaximumOfVersionsSafe(v1: string, v2?: string) {\n if (!v2) return v1;\n const comparison = compare(v1, v2);\n if (comparison === 0) return v1;\n if (comparison === -1) return v2;\n return v1;\n}\n"],"mappings":";;AAEA,SAAgB,6BAA6B,YAAoB,gBAAwB;
|
|
1
|
+
{"version":3,"file":"semver.utils.js","names":[],"sources":["../../src/utils/semver.utils.ts"],"sourcesContent":["import { compare } from \"semver\";\n\nexport function checkVersionSatisfiesMinimum(newVersion: string, minimumVersion: string) {\n const comparison = compare(newVersion, minimumVersion);\n // -1 => newVersion less than\n // 0 => newVersion equal to\n // 1 => newVersion greater than\n return comparison !== -1;\n}\n\nexport function getMaximumOfVersionsSafe(v1: string, v2?: string) {\n if (!v2) return v1;\n const comparison = compare(v1, v2);\n if (comparison === 0) return v1;\n if (comparison === -1) return v2;\n return v1;\n}\n"],"mappings":";;AAEA,SAAgB,6BAA6B,YAAoB,gBAAwB;CAKvF,OAJmB,QAAQ,YAAY,eAItB,KAAK;;AAGxB,SAAgB,yBAAyB,IAAY,IAAa;CAChE,IAAI,CAAC,IAAI,OAAO;CAChB,MAAM,aAAa,QAAQ,IAAI,GAAG;CAClC,IAAI,eAAe,GAAG,OAAO;CAC7B,IAAI,eAAe,IAAI,OAAO;CAC9B,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.js","names":[],"sources":["../../../src/utils/swagger/decorators.ts"],"sourcesContent":["// src/decorators/api.decorator.ts\nimport \"reflect-metadata\";\n\nexport const API_METADATA_KEY = \"Router Config\";\n\nexport interface ApiPropertyOptions {\n description?: string;\n required?: boolean;\n type?: any;\n isArray?: boolean;\n example?: any;\n}\n\nexport interface ApiOperationOptions {\n summary?: string;\n description?: string;\n responses?: Record<string, any>;\n}\n\nexport function ApiProperty(options: ApiPropertyOptions = {}) {\n return function (target: any, propertyKey: string) {\n const metadata = Reflect.getMetadata(API_METADATA_KEY, target.constructor) || {};\n metadata[propertyKey] = options;\n Reflect.defineMetadata(API_METADATA_KEY, metadata, target.constructor);\n };\n}\n\nexport function ApiOperation(options: ApiOperationOptions = {}) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n const metadata = Reflect.getMetadata(API_METADATA_KEY, target.constructor) || {};\n metadata[`${propertyKey}:operation`] = options;\n Reflect.defineMetadata(API_METADATA_KEY, metadata, target.constructor);\n };\n}\n"],"mappings":";;AAGA,MAAa,mBAAmB;AAgBhC,SAAgB,YAAY,UAA8B,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"decorators.js","names":[],"sources":["../../../src/utils/swagger/decorators.ts"],"sourcesContent":["// src/decorators/api.decorator.ts\nimport \"reflect-metadata\";\n\nexport const API_METADATA_KEY = \"Router Config\";\n\nexport interface ApiPropertyOptions {\n description?: string;\n required?: boolean;\n type?: any;\n isArray?: boolean;\n example?: any;\n}\n\nexport interface ApiOperationOptions {\n summary?: string;\n description?: string;\n responses?: Record<string, any>;\n}\n\nexport function ApiProperty(options: ApiPropertyOptions = {}) {\n return function (target: any, propertyKey: string) {\n const metadata = Reflect.getMetadata(API_METADATA_KEY, target.constructor) || {};\n metadata[propertyKey] = options;\n Reflect.defineMetadata(API_METADATA_KEY, metadata, target.constructor);\n };\n}\n\nexport function ApiOperation(options: ApiOperationOptions = {}) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n const metadata = Reflect.getMetadata(API_METADATA_KEY, target.constructor) || {};\n metadata[`${propertyKey}:operation`] = options;\n Reflect.defineMetadata(API_METADATA_KEY, metadata, target.constructor);\n };\n}\n"],"mappings":";;AAGA,MAAa,mBAAmB;AAgBhC,SAAgB,YAAY,UAA8B,EAAE,EAAE;CAC5D,OAAO,SAAU,QAAa,aAAqB;EACjD,MAAM,WAAW,QAAQ,YAAA,iBAA8B,OAAO,YAAY,IAAI,EAAE;EAChF,SAAS,eAAe;EACxB,QAAQ,eAAe,kBAAkB,UAAU,OAAO,YAAY;;;AAI1E,SAAgB,aAAa,UAA+B,EAAE,EAAE;CAC9D,OAAO,SAAU,QAAa,aAAqB,YAAgC;EACjF,MAAM,WAAW,QAAQ,YAAA,iBAA8B,OAAO,YAAY,IAAI,EAAE;EAChF,SAAS,GAAG,YAAY,eAAe;EACvC,QAAQ,eAAe,kBAAkB,UAAU,OAAO,YAAY"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","names":[],"sources":["../../../src/utils/swagger/generator.ts"],"sourcesContent":["import { OpenAPIObject, PathItemObject, SchemaObject } from \"openapi3-ts/oas31\";\nimport { API_METADATA_KEY } from \"@/utils/swagger/decorators\";\nimport { findControllers, FindControllersResult } from \"awilix-express\";\nimport { MethodName, type IRouteConfig } from \"awilix-router-core/lib/state-util\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { getDirname } from \"@/utils/fs.utils\";\n\nexport class SwaggerGenerator {\n private readonly logger: LoggerService;\n\n constructor(logger: LoggerService) {\n this.logger = logger;\n }\n private readonly openApiDoc: OpenAPIObject = {\n openapi: \"3.1.0\",\n info: {\n title: \"FDM Monster API\",\n version: process.env.npm_package_version || \"2.0.0\",\n description:\n \"FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.\",\n license: {\n name: \"AGPL-3.0-or-later\",\n url: \"https://www.gnu.org/licenses/agpl-3.0.en.html\",\n },\n contact: {\n name: \"FDM Monster GitHub\",\n url: \"https://github.com/fdm-monster/fdm-monster\",\n },\n },\n servers: [\n {\n url: \"/api\",\n description: \"API Server\",\n },\n ],\n paths: {},\n components: {\n schemas: {},\n securitySchemes: {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n description: \"Enter your JWT token\",\n },\n },\n },\n security: [\n {\n bearerAuth: [],\n },\n ],\n };\n\n public async generate(): Promise<OpenAPIObject> {\n try {\n const routePath = \"../../controllers\";\n const discoveredControllers = await findControllers(`${routePath}/*.controller.js`, {\n cwd: getDirname(import.meta.url),\n ignore: [\"**/*.map\", \"**/*.d.ts\"],\n absolute: true,\n esModules: true,\n });\n for (const registration of discoveredControllers) {\n await this.processController(registration);\n }\n this.logger.log(`Generated OpenAPI spec with ${Object.keys(this.openApiDoc.paths || {}).length} paths`);\n } catch (error) {\n this.logger.error(\"Failed to generate swagger specification\", error);\n }\n\n return this.openApiDoc;\n }\n\n private async processController(prototype: Awaited<FindControllersResult>[number]) {\n for (const [methodName, methodConfig] of prototype.state.methods) {\n if (methodConfig.paths.length > 0) {\n await this.processMethod(prototype, prototype.state.root, methodName, methodConfig);\n }\n }\n }\n\n private async processMethod(\n controller: Awaited<FindControllersResult>[number],\n root: IRouteConfig,\n methodName: MethodName,\n methodConfig: IRouteConfig,\n ) {\n if (!methodName) return;\n\n const method = methodName.toString();\n const name = method.toLowerCase();\n for (let methodPath of methodConfig.paths) {\n methodPath = root.paths[0] + methodPath;\n // Convert Express path format (:id) to OpenAPI format ({id})\n methodPath = methodPath.replaceAll(/:([a-zA-Z0-9_]+)/g, \"{$1}\");\n\n const metadata = Reflect.getMetadata(API_METADATA_KEY, controller.target);\n for (const verb of methodConfig.verbs) {\n const key = `${name}:operation`;\n const description = metadata?.hasOwnProperty(key) ? metadata[key] : null;\n\n const httpMethod = verb.toLowerCase() as\n | \"get\"\n | \"post\"\n | \"put\"\n | \"delete\"\n | \"patch\"\n | \"options\"\n | \"head\"\n | \"trace\";\n\n const operationObject = {\n tags: [controller.target.name],\n summary: description?.summary ?? method,\n description: description?.description ?? \"\",\n responses: description?.responses ?? {\n \"200\": {\n description: \"Successful response\",\n },\n },\n } as any;\n\n // Extract path parameters from the path (e.g., {id}, {userId}, etc.)\n operationObject.parameters = this.extractPathParameters(methodPath);\n\n // Process method parameters and response type\n const paramTypes = Reflect.getMetadata(\"design:paramtypes\", controller.target, name);\n const returnType = Reflect.getMetadata(\"design:returntype\", controller.target, name);\n\n if (paramTypes) {\n const additionalParams = this.processParameterTypes(paramTypes);\n operationObject.parameters = [...operationObject.parameters, ...additionalParams];\n }\n\n if (returnType && operationObject.responses?.[\"200\"]) {\n operationObject.responses[\"200\"].content = {\n \"application/json\": {\n schema: this.processReturnType(returnType),\n },\n };\n }\n\n const operation: PathItemObject = {\n [httpMethod]: operationObject,\n };\n\n this.openApiDoc.paths ??= {};\n\n this.openApiDoc.paths[methodPath] = {\n ...this.openApiDoc.paths[methodPath],\n ...operation,\n };\n }\n }\n }\n\n private extractPathParameters(path: string): any[] {\n const paramRegex = /\\{([a-zA-Z0-9_]+)\\}/g;\n const params: any[] = [];\n let match;\n\n while ((match = paramRegex.exec(path)) !== null) {\n const paramName = match[1];\n params.push({\n name: paramName,\n in: \"path\",\n required: true,\n schema: {\n type: \"string\",\n },\n description: `The ${paramName} parameter`,\n });\n }\n\n return params;\n }\n\n private processParameterTypes(types: any[]): any[] {\n return types.map((type) => this.createParameterDefinition(type));\n }\n\n private processReturnType(type: any): SchemaObject {\n return this.createSchemaDefinition(type);\n }\n\n private createSchemaDefinition(type: any): SchemaObject {\n // Implementation for creating OpenAPI schema from TypeScript type\n // This is a simplified version - you'll want to expand this\n return {\n type: \"object\",\n properties: this.getTypeProperties(type),\n };\n }\n\n private createParameterDefinition(type: any): any {\n // Implementation for creating parameter definitions\n return {\n in: \"body\",\n schema: this.createSchemaDefinition(type),\n };\n }\n\n private getTypeProperties(type: any): Record<string, any> {\n const properties: Record<string, any> = {};\n const metadata = Reflect.getMetadata(API_METADATA_KEY, type) || {};\n\n Object.keys(metadata).forEach((key) => {\n properties[key] = {\n type: this.getPropertyType(metadata[key].type),\n description: metadata[key].description,\n example: metadata[key].example,\n };\n });\n\n return properties;\n }\n\n private getPropertyType(type: any): string {\n const typeMap: Record<string, string> = {\n String: \"string\",\n Number: \"number\",\n Boolean: \"boolean\",\n Object: \"object\",\n Array: \"array\",\n };\n\n return typeMap[type?.name] || \"string\";\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generator.js","names":[],"sources":["../../../src/utils/swagger/generator.ts"],"sourcesContent":["import { OpenAPIObject, PathItemObject, SchemaObject } from \"openapi3-ts/oas31\";\nimport { API_METADATA_KEY } from \"@/utils/swagger/decorators\";\nimport { findControllers, FindControllersResult } from \"awilix-express\";\nimport { MethodName, type IRouteConfig } from \"awilix-router-core/lib/state-util\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { getDirname } from \"@/utils/fs.utils\";\n\nexport class SwaggerGenerator {\n private readonly logger: LoggerService;\n\n constructor(logger: LoggerService) {\n this.logger = logger;\n }\n private readonly openApiDoc: OpenAPIObject = {\n openapi: \"3.1.0\",\n info: {\n title: \"FDM Monster API\",\n version: process.env.npm_package_version || \"2.0.0\",\n description:\n \"FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.\",\n license: {\n name: \"AGPL-3.0-or-later\",\n url: \"https://www.gnu.org/licenses/agpl-3.0.en.html\",\n },\n contact: {\n name: \"FDM Monster GitHub\",\n url: \"https://github.com/fdm-monster/fdm-monster\",\n },\n },\n servers: [\n {\n url: \"/api\",\n description: \"API Server\",\n },\n ],\n paths: {},\n components: {\n schemas: {},\n securitySchemes: {\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n bearerFormat: \"JWT\",\n description: \"Enter your JWT token\",\n },\n },\n },\n security: [\n {\n bearerAuth: [],\n },\n ],\n };\n\n public async generate(): Promise<OpenAPIObject> {\n try {\n const routePath = \"../../controllers\";\n const discoveredControllers = await findControllers(`${routePath}/*.controller.js`, {\n cwd: getDirname(import.meta.url),\n ignore: [\"**/*.map\", \"**/*.d.ts\"],\n absolute: true,\n esModules: true,\n });\n for (const registration of discoveredControllers) {\n await this.processController(registration);\n }\n this.logger.log(`Generated OpenAPI spec with ${Object.keys(this.openApiDoc.paths || {}).length} paths`);\n } catch (error) {\n this.logger.error(\"Failed to generate swagger specification\", error);\n }\n\n return this.openApiDoc;\n }\n\n private async processController(prototype: Awaited<FindControllersResult>[number]) {\n for (const [methodName, methodConfig] of prototype.state.methods) {\n if (methodConfig.paths.length > 0) {\n await this.processMethod(prototype, prototype.state.root, methodName, methodConfig);\n }\n }\n }\n\n private async processMethod(\n controller: Awaited<FindControllersResult>[number],\n root: IRouteConfig,\n methodName: MethodName,\n methodConfig: IRouteConfig,\n ) {\n if (!methodName) return;\n\n const method = methodName.toString();\n const name = method.toLowerCase();\n for (let methodPath of methodConfig.paths) {\n methodPath = root.paths[0] + methodPath;\n // Convert Express path format (:id) to OpenAPI format ({id})\n methodPath = methodPath.replaceAll(/:([a-zA-Z0-9_]+)/g, \"{$1}\");\n\n const metadata = Reflect.getMetadata(API_METADATA_KEY, controller.target);\n for (const verb of methodConfig.verbs) {\n const key = `${name}:operation`;\n const description = metadata?.hasOwnProperty(key) ? metadata[key] : null;\n\n const httpMethod = verb.toLowerCase() as\n | \"get\"\n | \"post\"\n | \"put\"\n | \"delete\"\n | \"patch\"\n | \"options\"\n | \"head\"\n | \"trace\";\n\n const operationObject = {\n tags: [controller.target.name],\n summary: description?.summary ?? method,\n description: description?.description ?? \"\",\n responses: description?.responses ?? {\n \"200\": {\n description: \"Successful response\",\n },\n },\n } as any;\n\n // Extract path parameters from the path (e.g., {id}, {userId}, etc.)\n operationObject.parameters = this.extractPathParameters(methodPath);\n\n // Process method parameters and response type\n const paramTypes = Reflect.getMetadata(\"design:paramtypes\", controller.target, name);\n const returnType = Reflect.getMetadata(\"design:returntype\", controller.target, name);\n\n if (paramTypes) {\n const additionalParams = this.processParameterTypes(paramTypes);\n operationObject.parameters = [...operationObject.parameters, ...additionalParams];\n }\n\n if (returnType && operationObject.responses?.[\"200\"]) {\n operationObject.responses[\"200\"].content = {\n \"application/json\": {\n schema: this.processReturnType(returnType),\n },\n };\n }\n\n const operation: PathItemObject = {\n [httpMethod]: operationObject,\n };\n\n this.openApiDoc.paths ??= {};\n\n this.openApiDoc.paths[methodPath] = {\n ...this.openApiDoc.paths[methodPath],\n ...operation,\n };\n }\n }\n }\n\n private extractPathParameters(path: string): any[] {\n const paramRegex = /\\{([a-zA-Z0-9_]+)\\}/g;\n const params: any[] = [];\n let match;\n\n while ((match = paramRegex.exec(path)) !== null) {\n const paramName = match[1];\n params.push({\n name: paramName,\n in: \"path\",\n required: true,\n schema: {\n type: \"string\",\n },\n description: `The ${paramName} parameter`,\n });\n }\n\n return params;\n }\n\n private processParameterTypes(types: any[]): any[] {\n return types.map((type) => this.createParameterDefinition(type));\n }\n\n private processReturnType(type: any): SchemaObject {\n return this.createSchemaDefinition(type);\n }\n\n private createSchemaDefinition(type: any): SchemaObject {\n // Implementation for creating OpenAPI schema from TypeScript type\n // This is a simplified version - you'll want to expand this\n return {\n type: \"object\",\n properties: this.getTypeProperties(type),\n };\n }\n\n private createParameterDefinition(type: any): any {\n // Implementation for creating parameter definitions\n return {\n in: \"body\",\n schema: this.createSchemaDefinition(type),\n };\n }\n\n private getTypeProperties(type: any): Record<string, any> {\n const properties: Record<string, any> = {};\n const metadata = Reflect.getMetadata(API_METADATA_KEY, type) || {};\n\n Object.keys(metadata).forEach((key) => {\n properties[key] = {\n type: this.getPropertyType(metadata[key].type),\n description: metadata[key].description,\n example: metadata[key].example,\n };\n });\n\n return properties;\n }\n\n private getPropertyType(type: any): string {\n const typeMap: Record<string, string> = {\n String: \"string\",\n Number: \"number\",\n Boolean: \"boolean\",\n Object: \"object\",\n Array: \"array\",\n };\n\n return typeMap[type?.name] || \"string\";\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swagger.js","names":["expressStatic"],"sources":["../../../src/utils/swagger/swagger.ts"],"sourcesContent":["import { SwaggerGenerator } from \"./generator\";\nimport { Application, static as expressStatic } from \"express\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { AppConstants } from \"@/server.constants\";\nimport { ensureDirExists, getMediaPath } from \"@/utils/fs.utils\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { writeFile } from \"node:fs/promises\";\n\nfunction getSwaggerUiDistPath(): string {\n try {\n const resolved = import.meta.resolve(\"swagger-ui-dist/package.json\");\n return dirname(fileURLToPath(resolved));\n } catch {\n return join(process.cwd(), \"node_modules\", \"swagger-ui-dist\");\n }\n}\n\nfunction generateSwaggerHTML(): string {\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>FDM Monster API Documentation</title>\n <link rel=\"stylesheet\" href=\"/api-docs/static/swagger-ui.css\" />\n <style>\n html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }\n *, *:before, *:after { box-sizing: inherit; }\n body { margin: 0; padding: 0; }\n </style>\n</head>\n<body>\n <div id=\"swagger-ui\"></div>\n <script src=\"/api-docs/static/swagger-ui-bundle.js\"></script>\n <script src=\"/api-docs/static/swagger-ui-standalone-preset.js\"></script>\n <script>\n window.onload = function() {\n window.ui = SwaggerUIBundle({\n url: '/api-docs/swagger.json',\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [\n SwaggerUIBundle.presets.apis,\n SwaggerUIStandalonePreset\n ],\n plugins: [\n SwaggerUIBundle.plugins.DownloadUrl\n ],\n layout: \"StandaloneLayout\"\n });\n };\n </script>\n</body>\n</html>\n `.trim();\n}\n\nexport async function setupSwagger(app: Application, logger: LoggerService) {\n const generator = new SwaggerGenerator(logger);\n const specification = await generator.generate();\n\n const generateJsonFile = process.env[AppConstants.GENERATE_SWAGGER_JSON] === \"true\";\n if (generateJsonFile) {\n try {\n const mediaPath = getMediaPath();\n ensureDirExists(mediaPath);\n\n const swaggerJsonPath = join(mediaPath, \"swagger.json\");\n const jsonContent = JSON.stringify(specification, null, 2);\n\n await writeFile(swaggerJsonPath, jsonContent, \"utf-8\");\n logger.log(`Swagger JSON file generated at: ${swaggerJsonPath}`);\n } catch (error) {\n logger.error(\"Failed to generate swagger.json file\", error);\n }\n }\n\n const swaggerUiPath = getSwaggerUiDistPath();\n app.use(\"/api-docs/static\", expressStatic(swaggerUiPath));\n app.get(\"/api-docs/swagger.json\", (_req, res) => {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.send(specification);\n });\n app.get(\"/api-docs\", (_req, res) => {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.send(generateSwaggerHTML());\n });\n\n return specification;\n}\n"],"mappings":";;;;;;;;AASA,SAAS,uBAA+B;
|
|
1
|
+
{"version":3,"file":"swagger.js","names":["expressStatic"],"sources":["../../../src/utils/swagger/swagger.ts"],"sourcesContent":["import { SwaggerGenerator } from \"./generator\";\nimport { Application, static as expressStatic } from \"express\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { AppConstants } from \"@/server.constants\";\nimport { ensureDirExists, getMediaPath } from \"@/utils/fs.utils\";\nimport { LoggerService } from \"@/handlers/logger\";\nimport { writeFile } from \"node:fs/promises\";\n\nfunction getSwaggerUiDistPath(): string {\n try {\n const resolved = import.meta.resolve(\"swagger-ui-dist/package.json\");\n return dirname(fileURLToPath(resolved));\n } catch {\n return join(process.cwd(), \"node_modules\", \"swagger-ui-dist\");\n }\n}\n\nfunction generateSwaggerHTML(): string {\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>FDM Monster API Documentation</title>\n <link rel=\"stylesheet\" href=\"/api-docs/static/swagger-ui.css\" />\n <style>\n html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }\n *, *:before, *:after { box-sizing: inherit; }\n body { margin: 0; padding: 0; }\n </style>\n</head>\n<body>\n <div id=\"swagger-ui\"></div>\n <script src=\"/api-docs/static/swagger-ui-bundle.js\"></script>\n <script src=\"/api-docs/static/swagger-ui-standalone-preset.js\"></script>\n <script>\n window.onload = function() {\n window.ui = SwaggerUIBundle({\n url: '/api-docs/swagger.json',\n dom_id: '#swagger-ui',\n deepLinking: true,\n presets: [\n SwaggerUIBundle.presets.apis,\n SwaggerUIStandalonePreset\n ],\n plugins: [\n SwaggerUIBundle.plugins.DownloadUrl\n ],\n layout: \"StandaloneLayout\"\n });\n };\n </script>\n</body>\n</html>\n `.trim();\n}\n\nexport async function setupSwagger(app: Application, logger: LoggerService) {\n const generator = new SwaggerGenerator(logger);\n const specification = await generator.generate();\n\n const generateJsonFile = process.env[AppConstants.GENERATE_SWAGGER_JSON] === \"true\";\n if (generateJsonFile) {\n try {\n const mediaPath = getMediaPath();\n ensureDirExists(mediaPath);\n\n const swaggerJsonPath = join(mediaPath, \"swagger.json\");\n const jsonContent = JSON.stringify(specification, null, 2);\n\n await writeFile(swaggerJsonPath, jsonContent, \"utf-8\");\n logger.log(`Swagger JSON file generated at: ${swaggerJsonPath}`);\n } catch (error) {\n logger.error(\"Failed to generate swagger.json file\", error);\n }\n }\n\n const swaggerUiPath = getSwaggerUiDistPath();\n app.use(\"/api-docs/static\", expressStatic(swaggerUiPath));\n app.get(\"/api-docs/swagger.json\", (_req, res) => {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.send(specification);\n });\n app.get(\"/api-docs\", (_req, res) => {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.send(generateSwaggerHTML());\n });\n\n return specification;\n}\n"],"mappings":";;;;;;;;AASA,SAAS,uBAA+B;CACtC,IAAI;EAEF,OAAO,QAAQ,cADE,OAAO,KAAK,QAAQ,+BACA,CAAC,CAAC;SACjC;EACN,OAAO,KAAK,QAAQ,KAAK,EAAE,gBAAgB,kBAAkB;;;AAIjE,SAAS,sBAA8B;CACrC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAqCL,MAAM;;AAGV,eAAsB,aAAa,KAAkB,QAAuB;CAE1E,MAAM,gBAAgB,MAAM,IADN,iBAAiB,OACF,CAAC,UAAU;CAGhD,IADyB,QAAQ,IAAI,aAAa,2BAA2B,QAE3E,IAAI;EACF,MAAM,YAAY,cAAc;EAChC,gBAAgB,UAAU;EAE1B,MAAM,kBAAkB,KAAK,WAAW,eAAe;EAGvD,MAAM,UAAU,iBAFI,KAAK,UAAU,eAAe,MAAM,EAEZ,EAAE,QAAQ;EACtD,OAAO,IAAI,mCAAmC,kBAAkB;UACzD,OAAO;EACd,OAAO,MAAM,wCAAwC,MAAM;;CAI/D,MAAM,gBAAgB,sBAAsB;CAC5C,IAAI,IAAI,oBAAoBA,SAAc,cAAc,CAAC;CACzD,IAAI,IAAI,2BAA2B,MAAM,QAAQ;EAC/C,IAAI,UAAU,gBAAgB,mBAAmB;EACjD,IAAI,KAAK,cAAc;GACvB;CACF,IAAI,IAAI,cAAc,MAAM,QAAQ;EAClC,IAAI,UAAU,gBAAgB,YAAY;EAC1C,IAAI,KAAK,qBAAqB,CAAC;GAC/B;CAEF,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thumbnail.util.js","names":[],"sources":["../../src/utils/thumbnail.util.ts"],"sourcesContent":["export interface ThumbnailInfo {\n index: number;\n width: number;\n height: number;\n format: string;\n size: number;\n}\n\nexport function extractThumbnailsFromMetadata(metadata: any): ThumbnailInfo[] {\n if (!metadata?._thumbnails) {\n return [];\n }\n\n return (metadata._thumbnails || []).map((thumb: any) => ({\n index: thumb.index,\n width: thumb.width,\n height: thumb.height,\n format: thumb.format,\n size: thumb.size,\n }));\n}\n"],"mappings":";AAQA,SAAgB,8BAA8B,UAAgC;
|
|
1
|
+
{"version":3,"file":"thumbnail.util.js","names":[],"sources":["../../src/utils/thumbnail.util.ts"],"sourcesContent":["export interface ThumbnailInfo {\n index: number;\n width: number;\n height: number;\n format: string;\n size: number;\n}\n\nexport function extractThumbnailsFromMetadata(metadata: any): ThumbnailInfo[] {\n if (!metadata?._thumbnails) {\n return [];\n }\n\n return (metadata._thumbnails || []).map((thumb: any) => ({\n index: thumb.index,\n width: thumb.width,\n height: thumb.height,\n format: thumb.format,\n size: thumb.size,\n }));\n}\n"],"mappings":";AAQA,SAAgB,8BAA8B,UAAgC;CAC5E,IAAI,CAAC,UAAU,aACb,OAAO,EAAE;CAGX,QAAQ,SAAS,eAAe,EAAE,EAAE,KAAK,WAAgB;EACvD,OAAO,MAAM;EACb,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,QAAQ,MAAM;EACd,MAAM,MAAM;EACb,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time.utils.js","names":[],"sources":["../../src/utils/time.utils.ts"],"sourcesContent":["export function isParsableDate(value: string): boolean {\n const timestamp = Date.parse(value);\n return !isNaN(timestamp);\n}\n"],"mappings":";AAAA,SAAgB,eAAe,OAAwB;
|
|
1
|
+
{"version":3,"file":"time.utils.js","names":[],"sources":["../../src/utils/time.utils.ts"],"sourcesContent":["export function isParsableDate(value: string): boolean {\n const timestamp = Date.parse(value);\n return !isNaN(timestamp);\n}\n"],"mappings":";AAAA,SAAgB,eAAe,OAAwB;CAErD,OAAO,CAAC,MADU,KAAK,MAAM,MACN,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.utils.js","names":[],"sources":["../../src/utils/url.utils.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { normalizeUrl } from \"./normalize-url\";\n\nexport const defaultHttpProtocol = \"https\";\n\nexport function httpToWsUrl(url?: string) {\n const validUrl = z.string().parse(url);\n\n const protocolNormalizedAddress = normalizeUrl(validUrl, { defaultProtocol: defaultHttpProtocol });\n\n const wsUrl = new URL(protocolNormalizedAddress);\n wsUrl.protocol = wsUrl.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return wsUrl;\n}\n"],"mappings":";;;AAGA,MAAa,sBAAsB;AAEnC,SAAgB,YAAY,KAAc;CAGxC,MAAM,4BAA4B,aAFjB,EAAE,QAAQ,CAAC,MAAM,IAEqB,EAAE,EAAE,iBAAiB,qBAAqB,CAAC;CAElG,MAAM,QAAQ,IAAI,IAAI,0BAA0B;
|
|
1
|
+
{"version":3,"file":"url.utils.js","names":[],"sources":["../../src/utils/url.utils.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { normalizeUrl } from \"./normalize-url\";\n\nexport const defaultHttpProtocol = \"https\";\n\nexport function httpToWsUrl(url?: string) {\n const validUrl = z.string().parse(url);\n\n const protocolNormalizedAddress = normalizeUrl(validUrl, { defaultProtocol: defaultHttpProtocol });\n\n const wsUrl = new URL(protocolNormalizedAddress);\n wsUrl.protocol = wsUrl.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return wsUrl;\n}\n"],"mappings":";;;AAGA,MAAa,sBAAsB;AAEnC,SAAgB,YAAY,KAAc;CAGxC,MAAM,4BAA4B,aAFjB,EAAE,QAAQ,CAAC,MAAM,IAEqB,EAAE,EAAE,iBAAiB,qBAAqB,CAAC;CAElG,MAAM,QAAQ,IAAI,IAAI,0BAA0B;CAChD,MAAM,WAAW,MAAM,aAAa,WAAW,SAAS;CACxD,OAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fdm-monster/server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"3d printing",
|
|
@@ -53,18 +53,22 @@
|
|
|
53
53
|
"console:generate": "tsx src/consoles/typeorm-generate.ts",
|
|
54
54
|
"console:migrate": "tsx src/consoles/typeorm-migrate.ts"
|
|
55
55
|
},
|
|
56
|
+
"dependenciesMeta": {
|
|
57
|
+
"better-sqlite3": {
|
|
58
|
+
"built": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
56
61
|
"dependencies": {
|
|
57
|
-
"@fdm-monster/client-next": "2.
|
|
62
|
+
"@fdm-monster/client-next": "2.4.2",
|
|
58
63
|
"@octokit/plugin-throttling": "11.0.3",
|
|
59
|
-
"@sentry/node": "10.
|
|
64
|
+
"@sentry/node": "10.53.1",
|
|
60
65
|
"adm-zip": "0.5.17",
|
|
61
66
|
"awilix": "13.0.3",
|
|
62
67
|
"awilix-express": "11.0.1",
|
|
63
|
-
"axios": "1.16.
|
|
68
|
+
"axios": "1.16.1",
|
|
64
69
|
"basic-ftp": "6.0.1",
|
|
65
70
|
"bcryptjs": "3.0.3",
|
|
66
|
-
"better-sqlite3": "12.
|
|
67
|
-
"chalk": "5.6.2",
|
|
71
|
+
"better-sqlite3": "12.10.0",
|
|
68
72
|
"class-validator": "0.15.1",
|
|
69
73
|
"connect-history-api-fallback": "2.0.0",
|
|
70
74
|
"cookie-parser": "1.4.7",
|
|
@@ -77,7 +81,6 @@
|
|
|
77
81
|
"helmet": "8.1.0",
|
|
78
82
|
"js-yaml": "4.1.1",
|
|
79
83
|
"jsonwebtoken": "9.0.3",
|
|
80
|
-
"luxon": "3.7.2",
|
|
81
84
|
"mqtt": "5.15.1",
|
|
82
85
|
"multer": "2.1.1",
|
|
83
86
|
"octokit": "5.0.5",
|
|
@@ -90,10 +93,10 @@
|
|
|
90
93
|
"socket.io": "4.8.3",
|
|
91
94
|
"toad-scheduler": "4.0.1",
|
|
92
95
|
"typeorm": "0.3.29",
|
|
93
|
-
"uuid": "
|
|
96
|
+
"uuid": "14.0.0",
|
|
94
97
|
"winston": "3.19.0",
|
|
95
98
|
"winston-loki": "6.1.4",
|
|
96
|
-
"ws": "8.20.
|
|
99
|
+
"ws": "8.20.1",
|
|
97
100
|
"zod": "3.25.76"
|
|
98
101
|
},
|
|
99
102
|
"devDependencies": {
|
|
@@ -112,16 +115,16 @@
|
|
|
112
115
|
"@types/passport-jwt": "4.0.1",
|
|
113
116
|
"@types/semver": "7.7.1",
|
|
114
117
|
"@types/supertest": "7.2.0",
|
|
115
|
-
"@types/uuid": "
|
|
118
|
+
"@types/uuid": "11.0.0",
|
|
116
119
|
"@types/ws": "8.18.1",
|
|
117
120
|
"@vitest/coverage-v8": "4.1.6",
|
|
118
121
|
"@vitest/ui": "4.1.6",
|
|
119
|
-
"
|
|
122
|
+
"chalk": "5.6.2",
|
|
120
123
|
"nock": "13.5.6",
|
|
121
124
|
"openapi3-ts": "4.5.0",
|
|
122
125
|
"supertest": "7.2.2",
|
|
123
|
-
"swagger-ui-dist": "5.32.
|
|
124
|
-
"tsx": "4.
|
|
126
|
+
"swagger-ui-dist": "5.32.6",
|
|
127
|
+
"tsx": "4.22.0",
|
|
125
128
|
"typescript": "6.0.3",
|
|
126
129
|
"vite-plus": "latest",
|
|
127
130
|
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
|
|
@@ -135,7 +138,7 @@
|
|
|
135
138
|
"npm": ">= 10.9.0",
|
|
136
139
|
"yarn": ">= 4.12.0"
|
|
137
140
|
},
|
|
138
|
-
"packageManager": "yarn@4.
|
|
141
|
+
"packageManager": "yarn@4.14.1",
|
|
139
142
|
"engine-strict": true,
|
|
140
143
|
"ignore-engines": false
|
|
141
144
|
}
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"bambu:diagnostic": "cross-env NODE_ENV=development node --enable-source-maps dist/bambu-mqtt-diagnostic.console.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"axios": "1.16.
|
|
21
|
-
"better-sqlite3": "12.
|
|
20
|
+
"axios": "1.16.1",
|
|
21
|
+
"better-sqlite3": "12.10.0",
|
|
22
22
|
"chalk": "5.6.2",
|
|
23
23
|
"dotenv": "17.4.2",
|
|
24
24
|
"express": "4.22.2",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"multer": "2.1.1",
|
|
28
28
|
"selfsigned": "5.5.0",
|
|
29
29
|
"typeorm": "0.3.29",
|
|
30
|
-
"ws": "8.20.
|
|
30
|
+
"ws": "8.20.1",
|
|
31
31
|
"zod": "3.25.76"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@types/ws": "8.18.1",
|
|
38
38
|
"ts-node": "10.9.2",
|
|
39
39
|
"tsconfig-paths": "4.2.0",
|
|
40
|
-
"tsx": "4.
|
|
40
|
+
"tsx": "4.22.0",
|
|
41
41
|
"typescript": "6.0.3",
|
|
42
42
|
"vite-plus": "latest"
|
|
43
43
|
}
|