Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bf7a7d0
feat(devportal-docs): scaffold package with shared utils and types
larkiny Feb 25, 2026
166f923
feat(devportal-docs): add CLI commands — normalize-links, build-sideb…
larkiny Feb 25, 2026
549eda0
feat(devportal-docs): add init command, CLI entry point, and theme as…
larkiny Feb 25, 2026
73a00fd
feat(devportal-docs): add vitest config and fix build
larkiny Feb 25, 2026
2ae07b3
refactor: remove legacy library-templates in favor of devportal-docs …
larkiny Feb 25, 2026
4a93561
docs(devportal-docs): add package README with onboarding guide
larkiny Feb 25, 2026
b4d9237
feat(devportal-docs): init command auto-adds theme CSS to astro.confi…
larkiny Feb 25, 2026
1c74c5f
feat(devportal-docs): init checks for Tailwind CSS v4, add publishConfig
larkiny Feb 25, 2026
979043f
Removed obsolete file; updated theme.css
larkiny Feb 25, 2026
c65a912
fix(devportal-docs): audit fixes — robustness, dedup, integration tests
larkiny Feb 26, 2026
a6bcc2b
fix(devportal-docs): address PR review findings
larkiny Feb 26, 2026
6a42822
fix: resolve lint errors for devportal-docs package
larkiny Feb 26, 2026
f8ada28
Cleaning up readme and obsolete files
larkiny Feb 26, 2026
9dc736a
chore: remove obsolete clean-docs-import script
larkiny Feb 26, 2026
2433bc3
Updated readme
larkiny Feb 26, 2026
11f4d17
docs: add design for macOS case-insensitive filename fix
larkiny Feb 28, 2026
e882668
docs: add implementation plan for macOS case-rename fix
larkiny Feb 28, 2026
7aad82e
fix: use two-step rename + git mv for case-insensitive FS in normaliz…
larkiny Feb 28, 2026
7150005
fix: reconcile git index case mismatches after tarball extraction
larkiny Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

The official Algorand Developer Portal - a comprehensive documentation site for Algorand blockchain developers.

