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 β
- fetchGitHubRepo / fetchNpmPackage Return Type Change
- normalizeStats Renamed to normaliseStats
- ProjectStats Changed to Tagged Union
- DefineProjectsOptions onError Option
- New SmartProjectGrid Component
- CLI Export Entry Point
- escapeString Utility
- fetchProjectData Utility
- Strict Type-Specific Field Validation (1.2.1)
- ProjectStruggle Type Changes
- Project Rename (Folio β Projex)
- Upgrading Projex
- Common Upgrade Issues
- Need Help?
- Staying Updated
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 β
# Uninstall old package
pnpm remove @reallukemanning/folio
# Install new package
pnpm add @manningworks/projexConfig File Rename β
mv folio.config.ts projex.config.tsImport Updates β
Update your imports:
// 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:
// Old
const project: FolioProject = ...
const input: FolioProjectInput = ...
// New
const project: ProjexProject = ...
const input: ProjexProjectInput = ...CLI Command Updates β
# Old
npx folio init
npx folio add
# New
npx @manningworks/projex init
npx @manningworks/projex addCSS 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:
# 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 Attribute | New Attribute |
|---|---|
data-folio-card | data-projex-card |
data-folio-card-header | data-projex-card-header |
data-folio-card-description | data-projex-card-description |
data-folio-card-tags | data-projex-card-tags |
data-folio-card-stats | data-projex-card-stats |
data-folio-card-links | data-projex-card-links |
data-folio-featured | data-projex-featured |
data-folio-grid | data-projex-grid |
data-folio-list | data-projex-list |
data-folio-tag | data-projex-tag |
data-folio-link | data-projex-link |
data-folio-stat | data-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:
# 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 Variable | New 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 URL | New URL |
|---|---|
https://github.com/RealLukeManning/Folio | https://github.com/ManningWorks/Projex |
https://folio-guide.vercel.app | https://projex.manningworks.dev |
https://www.npmjs.com/package/@reallukemanning/folio | https://www.npmjs.com/package/@manningworks/projex |
fetchGitHubRepo / fetchNpmPackage Return Type Change β
Impact: Breaking change for code that calls
fetchGitHubRepo()orfetchNpmPackage()directly
What Changed β
Both functions now return a structured result object instead of raw data or null.
Before:
const data = await fetchGitHubRepo('user/repo')
if (data) {
console.log(data.stargazers_count)
}After:
const { data, error } = await fetchGitHubRepo('user/repo')
if (data) {
console.log(data.stargazers_count)
} else if (error) {
console.error(error.message)
}New Return Types β
// 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 β
- Search for direct calls to
fetchGitHubRepoandfetchNpmPackage - Destructure the return value as
{ data, error } - Update null checks from
if (data)toif (data)(unchanged) and add error handling forerror - If you were relying on
normalise()internally, no changes are needed βnormalisehandles the new return type automatically
Not Affected β
- Code using
normalise()ordefineProjects()is not affected - Code using
fetchGitHubRepos()(plural) is not affected β it already used the{ data, error }pattern
normalizeStats Renamed to normaliseStats β
Impact: Non-breaking β
normalizeStatsis 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:
// 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.statsproperties without checking the type
What Changed β
ProjectStats changed from an intersection type to a tagged union with a type discriminator field.
Before:
type ProjectStats = GitHubStats & NpmStats & ProductHuntStats & ...After:
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 β
If you access stats without type guards, add a
typecheck:Before:
tsxif (project.stats?.stars) { ... }After (still works for most cases, but TypeScript is stricter):
tsxif (project.stats && project.stats.type === 'github') { console.log(project.stats.stars) }If you use
normaliseStats, no changes needed β it acceptsRecord<string, unknown>and ignores the type tag.If you manually construct stats objects, add the
typefield:Before:
tsxstats: { stars: 100, forks: 10 }After:
tsxstats: { 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().
interface DefineProjectsOptions {
commits?: number
fetchNpmTimestamps?: boolean
onError?: 'throw' | 'warn' | 'silent'
}| Value | Behavior |
|---|---|
'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:
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:
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:
{
"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:
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:
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 projectwith 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, packageMigration Steps β
If you have scripts that set type-specific fields regardless of project type:
- Check which project type you're editing before setting type-specific fields
- Only set fields valid for that project type (e.g.,
repo/packageforhybrid,channelIdforyoutube)
Valid Type-Specific Fields β
| Project Type | Valid Fields |
|---|---|
github | repo |
npm | package |
hybrid | repo, package |
product-hunt | slug |
youtube | channelId |
gumroad | productId |
lemonsqueezy | storeId |
devto | username |
manual | (none) |
ProjectStruggle Type Changes β
Version: Latest Impact: Breaking change for users with
strugglesin 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 β
Search for all struggle definitions in your config:
bashgrep -r "struggles:" projex.config.tsUpdate 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' }, ] }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.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 Type | New Type | When 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:
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:
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 β
- ProjectStruggle API Reference β Complete type documentation with examples
- Project Types Guide β Using struggles in project configurations
- Styling Guide β CSS patterns for struggles
Upgrading Projex β
Update the Package β
# Check current version
npm list @manningworks/projex
# Update to latest
pnpm update @manningworks/projexUpdate CLI Components β
If you copied components using the CLI, re-add them after updating to get the latest versions:
npx @manningworks/projex add project-card --force
npx @manningworks/projex add project-view --forceThe --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:
import { defineProjects } from '@manningworks/projex'Problem: Type 'FolioProject' not found
Solution: Update type names:
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:
mv folio.config.ts projex.config.tsBuild Errors After Folio Migration β
Problem: Module not found: Error: Can't resolve '@reallukemanning/folio'
Solution:
- Clear cache and reinstall:
rm -rf node_modules .next
pnpm installVerify all imports are updated to
@manningworks/projexRebuild:
pnpm buildStyles 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:
# 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:
Clear TypeScript cache:
bashrm -rf .next node_modules/.cacheRestart your TypeScript server (VS Code: Cmd/Ctrl + Shift + P β "TypeScript: Restart TS Server")
If using CLI-copied components, re-add them:
bashnpx @manningworks/projex add project-card --force
Missing Features After Upgrade β
Problem: New features from the changelog aren't available.
Solution:
Ensure you've updated to the latest version:
bashpnpm update @manningworks/projexVerify the version:
bashnpm list @manningworks/projexIf using CLI-copied components, re-add them to get latest code:
bashnpx @manningworks/projex add <component-name> --force
Build Fails After Upgrade β
Problem: Build fails after upgrading with errors about missing modules or types.
Solution:
Clean install dependencies:
bashrm -rf node_modules pnpm-lock.yaml pnpm installClear Next.js cache:
bashrm -rf .nextRebuild:
bashpnpm build
CLI Commands Not Working β
Problem: npx @manningworks/projex commands fail after upgrade.
Solution:
Clear npx cache:
bashnpx clear-npx-cache # Or manually: rm -rf ~/.npm/_npxTry the full package name:
bashnpx @manningworks/projex initIf that fails, install globally:
bashnpm install -g @manningworks/projex projex init
Need Help? β
If you encounter issues not covered here:
- Check the Troubleshooting Guide
- Review the CHANGELOG
- Search GitHub Issues
- Open a new issue if the problem isn't documented
Staying Updated β
To stay informed about new releases:
- Watch the GitHub Repository
- Follow GitHub Releases
- Subscribe to npm releases
Enable Dependabot or Renovate to automatically receive pull requests for updates.