Files
Ready2Blob/.planning/phases/02-generators/02-02-PLAN.md
T

167 lines
5.9 KiB
Markdown

---
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<string, string> lookup"
pattern: "'s3-compatible': 's3'"
---
<objective>
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.
</objective>
<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<interfaces>
<!-- Types consumed by buildRcloneConf. -->
From src/store/types.ts:
```typescript
export interface WizardState {
currentStep: number;
remote: {
name: string;
backendType: BackendType | null; // null must throw
params: Record<string, string>; // 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
```
</interfaces>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Implement buildRcloneConf</name>
<files>src/generators/rclone-conf.ts</files>
<behavior>
- 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(...) }`
</behavior>
<action>
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<string, string> = {
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.
</action>
<verify>
<automated>npm test -- src/generators/rclone-conf.test.ts</automated>
</verify>
<done>
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).
</done>
</task>
</tasks>
<verification>
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.
</verification>
<success_criteria>
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).
</success_criteria>
<output>
After completion, create `.planning/phases/02-generators/02-02-SUMMARY.md`
</output>