JSONAPI Resources Anchor

CI Sync Enforcement

You may want to use CI to automatically enforce that the schema does not go out of sync.

This guide provides approaches for single file and multifile schemas.

This guide assumes:

  • the repo uses the default vite_rails file structure, i.e. a monorepo with backend and frontend
  • yarn package manager

However, these are not requirements. The intention of this guide is to provide a starting point for CI enforcement and should be adaptable to other repo structures, package managers, shells, etc.

The Gist

  • Add schema:generate for local development.
  • Add schema:check for CI.
package.json
{
  "scripts": {
    "schema:generate": "./bin/jsonapi-schema-generate.sh",
    "schema:check": "./bin/jsonapi-schema-check.sh"
  }
}

yarn schema:check will fail if the schema being checked in is different from what CI generates.

Single File Quick Reference

File Structure

schema.ts
jsonapi-schema-generate.sh
jsonapi-schema-check.sh
jsonapi.rake
package.json
package.json
{
  "scripts": {
    "schema:generate": "./bin/jsonapi-schema-generate.sh",
    "schema:check": "./bin/jsonapi-schema-check.sh"
  }
}

yarn schema:generate

bin/jsonapi-schema-generate.sh
set -e
bundle exec rails jsonapi:generate
yarn run eslint --fix ./app/frontend/models/schema.ts
yarn run prettier --write ./app/frontend/models/schema.ts

yarn schema:check

bin/jsonapi-schema-check.sh
set -e
bundle exec rails jsonapi:generate
yarn run eslint --fix ./app/frontend/models/schema.ts
yarn run prettier --write ./app/frontend/models/schema.ts

if [$(git status ./app/frontend/models/schema.ts --porcelain=1 | wc-l) -ne 0 ]; then
  git diff
  echo "Schema is out of sync. Run 'yarn schema:generate'."
  exit 1
fi

rails jsonapi:generate

lib/tasks/jsonapi.rake
namespace :jsonapi do
  desc "Generate JSONAPI::Resource Anchor schema"
  task generate: :environment do
    puts "Generating JSONAPI::Resource Anchor schema..."

    content = Anchor::TypeScript::SchemaGenerator(
      register: Schema.register,
    ).call

    path = Rails.root.join("schema.ts")
    File.open(path, "w") { |f| f.write(content) }
    puts "#{File.basename(path)}"
  end
end

Multifile Quick Reference

This guide also works for manually_editable multifile schemas.

As long as manual edits are made after the // END AUTOGEN comment the strategy below is able to enforce that the generated schema is in sync.

See the Incremental Migration guide for an example use case.

User.model.ts
Post.model.ts
Comment.model.ts
shared.ts
hash.json
jsonapi-schema-generate.sh
jsonapi-schema-check.sh
jsonapi.rake
package.json
package.json
{
  "scripts": {
    "schema:generate": "./bin/jsonapi-schema-generate.sh",
    "schema:check": "./bin/jsonapi-schema-check.sh"
  }
}

yarn schema:generate

bin/jsonapi-schema-generate.sh
set -e
FILES=$(bundle exec rails jsonapi:generate | tr ' ' '\n' | grep 'frontend' | tr '\n' ' ')

if [[ -n "{$FILES//[[:space:]]/}" ]]; then
  yarn run eslint --fix $FILES
  yarn run prettier --write $FILES
fi
yarn run prettier --write ./app/frontend/models/gen/hash.json

yarn schema:check

bin/jsonapi-schema-check.sh
set -e
FILES=$(bundle exec rails jsonapi:generate_no_trust | tr ' ' '\n' | grep 'frontend' | tr '\n' ' ')

if [[ -n "{$FILES//[[:space:]]/}" ]]; then
  yarn run eslint --fix $FILES
  yarn run prettier --write $FILES
fi
yarn run prettier --write ./app/frontend/models/gen/hash.json

if [$(git status ./app/frontend/models/gen --porcelain=1 | wc-l) -ne 0 ]; then
  git diff
  echo "Schema is out of sync. Run 'yarn schema:generate'."
  exit 1
fi

rails jsonapi:generate

lib/tasks/jsonapi.rake
namespace :jsonapi do
  desc "Generate JSONAPI::Resource Anchor schema"
  task generate: :environment do
    Rails.application.eager_load!

    generator = Anchor::TypeScript::MultifileSchemaGenerator.new(
      register: Schema.register,
      context: {},
      resource_file_extension: '.model.ts'
    )

    modified_files = Anchor::TypeScript::MultifileSaveService.call(
      generator:,
      folder_path: 'app/frontend/models/gen',
      force: false
    )

    puts modifed_files.map { |name| Rails.root.join(folder_path, name) }.join(' ')
  end

  desc "Generate JSONAPI::Resource Anchor schema without trusting hash.json"
  task generate_no_trust: :environment do
    Rails.application.eager_load!

    generator = Anchor::TypeScript::MultifileSchemaGenerator.new(
      register: Schema.register,
      context: {},
      resource_file_extension: '.model.ts'
    )

    modified_files = Anchor::TypeScript::MultifileSaveService.call(
      generator:,
      folder_path: 'app/frontend/models/gen',
      force: false,
      trust_hash: false
    )
    puts modifed_files.map { |name| Rails.root.join(folder_path, name) }.join(' ')
  end
end

On this page