Skip to content

Migration Guide ​

This guide helps you upgrade between versions of Projex and migrate from earlier versions. Follow the instructions for the version you're upgrading from and to.

Version Policy ​

Projex follows Semantic Versioning:

  • Major (X.0.0) β€” Breaking changes, may require code changes
  • Minor (1.X.0) β€” New features, backward compatible
  • Patch (1.0.X) β€” Bug fixes, backward compatible

See the CHANGELOG for detailed release notes.


Table of Contents ​


Project Rename (Folio β†’ Projex) ​

Note: This section is only for users migrating from the old Folio package. If you're a new user, skip to Upgrading Projex.

If you were using this library when it was named Folio, follow these steps to migrate to Projex.

Package Installation ​

bash
# Uninstall old package
pnpm remove @reallukemanning/folio

# Install new package
pnpm add @manningworks/projex

Config File Rename ​

bash
mv folio.config.ts projex.config.ts

Import Updates ​

Update your imports:

typescript
// Old
import { defineProjects } from '@reallukemanning/folio'
import type { FolioProject, FolioProjectInput } from '@reallukemanning/folio'

// New
import { defineProjects } from '@manningworks/projex'
import type { ProjexProject, ProjexProjectInput } from '@manningworks/projex'

Type Updates ​

Update all type references in your code:

typescript
// Old
const project: FolioProject = ...
const input: FolioProjectInput = ...

// New
const project: ProjexProject = ...
const input: ProjexProjectInput = ...

CLI Command Updates ​

bash
# Old
npx folio init
npx folio add

# New
npx @manningworks/projex init
npx @manningworks/projex add

CSS Styling Updates ​

All data attributes have been renamed from data-folio-* to data-projex-*. Update your CSS selectors:

Global CSS Replace:

Find: data-folio-Replace with: data-projex-

Or use command-line tools:

bash
# Using sed (Linux/Mac)
find . -name "*.css" -o -name "*.scss" | xargs sed -i '' 's/data-folio-/data-projex-/g'

# Using PowerShell (Windows)
Get-ChildItem -Recurse -Include *.css,*.scss | ForEach-Object {
  (Get-Content $_) -replace 'data-folio-', 'data-projex-' | Set-Content $_
}

Common Attribute Changes:

Old AttributeNew Attribute
data-folio-carddata-projex-card
data-folio-card-headerdata-projex-card-header
data-folio-card-descriptiondata-projex-card-description
data-folio-card-tagsdata-projex-card-tags
data-folio-card-statsdata-projex-card-stats
data-folio-card-linksdata-projex-card-links
data-folio-featureddata-projex-featured
data-folio-griddata-projex-grid
data-folio-listdata-projex-list
data-folio-tagdata-projex-tag
data-folio-linkdata-projex-link
data-folio-statdata-projex-stat

CSS Custom Properties ​

All CSS custom properties (variables) have been renamed from --folio-* to --projex-*. Update your CSS:

Global CSS Replace:

Find: --folio-Replace with: --projex-

Or use command-line tools:

bash
# Using sed (Linux/Mac)
find . -name "*.css" -o -name "*.scss" | xargs sed -i '' 's/--folio-/--projex-/g'

# Using PowerShell (Windows)
Get-ChildItem -Recurse -Include *.css,*.scss | ForEach-Object {
  (Get-Content $_) -replace '--folio-', '--projex-' | Set-Content $_
}

Common Variable Changes:

Old VariableNew Variable
--folio-card-bg--projex-card-bg
--folio-card-border--projex-card-border
--folio-card-radius--projex-card-radius
--folio-card-padding--projex-card-padding
--folio-card-text--projex-card-text
--folio-tag-bg--projex-tag-bg
--folio-tag-text--projex-tag-text
--folio-tag-radius--projex-tag-radius
--folio-stats-label--projex-stats-label
--folio-stats-value--projex-stats-value
--folio-link-text--projex-link-text
--folio-status-active-bg--projex-status-active-bg
--folio-status-active-text--projex-status-active-text
--folio-status-shipped-bg--projex-status-shipped-bg
--folio-status-shipped-text--projex-status-shipped-text
--folio-status-in-progress-bg--projex-status-in-progress-bg
--folio-status-in-progress-text--projex-status-in-progress-text
--folio-status-coming-soon-bg--projex-status-coming-soon-bg
--folio-status-coming-soon-text--projex-status-coming-soon-text
--folio-status-archived-bg--projex-status-archived-bg
--folio-status-archived-text--projex-status-archived-text
--folio-status-for-sale-bg--projex-status-for-sale-bg
--folio-status-for-sale-text--projex-status-for-sale-text

CSS Class Names ​

The folio-link CSS class has been renamed to projex-link. If you have any custom styles targeting this class, update them:

Find: .folio-linkReplace with: .projex-link

URL Updates ​

