From 1e1ac25ea3074584337b703ae865f9b5b1c9b6cb Mon Sep 17 00:00:00 2001 From: Kawa Date: Thu, 26 Mar 2026 14:06:51 +0100 Subject: [PATCH] feat(02-04): implement buildRmmScript pure function - Imports CONFIG_DIR and buildRcloneInstallBlock from ps-helpers.ts - Uses \$ErrorActionPreference = 'Stop' for RMM platform exit code surfacing - Creates config directory idempotently with Test-Path guard - Writes rclone.conf using [System.IO.File]::WriteAllText UTF-8 no-BOM - Conditionally includes rclone download block based on includeInstall flag - All 6 rmm-script.test.ts assertions pass GREEN --- src/generators/rmm-script.ts | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/generators/rmm-script.ts diff --git a/src/generators/rmm-script.ts b/src/generators/rmm-script.ts new file mode 100644 index 0000000..d669a5b --- /dev/null +++ b/src/generators/rmm-script.ts @@ -0,0 +1,57 @@ +// src/generators/rmm-script.ts +// Generator for RMM (Remote Monitoring and Management) PowerShell deployment scripts. +// Compatible with NinjaRMM, Datto, ConnectWise, and similar RMM platforms. +// Runs as SYSTEM. Idempotent (safe to re-run). + +import type { WizardState } from '../store/types'; +import { CONFIG_DIR, buildRcloneInstallBlock } from './ps-helpers'; + +export function buildRmmScript(state: WizardState): string { + const configDir = CONFIG_DIR[state.deployment.configPath]; + const confContent = buildRcloneConfContent(state); + const installBlock = state.deployment.includeInstall + ? buildRcloneInstallBlock(configDir) + : ''; + + return [ + `# RMM Deployment Script — compatible with NinjaRMM, Datto, ConnectWise`, + `# Runs as SYSTEM. Idempotent (safe to re-run).`, + `$ErrorActionPreference = 'Stop'`, + ``, + `$configDir = '${configDir}'`, + `$configPath = "$configDir\\rclone.conf"`, + ``, + `# Idempotent: create directory if it doesn't exist`, + `if (-not (Test-Path $configDir)) {`, + ` New-Item -ItemType Directory -Path $configDir -Force | Out-Null`, + `}`, + ``, + `# Write config using UTF-8 no-BOM encoding`, + `# Note: Set-Content -Encoding UTF8 in PS 5.1 writes UTF-8 WITH BOM — do not use`, + `$confContent = @'`, + confContent.trimEnd(), + `'@`, + `[System.IO.File]::WriteAllText($configPath, $confContent, [System.Text.UTF8Encoding]::new($false))`, + installBlock, + ``, + `exit 0`, + ].join('\n') + '\n'; +} + +// Build the rclone.conf content string to embed in the here-string. +// Inlined here (not imported from rclone-conf.ts) to keep the file self-contained +// and avoid any circular dependency risk. +function buildRcloneConfContent(state: WizardState): string { + const RCLONE_TYPE_MAP: Record = { + azureblob: 'azureblob', + s3: 's3', + 's3-compatible': 's3', + }; + const { name, backendType, params } = state.remote; + const rcloneType = backendType ? RCLONE_TYPE_MAP[backendType] : 'unknown'; + const lines: string[] = [`[${name}]`, `type = ${rcloneType}`]; + for (const [key, value] of Object.entries(params)) { + if (value !== '') lines.push(`${key} = ${value}`); + } + return lines.join('\n') + '\n'; +}