Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrations run cmd #155

Open
wants to merge 27 commits into
base: next
Choose a base branch
from
Open

feat: migrations run cmd #155

wants to merge 27 commits into from

Conversation

alvarosabu
Copy link
Contributor

@alvarosabu alvarosabu commented Mar 6, 2025

Migrations Run Command

The storyblok migrations run command allows you to execute content migrations on your Storyblok stories. This is particularly useful for batch updating content, transforming field values, or restructuring content across multiple stories in a space.

Prerequisites

Before running migrations, ensure you have:

  1. The necessary migration files in your .storyblok/migrations/{spaceId} directory
  2. Proper access to the target space
  3. A backup of your content (recommended)

Basic Usage

# Run all migrations in a space
storyblok migrations run --space 12345

# Run migrations for a specific component
storyblok migrations run hero --space 12345

# Preview changes without applying them
storyblok migrations run --space 12345 --dry-run

Architecture & Flow

The migrations command is organized in three main layers with a specific processing flow to ensure safe and efficient content updates.

Command Structure

// 1. Command Layer (index.ts)
migrationsCommand
  .command('run [componentName]')
  // ... options

// 2. Operations Layer (operations.ts)
  - handleMigrations()
  - summarizeMigrationResults()

// 3. Actions Layer (actions.ts)
  - readMigrationFiles()
  - updateStory()
  - fetchStoriesByComponent();

Processing Flow Examples

1. Simple Migration Run

storyblok migrations run --space 12345

Flow:

  1. Read Phase
// 1. Read migration files from .storyblok/migrations/12345/
const migrationFiles = await readMigrationFiles({
  space: '12345',
  path: undefined,
  filter: undefined
});

// 2. Fetch stories from space
const stories = await fetchStoriesByComponent({
  spaceId: '12345',
  token: 'valid-token',
  region: 'eu'
});
  1. Migration Phase
// 3. Process migrations
const migrationResults = await handleMigrations({
  migrationFiles,
  stories,
  space: '12345',
  // ... other options
});
  1. Update Phase
// 4. Update modified stories
for (const story of storiesToUpdate) {
  await updateStory('12345', token, region, story.id, {
    story: {
      content: story.content,
      // ... other fields
    },
    force_update: '1'
  });
}

2. Component-Specific Migration

storyblok migrations run hero --space 12345

Flow:

  1. Filter Phase
// 1. Filter migrations by component name
const filteredMigrations = migrationFiles.filter((file) => {
  return file.name.match(new RegExp(`^${componentName}(\\..*)?\.js$`));
});

// 2. Fetch stories with component
const stories = await fetchStoriesByComponent({
  spaceId: '12345',
  componentName: 'hero'
});
  1. Process & Update
// 3. Apply migrations only to matching stories
const results = await handleMigrations({
  migrationFiles: filteredMigrations,
  stories,
  // ... other options
});

3. Publication Control

storyblok migrations run --space 12345 --publish published

Flow:

  1. Story Update
// 1. Prepare update payload with publish flag
const payload = {
  story: {
    content: story.content,
    id: story.id,
    name: story.name
  },
  force_update: '1'
};

// 2. Add publish flag based on conditions
if (publish === 'published' && story.published) {
  payload.publish = 1;
}

// 3. Update story with publish control
await updateStory('12345', token, region, story.id, payload);

4. Story Filtering by Path

storyblok migrations run --space 12345 --starts-with "/en/blog/"

Flow:

  1. Story Fetching with Path Filter
// 1. Fetch stories with path filter
const stories = await fetchStoriesByComponent({
  spaceId: '12345',
  token: 'valid-token',
  region: 'eu',
  starts_with: '/en/blog/'
});

// 2. Process only stories that match the path
const migrationResults = await handleMigrations({
  migrationFiles,
  stories, // Only contains stories under /en/blog/
  space: '12345',
  // ... other options
});

5. Story Filtering by Query

storyblok migrations run --space 12345 --query "[highlighted][in]=true"

Flow:

  1. Story Fetching with Query Filter
