Codemods

Ship codemods that `astryx upgrade` runs to migrate app code, and know which of them an app runs.

Add a codemod

integration add codemod writes a codemod into a folder named after a version. Apps run it with astryx upgrade to migrate their code.

bash
npx astryx integration add codemod rename-delay --to 0.7.0
text
codemods/
0.7.0/
rename-delay.mjs # the codemod
rename-delay.test.mjs # skipped: a test file
__tests__/ # skipped: a test folder

The first add declares codemods: './codemods' in astryx.integration.mjs. The codemod's id is its path inside the version folder, without the extension: rename-delay. An id must be unique across all version folders in the package.

The loader skips *.test.*, *.spec.*, and *.fixture.* files and everything under __tests__/ or __fixtures__/, so tests can sit beside the codemod. The folder name decides when an app runs the codemod; see "Choose when a codemod runs".

Write the transform

A codemod default-exports a plain object with a type, a title, and a transform function. transform returns the new source, or null to leave the file as it is.

js
// codemods/0.7.0/rename-delay.mjs
/** @type {import('@astryxdesign/cli/authoring').AstryxCodemod} */
export default {
type: 'code',
title: 'Rename AcmeCarousel delay to interval',
description: 'Renames the delay prop on AcmeCarousel.',
fileExtensions: ['.tsx', '.jsx'],
transform(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
const props = root
.find(j.JSXOpeningElement, {name: {name: 'AcmeCarousel'}})
.find(j.JSXAttribute, {name: {name: 'delay'}});
if (props.size() === 0) return null;
props.forEach(path => {
path.node.name.name = 'interval';
});
return root.toSource();
},
};

type: 'code' rewrites the app's source files that match fileExtensions. type: 'config' rewrites the app's astryx.config file instead, and runs before code codemods. title shows in the upgrade output, and api.jscodeshift is a jscodeshift instance for the file. Every field is in astryx docs authoring.

Choose when a codemod runs

Two rules decide whether an app's astryx upgrade runs your codemods: the app's Core versions, and whether the app names your package.

  1. Version folders are matched against the app's @astryxdesign/core versions, not your package's version. upgrade --from <version> runs each folder above --from, up to and including the Core version installed in the app. With Core 0.7.0 installed, --from 0.6.3 runs 0.7.0/, and --from 0.7.0 runs nothing. A folder named after your own release, such as 1.0.0/, waits until the app has Core 1.0.0.
  2. upgrade runs your codemods only when the app lists your package in integrations in its astryx.config, or passes --integration @acme/astryx-widgets. Having your package installed is not enough: the run then skips your codemods with no warning.

Run codemods in an app

An app previews codemods with astryx upgrade and writes the changes with --apply. Without --apply, nothing on disk changes.

bash
# Preview each codemod and the files it would change
npx astryx upgrade --from 0.6.3 --integration @acme/astryx-widgets
# Write the changes
npx astryx upgrade --from 0.6.3 --integration @acme/astryx-widgets --apply
text
Integrations: @acme/astryx-widgets
1 codemod to run (dry run)
Applying integration codemods...
Rename AcmeCarousel delay to interval (v0.7.0, @acme/astryx-widgets)
! ~ src/Hero.tsx (would change)

The count includes Core's codemods for the same versions, which run first and print above Integrations:. To run only yours, as when you test it, add --codemod rename-delay.

--from is the Core version the app had before it upgraded. The run scans ./src unless the app passes --path, and it never writes a file the app marks as generated, vendored, or ignored; see astryx docs cli/commands/upgrade. upgrade --list shows only Core codemods.

An integration manifest has no hooks field. Commands that run after codemods, such as a formatter, are the app's to set, in hooks.postCodemod in its astryx.config.