Create @fedify/codemods package
Summary
Following the discussion in #375, we should create a dedicated @fedify/codemods package to provide automated code transformations for Fedify projects. This would initially support migrating from @fedify/fedify/x/* modules to separate packages, and establish infrastructure for future breaking changes.
In #375, @ThisIsMissEm suggested offering a codemod to handle the @fedify/fedify/x/* modules migration instead of just making it a breaking change. This is a great idea since the migration is essentially just changing import paths, which is perfect for automation. As @ThisIsMissEm also mentioned, we could publish a @fedify/codemods package and keep codemods available for several releases.
The broader benefit is that having codemod infrastructure in place makes future breaking changes much more palatable for users. We can deprecate APIs, ship the codemod during the deprecation period, and then safely remove the old APIs knowing users have an automated migration path.
Implementation
Build a jscodeshift-based package that can transform import statements from the old x-module paths to new package names:
// Before
import { federation } from '@fedify/fedify/x/sveltekit';
const hono = await import('@fedify/fedify/x/hono');
export * from '@fedify/fedify/x/deno';
// After
import { federation } from '@fedify/sveltekit';
const hono = await import('@fedify/hono');
export * from '@fedify/deno';
The CLI would be something like:
npx @fedify/codemods migrate-x-modules
npx @fedify/codemods migrate-x-modules --dry-run
jscodeshift is the obvious choice here since it's the industry standard for JavaScript codemods. We'd need to handle static imports, dynamic imports, and re-exports. The parser should support both TypeScript and JSX since Fedify is used across different project types.
Beyond just transforming imports, it could also update package.json dependencies automatically and provide rollback functionality. The package should be thoroughly tested to ensure it doesn't break existing code.
This would need to be ready before we ship the deprecation warnings for the @fedify/fedify/x/* modules, so it should be prioritized alongside the work in #375. Having the codemod available when we announce the deprecation makes the whole transition much smoother for users.
Users get a one-command migration instead of manually updating dozens of import statements, and we establish a pattern for future API changes that builds trust with the community. The React ecosystem has shown how valuable codemods are for major migrations, and this would put Fedify in the same category of well-maintained projects that care about developer experience.
We'd use https://github.com/codemod-com/codemod to set it up. Which appears to be the "paved" path for doing codemods, as it's what's done for react, next.js and node.js
Apparently it'd be:
# main-rule.yml
# Simple rule to find @fedify/fedify/x/ imports (for search purposes)
id: find-fedify-imports
language: tsx
rule:
kind: string
regex: '@fedify/fedify/x/\w+'
inside:
any:
- kind: import_statement
- kind: export_statement
// main-rule.ts
import type { SgRoot } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
async function transform(root: SgRoot<TSX>): Promise<string> {
const rootNode = root.root();
// Find all import statements and export statements with @fedify/fedify/x/ pattern
const nodes = rootNode.findAll({
rule: {
any: [
// Static default imports
{ pattern: "import $SPECIFIER from '@fedify/fedify/x/$PACKAGE'" },
// Named imports
{ pattern: "import { $IMPORTS } from '@fedify/fedify/x/$PACKAGE'" },
// Namespace imports
{ pattern: "import * as $NAMESPACE from '@fedify/fedify/x/$PACKAGE'" },
// Export from statements
{ pattern: "export * from '@fedify/fedify/x/$PACKAGE'" },
{ pattern: "export { $EXPORTS } from '@fedify/fedify/x/$PACKAGE'" },
// Dynamic import calls
{ pattern: "import('@fedify/fedify/x/$PACKAGE')" }
]
},
});
const edits = nodes.map(node => {
// Get the current text and replace the path
const currentText = node.text();
const newText = currentText.replace(/@fedify\/fedify\/x\//g, '@fedify/');
return node.replace(newText);
});
const newSource = rootNode.commitEdits(edits);
return newSource;
}
export default transform;
At least that's what their AI thing generated, I'm not 100% certain it's correct though.
@dahlia I think it'd probably make sense to have the codemods in a separate repository, so fedify-dev/codemods?
Would it? I thought it should be inside the monorepo so that when we deprecate an API we could add a codemod in a pull request… Doesn't it make sense?
@dahlia I'm not sure how that'll play with the codemod package
@ThisIsMissEm After looking into codemod.com's platform requirements, I see why you raised the integration concerns. Their architecture assumes dedicated repositories, right?
So… a separate fedify-dev/codemods repo seems the way to go. Thanks for the heads-up on it!
@dahlia yeah, that was the impression I got.