Update any documentation or repository URLs:

Old URLNew URL
https://github.com/RealLukeManning/Foliohttps://github.com/ManningWorks/Projex
https://folio-guide.vercel.apphttps://projex.manningworks.dev
https://www.npmjs.com/package/@reallukemanning/foliohttps://www.npmjs.com/package/@manningworks/projex

fetchGitHubRepo / fetchNpmPackage Return Type Change ​

Impact: Breaking change for code that calls fetchGitHubRepo() or fetchNpmPackage() directly

What Changed ​

Both functions now return a structured result object instead of raw data or null.

Before:

tsx
const data = await fetchGitHubRepo('user/repo')
if (data) {
  console.log(data.stargazers_count)
}

After:

tsx
const { data, error } = await fetchGitHubRepo('user/repo')
if (data) {
  console.log(data.stargazers_count)
} else if (error) {
  console.error(error.message)
}

New Return Types ​

tsx
// fetchGitHubRepo returns FetchRepoResult
interface FetchRepoResult {
  data: GitHubRepoData | null
  error: FetchRepoError | null
}

interface FetchRepoError {
  type: FetchRepoErrorType
  message: string
}

type FetchRepoErrorType = 'not_found' | 'rate_limited' | 'network' | 'auth' | 'other'

// fetchNpmPackage returns FetchNpmResult
interface FetchNpmResult {
  data: NpmPackageData | null
  error: FetchNpmError | null
}

interface FetchNpmError {
  type: FetchNpmErrorType
  message: string
}

type FetchNpmErrorType = 'not_found' | 'network' | 'other'

Migration Steps ​

  1. Search for direct calls to fetchGitHubRepo and fetchNpmPackage
  2. Destructure the return value as { data, error }
  3. Update null checks from if (data) to if (data) (unchanged) and add error handling for error
  4. If you were relying on normalise() internally, no changes are needed β€” normalise handles the new return type automatically

Not Affected ​

  • Code using normalise() or defineProjects() is not affected
  • Code using fetchGitHubRepos() (plural) is not affected β€” it already used the { data, error } pattern

normalizeStats Renamed to normaliseStats ​

Impact: Non-breaking β€” normalizeStats is still available as a deprecated alias

What Changed ​

normalizeStats has been renamed to normaliseStats for naming consistency with the normalise function. The old name is still exported as a deprecated alias.

Migration Steps ​

Update imports in new code:

tsx
// Old (still works, deprecated)
import { normalizeStats } from '@manningworks/projex'

// New (preferred)
import { normaliseStats } from '@manningworks/projex'

No immediate action required β€” the deprecated alias will continue to work.


ProjectStats Changed to Tagged Union ​

Impact: Breaking change for code that accesses project.stats properties without checking the type

What Changed ​

ProjectStats changed from an intersection type to a tagged union with a type discriminator field.

Before:

tsx
type ProjectStats = GitHubStats & NpmStats & ProductHuntStats & ...

After:

tsx
type ProjectStats =
  | ({ type: 'github' } & GitHubStats)
  | ({ type: 'manual' } & Record<string, never>)
  | ({ type: 'npm' } & NpmStats)
  | ({ type: 'product-hunt' } & ProductHuntStats)
  | ({ type: 'youtube' } & YouTubeStats)
  | ({ type: 'gumroad' } & GumroadStats)
  | ({ type: 'lemonsqueezy' } & LemonSqueezyStats)
  | ({ type: 'devto' } & DevToStats)
  | ({ type: 'hybrid' } & GitHubStats & NpmStats)

Migration Steps ​

  1. If you access stats without type guards, add a type check:

    Before:

    tsx
    if (project.stats?.stars) { ... }

    After (still works for most cases, but TypeScript is stricter):

    tsx
    if (project.stats && project.stats.type === 'github') {
      console.log(project.stats.stars)
    }
  2. If you use normaliseStats, no changes needed β€” it accepts Record<string, unknown> and ignores the type tag.

  3. If you manually construct stats objects, add the type field:

    Before:

    tsx
    stats: { stars: 100, forks: 10 }

    After:

    tsx
    stats: { type: 'github', stars: 100, forks: 10 }

DefineProjectsOptions onError Option ​

Impact: Non-breaking β€” new optional option with a default value

What Changed ​

A new onError option has been added to DefineProjectsOptions to control how fetch errors are handled during normalise().

tsx
interface DefineProjectsOptions {
  commits?: number
  fetchNpmTimestamps?: boolean
  onError?: 'throw' | 'warn' | 'silent'
}
ValueBehavior
'warn' (default)Logs fetch error messages to console
'throw'Throws an error with all fetch error messages
'silent'Suppresses all fetch error output

No migration needed β€” existing behavior is preserved by default.


New SmartProjectGrid Component ​

Impact: Non-breaking β€” new component, no existing code affected