// 1. Fetch stories with query filter
const stories = await fetchStoriesByComponent({
  spaceId: '12345',
  token: 'valid-token',
  region: 'eu',
  query: '[highlighted][in]=true'
});

// 2. Process only stories that match the query
const migrationResults = await handleMigrations({
  migrationFiles,
  stories, // Only contains stories where highlighted=true
  space: '12345',
  // ... other options
});

// 3. Example with multiple query conditions
const storiesWithMultipleConditions = await fetchStoriesByComponent({
  spaceId: '12345',
  token: 'valid-token',
  region: 'eu',
  query: '[highlighted][in]=true,[status][in]=published'
});

Key Features

  1. Selective Migration

    • Run migrations on specific components
    • Filter migrations by pattern
    • Target stories by path or query
  2. Safe Execution

    • Dry run mode for previewing changes
    • Progress tracking and reporting
  3. Publication Control

    • Flexible publishing options
    • Control over which stories get published
    • Support for different publication scenarios
  4. Content Filtering

    • Filter by component type
    • Filter by story path
    • Filter by content attributes

Migration File Structure

Migration files should follow this structure:

// hero.amount.js
export default function (block) {
  // Transform block content
  block.amount = Number(block.amount) + 1;
  return block;
}

Testing Strategy

The command includes comprehensive test coverage:

  1. Unit Tests
    • Migration file reading
    • Story fetching and filtering
    • Content transformation logic

Testing checklist

Running migrations storyblok migrations run

General

  • It should show the command title
  • It should throw an error if the user is not logged in You are currently not logged in. Please login first to get your user info.

Required Arguments

[componentName] (optional)

  • It should run migrations for all components if no component name is provided
  • It should run only migrations that match the component name pattern if provided
  • It should match migrations that start with the component name and are followed by either the end of the filename or a dot

-s, --space=TARGET_SPACE_ID

  • It should read migration files from .storyblok/migrations/<TARGET_SPACE_ID>/
  • It should fetch stories from the target space
  • It should fetch full content for each story
  • It should apply migrations to the stories
  • It should update the modified stories in Storyblok

Error handling

  • It should throw an error if the space is not provided: Please provide the space as argument --space YOUR_SPACE_ID.
  • It should throw an error if no migration files are found
  • It should throw an error if no stories are found

Options

--filter, --fi=<pattern>

  • It should apply glob filter to migration files before running them
  • It should support patterns like *.amount.js to run specific migrations
  • It should show a warning if no migrations match the filter

--dry-run, -d

  • It should preview changes without applying them to Storyblok
  • It should show what changes would be made
  • It should not call updateStory API

--query, -q=<query>

  • It should filter stories by content attributes using Storyblok filter query syntax
  • Example: --query="[highlighted][in]=true"

--starts-with=<path>

  • It should filter stories by path
  • Example: --starts-with="/en/blog/"

--publish=<mode>

Supports different publication modes:

  • all: Should publish all stories after migration
  • published: Should only publish stories that were already published
  • published-with-changes: Should only publish stories that have unpublished changes after migration
  • No value: Should not publish any stories

Migration Results

  • It should show a summary of successful migrations
  • It should show a summary of failed migrations
  • It should show a summary of skipped migrations
  • It should show update progress for each story
  • It should show final success/failure counts

- Introduced new migrations command with generate subcommand
- Added constants for migrations command and color palette
- Implemented migration file generation for specific component fields
- Created actions and tests for migration file creation
- Updated index to import migrations command and related modules
- Added comprehensive test cases for migrations generate command
- Implemented tests for successful migration generation with and without custom path
- Added error handling tests for missing component name, field, and non-existent components
- Updated test file to improve error checking and mock session state
- Created detailed documentation for migrations generate command
- Included usage examples, command options, and output structure
- Added manual testing checklist and best practices
- Provided insights into migration file generation and usage scenarios
- Enhanced migration generation to support optional file name suffix
- Updated `generateMigration` function to accept optional suffix parameter
- Modified command options to include `--suffix` flag
- Updated README with new suffix generation example and documentation
- Adjusted file name generation logic to incorporate optional suffix
…rameter

