From beff9f08860296e4167a3d7ea4135b615a214ea9 Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Tue, 6 Jan 2026 17:34:05 +0100 Subject: [PATCH] Added new deploy task to fix deployments duplicating the index.js file --- .../DEPLOY-000-cleanup-hashed-files.md | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-005-deployment-automation/DEPLOY-000-cleanup-hashed-files.md diff --git a/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-005-deployment-automation/DEPLOY-000-cleanup-hashed-files.md b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-005-deployment-automation/DEPLOY-000-cleanup-hashed-files.md new file mode 100644 index 0000000..2451a11 --- /dev/null +++ b/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-005-deployment-automation/DEPLOY-000-cleanup-hashed-files.md @@ -0,0 +1,235 @@ +# DEPLOY-000: Clean Up Old Hashed Files on Deploy + +## Overview + +Fix the deployment process to remove old hashed JavaScript files before creating new ones. Currently, each deployment generates a new `index-{hash}.js` file for cache-busting but never removes previous versions, causing the deployment folder to accumulate duplicate files and grow indefinitely. + +## Context + +### The Problem + +When deploying to a local folder, the build process generates content-hashed filenames for cache-busting: + +```typescript +// From deploy-index.ts +if (enableHash) { + const hash = createHash(); + hash.update(content, 'utf8'); + const hex = hash.digest('hex'); + filename = addSuffix(url, '-' + hex); // Creates index-abc123def.js +} +``` + +This is good practice—browsers fetch fresh code when the hash changes. However, **there's no cleanup step** to remove the previous hashed files. After 10 deployments, you have 10 `index-*.js` files. After 100 deployments, you have 100. + +### Impact + +- **Bloated project size**: Each index.js is ~500KB+ depending on project complexity +- **Confusing output folder**: Multiple index files make it unclear which is current +- **Upload waste**: Deploying to hosting platforms uploads unnecessary files +- **Git noise**: If committing builds, each deploy adds a new file + +### Existing Infrastructure + +**`deploy-index.ts`** - Handles file hashing and writing: +```typescript +// packages/noodl-editor/src/editor/src/utils/compilation/build/deploy-index.ts +export async function copyDeployFilesToFolder({ + project, direntry, files, exportJson, baseUrl, envVariables, runtimeType +}: CopyDeployFilesToFolderArgs) +``` + +**`cleanup.ts`** - Existing cleanup utility (only handles subfolders): +```typescript +// packages/noodl-editor/src/editor/src/utils/compilation/build/cleanup.ts +export async function clearFolders({ projectPath, outputPath, files }: ClearFoldersOptions) +``` + +**`deployer.ts`** - Main deployment orchestration: +```typescript +// packages/noodl-editor/src/editor/src/utils/compilation/build/deployer.ts +export async function deployToFolder({ project, direntry, environment, baseUrl, envVariables, runtimeType }) +``` + +## Requirements + +### Functional Requirements + +1. **Remove old hashed files** before writing new ones during deployment +2. **Pattern matching** for `index-{hash}.js` files (hash is hex string from xxhash64) +3. **Preserve non-hashed files** like `index.html`, static assets, `noodl_bundles/`, etc. +4. **Work for all deploy types**: local folder, and as foundation for cloud deploys + +### Non-Functional Requirements + +- No user-facing changes (silent fix) +- Minimal performance impact (single directory scan) +- Backward compatible with existing projects + +## Technical Approach + +### Option A: Extend `cleanup.ts` (Recommended) + +Add a new function to the existing cleanup module: + +```typescript +// packages/noodl-editor/src/editor/src/utils/compilation/build/cleanup.ts + +export interface CleanupHashedFilesOptions { + /** The output directory path */ + outputPath: string; + /** File patterns to clean (regex) */ + patterns?: RegExp[]; +} + +const DEFAULT_HASHED_PATTERNS = [ + /^index-[a-f0-9]+\.js$/, // index-abc123.js + /^index-[a-f0-9]+\.js\.map$/, // source maps if we add them later +]; + +export async function cleanupHashedFiles({ + outputPath, + patterns = DEFAULT_HASHED_PATTERNS +}: CleanupHashedFilesOptions): Promise { + const removedFiles: string[] = []; + + if (!filesystem.exists(outputPath)) { + return removedFiles; + } + + const files = await filesystem.listDirectory(outputPath); + + for (const file of files) { + // Skip directories + if (file.isDirectory) continue; + + // Check against patterns + const shouldRemove = patterns.some(pattern => pattern.test(file.name)); + + if (shouldRemove) { + const filePath = filesystem.join(outputPath, file.name); + await filesystem.remove(filePath); + removedFiles.push(file.name); + } + } + + return removedFiles; +} +``` + +### Option B: Inline in `copyDeployFilesToFolder` + +Add cleanup directly in `deploy-index.ts` before writing files: + +```typescript +export async function copyDeployFilesToFolder({ + project, direntry, files, exportJson, baseUrl, envVariables, runtimeType +}: CopyDeployFilesToFolderArgs) { + // NEW: Clean up old hashed files first + await cleanupOldHashedFiles(direntry); + + // ... existing logic +} + +async function cleanupOldHashedFiles(direntry: string) { + if (!filesystem.exists(direntry)) return; + + const files = await filesystem.listDirectory(direntry); + const hashedPattern = /^index-[a-f0-9]+\.js$/; + + for (const file of files) { + if (!file.isDirectory && hashedPattern.test(file.name)) { + await filesystem.remove(filesystem.join(direntry, file.name)); + } + } +} +``` + +### Recommendation + +**Option A** is preferred because: +- Follows existing pattern of `cleanup.ts` module +- More extensible for future hashed assets (CSS, source maps) +- Can be reused by cloud deploy providers +- Easier to test in isolation + +## Implementation Checklist + +### Phase 1: Core Fix + +- [ ] **Add `cleanupHashedFiles` function to `cleanup.ts`** + - Accept output path and optional patterns array + - Default patterns for `index-*.js` files + - Return list of removed files (for logging/debugging) + - Handle non-existent directories gracefully + +- [ ] **Integrate into deployment flow** + - Import in `deploy-index.ts` + - Call before `writeIndexFiles()` in `copyDeployFilesToFolder()` + - Only run when `enableHash` is true (matches current behavior) + +- [ ] **Add logging** (optional but helpful) + - Debug log removed files count + - Integrate with existing deployment progress indicators + +### Phase 2: Testing + +- [ ] **Manual testing** + - Deploy project to local folder + - Verify single `index-{hash}.js` exists + - Deploy again with code changes + - Verify old hash file removed, new one exists + - Deploy without changes, verify same hash retained + +- [ ] **Edge cases** + - Empty output directory (first deploy) + - Output directory doesn't exist yet + - Read-only files (if possible on target OS) + - Very long hash patterns (shouldn't occur with xxhash64) + +### Phase 3: Future Considerations (Out of Scope) + +- [ ] Clean up other hashed assets when added (CSS, fonts) +- [ ] Manifest file tracking deployed assets +- [ ] Atomic deploys (write to temp, swap folders) + +## Files to Modify + +| File | Changes | +|------|---------| +| `packages/noodl-editor/src/editor/src/utils/compilation/build/cleanup.ts` | Add `cleanupHashedFiles()` function | +| `packages/noodl-editor/src/editor/src/utils/compilation/build/deploy-index.ts` | Import and call cleanup before writing | + +## Estimated Effort + +| Task | Hours | +|------|-------| +| Implement `cleanupHashedFiles` | 0.5 | +| Integrate into deploy flow | 0.5 | +| Testing & edge cases | 1.0 | +| **Total** | **2 hours** | + +## Dependencies + +- None (uses existing `@noodl/platform` filesystem APIs) + +## Risks & Mitigations + +| Risk | Likelihood | Impact | Mitigation | +|------|------------|--------|------------| +| Accidentally delete non-hashed files | Low | High | Strict regex pattern, only match expected format | +| Performance on large folders | Very Low | Low | Single directory scan, typically <100 files | +| Break existing deploys | Very Low | Medium | Pattern only matches hash format, preserves all other files | + +## Success Criteria + +1. After multiple deployments to the same folder, only ONE `index-{hash}.js` file exists +2. The correct (latest) hash file is retained +3. All other deployment files remain intact +4. No user-visible changes to deployment flow + +## Related Tasks + +- **DEPLOY-001**: One-Click Deploy (will benefit from clean output) +- **DEPLOY-002**: Preview Deployments (needs clean folders per preview) +- **DEPLOY-003**: Deploy Settings (may add "clean build" toggle)