What's New ​

A new SmartProjectGrid client component provides search, filters, and sort out of the box:

tsx
import { SmartProjectGrid, ProjectCard } from '@manningworks/projex'

<SmartProjectGrid projects={projects} showSearch showFilters>
  {(project) => (
    <ProjectCard>
      <ProjectCard.Header />
      <ProjectCard.Description />
      <ProjectCard.Stats />
    </ProjectCard>
  )}
</SmartProjectGrid>

Context Provider ​

ProjectCard sub-components (.Header, .Description, .Tags, .Stats, .Status, .Links) now accept an optional project prop. When used inside a SmartProjectGrid, they can read the project from context instead:

tsx
import { ProjectGridProvider, useProjectContext } from '@manningworks/projex'

CLI Export Entry Point ​

Impact: Non-breaking β€” new export map entry

What Changed ​

The package.json exports map now includes a ./cli entry point:

json
{
  "exports": {
    ".": { ... },
    "./cli": {
      "types": "./dist/cli.d.ts",
      "import": "./dist/cli.js"
    }
  }
}

No migration needed β€” existing imports from @manningworks/projex are unaffected.


escapeString Utility ​

Impact: Non-breaking β€” new utility export

What's New ​

A new escapeString utility is exported for escaping strings in generated code:

tsx
import { escapeString } from '@manningworks/projex'

escapeString("It's a test\n")
// "It\\'s a test\\n"

fetchProjectData Utility ​

Impact: Non-breaking β€” new utility export

What's New ​

A new fetchProjectData function fetches all external API data for a project in parallel via Promise.all:

tsx
import { fetchProjectData } from '@manningworks/projex'

const result = await fetchProjectData(input, options)
// result.githubData, result.npmData, result.githubError, result.npmError, ...

See the normalise API reference for full details.


Strict Type-Specific Field Validation (1.2.1) ​

Version: 1.2.1+ Impact: Scripts using edit project with type-incompatible fields will now error instead of warning

What Changed ​

The edit project command previously allowed setting any type-specific field on any project type with a warning. For example, --channel-id on a hybrid project would warn but proceed. Now the command errors and exits:

βœ– 'channelId' is not a valid field for type 'hybrid'. Valid type-specific fields: repo, package

Migration Steps ​

If you have scripts that set type-specific fields regardless of project type:

  1. Check which project type you're editing before setting type-specific fields
  2. Only set fields valid for that project type (e.g., repo/package for hybrid, channelId for youtube)

Valid Type-Specific Fields ​

Project TypeValid Fields
githubrepo
npmpackage
hybridrepo, package
product-huntslug
youtubechannelId
gumroadproductId
lemonsqueezystoreId
devtousername
manual(none)

ProjectStruggle Type Changes ​

Version: Latest Impact: Breaking change for users with struggles in their project configurations

The ProjectStruggle type has been updated from log-level terminology to semantic content categories that better represent project narratives.

What Changed ​

Old types (log-level terminology):

  • 'warn' β€” Warnings or cautionary notes
  • 'error' β€” Errors or critical issues

New types (semantic content categories):

  • 'challenge' β€” Obstacles or struggles overcome during the project
  • 'learning' β€” Insights and growth from the project

Why This Change ​

The old types ('warn' | 'error') were borrowed from logging systems and didn't make semantic sense for project showcases. A project portfolio isn't a log fileβ€”it's a narrative about your work. The new types ('challenge' | 'learning') represent the actual content you want to showcase: the obstacles you overcame and the lessons you learned.

Migration Steps ​

  1. Search for all struggle definitions in your config:

    bash
    grep -r "struggles:" projex.config.ts
  2. Update struggle type values:

    Before:

    typescript
    {
      id: 'my-project',
      type: 'github',
      repo: 'user/repo',
      status: 'shipped',
      struggles: [
        { type: 'warn', text: 'API rate limits were tight' },
        { type: 'error', text: 'Initial architecture was flawed' },
      ]
    }

    After:

    typescript
    {
      id: 'my-project',
      type: 'github',
      repo: 'user/repo',
      status: 'shipped',
      struggles: [
        { type: 'challenge', text: 'API rate limits were tight' },
        { type: 'learning', text: 'Initial architecture was flawed' },
      ]
    }
  3. Review your content:

    The new types are semantically different, so consider whether each struggle is better described as:

    • A challenge you overcame (something difficult that required effort/solution)
    • A learning you gained (insight, growth, or surprising discovery)

    Some 'error' entries may be better as 'learning' since they represent something you learned from, not just a technical error.

  4. Update your CSS (if styling struggles):

    Before:

    css
    [data-projex-struggle-type="warn"] {
      background: #fef3c7;
    }
    [data-projex-struggle-type="error"] {
      background: #fee2e2;
    }

    After:

    css
    [data-projex-struggle-type="challenge"] {
      background: #fef3c7;
    }
    [data-projex-struggle-type="learning"] {
      background: #dbeafe;
    }