- Modified test cases to include optional suffix parameter in `generateMigration` function calls
- Ensured test coverage reflects the recent enhancement of migration file generation
- Removed required field parameter from migration generation
- Updated migration template with more generic examples
- Simplified file naming convention for migration files
- Removed unnecessary field-specific logic from generate command
- Updated tests to reflect new migration generation approach
- Created comand-less story module
- Implemented `objectToStringParams` utility function in `utils/index.ts`
- Converts object values to strings for use with URLSearchParams
- Created `createRegexFromGlob` utility function in `utils/index.ts`
- Moved regex creation logic from `filterSpaceDataByPattern` to new utility function
- Simplified pattern matching with a reusable glob-to-regex conversion method
…upport

- Implemented new migrations run command with advanced migration processing
- Added support for running migrations on specific components or entire spaces
- Created utility functions for reading and applying migration files
- Introduced dynamic migration function loading and content transformation
- Implemented dry-run mode and detailed migration result reporting
- Added new actions for fetching and updating stories
- Enhanced error handling and logging for migration operations
…spinner management

- Added filtering of migrations based on component name
- Optimized spinner usage and logging for migration operations
- Simplified migration processing logic
- Enhanced error handling and migration result tracking
- Moved spinner initialization to more precise locations in the code
- Enhanced code readability by adding multi-line conditionals in migration and utility files
- Updated JSDoc comments in `operations.ts` with more detailed parameter descriptions
- Refined logging output formatting in migration result summary
- Minor improvements to code structure and error reporting
- Updated MSW handlers to use dynamic space ID and simplified mock logic
- Improved error handling and test coverage for fetchStories method
- Removed redundant handler and consolidated API mocking
- Updated test cases to handle async errors more robustly
- Simplified error catching and assertion in test scenarios
- Removed unnecessary return statement in updateStory action
- Created test file for migration file reading actions
- Added test cases for `readJavascriptFile` method covering successful reads, error scenarios, and file validation
- Implemented tests for `readMigrationFiles` method with various scenarios including file filtering, empty directories, and error handling
- Used memfs for virtual file system testing to simulate different file system conditions
- Implemented tests for `getMigrationFunction` method
- Added test cases covering successful migration function loading
- Tested scenarios for missing default exports and non-existent files
- Used memfs and vi.doMock for simulating dynamic module imports
- Ensured robust error handling and function validation
- Implemented detailed test suite for migrations run command
- Added test cases for running migrations with and without component filtering
- Mocked session, stories actions, and migration operations
- Verified correct method calls and migration processing logic
- Ensured comprehensive coverage of migration run scenarios
- Added comprehensive test cases for migrations run command
- Implemented tests for migration filtering, dry run mode, and handling of migration results
- Verified correct behavior when no stories are modified
- Added mock scenarios for different migration outcomes
- Expanded test suite to cover edge cases in migration processing
- Introduced new `--query` option for filtering stories using Storyblok filter query syntax
- Updated `MigrationsRunOptions` interface to include optional query parameter
- Modified `fetchStoriesByComponent` method to handle filter query parameters
- Added CLI option for query-based story filtering in migrations run command
- Enhanced `fetchStories` method to support complex filter query parsing
- Updated `StoriesQueryParams` interface to allow flexible filter query handling
- Modified test cases in `migrations/run/index.test.ts` to accommodate new `fetchStoriesByComponent` method signature
- Updated `stories/actions.test.ts` to expect two stories instead of one in a specific test scenario
- Adjusted method call expectations to include optional parameters
- Ensured test coverage reflects recent changes in method signatures
- Introduced new `--starts-with` option for filtering stories by path
- Updated `MigrationsRunOptions` interface to include optional `startsWith` parameter
- Modified `fetchStoriesByComponent` method to handle `starts_with` filter
- Enhanced `StoriesFilterOptions` interface to support path-based filtering
- Added CLI option for path-based story filtering in migrations run command
- Updated test cases to cover new `starts-with` filter functionality
- Improved logging to display applied filters in story fetching process
…erations

