--- phase: 02-generators plan: 02 type: execute wave: 2 depends_on: - 02-01 files_modified: - src/generators/rclone-conf.ts autonomous: true requirements: - CONF-01 must_haves: truths: - "buildRcloneConf(azureState) produces a valid INI block with type = azureblob and non-empty fields" - "buildRcloneConf(s3State) produces type = s3 with provider = AWS" - "buildRcloneConf(s3CompatState) produces type = s3 (not s3-compatible) with provider = Other" - "Empty optional fields are omitted from the output (no 'sas_url =' line when sas_url is empty)" - "All rclone-conf.test.ts assertions pass GREEN" artifacts: - path: "src/generators/rclone-conf.ts" provides: "buildRcloneConf pure function" exports: ["buildRcloneConf"] min_lines: 25 key_links: - from: "src/generators/rclone-conf.ts" to: "src/store/types.ts" via: "import type { WizardState } from '../store/types'" pattern: "WizardState" - from: "RCLONE_TYPE_MAP" to: "rclone backend type strings" via: "Record lookup" pattern: "'s3-compatible': 's3'" --- Implement buildRcloneConf() — the pure TypeScript function that converts WizardState into a valid rclone.conf INI string. Purpose: CONF-01 — the foundational output artifact. Every downstream phase depends on this being correct. Wrong type values or included empty fields silently break rclone remotes. Output: src/generators/rclone-conf.ts with all rclone-conf.test.ts assertions green. @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md @.planning/ROADMAP.md @.planning/phases/02-generators/02-01-SUMMARY.md @src/store/types.ts @src/schemas/registry.ts @src/generators/rclone-conf.test.ts From src/store/types.ts: ```typescript export interface WizardState { currentStep: number; remote: { name: string; backendType: BackendType | null; // null must throw params: Record; // iterate, skip empty values }; deployment: { includeInstall: boolean; configPath: 'machine-wide' | 'user-profile'; scriptTargets: ('intune' | 'rmm')[]; }; } ``` From src/schemas/registry.ts: ```typescript export type BackendType = 'azureblob' | 's3' | 's3-compatible'; ``` Critical type mapping (from research — verified against rclone.org): ```typescript // BackendType → rclone config 'type' value // 'azureblob' → 'azureblob' // 's3' → 's3' // 's3-compatible' → 's3' (NOT 's3-compatible' — rclone does not recognize that string) // S3-compatible backends use type = s3, differentiated by provider = Other ``` Task 1: Implement buildRcloneConf src/generators/rclone-conf.ts - azureblob: '[my-azure]\ntype = azureblob\naccount = mystorageaccount\nkey = BASE64KEY\n' (sas_url omitted — empty) - s3: contains 'type = s3', 'provider = AWS', 'access_key_id = AKID', 'region = us-east-1' - s3-compatible: contains 'type = s3', NOT 'type = s3-compatible', 'provider = Other', 'endpoint = https://s3.wasabisys.com', NOT 'region' line (empty optional field) - throws Error if backendType is null - throws Error if name is empty string - output ends with '\n' (trailing newline — rclone convention) - no empty value lines: `if (value !== '') { lines.push(...) }` Create src/generators/rclone-conf.ts implementing buildRcloneConf(state: WizardState): string. Follow this exact structure: ```typescript import type { WizardState } from '../store/types'; // Maps our BackendType to rclone's internal type string. // IMPORTANT: s3-compatible uses 'type = s3' — rclone does not recognize 's3-compatible' as a type. // S3-compatible backends are differentiated by provider = Other in params. const RCLONE_TYPE_MAP: Record = { azureblob: 'azureblob', s3: 's3', 's3-compatible': 's3', }; export function buildRcloneConf(state: WizardState): string { const { name, backendType, params } = state.remote; if (!backendType) throw new Error('buildRcloneConf: backendType is required'); if (!name) throw new Error('buildRcloneConf: remote name is required'); const rcloneType = RCLONE_TYPE_MAP[backendType]; 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'; } ``` Do NOT import BACKEND_REGISTRY — the function iterates over state.remote.params directly (already contains the right keys from the registry-driven form). Do NOT add any console.log or Write-Host equivalent — credentials must not be logged. Anti-pattern to avoid: Do not write `type = s3-compatible`. The RCLONE_TYPE_MAP lookup handles the translation. npm test -- src/generators/rclone-conf.test.ts All rclone-conf.test.ts assertions pass GREEN. buildRcloneConf produces correct INI output for all three backends. Empty fields are omitted. Type mapping is correct (s3-compatible → type = s3). npm test -- src/generators/rclone-conf.test.ts is fully green. npm test (full suite) still passes. src/generators/rclone-conf.ts is importable from index.ts without TypeScript errors. CONF-01 is satisfied: buildRcloneConf produces valid INI-format rclone.conf strings with correct key/value pairs for all three supported backends (azureblob, s3, s3-compatible). After completion, create `.planning/phases/02-generators/02-02-SUMMARY.md`