Type Mapping ​

Old TypeNew TypeWhen to Use
'warn''challenge'Obstacles, blockers, difficulties faced
'error''challenge' OR 'learning'Use 'challenge' if it was an obstacle to overcome, or 'learning' if it represents insight gained

Example Migration ​

Before:

typescript
struggles: [
  { type: 'warn', text: 'Limited API calls required smart caching' },
  { type: 'error', text: 'First database design had performance issues' },
  { type: 'warn', text: 'Browser compatibility took extra time' },
]

After:

typescript
struggles: [
  { type: 'challenge', text: 'Limited API calls required smart caching' },
  { type: 'learning', text: 'First database design had performance issuesβ€”learned the importance of indexing' },
  { type: 'challenge', text: 'Browser compatibility took extra time' },
]

Note how the second entry was converted to 'learning' with a more descriptive text that emphasizes what was learned.

See Also ​


Upgrading Projex ​

Update the Package ​

bash
# Check current version
npm list @manningworks/projex

# Update to latest
pnpm update @manningworks/projex

Update CLI Components ​

If you copied components using the CLI, re-add them after updating to get the latest versions:

bash
npx @manningworks/projex add project-card --force
npx @manningworks/projex add project-view --force

The --force flag overwrites existing files with the latest versions.


Common Upgrade Issues ​

Import Errors After Folio β†’ Projex Migration ​

Problem: Cannot find module '@reallukemanning/folio'

Solution: Update all import statements:

typescript
import { defineProjects } from '@manningworks/projex'

Problem: Type 'FolioProject' not found

Solution: Update type names:

typescript
import type { ProjexProject, ProjexProjectInput } from '@manningworks/projex'

Config File Not Found After Migration ​

Problem: Could not find folio.config.ts

Solution: Rename your config file:

bash
mv folio.config.ts projex.config.ts

Build Errors After Folio Migration ​

Problem: Module not found: Error: Can't resolve '@reallukemanning/folio'

Solution:

  1. Clear cache and reinstall:
bash
rm -rf node_modules .next
pnpm install
  1. Verify all imports are updated to @manningworks/projex

  2. Rebuild:

bash
pnpm build

Styles Not Applying After Migration ​

Problem: Styles not applying after Folio β†’ Projex migration

Cause: CSS selectors and/or custom properties still reference old Folio names.

Solution: Run global find and replace in CSS files for both data attributes and CSS custom properties:

bash
# Using sed (Linux/Mac)
find . -name "*.css" -o -name "*.scss" | xargs sed -i '' 's/data-folio-/data-projex-/g'
find . -name "*.css" -o -name "*.scss" | xargs sed -i '' 's/--folio-/--projex-/g'
find . -name "*.css" -o -name "*.scss" | xargs sed -i '' 's/.folio-link/.projex-link/g'

TypeScript Errors After Upgrade ​

Problem: TypeScript errors about missing exports or type mismatches.

Solution:

  1. Clear TypeScript cache:

    bash
    rm -rf .next node_modules/.cache
  2. Restart your TypeScript server (VS Code: Cmd/Ctrl + Shift + P β†’ "TypeScript: Restart TS Server")

  3. If using CLI-copied components, re-add them:

    bash
    npx @manningworks/projex add project-card --force

Missing Features After Upgrade ​

Problem: New features from the changelog aren't available.

Solution:

  1. Ensure you've updated to the latest version:

    bash
    pnpm update @manningworks/projex
  2. Verify the version:

    bash
    npm list @manningworks/projex
  3. If using CLI-copied components, re-add them to get latest code:

    bash
    npx @manningworks/projex add <component-name> --force

Build Fails After Upgrade ​

Problem: Build fails after upgrading with errors about missing modules or types.

Solution:

  1. Clean install dependencies:

    bash
    rm -rf node_modules pnpm-lock.yaml
    pnpm install
  2. Clear Next.js cache:

    bash
    rm -rf .next
  3. Rebuild:

    bash
    pnpm build

CLI Commands Not Working ​

Problem: npx @manningworks/projex commands fail after upgrade.

Solution:

  1. Clear npx cache:

    bash
    npx clear-npx-cache
    # Or manually:
    rm -rf ~/.npm/_npx
  2. Try the full package name:

    bash
    npx @manningworks/projex init
  3. If that fails, install globally:

    bash
    npm install -g @manningworks/projex
    projex init

Need Help? ​

If you encounter issues not covered here:

  1. Check the Troubleshooting Guide
  2. Review the CHANGELOG
  3. Search GitHub Issues
  4. Open a new issue if the problem isn't documented

Staying Updated ​

To stay informed about new releases:

Enable Dependabot or Renovate to automatically receive pull requests for updates.