- Deleted unnecessary `storyModified` variable that was not being used
- Simplified code by removing redundant variable declaration
- Maintained existing logic for migration content processing
@alvarosabu alvarosabu marked this pull request as draft March 6, 2025 09:42
- Introduced new `--publish` option with modes: 'all', 'published', and 'published-with-changes'
- Updated `MigrationsRunOptions` interface to include optional `publish` parameter
- Added utility functions `isStoryPublishedWithoutChanges` and `isStoryWithUnpublishedChanges`
- Modified `updateStory` action to support optional publish flag
- Enhanced migration run command to handle different publication scenarios
- Updated test cases to cover new publish option functionality
- Improved story update logic with flexible publishing options
- Created detailed README.md for migrations run command
- Documented test scenarios for various command options and behaviors
- Included examples of command usage with different flags
- Provided notes on migration file structure and best practices
- Outlined comprehensive testing requirements for command functionality
…tion

- Removed unused imports from `index.test.ts`
- Enhanced JSDoc for `updateStory` function in `actions.ts`
- Removed unused `StoryContent` type import
- Clarified parameters and added more descriptive documentation for story update method
@alvarosabu alvarosabu marked this pull request as ready for review March 6, 2025 13:13
@alvarosabu alvarosabu requested a review from edodusi March 6, 2025 13:13
@alvarosabu alvarosabu added the p3-significant [Priority] Moderate issues, major enhancements label Mar 6, 2025
@alvarosabu alvarosabu self-assigned this Mar 6, 2025
Copy link

run-migration

@alvarosabu alvarosabu added the feature [Issue] New feature or request label Mar 7, 2025
@edodusi
Copy link
Contributor

edodusi commented Mar 7, 2025

@alvarosabu what do you think if we add an extra-confirmation after the run command? I imagine something like:

$ pnpm dev mig run simple_component --space 325977

 Storyblok CLI
 
✔ Found 1 migration files.
✔ Fetched 1 story with related content (filtered by component "simple_component").

Migration "simple-component.js" will be run. This will potentially alter the content of 1 story in your space. Are you sure you want to proceed?
> Yes/No

 Success
✔ Successfully applied 1 migrations to 1 stories
✔ No failures reported

✔ Found 1 stories to update.
✔ Updated story Home - Completed in 182.05ms

 Success
✔ Successfully updated 1 stories in Storyblok.

Stopping at the first question. This is because this command changes content and to me it should require an extra-step, but I don't know if I'm too conservative. What do you think?

@edodusi
Copy link
Contributor

edodusi commented Mar 7, 2025

@alvarosabu every other tests passed 👍

@alvarosabu
Copy link
Contributor Author

@alvarosabu what do you think if we add an extra-confirmation after the run command? I imagine something like:

$ pnpm dev mig run simple_component --space 325977

 Storyblok CLI
 
✔ Found 1 migration files.
✔ Fetched 1 story with related content (filtered by component "simple_component").

Migration "simple-component.js" will be run. This will potentially alter the content of 1 story in your space. Are you sure you want to proceed?
> Yes/No

 Success
✔ Successfully applied 1 migrations to 1 stories
✔ No failures reported

✔ Found 1 stories to update.
✔ Updated story Home - Completed in 182.05ms

 Success
✔ Successfully updated 1 stories in Storyblok.

Stopping at the first question. This is because this command changes content and to me it should require an extra-step, but I don't know if I'm too conservative. What do you think?

That is a good point, mm, considering the client's feedback they actually use the cli migration command as part of their CIs and scripts, so adding a prompt would interfere with that being autonomous. There is also a plan to add interactive or wizard mode in the future. I would say it's ok because the content migration is done by default as a save functionality on the Product, so they could always rollback there or rollback using the migration rollback functionality if something went south

@alvarosabu alvarosabu mentioned this pull request Mar 10, 2025
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature [Issue] New feature or request p3-significant [Priority] Moderate issues, major enhancements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants