docs(02-generators): create phase 2 plan — 4 plans in 2 waves
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
---
|
||||
phase: 02-generators
|
||||
plan: 04
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on:
|
||||
- 02-01
|
||||
files_modified:
|
||||
- src/generators/rmm-script.ts
|
||||
autonomous: true
|
||||
requirements:
|
||||
- DEPL-03
|
||||
- DEPL-04
|
||||
- DEPL-05
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "buildRmmScript() produces a self-contained PowerShell script with $ErrorActionPreference = 'Stop'"
|
||||
- "buildRmmScript() creates the config directory idempotently and writes rclone.conf with UTF-8 no-BOM"
|
||||
- "buildRmmScript() with includeInstall=true adds rclone download block; with false, omits it"
|
||||
- "buildRmmScript() uses C:\\ProgramData\\rclone for machine-wide and %APPDATA%\\rclone for user-profile"
|
||||
- "All rmm-script.test.ts assertions pass GREEN"
|
||||
artifacts:
|
||||
- path: "src/generators/rmm-script.ts"
|
||||
provides: "buildRmmScript pure function"
|
||||
exports: ["buildRmmScript"]
|
||||
min_lines: 35
|
||||
key_links:
|
||||
- from: "src/generators/rmm-script.ts"
|
||||
to: "src/generators/ps-helpers.ts"
|
||||
via: "import { CONFIG_DIR, buildRcloneInstallBlock }"
|
||||
pattern: "from './ps-helpers'"
|
||||
- from: "CONFIG_DIR lookup"
|
||||
to: "WizardState.deployment.configPath"
|
||||
via: "Record<'machine-wide'|'user-profile', string>"
|
||||
pattern: "machine-wide|user-profile"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement buildRmmScript() — the generic RMM PowerShell installer using shared ps-helpers.ts from Plan 03.
|
||||
|
||||
Purpose: DEPL-03. RMM script is structurally similar to the Intune install script but has no separate detection artifact, no Intune packaging constraints, and explicitly uses $ErrorActionPreference = 'Stop' (RMM platforms surface exit codes, not STDERR).
|
||||
Output: src/generators/rmm-script.ts with all rmm-script.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
|
||||
@.planning/phases/02-generators/02-03-SUMMARY.md
|
||||
@src/store/types.ts
|
||||
@src/generators/ps-helpers.ts
|
||||
@src/generators/rmm-script.test.ts
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Shared helpers available from Plan 03. -->
|
||||
|
||||
From src/generators/ps-helpers.ts (created in Plan 03):
|
||||
```typescript
|
||||
export const CONFIG_DIR: Record<'machine-wide' | 'user-profile', string> = {
|
||||
'machine-wide': 'C:\\ProgramData\\rclone',
|
||||
'user-profile': '%APPDATA%\\rclone',
|
||||
};
|
||||
|
||||
export function buildRcloneInstallBlock(installPath: string): string;
|
||||
// Returns PowerShell snippet with Invoke-WebRequest -UseBasicParsing and zip extraction.
|
||||
// Returns empty string if called conditionally — caller decides whether to include.
|
||||
```
|
||||
|
||||
From src/store/types.ts:
|
||||
```typescript
|
||||
export interface WizardState {
|
||||
remote: { name: string; backendType: BackendType | null; params: Record<string, string> };
|
||||
deployment: {
|
||||
includeInstall: boolean;
|
||||
configPath: 'machine-wide' | 'user-profile';
|
||||
scriptTargets: ('intune' | 'rmm')[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Key difference from Intune install:
|
||||
- RMM scripts DO use $ErrorActionPreference = 'Stop' (RMM platforms capture exit codes; no STDERR-breaks-detection contract)
|
||||
- No exit 0 / exit 1 convention required (but including for clarity is fine)
|
||||
- No separate detection script — the RMM platform handles existence checks natively
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Implement buildRmmScript</name>
|
||||
<files>src/generators/rmm-script.ts</files>
|
||||
<behavior>
|
||||
- output contains '$ErrorActionPreference = ''Stop''' (RMM scripts use Stop — unlike detection)
|
||||
- machine-wide path: output contains 'C:\ProgramData\rclone'
|
||||
- user-profile path: output contains '%APPDATA%\rclone'
|
||||
- includeInstall=true: output contains 'downloads.rclone.org/rclone-current-windows-amd64.zip'
|
||||
- includeInstall=false: output does NOT contain 'downloads.rclone.org'
|
||||
- output contains '[System.IO.File]::WriteAllText' (UTF-8 no-BOM write)
|
||||
- output contains '@'' / ''@' or equivalent here-string (credentials not interpolated at PS runtime)
|
||||
- config directory creation is idempotent: 'if (-not (Test-Path $configDir))'
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/generators/rmm-script.ts importing from ps-helpers.ts. Structure mirrors buildIntuneInstall but with explicit comments that this is for generic RMM tools (NinjaRMM, Datto, ConnectWise).
|
||||
|
||||
```typescript
|
||||
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';
|
||||
}
|
||||
|
||||
function buildRcloneConfContent(state: WizardState): string {
|
||||
const RCLONE_TYPE_MAP: Record<string, string> = {
|
||||
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';
|
||||
}
|
||||
```
|
||||
|
||||
Note: The buildRcloneConfContent helper is duplicated from intune-install.ts intentionally — both files are self-contained. If this feels wrong, the alternative is to import buildRcloneConf from './rclone-conf', which is also fine (no circular dependency). Use whichever approach keeps the code clear. Either satisfies the tests.
|
||||
|
||||
Anti-patterns to avoid:
|
||||
- Do NOT use 'Write-Error' or 'Write-Host' for logging (leaks to STDERR / logs)
|
||||
- Do NOT interpolate credential values in the here-string using PowerShell variables
|
||||
- Do NOT use 'Set-Content -Encoding UTF8' (writes BOM in PS 5.1)
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm test -- src/generators/rmm-script.test.ts</automated>
|
||||
</verify>
|
||||
<done>
|
||||
All rmm-script.test.ts assertions pass GREEN. The RMM script is self-contained, idempotent, uses UTF-8 no-BOM write, and conditionally includes the rclone binary download block.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
npm test -- src/generators/rmm-script.test.ts — all GREEN.
|
||||
npm test (full suite) — all GREEN (Plans 01, 02, 03, 04 all passing).
|
||||
src/generators/index.ts re-exports buildRmmScript without TypeScript errors.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
DEPL-03: RMM script is self-contained, idempotent, runs correctly in a generic SYSTEM context.
|
||||
DEPL-04: includeInstall toggle adds/removes rclone download block in RMM script.
|
||||
DEPL-05: configPath correctly maps to C:\ProgramData\rclone or %APPDATA%\rclone in RMM script.
|
||||
Phase 2 is complete: all six requirements (CONF-01, DEPL-01 through DEPL-05) have green unit tests.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-generators/02-04-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user