fedify icon indicating copy to clipboard operation
fedify copied to clipboard

Create @fedify/codemods package

Open dahlia opened this issue 4 months ago • 7 comments

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.

dahlia avatar Aug 21 '25 17:08 dahlia

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

ThisIsMissEm avatar Aug 21 '25 17:08 ThisIsMissEm

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.

ThisIsMissEm avatar Aug 21 '25 18:08 ThisIsMissEm

@dahlia I think it'd probably make sense to have the codemods in a separate repository, so fedify-dev/codemods?

ThisIsMissEm avatar Aug 21 '25 18:08 ThisIsMissEm

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 avatar Aug 21 '25 20:08 dahlia

@dahlia I'm not sure how that'll play with the codemod package

ThisIsMissEm avatar Aug 22 '25 00:08 ThisIsMissEm

@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 avatar Aug 22 '25 06:08 dahlia

@dahlia yeah, that was the impression I got.

ThisIsMissEm avatar Aug 22 '25 08:08 ThisIsMissEm