**Live site: [dev.algorand.co](https://dev.algorand.co)**

## Table of Contents

- [Prerequisites](#prerequisites)
Expand Down Expand Up @@ -78,7 +80,6 @@ Before you begin, ensure you have the following installed:
│ └── transforms/ # Content transformation utilities
├── public/ # Static assets (favicons, etc.)
├── scripts/ # Build and utility scripts
│ ├── clean-docs-import.ts # Clear imported documentation
│ ├── generate-opcode-list.js # Generate Algorand opcodes list
│ ├── manage-sidebar-meta.ts # Sidebar metadata generator
│ └── prose-check.ts # AI-powered prose quality checker
Expand Down Expand Up @@ -168,7 +169,6 @@ Documentation is imported from external GitHub repositories using `@larkiny/astr
| `pnpm run import:docs` | Import all content from GitHub, regenerate sidebar, and fix linting |
| `pnpm run import:force` | Force re-import all content, ignoring cache |
| `pnpm run import:dry-run` | Preview GitHub content imports without making changes |
| `pnpm run import:clear` | Remove all imported documentation content |

### Auto-Sidebar Management

Expand Down
72 changes: 72 additions & 0 deletions docs/plans/2026-02-28-macos-case-rename-fix-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# macOS Case-Insensitive Filename Fix

## Problem

On macOS (case-insensitive APFS), two scripts fail to produce git-visible case
changes when renaming PascalCase filenames to lowercase:

1. **`packages/devportal-docs/src/commands/normalize-links.ts`** —
`lowercaseContentPaths()` uses `renameSync()` which changes the on-disk name
(APFS is case-preserving) but git's `core.ignorecase=true` prevents the index
from updating. Files stay PascalCase in git.

2. **`scripts/import-release-docs.ts`** — after `rmSync` + `cp -R` with
correctly-lowercased tarball content, git's index still tracks the old
PascalCase entries. `git add` doesn't detect the case-only change.

On Linux (CI), both scripts work correctly because the filesystem is
case-sensitive.

## Approach

Git-aware renames in each script. Each fix is self-contained — no shared imports
between the two scripts.

## Design

### Fix 1: `normalize-links.ts`

Add a local `caseRename` helper in
`packages/devportal-docs/src/commands/normalize-links.ts`.

Replace the current `renameSync(old, new)` call in `lowercaseContentPaths` with
`caseRename(dir, name, target)`.

**`caseRename(dir, oldName, newName)` behavior:**

1. Two-step filesystem rename: `oldName` -> `oldName.__tmp__` -> `newName`
(safe on case-insensitive filesystems where a direct rename is a no-op)
2. Best-effort `execFileSync('git', ['mv', '-f', old, new])` to update the git
index (uses execFileSync, not execSync, to avoid shell injection)
3. If git is unavailable or the file is untracked, fall back silently
(the filesystem rename already succeeded)

### Fix 2: `import-release-docs.ts`

Add a local `fixGitCaseMismatches(dir)` function in
`scripts/import-release-docs.ts`. Called after content extraction (after the
`cp -R` step, before link normalization).

**`fixGitCaseMismatches(dir)` behavior:**

1. Run `git ls-files <dir>` (via execFileSync) to get tracked filenames with
their index case
2. Walk the filesystem to get actual filenames
3. For case-only mismatches, run `git mv -f <old-case> <new-case>`
4. Best-effort — if git unavailable, skip silently (Linux CI doesn't need this)

### Tests

- Add a test for the two-step rename in `normalize-links.test.ts` verifying the
temp-name approach
- Existing `lowercaseContentPaths` tests use `/tmp` dirs and continue passing
- No test changes for `import-release-docs.ts` (no existing test suite)

## Files Modified

- `packages/devportal-docs/src/commands/normalize-links.ts` — add `caseRename`
helper, update `lowercaseContentPaths` to use it
- `scripts/import-release-docs.ts` — add `fixGitCaseMismatches`, call it after
extraction
- `packages/devportal-docs/tests/commands/normalize-links.test.ts` — add test
for two-step rename
234 changes: 234 additions & 0 deletions docs/plans/2026-02-28-macos-case-rename-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# macOS Case-Insensitive Filename Fix — Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Fix PascalCase-to-lowercase renames so git tracks them correctly on macOS (case-insensitive APFS with `core.ignorecase=true`).

**Architecture:** Two self-contained fixes — one in `normalize-links.ts` (two-step rename + best-effort `git mv`), one in `import-release-docs.ts` (post-extract git index reconciliation). No shared imports between them. All new git interactions use `execFileSync` (not shell-based exec) to avoid injection risks.

**Tech Stack:** Node.js `fs`, `child_process.execFileSync`, git CLI, vitest

---

### Task 1: Add `caseRename` helper and test to `normalize-links.ts`

**Files:**
- Modify: `packages/devportal-docs/src/commands/normalize-links.ts:1,35-66`
- Modify: `packages/devportal-docs/tests/commands/normalize-links.test.ts:30-60`

**Step 1: Write the failing test**

In `packages/devportal-docs/tests/commands/normalize-links.test.ts`, add a new
test inside the existing `describe('lowercaseContentPaths', ...)` block, after
the last test (line 59):

```typescript
it('uses two-step rename for case-only changes', () => {
const dir = makeTmpDir();
writeFileSync(join(dir, 'MyFile.md'), '# Content');
lowercaseContentPaths(dir);
// On any filesystem (case-sensitive or not), the file should be lowercase
const files = readdirSync(dir);
expect(files).toContain('myfile.md');
expect(files).not.toContain('MyFile.md');
// Content is preserved
expect(readFileSync(join(dir, 'myfile.md'), 'utf-8')).toBe('# Content');
});
```

**Step 2: Run test to verify it passes (existing behavior already handles this in /tmp)**

Run: `cd packages/devportal-docs && pnpm test -- --reporter=verbose 2>&1 | head -60`
Expected: PASS (the existing `renameSync` works in /tmp on macOS because it's
case-preserving — the test validates the contract, not the git fix specifically)

**Step 3: Add `caseRename` helper and update `lowercaseContentPaths`**

In `packages/devportal-docs/src/commands/normalize-links.ts`:

a) Add `execFileSync` to the imports (line 1 area):

```typescript
import { execFileSync } from 'node:child_process';
```

b) Add `caseRename` function after the `targetName` function (after line 33),
before `lowercaseContentPaths`:

```typescript
function caseRename(dir: string, oldName: string, newName: string): void {
const oldPath = join(dir, oldName);
const newPath = join(dir, newName);

// Two-step rename via temp name — safe on case-insensitive filesystems
// where renameSync('FooBar.md', 'foobar.md') may not update git's index.
const tmpPath = join(dir, `${oldName}.__tmp__`);
renameSync(oldPath, tmpPath);
renameSync(tmpPath, newPath);

// Best-effort: update git index so the case change is tracked.
// Silently ignored if git is unavailable or the file is untracked.
try {
execFileSync('git', ['mv', '-f', oldPath, newPath], { stdio: 'pipe' });
} catch {
// Not in a git repo, or file not tracked — filesystem rename is enough.
}
}
```

c) In `lowercaseContentPaths`, replace the `renameSync` call (line 60):

Change:
```typescript
renameSync(join(dir, name), join(dir, target));
```

To:
```typescript
caseRename(dir, name, target);
```

**Step 4: Run tests to verify everything passes**

Run: `cd packages/devportal-docs && pnpm test -- --reporter=verbose 2>&1 | head -60`
Expected: All tests PASS (including the new one)

**Step 5: Commit**

```bash
git add packages/devportal-docs/src/commands/normalize-links.ts packages/devportal-docs/tests/commands/normalize-links.test.ts
git commit -m "fix: use two-step rename + git mv for case-insensitive FS in normalize-links

On macOS (core.ignorecase=true), renameSync('FooBar.md', 'foobar.md')
doesn't update git's index. Use a two-step rename via temp name and
best-effort git mv -f to ensure git tracks the case change."
```

---

### Task 2: Add `fixGitCaseMismatches` to `import-release-docs.ts`

**Files:**
- Modify: `scripts/import-release-docs.ts:21,104-116,325-330`

**Step 1: Add `execFileSync` import**

In `scripts/import-release-docs.ts`, update the `child_process` import (line 21):

Change:
```typescript
import { execSync } from 'child_process';
```

To:
```typescript
import { execFileSync, execSync } from 'child_process';
```

Note: The existing `execSync` calls for `tar` and `cp` use hardcoded commands
with quoted paths — they are retained as-is. New git interactions use
`execFileSync` with array arguments to avoid shell injection.

**Step 2: Add `fixGitCaseMismatches` function**

Add after the `walkFiles` function (after line 116), before
`applyPostImportTransforms`:

```typescript
/**
* Fix case-only mismatches between git's index and the filesystem.
*
* On macOS (core.ignorecase=true), after rmSync + cp -R with correctly-cased
* content from a tarball, git's index may still track the old PascalCase
* names. This function detects mismatches and uses git mv -f to reconcile.
*/
function fixGitCaseMismatches(dir: string): void {
let trackedFiles: string[];
try {
const output = execFileSync('git', ['ls-files', dir], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
trackedFiles = output.trim().split('\n').filter(Boolean);
} catch {
// Not in a git repo — nothing to fix.
return;
}

if (trackedFiles.length === 0) return;

// Build a map from lowercase path to actual filesystem path
const fsFiles = walkFiles(dir);
const fsMap = new Map<string, string>();
for (const f of fsFiles) {
fsMap.set(f.toLowerCase(), f);
}

let fixCount = 0;
for (const tracked of trackedFiles) {
const fsPath = fsMap.get(tracked.toLowerCase());
if (!fsPath || fsPath === tracked) continue;

// Case mismatch: git tracks 'FooBar.md' but filesystem has 'foobar.md'
try {
execFileSync('git', ['mv', '-f', tracked, fsPath], { stdio: 'pipe' });
fixCount++;
} catch {
// File may have been deleted or is otherwise not fixable — skip.
}
}

if (fixCount > 0) {
console.log(` Fixed ${fixCount} case mismatch(es) in git index`);
}
}
```

**Step 3: Call `fixGitCaseMismatches` after content extraction**

In `downloadAndUnpack`, after step 6 (the `cp -R` block, around line 330) and
before step 7 (sidebar.json copy), add:

```typescript
// 6b. Fix case mismatches between git index and extracted content.
// On macOS, git may still track PascalCase names from a prior import.
fixGitCaseMismatches(destDir);
```

**Step 4: Verify the script still works**

Run: `pnpm run import:release:dry-run 2>&1 | head -20`
Expected: Dry run output listing configured variants (no errors)

**Step 5: Commit**

```bash
git add scripts/import-release-docs.ts
git commit -m "fix: reconcile git index case mismatches after tarball extraction

On macOS (core.ignorecase=true), after rmSync + cp -R with lowercase
content from a tarball, git's index may still track old PascalCase names.
After extraction, compare git ls-files against the filesystem and use
git mv -f to fix any case-only mismatches."
```

---

### Task 3: Run full test suite and verify

**Files:** None (verification only)

**Step 1: Run devportal-docs tests**

Run: `cd packages/devportal-docs && pnpm test -- --reporter=verbose`
Expected: All tests PASS

**Step 2: Run lint**

Run: `pnpm run lint 2>&1 | tail -20`
Expected: No new errors

**Step 3: Run dry-run import to verify no regressions**

Run: `pnpm run import:release:dry-run 2>&1 | head -20`
Expected: Lists configured variants without errors
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default [
'src/global.d.ts',
'imports/*',
'scripts/*',
'packages/*',
],
},
eslintConfigPrettier,
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"import:docs": "pnpm run import:release && IMPORT_GITHUB=true astro sync && pnpm run sidebar:generate && pnpm run lint:fix",
"import:force": "FORCE_IMPORT=true pnpm run import:docs",
"import:dry-run": "IMPORT_GITHUB=true IMPORT_DRY_RUN=true astro sync",
"import:clear": "tsx scripts/clean-docs-import.ts",
"import:release": "npx tsx scripts/import-release-docs.ts",
"import:release:dry-run": "npx tsx scripts/import-release-docs.ts --dry-run",
"_section:sidebar": "====================== Sidebar Scripts ======================",
Expand Down
3 changes: 3 additions & 0 deletions packages/devportal-docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
node_modules/
*.tgz
Loading
Loading