├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── config.yml
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── build.yml
├── .gitignore
├── .husky
├── .gitignore
├── commit-msg
└── pre-commit
├── .npmrc
├── .rspec
├── .rubocop.yml
├── .standard.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── lint
├── rake
├── rspec
├── rubocop
└── standardrb
├── gemfiles
├── Gemfile-rails-edge
├── Gemfile-rails.7.1.x
└── Gemfile-rails.7.2.x
├── netlify.toml
├── package.json
├── playground
└── vanilla
│ ├── .browserslistrc
│ ├── .gitignore
│ ├── .npmrc
│ ├── .pnpm-debug.log
│ ├── .rspec
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Procfile.dev
│ ├── Rakefile
│ ├── app
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── composers_controller.rb
│ │ ├── songs_controller.rb
│ │ └── videos_controller.rb
│ ├── frontend
│ │ ├── api
│ │ │ ├── ComposersApi.ts
│ │ │ ├── SongsApi.ts
│ │ │ ├── VideosApi.ts
│ │ │ ├── all.ts
│ │ │ └── index.ts
│ │ ├── app.ts
│ │ ├── components.d.ts
│ │ ├── components
│ │ │ ├── InertiaLink.vue
│ │ │ ├── Link.vue
│ │ │ ├── PageList.vue
│ │ │ ├── PageSubtitle.vue
│ │ │ ├── PageTitle.vue
│ │ │ ├── TheNavBar.vue
│ │ │ └── YouTubePlayer.vue
│ │ ├── composables
│ │ │ └── page.ts
│ │ ├── entrypoints
│ │ │ └── application.ts
│ │ ├── helpers
│ │ │ └── object.ts
│ │ ├── index.d.ts
│ │ ├── inertia.ts
│ │ ├── layouts
│ │ │ └── DefaultLayout.vue
│ │ ├── pages.ts
│ │ ├── pages
│ │ │ ├── composers
│ │ │ │ ├── index.vue
│ │ │ │ └── show.vue
│ │ │ ├── songs
│ │ │ │ ├── index.vue
│ │ │ │ └── show.vue
│ │ │ └── videos
│ │ │ │ └── index.vue
│ │ ├── services
│ │ │ └── axios.ts
│ │ ├── tsconfig.json
│ │ ├── types
│ │ │ ├── AnyModel.ts
│ │ │ └── serializers
│ │ │ │ ├── Composer.ts
│ │ │ │ ├── ComposerWithSongs.ts
│ │ │ │ ├── ComposerWithSongs
│ │ │ │ └── Song.ts
│ │ │ │ ├── Model.ts
│ │ │ │ ├── Nested
│ │ │ │ └── Album.ts
│ │ │ │ ├── SnakeComposer.ts
│ │ │ │ ├── Song.ts
│ │ │ │ ├── SongWithVideos.ts
│ │ │ │ ├── Video.ts
│ │ │ │ ├── VideoWithSong.ts
│ │ │ │ └── index.ts
│ │ └── windi.config.ts
│ ├── models
│ │ ├── application_record.rb
│ │ ├── composer.rb
│ │ ├── song.rb
│ │ └── video_clip.rb
│ ├── serializers
│ │ ├── base_serializer.rb
│ │ ├── composer_serializer.rb
│ │ ├── composer_with_songs_serializer.rb
│ │ ├── model_serializer.rb
│ │ ├── nested
│ │ │ └── album_serializer.rb
│ │ ├── snake_composer_serializer.rb
│ │ ├── song_serializer.rb
│ │ ├── song_with_videos_serializer.rb
│ │ ├── video_serializer.rb
│ │ └── video_with_song_serializer.rb
│ └── views
│ │ └── layouts
│ │ └── application.html.erb
│ ├── bin
│ ├── bundle
│ ├── rails
│ ├── rake
│ ├── setup
│ └── vite
│ ├── config.ru
│ ├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── application_controller_renderer.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── content_security_policy.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── js_from_routes.rb
│ │ ├── mime_types.rb
│ │ ├── types_from_serializers.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ └── vite.json
│ ├── db
│ ├── migrate
│ │ ├── 20220709144820_create_composers.rb
│ │ ├── 20220709151241_create_video_clips.rb
│ │ ├── 20220709151259_create_songs.rb
│ │ └── 20240227112250_add_enums_to_songs.rb
│ ├── schema.rb
│ └── seeds.rb
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── favicon.ico
│ └── robots.txt
│ └── vite.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── scripts
├── changelog.js
├── release.js
└── verifyCommit.js
├── spec
├── spec_helper.rb
└── types_from_serializers
│ ├── __snapshots__
│ ├── interfaces_ComposerSerializer.snap
│ ├── interfaces_ComposerWithSongsSerializer.snap
│ ├── interfaces_ComposerWithSongsSerializer__SongSerializer.snap
│ ├── interfaces_ModelSerializer.snap
│ ├── interfaces_Nested__AlbumSerializer.snap
│ ├── interfaces_SnakeComposerSerializer.snap
│ ├── interfaces_SongSerializer.snap
│ ├── interfaces_SongWithVideosSerializer.snap
│ ├── interfaces_VideoSerializer.snap
│ ├── interfaces_VideoWithSongSerializer.snap
│ ├── interfaces_index.snap
│ ├── namespace_interfaces_ComposerSerializer.snap
│ ├── namespace_interfaces_ComposerWithSongsSerializer.snap
│ ├── namespace_interfaces_ComposerWithSongsSerializer__SongSerializer.snap
│ ├── namespace_interfaces_ModelSerializer.snap
│ ├── namespace_interfaces_Nested__AlbumSerializer.snap
│ ├── namespace_interfaces_SnakeComposerSerializer.snap
│ ├── namespace_interfaces_SongSerializer.snap
│ ├── namespace_interfaces_SongWithVideosSerializer.snap
│ ├── namespace_interfaces_VideoSerializer.snap
│ └── namespace_interfaces_VideoWithSongSerializer.snap
│ └── generator_spec.rb
├── tsconfig.json
└── types_from_serializers
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
└── release
├── lib
├── types_from_serializers.rb
└── types_from_serializers
│ ├── dsl.rb
│ ├── generator.rb
│ ├── railtie.rb
│ └── version.rb
└── types_from_serializers.gemspec
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | coverage/
3 | build/
4 | examples/
5 | public/
6 | node_modules/
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | },
5 | extends: ['@antfu/eslint-config'],
6 | rules: {
7 | '@typescript-eslint/space-before-function-paren': ['warn', 'always'],
8 | '@typescript-eslint/quotes': 'off',
9 | 'vue/attribute-hyphenation': ['warn', 'never'],
10 | 'vue/html-closing-bracket-spacing': ['warn', {
11 | startTag: 'never',
12 | endTag: 'never',
13 | selfClosingTag: 'never',
14 | }],
15 | },
16 | globals: {
17 | defineProps: 'readonly',
18 | defineEmits: 'readonly',
19 | defineExpose: 'readonly',
20 | withDefaults: 'readonly',
21 | $: 'readonly',
22 | $$: 'readonly',
23 | $ref: 'readonly',
24 | $computed: 'readonly',
25 | },
26 | }
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: 'bug: pending triage'
6 | assignees: ''
7 | ---
8 |
9 | [troubleshooting section]: https://types-from-serializers.netlify.app/faqs/
10 |
11 | - [ ] I have tried upgrading by running `bundle update types_from_serializers`.
12 | - [ ] I have read the __[troubleshooting section]__ before opening an issue.
13 |
14 | ### Description 📖
15 |
16 | _Provide a clear and concise description of what the bug is._
17 |
18 | ### Reproduction/Logs 🐞📜
19 |
20 | _Please provide a link to a repo that can reproduce the problem you ran into, or logs about the problem._
21 |
22 | ### Screenshots 📷
23 |
24 | _Provide console or browser screenshots of the problem_.
25 |
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Troubleshooting & FAQs
4 | url: https://types-from-serializers.netlify.app/faqs/
5 | about: 'Please check the most common problems before opening an issue'
6 | - name: Questions & Discussions
7 | url: https://github.com/ElMassimo/types_from_serializers/discussions
8 | about: Use GitHub discussions for message-board style questions and discussions.
9 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description 📖
2 |
3 | This pull request
4 |
5 | ### Background 📜
6 |
7 | This was happening because
8 |
9 | ### The Fix 🔨
10 |
11 | By changing
12 |
13 | ### Screenshots 📷
14 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - '**'
10 |
11 | jobs:
12 | lint:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 |
18 | - uses: pnpm/action-setup@v4
19 | with:
20 | version: 9.8.0
21 |
22 | - uses: actions/setup-node@v4
23 | with:
24 | cache: 'pnpm'
25 | node-version: 22
26 |
27 | - run: pnpm install --frozen-lockfile
28 |
29 | - uses: ruby/setup-ruby@v1
30 | with:
31 | ruby-version: "3.0"
32 | bundler-cache: true
33 |
34 | - name: Rubocop
35 | run: bin/rubocop
36 |
37 | - name: Clean Types
38 | run: pnpm clean
39 |
40 | - name: Run Migrations
41 | run: pnpm migrate
42 |
43 | - name: Generate Types
44 | run: pnpm gen
45 |
46 | - name: Type Check
47 | run: pnpm tsc
48 |
49 | - name: ESLint
50 | run: pnpm lint
51 |
52 | test:
53 | name: test
54 | runs-on: ${{ matrix.os }}
55 | continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' || matrix.experimental }}
56 | strategy:
57 | fail-fast: false
58 | matrix:
59 | os: [ubuntu-latest]
60 | ruby: [
61 | "3.2",
62 | "3.3",
63 | ]
64 | gemfile: [
65 | "Gemfile-rails.7.1.x",
66 | "Gemfile-rails.7.2.x",
67 | ]
68 | experimental: [false]
69 | include:
70 | - ruby: "3.3"
71 | os: ubuntu-latest
72 | gemfile: Gemfile-rails-edge
73 | experimental: true
74 | env:
75 | BUNDLE_JOBS: 4
76 | BUNDLE_RETRY: 3
77 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}
78 |
79 | steps:
80 | - uses: actions/checkout@v4
81 |
82 | - uses: ruby/setup-ruby@v1
83 | with:
84 | ruby-version: ${{ matrix.ruby }}
85 | bundler-cache: true
86 |
87 | - name: Setup Code Climate test-reporter
88 | run: |
89 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
90 | chmod +x ./cc-test-reporter
91 | ./cc-test-reporter before-build
92 |
93 | - name: Run Migrations
94 | run: cd playground/vanilla && bin/rails db:prepare
95 |
96 | - name: Ruby Specs
97 | run: bin/rspec
98 |
99 | - name: Upload code coverage to Code Climate
100 | if: ${{ contains(github.ref, 'main') }}
101 | run: |
102 | export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}"
103 | ./cc-test-reporter after-build -r ${{secrets.CC_TEST_REPORTER_ID}}
104 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | spec/support/generated
2 | spec/support/sample_app/bin
3 |
4 | /.bundle
5 | pkg
6 | log
7 | tmp
8 | node_modules
9 | .byebug_history
10 | yarn-debug.log*
11 | yarn-error.log*
12 | .yarn-integrity
13 | gemfiles/*.lock
14 | .DS_Store
15 |
16 | # Vite on Rails
17 | /public/vite
18 | /public/vite-dev
19 | /public/vite-test
20 | test/test_app/public/vite-production
21 | node_modules
22 | *.local
23 | .DS_Store
24 |
25 | # Vitepress
26 | dist
27 | examples_dist
28 | node_modules
29 | coverage
30 | .nyc_output
31 | .rpt2_cache
32 | .env
33 | local.log
34 | .DS_Store
35 | e2e/reports
36 | e2e/screenshots
37 | __build__
38 | playground_dist
39 | yarn-error.log
40 | temp
41 | markdown
42 | explorations
43 | selenium-server.log
44 |
45 | # Algolia
46 | .algolia.env
47 |
48 | # Hanami
49 | .env.local
50 | .env.*.local
51 |
52 | docs/pnpm-lock.yaml
53 | docs
54 |
55 | # Ruby version
56 | .ruby-version
57 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | node scripts/verifyCommit.js "$1"
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged || echo "⚠️ WARNING: This commit failed some of the linter checks. ⚠️"
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | ignore-workspace-root-check=true
2 | shamefully-hoist=true
3 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format=progress
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | require:
2 | - standard
3 | - rubocop-rails
4 | - rubocop-rspec
5 | - rubocop-performance
6 |
7 | inherit_mode:
8 | merge:
9 | - Exclude
10 |
11 | inherit_gem:
12 | standard: config/base.yml
13 |
14 | AllCops:
15 | SuggestExtensions: false
16 | NewCops: enable
17 |
18 | Exclude:
19 | - bin/**/*
20 | - node_modules/**/*
21 | - public/**/*
22 | - bin/bundle
23 | - playground/vanilla/bin/bundle
24 |
25 | Rails:
26 | Enabled: true # enable rubocop-rails cops
27 | Rails/Delegate:
28 | Enabled: false
29 | RSpec:
30 | Enabled: true # enable rubocop-rspec cops
31 | RSpec/DescribeClass:
32 | Enabled: false # ignore missing comments on classes
33 | RSpec/MultipleExpectations:
34 | Enabled: false
35 | RSpec/ExampleLength:
36 | Enabled: false
37 | RSpec/DescribedClass:
38 | Enabled: false
39 | RSpec/Capybara/FeatureMethods:
40 | Enabled: false
41 |
42 | # Trailing Commas
43 | Style/TrailingCommaInArrayLiteral:
44 | EnforcedStyleForMultiline: comma
45 | Style/TrailingCommaInHashLiteral:
46 | EnforcedStyleForMultiline: comma
47 | Style/TrailingCommaInArguments:
48 | EnforcedStyleForMultiline: comma
49 |
50 | # Outdent Access Modifier
51 | Layout/AccessModifierIndentation:
52 | EnforcedStyle: outdent
53 |
--------------------------------------------------------------------------------
/.standard.yml:
--------------------------------------------------------------------------------
1 | fix: true
2 | ignore:
3 | - 'packages/**/*'
4 | - 'scripts/**/*'
5 | - 'playground/**/*'
6 | - '**/*':
7 | - Style/TrailingCommaInArrayLiteral
8 | - Style/TrailingCommaInHashLiteral
9 | - Style/TrailingCommaInArguments
10 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at maximomussini@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Setting Up a Development Environment
2 |
3 | 1. Install [pnpm](https://pnpm.js.org/)
4 |
5 | 2. Run the following commands to set up the development environment.
6 |
7 | ```
8 | bundle install
9 | ```
10 |
11 | ```
12 | pnpm install
13 | ```
14 |
15 | ```
16 | pnpm migrate
17 | ```
18 |
19 | ## Making sure your changes pass all tests
20 |
21 | There are a number of automated checks which run on GitHub Actions when a pull request is created.
22 | You can run those checks on your own locally to make sure that your changes would not break the CI build.
23 |
24 | ### 1. Check the code for JavaScript style violations
25 |
26 | ```
27 | pnpm lint
28 | ```
29 |
30 | ### 2. Check the code for Ruby style violations
31 |
32 | ```
33 | bin/rubocop
34 | ```
35 |
36 | ### 3. Run the test suite
37 |
38 | ```
39 | bin/rspec
40 | ```
41 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec path: "./types_from_serializers"
4 |
5 | gem "pry-byebug", require: false
6 | gem "debug"
7 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: types_from_serializers
3 | specs:
4 | types_from_serializers (2.4.0)
5 | listen (~> 3.2)
6 | oj_serializers (~> 2.0, >= 2.0.2)
7 | railties (>= 5.1)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | actionpack (7.0.4.3)
13 | actionview (= 7.0.4.3)
14 | activesupport (= 7.0.4.3)
15 | rack (~> 2.0, >= 2.2.0)
16 | rack-test (>= 0.6.3)
17 | rails-dom-testing (~> 2.0)
18 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
19 | actionview (7.0.4.3)
20 | activesupport (= 7.0.4.3)
21 | builder (~> 3.1)
22 | erubi (~> 1.4)
23 | rails-dom-testing (~> 2.0)
24 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
25 | activemodel (7.0.4.3)
26 | activesupport (= 7.0.4.3)
27 | activerecord (7.0.4.3)
28 | activemodel (= 7.0.4.3)
29 | activesupport (= 7.0.4.3)
30 | activesupport (7.0.4.3)
31 | concurrent-ruby (~> 1.0, >= 1.0.2)
32 | i18n (>= 1.6, < 2)
33 | minitest (>= 5.1)
34 | tzinfo (~> 2.0)
35 | ast (2.4.2)
36 | awesome_print (1.9.2)
37 | builder (3.2.4)
38 | byebug (11.1.3)
39 | coderay (1.1.3)
40 | concurrent-ruby (1.2.2)
41 | crass (1.0.6)
42 | debug (1.7.2)
43 | irb (>= 1.5.0)
44 | reline (>= 0.3.1)
45 | diff-lcs (1.5.0)
46 | docile (1.4.0)
47 | erubi (1.12.0)
48 | ffi (1.15.5)
49 | given_core (3.8.2)
50 | sorcerer (>= 0.3.7)
51 | i18n (1.12.0)
52 | concurrent-ruby (~> 1.0)
53 | io-console (0.6.0)
54 | irb (1.6.3)
55 | reline (>= 0.3.0)
56 | js_from_routes (2.1.0)
57 | railties (>= 5.1, < 8)
58 | json (2.6.3)
59 | language_server-protocol (3.17.0.3)
60 | listen (3.8.0)
61 | rb-fsevent (~> 0.10, >= 0.10.3)
62 | rb-inotify (~> 0.9, >= 0.9.10)
63 | loofah (2.20.0)
64 | crass (~> 1.0.2)
65 | nokogiri (>= 1.5.9)
66 | method_source (1.0.0)
67 | mini_portile2 (2.8.1)
68 | minitest (5.18.0)
69 | nokogiri (1.14.2)
70 | mini_portile2 (~> 2.8.0)
71 | racc (~> 1.4)
72 | oj (3.14.2)
73 | oj_serializers (2.0.2)
74 | oj (>= 3.14.0)
75 | parallel (1.22.1)
76 | parser (3.2.2.0)
77 | ast (~> 2.4.1)
78 | pry (0.14.2)
79 | coderay (~> 1.1)
80 | method_source (~> 1.0)
81 | pry-byebug (3.10.1)
82 | byebug (~> 11.0)
83 | pry (>= 0.13, < 0.15)
84 | racc (1.6.2)
85 | rack (2.2.6.4)
86 | rack-test (2.1.0)
87 | rack (>= 1.3)
88 | rails-dom-testing (2.0.3)
89 | activesupport (>= 4.2.0)
90 | nokogiri (>= 1.6)
91 | rails-html-sanitizer (1.5.0)
92 | loofah (~> 2.19, >= 2.19.1)
93 | railties (7.0.4.3)
94 | actionpack (= 7.0.4.3)
95 | activesupport (= 7.0.4.3)
96 | method_source
97 | rake (>= 12.2)
98 | thor (~> 1.0)
99 | zeitwerk (~> 2.5)
100 | rainbow (3.1.1)
101 | rake (13.0.6)
102 | rb-fsevent (0.11.2)
103 | rb-inotify (0.10.1)
104 | ffi (~> 1.0)
105 | regexp_parser (2.7.0)
106 | reline (0.3.3)
107 | io-console (~> 0.5)
108 | rexml (3.2.5)
109 | rspec (3.12.0)
110 | rspec-core (~> 3.12.0)
111 | rspec-expectations (~> 3.12.0)
112 | rspec-mocks (~> 3.12.0)
113 | rspec-core (3.12.1)
114 | rspec-support (~> 3.12.0)
115 | rspec-expectations (3.12.2)
116 | diff-lcs (>= 1.2.0, < 2.0)
117 | rspec-support (~> 3.12.0)
118 | rspec-given (3.8.2)
119 | given_core (= 3.8.2)
120 | rspec (>= 2.14.0)
121 | rspec-mocks (3.12.5)
122 | diff-lcs (>= 1.2.0, < 2.0)
123 | rspec-support (~> 3.12.0)
124 | rspec-snapshot (2.0.1)
125 | awesome_print (> 1.0.0)
126 | rspec (> 3.0.0)
127 | rspec-support (3.12.0)
128 | rubocop (1.48.1)
129 | json (~> 2.3)
130 | parallel (~> 1.10)
131 | parser (>= 3.2.0.0)
132 | rainbow (>= 2.2.2, < 4.0)
133 | regexp_parser (>= 1.8, < 3.0)
134 | rexml (>= 3.2.5, < 4.0)
135 | rubocop-ast (>= 1.26.0, < 2.0)
136 | ruby-progressbar (~> 1.7)
137 | unicode-display_width (>= 2.4.0, < 3.0)
138 | rubocop-ast (1.28.0)
139 | parser (>= 3.2.1.0)
140 | rubocop-capybara (2.17.1)
141 | rubocop (~> 1.41)
142 | rubocop-performance (1.16.0)
143 | rubocop (>= 1.7.0, < 2.0)
144 | rubocop-ast (>= 0.4.0)
145 | rubocop-rails (2.18.0)
146 | activesupport (>= 4.2.0)
147 | rack (>= 1.1)
148 | rubocop (>= 1.33.0, < 2.0)
149 | rubocop-rspec (2.19.0)
150 | rubocop (~> 1.33)
151 | rubocop-capybara (~> 2.17)
152 | ruby-progressbar (1.13.0)
153 | simplecov (0.17.1)
154 | docile (~> 1.1)
155 | json (>= 1.8, < 3)
156 | simplecov-html (~> 0.10.0)
157 | simplecov-html (0.10.2)
158 | sorcerer (2.0.1)
159 | sqlite3 (1.6.2)
160 | mini_portile2 (~> 2.8.0)
161 | standard (1.26.0)
162 | language_server-protocol (~> 3.17.0.2)
163 | rubocop (~> 1.48.1)
164 | rubocop-performance (~> 1.16.0)
165 | thor (1.2.1)
166 | tzinfo (2.0.6)
167 | concurrent-ruby (~> 1.0)
168 | unicode-display_width (2.4.2)
169 | zeitwerk (2.6.7)
170 |
171 | PLATFORMS
172 | ruby
173 |
174 | DEPENDENCIES
175 | activerecord
176 | bundler (~> 2)
177 | debug
178 | js_from_routes
179 | pry-byebug
180 | rake (~> 13)
181 | rspec-given (~> 3.8)
182 | rspec-snapshot
183 | rubocop
184 | rubocop-performance
185 | rubocop-rails
186 | rubocop-rspec
187 | simplecov (< 0.18)
188 | sqlite3
189 | standard (~> 1.0)
190 | types_from_serializers!
191 |
192 | BUNDLED WITH
193 | 2.4.10
194 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Máximo Mussini
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Types From Serializers
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | [oj]: https://github.com/ohler55/oj
13 | [oj_serializers]: https://github.com/ElMassimo/oj_serializers
14 | [ams]: https://github.com/rails-api/active_model_serializers
15 | [Rails]: https://github.com/rails/rails
16 | [Issues]: https://github.com/ElMassimo/types_from_serializers/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc
17 | [Discussions]: https://github.com/ElMassimo/types_from_serializers/discussions
18 | [TypeScript]: https://www.typescriptlang.org/
19 | [Vite Ruby]: https://github.com/ElMassimo/vite_ruby
20 | [vite-plugin-full-reload]: https://github.com/ElMassimo/vite-plugin-full-reload
21 | [base_serializers]: https://github.com/ElMassimo/types_from_serializers#base_serializers
22 | [config]: https://github.com/ElMassimo/types_from_serializers#configuration-%EF%B8%8F
23 |
24 | Automatically generate TypeScript interfaces from your [JSON serializers][oj_serializers].
25 |
26 | _Currently, this library targets [`oj_serializers`][oj_serializers] and `ActiveRecord` in [Rails] applications_.
27 |
28 | ## Demo 🎬
29 |
30 | For a schema such as [this one](https://github.com/ElMassimo/types_from_serializers/blob/main/playground/vanilla/db/schema.rb):
31 |
32 |
33 | DB Schema
34 |
35 | ```ruby
36 | create_table "composers", force: :cascade do |t|
37 | t.text "first_name"
38 | t.text "last_name"
39 | t.datetime "created_at", precision: 6, null: false
40 | t.datetime "updated_at", precision: 6, null: false
41 | end
42 |
43 | create_table "songs", force: :cascade do |t|
44 | t.text "title"
45 | t.integer "composer_id"
46 | t.datetime "created_at", precision: 6, null: false
47 | t.datetime "updated_at", precision: 6, null: false
48 | end
49 |
50 | create_table "video_clips", force: :cascade do |t|
51 | t.text "title"
52 | t.text "youtube_id"
53 | t.integer "song_id"
54 | t.integer "composer_id"
55 | t.datetime "created_at", precision: 6, null: false
56 | t.datetime "updated_at", precision: 6, null: false
57 | end
58 | ```
59 |
60 |
61 | and a serializer like the following:
62 |
63 | ```ruby
64 | class VideoSerializer < BaseSerializer
65 | object_as :video, model: :VideoClip
66 |
67 | attributes :id, :created_at, :title, :youtube_id
68 |
69 | type :string, optional: true
70 | def youtube_url
71 | "https://www.youtube.com/watch?v=#{video.youtube_id}" if video.youtube_id
72 | end
73 |
74 | has_one :song, serializer: SongSerializer
75 | end
76 | ```
77 |
78 | it would generate a TypeScript interface like:
79 |
80 | ```ts
81 | import type Song from './Song'
82 |
83 | export default interface Video {
84 | id: number
85 | createdAt: string | Date
86 | title?: string
87 | youtubeId?: string
88 | youtubeUrl?: string
89 | song: Song
90 | }
91 | ```
92 |
93 | > **Note**
94 | >
95 | > This is the default configuration, but you have [full control][config] over generation.
96 |
97 |
98 | ## Why? 🤔
99 |
100 | It's easy for the backend and the frontend to become out of sync.
101 | Traditionally, preventing bugs requires writing extensive integration tests.
102 |
103 | [TypeScript] is a great tool to catch this kind of bugs and mistakes, as it can
104 | detect incorrect usages and missing fields, but writing types manually is
105 | cumbersome, and they can become stale over time, giving a false sense of confidence.
106 |
107 | This library takes advantage of the declarative nature of serializer libraries
108 | such as [`active_model_serializers`][ams] and [`oj_serializers`][oj_serializers],
109 | extending them to allow embedding type information, as well as inferring types
110 | from the SQL schema when available.
111 |
112 | As a result, it's posible to easily detect mismatches between the backend and
113 | the frontend, as well as make the fields more discoverable and provide great
114 | autocompletion in the frontend, without having to manually write the types.
115 |
116 | ## Features ⚡️
117 |
118 | - Start simple, no additional syntax required
119 | - Infers types from a related `ActiveRecord` model, using the SQL schema
120 | - Understands JS native types and how to map SQL columns: `string`, `boolean`, etc
121 | - Automatically types [associations](https://github.com/ElMassimo/oj_serializers#associations-dsl-), importing the generated types for the referenced serializers
122 | - Detects [conditional attributes](https://github.com/ElMassimo/oj_serializers#rendering-an-attribute-conditionally) and marks them as optional: `name?: string`
123 | - Fallback to a custom interface using `type_from`
124 | - Supports custom types and automatically adds the necessary imports
125 |
126 |
127 | ## Installation 💿
128 |
129 | Add this line to your application's Gemfile:
130 |
131 | ```ruby
132 | gem 'types_from_serializers'
133 | ```
134 |
135 | And then run:
136 |
137 | $ bundle install
138 |
139 | ## Usage 🚀
140 |
141 | To get started, [create a `BaseSerializer`](https://github.com/ElMassimo/types_from_serializers/blob/main/playground/vanilla/app/serializers/base_serializer.rb) that extends [`Oj::Serializer`][oj_serializers], and include the `TypesFromSerializers::DSL` module.
142 |
143 | ```ruby
144 | # app/serializers/base_serializer.rb
145 |
146 | class BaseSerializer < Oj::Serializer
147 | include TypesFromSerializers::DSL
148 | end
149 | ```
150 |
151 | > **Note**
152 | >
153 | > You can customize this behavior using [`base_serializers`][base_serializers].
154 |
155 | > **Warning**
156 | >
157 | > All serializers should extend one of the [`base_serializers`][base_serializers], or they won't be
158 | detected.
159 |
160 |
161 | ### SQL Attributes
162 |
163 | In most cases, you'll want to let `TypesFromSerializers` infer the types from the [SQL schema](https://github.com/ElMassimo/types_from_serializers/blob/main/playground/vanilla/db/schema.rb).
164 |
165 | If you are using `ActiveRecord`, the model related to the serializer will be inferred can be inferred from the serializer name:
166 |
167 | ```ruby
168 | UserSerializer => User
169 | ```
170 |
171 | It can also be inferred from an [object alias](https://github.com/ElMassimo/oj_serializers#using-a-different-alias-for-the-internal-object) if provided:
172 |
173 | ```ruby
174 | class PersonSerializer < BaseSerializer
175 | object_as :user
176 | ```
177 |
178 | In cases where we want to use a different alias, you can provide the model name explicitly:
179 |
180 | ```ruby
181 | class PersonSerializer < BaseSerializer
182 | object_as :person, model: :User
183 | ```
184 |
185 | ### Model Attributes
186 |
187 | When you want to be more strict than the SQL schema, or for attributes that are methods in the model, you can use:
188 |
189 | ```ruby
190 | attributes(
191 | name: {type: :string},
192 | status: {type: :Status}, # a custom type in ~/types/Status.ts
193 | )
194 | ```
195 |
196 | ### Serializer Attributes
197 |
198 | For attributes defined in the serializer, use the `type` helper:
199 |
200 | ```ruby
201 | type :boolean
202 | def suspended
203 | user.status.suspended?
204 | end
205 | ```
206 |
207 | > **Note**
208 | >
209 | > When specifying a type, [`attribute`](https://github.com/ElMassimo/oj_serializers#serializer_attributes) will be called automatically.
210 |
211 | ### Fallback Attributes
212 |
213 | You can also specify `types_from` to provide a TypeScript interface that should
214 | be used to obtain the field types:
215 |
216 | ```ruby
217 | class LocationSerializer < BaseSerializer
218 | object_as :location, types_from: :GoogleMapsLocation
219 |
220 | attributes(
221 | :lat,
222 | :lng,
223 | )
224 | end
225 | ```
226 |
227 | ```ts
228 | import GoogleMapsLocation from '~/types/GoogleMapsLocation'
229 |
230 | export default interface Location {
231 | lat: GoogleMapsLocation['lat']
232 | lng: GoogleMapsLocation['lng']
233 | }
234 | ```
235 |
236 | ## Generation 📜
237 |
238 | To get started, run `bin/rails s` to start the `Rails` development server.
239 |
240 | `TypesFromSerializers` will automatically register a `Rails` reloader, which
241 | detects changes to serializer files, and will generate code on-demand only for
242 | the modified files.
243 |
244 | It can also detect when new serializer files are added, or removed, and update
245 | the generated code accordingly.
246 |
247 | ### Manually
248 |
249 | To generate types manually, use the rake task:
250 |
251 | ```
252 | bundle exec rake types_from_serializers:generate
253 | ```
254 |
255 | or if you prefer to do it manually from the console:
256 |
257 | ```ruby
258 | require "types_from_serializers/generator"
259 |
260 | TypesFromSerializers.generate(force: true)
261 | ```
262 |
263 | ### With [`vite-plugin-full-reload`][vite-plugin-full-reload] ⚡️
264 |
265 | When using _[Vite Ruby]_, you can add [`vite-plugin-full-reload`][vite-plugin-full-reload]
266 | to automatically reload the page when modifying serializers, causing the Rails
267 | reload process to be triggered, which is when generation occurs.
268 |
269 | ```ts
270 | // vite.config.ts
271 | import { defineConfig } from 'vite'
272 | import ruby from 'vite-plugin-ruby'
273 | import reloadOnChange from 'vite-plugin-full-reload'
274 |
275 | defineConfig({
276 | plugins: [
277 | ruby(),
278 | reloadOnChange(['app/serializers/**/*.rb'], { delay: 200 }),
279 | ],
280 | })
281 | ```
282 |
283 | As a result, when modifying a serializer and hitting save, the type for that
284 | serializer will be updated instantly!
285 |
286 | ## Configuration ⚙️
287 |
288 | You can configure generation in a Rails initializer:
289 |
290 | ```ruby
291 | # config/initializers/types_from_serializers.rb
292 |
293 | if Rails.env.development?
294 | TypesFromSerializers.config do |config|
295 | config.name_from_serializer = ->(name) { name }
296 | end
297 | end
298 | ```
299 |
300 | ### `namespace`
301 |
302 | _Default:_ `nil`
303 |
304 | Allows to specify a TypeScript namespace and generate `.d.ts` to make types
305 | available globally, avoiding the need to import types explicitly.
306 |
307 | ### `base_serializers`
308 |
309 | _Default:_ `["BaseSerializer"]`
310 |
311 | Allows you to specify the base serializers, that are used to detect other
312 | serializers in the app that you would like to generate interfaces for.
313 |
314 | ### `serializers_dirs`
315 |
316 | _Default:_ `["app/serializers"]`
317 |
318 | The dirs where the serializer files are located.
319 |
320 | ### `output_dir`
321 |
322 | _Default:_ `"app/frontend/types/serializers"`
323 |
324 | The dir where the generated TypeScript interface files are placed.
325 |
326 | ### `custom_types_dir`
327 |
328 | _Default:_ `"app/frontend/types"`
329 |
330 | The dir where the custom types are placed.
331 |
332 | ### `name_from_serializer`
333 |
334 | _Default:_ `->(name) { name.delete_suffix("Serializer") }`
335 |
336 | A `Proc` that specifies how to convert the name of the serializer into the name
337 | of the generated TypeScript interface.
338 |
339 | ### `global_types`
340 |
341 | _Default:_ `["Array", "Record", "Date"]`
342 |
343 | Types that don't need to be imported in TypeScript.
344 |
345 | You can extend this list as needed if you are using global definitions.
346 |
347 | ### `skip_serializer_if`
348 |
349 | _Default:_ `->(serializer) { false }`
350 |
351 | You can provide a proc to avoid generating serializers.
352 |
353 | Along with `base_serializers`, this provides more fine-grained control in cases
354 | where a single backend supports several frontends, allowing to generate types
355 | separately.
356 |
357 | ### `sql_to_typescript_type_mapping`
358 |
359 | Specifies [how to map](https://github.com/ElMassimo/types_from_serializers/blob/main/types_from_serializers/lib/types_from_serializers/generator.rb#L297-L308) SQL column types to TypeScript native and custom types.
360 |
361 | ```ruby
362 | # Example: You have response middleware that automatically converts date strings
363 | # into Date objects, and you want TypeScript to treat those fields as `Date`.
364 | config.sql_to_typescript_type_mapping.update(
365 | date: :Date,
366 | datetime: :Date,
367 | )
368 |
369 | # Example: You won't transform fields when receiving data in the frontend
370 | # (date fields are serialized to JSON as strings).
371 | config.sql_to_typescript_type_mapping.update(
372 | date: :string,
373 | datetime: :string,
374 | )
375 |
376 | # Example: You plan to introduce types slowly, and don't want to be strict with
377 | # untyped fields, so treat them as `any` instead of `unknown`.
378 | config.sql_to_typescript_type_mapping.default = :any
379 | ```
380 |
381 | ### `transform_keys`
382 |
383 | _Default:_ `->(key) { key.camelize(:lower).chomp("?") }`
384 |
385 | You can provide a proc to transform property names.
386 |
387 | This library assumes that you will transform the casing client-side, but you can
388 | generate types preserving case by using `config.transform_keys = ->(key) { key }`.
389 |
390 | ## Contact ✉️
391 |
392 | Please use [Issues] to report bugs you find, and [Discussions] to make feature requests or get help.
393 |
394 | Don't hesitate to _⭐️ star the project_ if you find it useful!
395 |
396 | Using it in production? Always love to hear about it! 😃
397 |
398 | ## License
399 |
400 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
401 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "rspec/core/rake_task"
2 |
3 | RSpec::Core::RakeTask.new
4 |
5 | task default: :spec
6 |
--------------------------------------------------------------------------------
/bin/lint:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | bin/rubocop -A "$@"
5 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rake' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rake", "rake")
30 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rspec' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | ENV["UPDATE_SNAPSHOTS"] = "true" if ARGV.delete('--update') || ARGV.delete('--update-snapshots')
30 |
31 | load Gem.bin_path("rspec-core", "rspec")
32 |
--------------------------------------------------------------------------------
/bin/rubocop:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rubocop' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rubocop", "rubocop")
30 |
--------------------------------------------------------------------------------
/bin/standardrb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'standardrb' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("standard", "standardrb")
30 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile-rails-edge:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
4 |
5 | gem "rails", github: "rails/rails", branch: "main"
6 |
7 | gemspec path: "../types_from_serializers"
8 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile-rails.7.1.x:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "rails", "~> 7.1.0"
4 |
5 | gemspec path: "../types_from_serializers"
6 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile-rails.7.2.x:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "rails", "~> 7.2.0"
4 |
5 | gemspec path: "../types_from_serializers"
6 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | base = "docs/"
3 | ignore = "git diff --quiet 'HEAD^' HEAD ."
4 | publish = ".vitepress/dist"
5 | command = "yarn run build"
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "types_from_serializers_monorepo",
3 | "version": "1.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "scripts": {
7 | "clean": "rm -rf playground/vanilla/app/frontend/types/serializers",
8 | "migrate": "cd playground/vanilla && bundle install && bin/rails db:prepare",
9 | "gen": "cd playground/vanilla && bundle exec rake types_from_serializers:generate",
10 | "tsc": "npm -C playground/vanilla run tsc",
11 | "release": "node scripts/release",
12 | "lint": "eslint playground/vanilla/app/frontend scripts --ext .ts,.js,.vue",
13 | "postinstall": "husky install",
14 | "changelog": "node scripts/changelog",
15 | "test": "vitest"
16 | },
17 | "devDependencies": {
18 | "@antfu/eslint-config": "^0.25.2",
19 | "@types/node": "^14.18.21",
20 | "@vue/tsconfig": "^0.1.3",
21 | "chalk": "^4.1.2",
22 | "conventional-changelog-cli": "^2.2.2",
23 | "enquirer": "^2.3.6",
24 | "eslint": "^8",
25 | "execa": "^5.1.1",
26 | "happy-dom": "^2.55.0",
27 | "husky": "^5.2.0",
28 | "lint-staged": "^10.5.4",
29 | "minimist": "^1.2.6",
30 | "semver": "^7.3.7",
31 | "typescript": "^4.7.4",
32 | "vitest": "^0.18.0"
33 | },
34 | "lint-staged": {
35 | "*.{js,ts,tsx,jsx,vue}": [
36 | "eslint --fix"
37 | ],
38 | "*.rb": [
39 | "bin/rubocop -A"
40 | ]
41 | },
42 | "repository": {
43 | "type": "git",
44 | "url": "https://github.com/ElMassimo/types_from_serializers"
45 | },
46 | "homepage": "https://github.com/ElMassimo/types_from_serializers",
47 | "pnpm": {
48 | "overrides": {
49 | "eslint-plugin-vue": "9.2.0"
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/playground/vanilla/.browserslistrc:
--------------------------------------------------------------------------------
1 | defaults
2 |
--------------------------------------------------------------------------------
/playground/vanilla/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 | /db/*.sqlite3-*
14 |
15 | # Ignore all logfiles and tempfiles.
16 | /log/*
17 | /tmp/*
18 | !/log/.keep
19 | !/tmp/.keep
20 |
21 | # Ignore pidfiles, but keep the directory.
22 | /tmp/pids/*
23 | !/tmp/pids/
24 | !/tmp/pids/.keep
25 |
26 | # Ignore uploaded files in development.
27 | /storage/*
28 | !/storage/.keep
29 |
30 | /public/assets
31 | .byebug_history
32 |
33 | # Ignore master key for decrypting credentials and more.
34 | /config/master.key
35 |
36 | /public/packs
37 | /public/packs-test
38 | /node_modules
39 | /yarn-error.log
40 | yarn-debug.log*
41 | .yarn-integrity
42 |
43 | # Vite Ruby
44 | /public/vite
45 | /public/vite-dev
46 | /public/vite-test
47 | node_modules
48 | *.local
49 | .DS_Store
50 |
51 |
--------------------------------------------------------------------------------
/playground/vanilla/.npmrc:
--------------------------------------------------------------------------------
1 | ignore-workspace-root-check=true
2 | shamefully-hoist=true
3 |
--------------------------------------------------------------------------------
/playground/vanilla/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper
2 |
--------------------------------------------------------------------------------
/playground/vanilla/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | gem "rails", "~> 6.0.3", ">= 6.0.3.2"
5 |
6 | # Use Puma as the app server
7 | gem "puma", "~> 4.1"
8 |
9 | # Use Vite.js as the frontend tool. Read more: https://github.com/ElMassimo/vite_ruby
10 | gem "vite_rails"
11 |
12 | # Use JS From Routes to generate Api helpers.
13 | gem "js_from_routes", "~> 2.0.6"
14 |
15 | # A more efficient version of ActiveModelSerializers (https://github.com/ElMassimo/oj_serializers)
16 | gem "oj_serializers"
17 |
18 | # Generate TypeScript interfaces from Ruby serializers.
19 | gem "types_from_serializers", path: "../.."
20 |
21 | # Use SQL Lite as the database.
22 | gem "sqlite3"
23 |
24 | # Use Inertia.js as the nexus between Rails and Vue (https://inertiajs.com/)
25 | gem "inertia_rails", ">= 1.2.2"
26 |
27 | group :development, :test do
28 | gem "listen", "~> 3.7"
29 | gem "debug"
30 | gem "rspec-rails"
31 | gem "rails-controller-testing"
32 | end
33 |
--------------------------------------------------------------------------------
/playground/vanilla/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../..
3 | specs:
4 | types_from_serializers (2.3.0)
5 | listen (~> 3.2)
6 | oj_serializers (~> 2.0, >= 2.0.2)
7 | railties (>= 5.1)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | actioncable (6.0.6.1)
13 | actionpack (= 6.0.6.1)
14 | nio4r (~> 2.0)
15 | websocket-driver (>= 0.6.1)
16 | actionmailbox (6.0.6.1)
17 | actionpack (= 6.0.6.1)
18 | activejob (= 6.0.6.1)
19 | activerecord (= 6.0.6.1)
20 | activestorage (= 6.0.6.1)
21 | activesupport (= 6.0.6.1)
22 | mail (>= 2.7.1)
23 | actionmailer (6.0.6.1)
24 | actionpack (= 6.0.6.1)
25 | actionview (= 6.0.6.1)
26 | activejob (= 6.0.6.1)
27 | mail (~> 2.5, >= 2.5.4)
28 | rails-dom-testing (~> 2.0)
29 | actionpack (6.0.6.1)
30 | actionview (= 6.0.6.1)
31 | activesupport (= 6.0.6.1)
32 | rack (~> 2.0, >= 2.0.8)
33 | rack-test (>= 0.6.3)
34 | rails-dom-testing (~> 2.0)
35 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
36 | actiontext (6.0.6.1)
37 | actionpack (= 6.0.6.1)
38 | activerecord (= 6.0.6.1)
39 | activestorage (= 6.0.6.1)
40 | activesupport (= 6.0.6.1)
41 | nokogiri (>= 1.8.5)
42 | actionview (6.0.6.1)
43 | activesupport (= 6.0.6.1)
44 | builder (~> 3.1)
45 | erubi (~> 1.4)
46 | rails-dom-testing (~> 2.0)
47 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
48 | activejob (6.0.6.1)
49 | activesupport (= 6.0.6.1)
50 | globalid (>= 0.3.6)
51 | activemodel (6.0.6.1)
52 | activesupport (= 6.0.6.1)
53 | activerecord (6.0.6.1)
54 | activemodel (= 6.0.6.1)
55 | activesupport (= 6.0.6.1)
56 | activestorage (6.0.6.1)
57 | actionpack (= 6.0.6.1)
58 | activejob (= 6.0.6.1)
59 | activerecord (= 6.0.6.1)
60 | marcel (~> 1.0)
61 | activesupport (6.0.6.1)
62 | concurrent-ruby (~> 1.0, >= 1.0.2)
63 | i18n (>= 0.7, < 2)
64 | minitest (~> 5.1)
65 | tzinfo (~> 1.1)
66 | zeitwerk (~> 2.2, >= 2.2.2)
67 | builder (3.2.4)
68 | concurrent-ruby (1.2.2)
69 | crass (1.0.6)
70 | date (3.3.3)
71 | debug (1.7.2)
72 | irb (>= 1.5.0)
73 | reline (>= 0.3.1)
74 | diff-lcs (1.5.0)
75 | dry-cli (1.0.0)
76 | erubi (1.12.0)
77 | ffi (1.15.5)
78 | globalid (1.1.0)
79 | activesupport (>= 5.0)
80 | i18n (1.12.0)
81 | concurrent-ruby (~> 1.0)
82 | inertia_rails (3.0.0)
83 | rails (>= 5)
84 | io-console (0.6.0)
85 | irb (1.6.3)
86 | reline (>= 0.3.0)
87 | js_from_routes (2.0.6)
88 | railties (>= 5.1, < 8)
89 | listen (3.8.0)
90 | rb-fsevent (~> 0.10, >= 0.10.3)
91 | rb-inotify (~> 0.9, >= 0.9.10)
92 | loofah (2.20.0)
93 | crass (~> 1.0.2)
94 | nokogiri (>= 1.5.9)
95 | mail (2.8.1)
96 | mini_mime (>= 0.1.1)
97 | net-imap
98 | net-pop
99 | net-smtp
100 | marcel (1.0.2)
101 | method_source (1.0.0)
102 | mini_mime (1.1.2)
103 | mini_portile2 (2.8.1)
104 | minitest (5.18.0)
105 | net-imap (0.3.4)
106 | date
107 | net-protocol
108 | net-pop (0.1.2)
109 | net-protocol
110 | net-protocol (0.2.1)
111 | timeout
112 | net-smtp (0.3.3)
113 | net-protocol
114 | nio4r (2.5.9)
115 | nokogiri (1.14.2)
116 | mini_portile2 (~> 2.8.0)
117 | racc (~> 1.4)
118 | oj (3.14.2)
119 | oj_serializers (2.0.2)
120 | oj (>= 3.14.0)
121 | puma (4.3.12)
122 | nio4r (~> 2.0)
123 | racc (1.6.2)
124 | rack (2.2.6.4)
125 | rack-proxy (0.7.6)
126 | rack
127 | rack-test (2.1.0)
128 | rack (>= 1.3)
129 | rails (6.0.6.1)
130 | actioncable (= 6.0.6.1)
131 | actionmailbox (= 6.0.6.1)
132 | actionmailer (= 6.0.6.1)
133 | actionpack (= 6.0.6.1)
134 | actiontext (= 6.0.6.1)
135 | actionview (= 6.0.6.1)
136 | activejob (= 6.0.6.1)
137 | activemodel (= 6.0.6.1)
138 | activerecord (= 6.0.6.1)
139 | activestorage (= 6.0.6.1)
140 | activesupport (= 6.0.6.1)
141 | bundler (>= 1.3.0)
142 | railties (= 6.0.6.1)
143 | sprockets-rails (>= 2.0.0)
144 | rails-controller-testing (1.0.5)
145 | actionpack (>= 5.0.1.rc1)
146 | actionview (>= 5.0.1.rc1)
147 | activesupport (>= 5.0.1.rc1)
148 | rails-dom-testing (2.0.3)
149 | activesupport (>= 4.2.0)
150 | nokogiri (>= 1.6)
151 | rails-html-sanitizer (1.5.0)
152 | loofah (~> 2.19, >= 2.19.1)
153 | railties (6.0.6.1)
154 | actionpack (= 6.0.6.1)
155 | activesupport (= 6.0.6.1)
156 | method_source
157 | rake (>= 0.8.7)
158 | thor (>= 0.20.3, < 2.0)
159 | rake (13.0.6)
160 | rb-fsevent (0.11.2)
161 | rb-inotify (0.10.1)
162 | ffi (~> 1.0)
163 | reline (0.3.3)
164 | io-console (~> 0.5)
165 | rspec-core (3.12.1)
166 | rspec-support (~> 3.12.0)
167 | rspec-expectations (3.12.2)
168 | diff-lcs (>= 1.2.0, < 2.0)
169 | rspec-support (~> 3.12.0)
170 | rspec-mocks (3.12.5)
171 | diff-lcs (>= 1.2.0, < 2.0)
172 | rspec-support (~> 3.12.0)
173 | rspec-rails (5.1.2)
174 | actionpack (>= 5.2)
175 | activesupport (>= 5.2)
176 | railties (>= 5.2)
177 | rspec-core (~> 3.10)
178 | rspec-expectations (~> 3.10)
179 | rspec-mocks (~> 3.10)
180 | rspec-support (~> 3.10)
181 | rspec-support (3.12.0)
182 | sprockets (4.2.0)
183 | concurrent-ruby (~> 1.0)
184 | rack (>= 2.2.4, < 4)
185 | sprockets-rails (3.4.2)
186 | actionpack (>= 5.2)
187 | activesupport (>= 5.2)
188 | sprockets (>= 3.0.0)
189 | sqlite3 (1.6.2)
190 | mini_portile2 (~> 2.8.0)
191 | thor (1.2.1)
192 | thread_safe (0.3.6)
193 | timeout (0.3.2)
194 | tzinfo (1.2.11)
195 | thread_safe (~> 0.1)
196 | vite_rails (3.0.14)
197 | railties (>= 5.1, < 8)
198 | vite_ruby (~> 3.0, >= 3.2.2)
199 | vite_ruby (3.3.0)
200 | dry-cli (>= 0.7, < 2)
201 | rack-proxy (~> 0.6, >= 0.6.1)
202 | zeitwerk (~> 2.2)
203 | websocket-driver (0.7.5)
204 | websocket-extensions (>= 0.1.0)
205 | websocket-extensions (0.1.5)
206 | zeitwerk (2.6.7)
207 |
208 | PLATFORMS
209 | ruby
210 |
211 | DEPENDENCIES
212 | debug
213 | inertia_rails (>= 1.2.2)
214 | js_from_routes (~> 2.0.6)
215 | listen (~> 3.7)
216 | oj_serializers
217 | puma (~> 4.1)
218 | rails (~> 6.0.3, >= 6.0.3.2)
219 | rails-controller-testing
220 | rspec-rails
221 | sqlite3
222 | types_from_serializers!
223 | vite_rails
224 |
225 | BUNDLED WITH
226 | 2.3.22
227 |
--------------------------------------------------------------------------------
/playground/vanilla/Procfile.dev:
--------------------------------------------------------------------------------
1 | web: bin/rails s --port 3000
2 | vite: bin/vite dev
3 |
--------------------------------------------------------------------------------
/playground/vanilla/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/playground/vanilla/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | # Internal: Render an Inertia page that matches the current controller and action.
5 | def render_page(**props)
6 | render inertia: "#{controller_name}/#{action_name}", props: props
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/playground/vanilla/app/controllers/composers_controller.rb:
--------------------------------------------------------------------------------
1 | class ComposersController < ApplicationController
2 | def index
3 | render_page composers: ComposerSerializer.many(Composer.all)
4 | end
5 |
6 | def show
7 | render_page composer: ComposerWithSongsSerializer.one(Composer.find(params[:id]))
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/controllers/songs_controller.rb:
--------------------------------------------------------------------------------
1 | class SongsController < ApplicationController
2 | def index
3 | render_page songs: SongSerializer.many(Song.all.order(:title))
4 | end
5 |
6 | def show
7 | render_page song: SongWithVideosSerializer.one(Song.find(params[:id]))
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/controllers/videos_controller.rb:
--------------------------------------------------------------------------------
1 | class VideosController < ApplicationController
2 | def index
3 | render_page videos: VideoWithSongSerializer.many(VideoClip.all.order(:created_at))
4 | end
5 |
6 | def show
7 | render_page video: VideoWithSongSerializer.one(VideoClip.find(params[:id]))
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/api/ComposersApi.ts:
--------------------------------------------------------------------------------
1 | // JsFromRoutes CacheKey 657f5e8dc19e6058650cf9bba2b0ef3d
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
4 | import { definePathHelper } from '@js-from-routes/inertia'
5 |
6 | export default {
7 | index: definePathHelper('get', '/composers'),
8 | show: definePathHelper('get', '/composers/:id'),
9 | }
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/api/SongsApi.ts:
--------------------------------------------------------------------------------
1 | // JsFromRoutes CacheKey 3c504c24ef7fee1ff1bf10bc1f5939b4
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
4 | import { definePathHelper } from '@js-from-routes/inertia'
5 |
6 | export default {
7 | index: definePathHelper('get', '/songs'),
8 | show: definePathHelper('get', '/songs/:id'),
9 | }
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/api/VideosApi.ts:
--------------------------------------------------------------------------------
1 | // JsFromRoutes CacheKey 460b3f8cfdc3c7b11c64fb451b5063a8
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
4 | import { definePathHelper } from '@js-from-routes/inertia'
5 |
6 | export default {
7 | index: definePathHelper('get', '/videos'),
8 | show: definePathHelper('get', '/videos/:id'),
9 | }
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/api/all.ts:
--------------------------------------------------------------------------------
1 | // JsFromRoutes CacheKey 5d0e017a4589ba07dd01a67b2f32ddaa
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
4 | export { default as composers } from '~/api/ComposersApi'
5 | export { default as songs } from '~/api/SongsApi'
6 | export { default as videos } from '~/api/VideosApi'
7 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/api/index.ts:
--------------------------------------------------------------------------------
1 | // JsFromRoutes CacheKey a376287889ecd8b72c981c0366ab5059
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by JsFromRoutes.
4 | import * as helpers from '~/api/all'
5 | export * from '~/api/all'
6 | export default helpers
7 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/app.ts:
--------------------------------------------------------------------------------
1 | import { createApp as createVueApp, h } from 'vue'
2 | import { type CreateInertiaAppProps, createInertiaApp } from '~/inertia'
3 | import { resolvePage } from '~/pages'
4 | import '~/services/axios'
5 |
6 | type InertiaOptions = Omit
7 |
8 | export function createApp (options?: InertiaOptions) {
9 | return createInertiaApp({
10 | resolve: resolvePage,
11 | setup ({ plugin, app, props, el }) {
12 | const vueApp = createVueApp({ render: () => h(app, props) })
13 | .use(plugin)
14 |
15 | if (el)
16 | vueApp.mount(el)
17 |
18 | return vueApp
19 | },
20 | ...options,
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // Generated by unplugin-vue-components
5 | // Read more: https://github.com/vuejs/core/pull/3399
6 | import '@vue/runtime-core'
7 |
8 | export {}
9 |
10 | declare module '@vue/runtime-core' {
11 | export interface GlobalComponents {
12 | InertiaLink: typeof import('./components/InertiaLink.vue')['default']
13 | Link: typeof import('./components/Link.vue')['default']
14 | PageList: typeof import('./components/PageList.vue')['default']
15 | PageSubtitle: typeof import('./components/PageSubtitle.vue')['default']
16 | PageTitle: typeof import('./components/PageTitle.vue')['default']
17 | TheNavBar: typeof import('./components/TheNavBar.vue')['default']
18 | YouTubePlayer: typeof import('./components/YouTubePlayer.vue')['default']
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components/InertiaLink.vue:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components/Link.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
25 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components/PageList.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 |
11 |
16 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components/PageSubtitle.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components/PageTitle.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components/TheNavBar.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
24 |
25 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/components/YouTubePlayer.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
27 |
28 |
29 |
30 |
36 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/composables/page.ts:
--------------------------------------------------------------------------------
1 | export { usePage } from '@inertiajs/inertia-vue3'
2 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/entrypoints/application.ts:
--------------------------------------------------------------------------------
1 | import 'windi.css'
2 | import { createApp } from '~/app'
3 |
4 | createApp()
5 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/helpers/object.ts:
--------------------------------------------------------------------------------
1 | import { camelCase, isPlainObject, mapKeys, mapValues, snakeCase } from 'lodash-es'
2 |
3 | function convertKeys (object: any, keyConverter = camelCase) {
4 | if (isPlainObject(object))
5 | return mapKeys(object, (value, key) => keyConverter(key))
6 | else
7 | return object
8 | }
9 |
10 | function deepConvertKeys (object: any, keyConverter = decamelizeKeys, decamelizer = snakeCase): any {
11 | if (isPlainObject(object)) {
12 | return mapValues(
13 | keyConverter(object, decamelizer),
14 | (value: any) => deepConvertKeys(value, keyConverter, decamelizer),
15 | )
16 | }
17 |
18 | if (Array.isArray(object))
19 | return object.map((item: any) => deepConvertKeys(item, keyConverter, decamelizer))
20 |
21 | return object
22 | }
23 |
24 | // Public: Converts all object keys to camelCase, preserving the values.
25 | export function camelizeKeys (object: any) {
26 | return convertKeys(object, camelCase)
27 | }
28 |
29 | // Public: Converts all object keys to snake_case, preserving the values.
30 | export function decamelizeKeys (object: any, decamelizer = snakeCase) {
31 | return convertKeys(object, decamelizer)
32 | }
33 |
34 | // Public: Converts all object keys to camelCase, as well as nested objects, or
35 | // objects in nested arrays.
36 | export function deepCamelizeKeys (object: any) {
37 | return deepConvertKeys(object, camelizeKeys)
38 | }
39 |
40 | // Public: Converts all object keys to snake_case, as well as nested objects, or
41 | // objects in nested arrays.
42 | export function deepDecamelizeKeys (object: any, decamelizer = snakeCase) {
43 | return deepConvertKeys(object, decamelizeKeys, decamelizer)
44 | }
45 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | interface ImportMeta {
5 | globEagerDefault(pattern: string): Record
6 | }
7 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/inertia.ts:
--------------------------------------------------------------------------------
1 | import { Inertia, type Page } from '@inertiajs/inertia'
2 | import { deepCamelizeKeys } from '~/helpers/object'
3 | export { createInertiaApp, type CreateInertiaAppProps } from '@inertiajs/inertia-vue3'
4 |
5 | const router = Inertia as any
6 | router._handleInitialPageVisit = router.handleInitialPageVisit
7 | router.handleInitialPageVisit = function (page: Page) {
8 | page.props = deepCamelizeKeys(page.props)
9 | return this._handleInitialPageVisit(page)
10 | }
11 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/layouts/DefaultLayout.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
37 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/pages.ts:
--------------------------------------------------------------------------------
1 | import DefaultLayout from '~/layouts/DefaultLayout.vue'
2 |
3 | // NOTE: Split the client bundle by page to make the initial load faster.
4 | const pages = import.meta.env.SSR
5 | ? import.meta.globEagerDefault('./pages/**/*.vue')
6 | : import.meta.glob('./pages/**/*.vue')
7 |
8 | export async function resolvePage (name: string) {
9 | const pageOrFn = pages[`./pages/${name}.vue`]
10 |
11 | if (!pageOrFn)
12 | throw new Error(`Unknown page ${name}. Is it located under app/frontend/pages with a .vue extension?`)
13 |
14 | const page = import.meta.env.SSR ? pageOrFn : (await pageOrFn()).default
15 | page.layout = DefaultLayout
16 | return page
17 | }
18 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/pages/composers/index.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | Composers
10 |
11 |
12 |
13 | {{ composer.name }}
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/pages/composers/show.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 | {{ composer.name }}
11 |
12 |
13 | Songs in Library
14 |
15 |
16 |
17 | {{ song.title }}
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/pages/songs/index.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | Songs
10 |
11 |
12 |
13 | {{ song.title }}
14 |
15 |
16 | by
17 | {{ song.composer.name }}
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/pages/songs/show.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 | {{ song.title }}
11 |
12 |
13 | composed by {{ song.composer.name }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/pages/videos/index.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 | Latest Videos
11 |
12 |
13 |
14 |
15 | {{ video.song.title }}
16 |
17 |
18 | by
19 | {{ video.song.composer.name }}
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/services/axios.ts:
--------------------------------------------------------------------------------
1 | import axios, { type AxiosResponse } from 'axios'
2 | import { deepCamelizeKeys } from '~/helpers/object'
3 |
4 | // Configure axios to camelize response data from Rails.
5 | axios.interceptors.response.use(camelizeResponse, (error) => {
6 | if (error.response)
7 | camelizeResponse(error.response)
8 | throw error
9 | })
10 |
11 | // Converts the snake_case from Ruby to camelCase in JS.
12 | function camelizeResponse (response: AxiosResponse) {
13 | if (response.data)
14 | response.data = deepCamelizeKeys(response.data)
15 | return response
16 | }
17 |
18 | export default axios
19 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.json",
3 | "compilerOptions": {
4 | "ignoreDeprecations": "5.0",
5 | "baseUrl": ".",
6 | "lib": ["ESNext", "DOM"],
7 | "paths": {
8 | "~/*": ["./*"],
9 | "@/*": ["./*"]
10 | }
11 | },
12 | "exclude": ["**/dist", "**/node_modules", "**/public"]
13 | }
14 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/AnyModel.ts:
--------------------------------------------------------------------------------
1 | import type Video from '~/types/serializers/Video'
2 | import type Song from '~/types/serializers/Song'
3 |
4 | type AnyModel = Video | Song
5 | export default AnyModel
6 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/Composer.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey b379726b6fef2dadbfd614384b868746
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface Composer {
6 | id: number
7 | firstName?: string
8 | lastName?: string
9 | name: string
10 | }
11 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/ComposerWithSongs.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 9111c6d881174b0013582fa6366868db
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type ComposerWithSongsSong from './ComposerWithSongs/Song'
5 |
6 | export default interface ComposerWithSongs {
7 | id: number
8 | firstName?: string
9 | lastName?: string
10 | name: string
11 | songs: ComposerWithSongsSong[]
12 | }
13 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/ComposerWithSongs/Song.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey a69c0dbcc904740e5de89181ef4b2837
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface ComposerWithSongsSong {
6 | id: number
7 | title?: string
8 | }
9 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/Model.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey ed8ff6fbc986e6b666d559749c666dc5
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type AnyModel from '../AnyModel'
5 |
6 | export default interface Model {
7 | id: AnyModel['id']
8 | title: AnyModel['title']
9 | }
10 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/Nested/Album.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 58897b61d86838c60d1f12700f326896
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type AnyModel from '../../AnyModel'
5 | import type Song from '../Song'
6 |
7 | export default interface NestedAlbum {
8 | id: AnyModel['id']
9 | title: AnyModel['title']
10 | tracks: Song[]
11 | }
12 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/SnakeComposer.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 89e5760366d01f7f21244d341e1af2d4
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface SnakeComposer {
6 | id: number
7 | first_name?: string
8 | last_name?: string
9 | name: string
10 | }
11 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/Song.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey f4f9e398dc092747d13607e2bdcb0846
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Composer from './Composer'
5 |
6 | export default interface Song {
7 | id: number
8 | composer: Composer
9 | genre: "fingerstyle" | "rock" | "classical"
10 | tempo: "slow" | "medium" | "fast"
11 | title?: string
12 | }
13 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/SongWithVideos.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey c2d167325e7342eba01d0a78482d6d76
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Composer from './Composer'
5 | import type Video from './Video'
6 |
7 | export default interface SongWithVideos {
8 | id: number
9 | composer: Composer
10 | genre: "fingerstyle" | "rock" | "classical"
11 | tempo: "slow" | "medium" | "fast"
12 | title?: string
13 | videos: Video[]
14 | }
15 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/Video.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 45d6b515fb6118eabbd39d586269cdd0
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface Video {
6 | id: number
7 | createdAt: string | Date
8 | title?: string
9 | untypedFieldExample: any
10 | youtubeId?: string
11 | youtubeUrl?: string
12 | }
13 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/VideoWithSong.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 4042db0fa9ebdf8668c7c5f0ec172e6d
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Song from './Song'
5 |
6 | export default interface VideoWithSong {
7 | id: number
8 | createdAt: string | Date
9 | song: Song
10 | title?: string
11 | untypedFieldExample: any
12 | youtubeId?: string
13 | youtubeUrl?: string
14 | }
15 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/types/serializers/index.ts:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 6e82f9b05515cf2d2fd0ea698478f2de
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | export type { default as Composer } from './Composer'
5 | export type { default as ComposerWithSongs } from './ComposerWithSongs'
6 | export type { default as Model } from './Model'
7 | export type { default as NestedAlbum } from './Nested/Album'
8 | export type { default as SnakeComposer } from './SnakeComposer'
9 | export type { default as Song } from './Song'
10 | export type { default as SongWithVideos } from './SongWithVideos'
11 | export type { default as Video } from './Video'
12 | export type { default as VideoWithSong } from './VideoWithSong'
13 |
--------------------------------------------------------------------------------
/playground/vanilla/app/frontend/windi.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite-plugin-windicss'
2 |
3 | export default defineConfig({
4 | preflight: {
5 | safelist: ['a'],
6 | },
7 | extract: {
8 | include: [
9 | '**/*.vue',
10 | ],
11 | exclude: [
12 | 'windi.config.ts',
13 | ],
14 | },
15 | theme: {
16 | screens: {
17 | 'sm': '450px',
18 | 'md': '450px',
19 | 'lg': '1024px',
20 | 'xl': '1024px',
21 | '2xl': '1024px',
22 | },
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/playground/vanilla/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/playground/vanilla/app/models/composer.rb:
--------------------------------------------------------------------------------
1 | class Composer < ApplicationRecord
2 | has_many :songs
3 | has_many :video_clips, through: :songs
4 |
5 | def name
6 | [first_name, last_name].compact.join(" ")
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/playground/vanilla/app/models/song.rb:
--------------------------------------------------------------------------------
1 | class Song < ApplicationRecord
2 | belongs_to :composer
3 | has_many :video_clips
4 |
5 | enum genre: {fingerstyle: "fingerstyle", rock: "rock", classical: "classical"}
6 | enum tempo: %w[slow medium fast]
7 | end
8 |
--------------------------------------------------------------------------------
/playground/vanilla/app/models/video_clip.rb:
--------------------------------------------------------------------------------
1 | class VideoClip < ApplicationRecord
2 | belongs_to :song
3 | has_one :composer, through: :song
4 |
5 | def youtube_url
6 | "https://www.youtube.com/watch?v=#{youtube_id}" if youtube_id
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/base_serializer.rb:
--------------------------------------------------------------------------------
1 | class BaseSerializer < Oj::Serializer
2 | include TypesFromSerializers::DSL
3 | end
4 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/composer_serializer.rb:
--------------------------------------------------------------------------------
1 | class ComposerSerializer < BaseSerializer
2 | attributes(
3 | :id,
4 | :last_name,
5 | :first_name,
6 | name: {type: :string},
7 | )
8 | end
9 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/composer_with_songs_serializer.rb:
--------------------------------------------------------------------------------
1 | class ComposerWithSongsSerializer < ComposerSerializer
2 | class SongSerializer < BaseSerializer
3 | attributes(:id, :title)
4 | end
5 |
6 | has_many :songs, serializer: SongSerializer
7 | end
8 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/model_serializer.rb:
--------------------------------------------------------------------------------
1 | class ModelSerializer < BaseSerializer
2 | object_as :model, types_from: :AnyModel
3 |
4 | attributes(
5 | :id,
6 | :title,
7 | )
8 | end
9 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/nested/album_serializer.rb:
--------------------------------------------------------------------------------
1 | class Nested::AlbumSerializer < BaseSerializer
2 | flat_one :album, serializer: ModelSerializer
3 |
4 | has_many :songs, as: :tracks, serializer: SongSerializer
5 | end
6 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/snake_composer_serializer.rb:
--------------------------------------------------------------------------------
1 | class SnakeComposerSerializer < ComposerSerializer
2 | transform_keys ->(key) { key }
3 | end
4 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/song_serializer.rb:
--------------------------------------------------------------------------------
1 | class SongSerializer < BaseSerializer
2 | attributes(
3 | :id,
4 | :title,
5 | :genre,
6 | :tempo,
7 | )
8 |
9 | has_one :composer, serializer: ComposerSerializer
10 | end
11 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/song_with_videos_serializer.rb:
--------------------------------------------------------------------------------
1 | class SongWithVideosSerializer < SongSerializer
2 | has_many :video_clips, as: :videos, serializer: VideoSerializer
3 | end
4 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/video_serializer.rb:
--------------------------------------------------------------------------------
1 | class VideoSerializer < BaseSerializer
2 | object_as :video_clip
3 |
4 | attributes(
5 | :id,
6 | :created_at,
7 | :title,
8 | :youtube_id,
9 | )
10 |
11 | type :string, optional: true
12 | def youtube_url
13 | video.youtube_url
14 | end
15 |
16 | attribute :untyped_field_example do
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/playground/vanilla/app/serializers/video_with_song_serializer.rb:
--------------------------------------------------------------------------------
1 | class VideoWithSongSerializer < VideoSerializer
2 | has_one :song, serializer: SongSerializer
3 | end
4 |
--------------------------------------------------------------------------------
/playground/vanilla/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Types From Serializers
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= vite_client_tag %>
10 | <%= vite_typescript_tag 'application' %>
11 |
12 |
13 |
14 | <%= yield %>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/playground/vanilla/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/o
34 | bundler_version = $1
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../../Gemfile", __FILE__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/o
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_version
64 | @bundler_version ||=
65 | env_var_version || cli_arg_version ||
66 | lockfile_version
67 | end
68 |
69 | def bundler_requirement
70 | return "#{Gem::Requirement.default}.a" unless bundler_version
71 |
72 | bundler_gem_version = Gem::Version.new(bundler_version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77 |
78 | requirement += ".a" if bundler_gem_version.prerelease?
79 |
80 | requirement
81 | end
82 |
83 | def load_bundler!
84 | ENV["BUNDLE_GEMFILE"] ||= gemfile
85 |
86 | activate_bundler
87 | end
88 |
89 | def activate_bundler
90 | gem_error = activation_error_handling do
91 | gem "bundler", bundler_requirement
92 | end
93 | return if gem_error.nil?
94 | require_error = activation_error_handling do
95 | require "bundler/version"
96 | end
97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99 | exit 42
100 | end
101 |
102 | def activation_error_handling
103 | yield
104 | nil
105 | rescue StandardError, LoadError => e
106 | e
107 | end
108 | end
109 |
110 | m.load_bundler!
111 |
112 | if m.invoked_as_script?
113 | load Gem.bin_path("bundler", "bundle")
114 | end
115 |
--------------------------------------------------------------------------------
/playground/vanilla/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/playground/vanilla/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/playground/vanilla/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path("..", __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to setup or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts "== Installing dependencies =="
17 | system! "gem install bundler --conservative"
18 | system("bundle check") || system!("bundle install")
19 |
20 | # Install JavaScript dependencies
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! "bin/rails db:prepare"
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! "bin/rails log:clear tmp:clear"
33 |
34 | puts "\n== Restarting application server =="
35 | system! "bin/rails restart"
36 | end
37 |
--------------------------------------------------------------------------------
/playground/vanilla/bin/vite:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'vite' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300))
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("vite_ruby", "vite")
30 |
--------------------------------------------------------------------------------
/playground/vanilla/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/playground/vanilla/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "action_controller/railtie"
4 | require "active_record/railtie"
5 |
6 | # Require the gems listed in Gemfile, including any gems
7 | # you've limited to :test, :development, or :production.
8 | Bundler.require(*Rails.groups)
9 |
10 | module SampleApp
11 | class Application < Rails::Application
12 | config.autoloader = :zeitwerk
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/playground/vanilla/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/playground/vanilla/config/database.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: sqlite3
3 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
4 | timeout: 5000
5 | database: db/development.sqlite3
6 |
7 | # Warning: The database defined as "test" will be erased and
8 | # re-generated from your development database when you run "rake".
9 | # Do not set this db to the same as development or production.
10 | test:
11 | adapter: sqlite3
12 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
13 | timeout: 5000
14 | database: db/test.sqlite3
15 |
16 | production:
17 | adapter: sqlite3
18 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
19 | timeout: 5000
20 | database: db/production.sqlite3
21 |
--------------------------------------------------------------------------------
/playground/vanilla/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/playground/vanilla/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Store uploaded files on the local file system (see config/storage.yml for options).
16 | # Print deprecation notices to the Rails logger.
17 | config.active_support.deprecation = :log
18 |
19 | # Raises error for missing translations.
20 | # config.action_view.raise_on_missing_translations = true
21 |
22 | # Use an evented file watcher to asynchronously detect changes in source code,
23 | # routes, locales, etc. This feature depends on the listen gem.
24 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
25 | end
26 |
--------------------------------------------------------------------------------
/playground/vanilla/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 | config.cache_classes = true
4 | config.eager_load = true
5 | end
6 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # Allow @vite/client to hot reload changes in development
14 | # policy.script_src *policy.script_src, :unsafe_eval, "http://localhost:3036" if Rails.env.development?
15 |
16 | # You may need to enable this in production as well depending on your setup.
17 | # policy.script_src *policy.script_src, :blob if Rails.env.test?
18 | # policy.style_src :self, :https
19 | # # If you are using webpack-dev-server then specify webpack-dev-server host
20 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
21 | # Allow @vite/client to hot reload changes in development
22 | # policy.connect_src *policy.connect_src, "ws://#{ ViteRuby.config.host_with_port }" if Rails.env.development?
23 |
24 | # # Specify URI for violation reports
25 | # # policy.report_uri "/csp-violation-report-endpoint"
26 | # end
27 |
28 | # If you are using UJS then enable automatic nonce generation
29 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
30 |
31 | # Set the nonce only to specific directives
32 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
33 |
34 | # Report CSP violations to a specified URI
35 | # For further information see the following documentation:
36 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
37 | # Rails.application.config.content_security_policy_report_only = true
38 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/js_from_routes.rb:
--------------------------------------------------------------------------------
1 | require "ostruct"
2 |
3 | if Rails.env.development?
4 | JsFromRoutes.config do |config|
5 | config.file_suffix = "Api.ts"
6 | config.client_library = "@js-from-routes/inertia"
7 | config.helper_mappings = {"index" => "index", "show" => "show"}
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/types_from_serializers.rb:
--------------------------------------------------------------------------------
1 | if Rails.env.development?
2 | TypesFromSerializers.config do |config|
3 | config.sql_to_typescript_type_mapping.default = :any
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/playground/vanilla/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) { wrap_parameters format: [:json] }
8 |
9 | # To enable root element in JSON for ActiveRecord objects.
10 | # ActiveSupport.on_load(:active_record) do
11 | # self.include_root_in_json = true
12 | # end
13 |
--------------------------------------------------------------------------------
/playground/vanilla/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/playground/vanilla/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS", max_threads_count)
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
12 | #
13 | port ENV.fetch("PORT", 3000)
14 |
15 | # Specifies the `environment` that Puma will run in.
16 | #
17 | environment ENV.fetch("RAILS_ENV") { "development" }
18 |
19 | # Specifies the `pidfile` that Puma will use.
20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
21 |
22 | # Specifies the number of `workers` to boot in clustered mode.
23 | # Workers are forked web server processes. If using threads and workers together
24 | # the concurrency of the application would be max `threads` * `workers`.
25 | # Workers do not work on JRuby or Windows (both of which do not support
26 | # processes).
27 | #
28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
29 |
30 | # Use the `preload_app!` method when specifying a `workers` number.
31 | # This directive tells Puma to first boot the application and load code
32 | # before forking the application. This takes advantage of Copy On Write
33 | # process behavior so workers use less memory.
34 | #
35 | # preload_app!
36 |
37 | # Allow puma to be restarted by `rails restart` command.
38 | plugin :tmp_restart
39 |
--------------------------------------------------------------------------------
/playground/vanilla/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root to: "videos#index"
3 |
4 | defaults export: true do
5 | resources :composers, only: %i[index show], export: true
6 | resources :songs, only: %i[index show], export: true
7 | resources :videos, only: %i[index show], export: true
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/playground/vanilla/config/vite.json:
--------------------------------------------------------------------------------
1 | {
2 | "all": {
3 | "sourceCodeDir": "app/frontend",
4 | "watchAdditionalPaths": []
5 | },
6 | "development": {
7 | "autoBuild": true,
8 | "publicOutputDir": "vite-dev",
9 | "port": 3036
10 | },
11 | "test": {
12 | "autoBuild": true,
13 | "publicOutputDir": "vite-test"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/playground/vanilla/db/migrate/20220709144820_create_composers.rb:
--------------------------------------------------------------------------------
1 | class CreateComposers < ActiveRecord::Migration[6.0]
2 | def change
3 | create_table :composers do |t|
4 | t.text :first_name
5 | t.text :last_name
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/playground/vanilla/db/migrate/20220709151241_create_video_clips.rb:
--------------------------------------------------------------------------------
1 | class CreateVideoClips < ActiveRecord::Migration[6.0]
2 | def change
3 | create_table :video_clips do |t|
4 | t.text :title
5 | t.text :youtube_id
6 | t.references :song, foreign_key: true
7 | t.references :composer, foreign_key: true
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/playground/vanilla/db/migrate/20220709151259_create_songs.rb:
--------------------------------------------------------------------------------
1 | class CreateSongs < ActiveRecord::Migration[6.0]
2 | def change
3 | create_table :songs do |t|
4 | t.text :title
5 | t.references :composer, foreign_key: true
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/playground/vanilla/db/migrate/20240227112250_add_enums_to_songs.rb:
--------------------------------------------------------------------------------
1 | class AddEnumsToSongs < ActiveRecord::Migration[6.0]
2 | def change
3 | add_column :songs, :genre, :string, null: false
4 | add_column :songs, :tempo, :integer, null: true
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/playground/vanilla/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # This file is the source Rails uses to define your schema when running `rails
6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
7 | # be faster and is potentially less error prone than running all of your
8 | # migrations from scratch. Old migrations may fail to apply correctly if those
9 | # migrations use external dependencies or application code.
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 2024_02_27_112250) do
14 | create_table "composers", force: :cascade do |t|
15 | t.text "first_name"
16 | t.text "last_name"
17 | t.datetime "created_at", precision: 6, null: false
18 | t.datetime "updated_at", precision: 6, null: false
19 | end
20 |
21 | create_table "songs", force: :cascade do |t|
22 | t.text "title"
23 | t.integer "composer_id"
24 | t.datetime "created_at", precision: 6, null: false
25 | t.datetime "updated_at", precision: 6, null: false
26 | t.string "genre", null: false
27 | t.integer "tempo"
28 | t.index ["composer_id"], name: "index_songs_on_composer_id"
29 | end
30 |
31 | create_table "video_clips", force: :cascade do |t|
32 | t.text "title"
33 | t.text "youtube_id"
34 | t.integer "song_id"
35 | t.integer "composer_id"
36 | t.datetime "created_at", precision: 6, null: false
37 | t.datetime "updated_at", precision: 6, null: false
38 | t.index ["composer_id"], name: "index_video_clips_on_composer_id"
39 | t.index ["song_id"], name: "index_video_clips_on_song_id"
40 | end
41 |
42 | add_foreign_key "songs", "composers"
43 | add_foreign_key "video_clips", "composers"
44 | add_foreign_key "video_clips", "songs"
45 | end
46 |
--------------------------------------------------------------------------------
/playground/vanilla/db/seeds.rb:
--------------------------------------------------------------------------------
1 | dylan_ryche = Composer.create!(first_name: "Dylan", last_name: "Ryche")
2 | maximo_mussini = Composer.create!(first_name: "Máximo", last_name: "Mussini")
3 | leon_gieco = Composer.create!(first_name: "León", last_name: "Gieco")
4 | calum_graham = Composer.create!(first_name: "Calum", last_name: "Graham")
5 | matteo_brenci = Composer.create!(first_name: "Matteo", last_name: "Brenci")
6 | michael_hedges = Composer.create!(first_name: "Michael", last_name: "Hedges")
7 | antonio_lauro = Composer.create!(first_name: "Antonio", last_name: "Lauro")
8 | celso_machado = Composer.create!(first_name: "Celso", last_name: "Machado")
9 | astor_piazzolla = Composer.create!(first_name: "Astor", last_name: "Piazzolla")
10 | jorge_cardoso = Composer.create!(first_name: "Jorge", last_name: "Cardoso")
11 | domingos_semenzato = Composer.create!(first_name: "Domingos", last_name: "Semenzato")
12 |
13 | smoke_signals = Song.create(title: "Smoke Signals", composer: dylan_ryche, genre: "fingerstyle")
14 | camino_libre = Song.create(title: "Camino Libre", composer: maximo_mussini, genre: "fingerstyle")
15 | sin_querer = Song.create(title: "Sin Querer", composer: leon_gieco, genre: "fingerstyle")
16 | tabula_rasa = Song.create(title: "Tabula Rasa", composer: calum_graham, genre: "fingerstyle")
17 | raindance = Song.create(title: "Raindance", composer: matteo_brenci, genre: "fingerstyle")
18 | ragamuffin = Song.create(title: "Ragamuffin", composer: michael_hedges, genre: "fingerstyle")
19 | vals_venezolano_n_2 = Song.create(title: "Vals Venezolano Nº 2", composer: antonio_lauro, genre: "classical")
20 | xaranga_do_vovo = Song.create(title: "Xaranga do Vovô", composer: celso_machado, genre: "classical")
21 | cafe_1930 = Song.create(title: "Café 1930", composer: astor_piazzolla, genre: "classical")
22 | milonga_uruguay = Song.create(title: "Milonga (Uruguay)", composer: jorge_cardoso, genre: "classical")
23 | divagando = Song.create(title: "Divagando", composer: domingos_semenzato, genre: "classical")
24 |
25 | VideoClip.create!(id: 11, song: smoke_signals, youtube_id: "3glhadBFKGc", title: "Máximo Mussini interpreta \"Smoke Signals\" por Dylan Ryche")
26 | VideoClip.create!(id: 10, song: camino_libre, youtube_id: "7cUCxXuBk_8", title: "\"Camino Libre\" by Máximo Mussini")
27 | VideoClip.create!(id: 9, song: sin_querer, youtube_id: "birhb9Ozmws", title: "Máximo Mussini plays \"Sin Querer\" by León Gieco")
28 | VideoClip.create!(id: 8, song: tabula_rasa, youtube_id: "AE00vTJQ7z4", title: "Máximo Mussini plays \"Tabula Rasa\" by Calum Graham")
29 | VideoClip.create!(id: 7, song: raindance, youtube_id: "UbeLr5dwwtI", title: "Máximo Mussini plays \"Raindance\" by Matteo Brenci")
30 | VideoClip.create!(id: 6, song: ragamuffin, youtube_id: "Yf8pSlJimIc", title: "Máximo Mussini plays \"Ragamuffin\" by Michael Hedges")
31 | VideoClip.create!(id: 5, song: vals_venezolano_n_2, youtube_id: "Qa-dX-iFrRg", title: "Máximo Mussini plays \"Vals Venezolano Nº 2\" by Antonio Lauro")
32 | VideoClip.create!(id: 4, song: xaranga_do_vovo, youtube_id: "p-932nEHYko", title: "Máximo Mussini plays \"Xaranga do Vovô\" by Celso Machado")
33 | VideoClip.create!(id: 3, song: cafe_1930, youtube_id: "gT6rDZVPPWo", title: "Máximo Mussini plays \"Café 1930 (Intro)\" by Astor Piazzolla")
34 | VideoClip.create!(id: 2, song: milonga_uruguay, youtube_id: "fUguoMhv7fs", title: "Máximo Mussini plays \"Milonga (Uruguay)\" by Jorge Cardoso")
35 | VideoClip.create!(id: 1, song: divagando, youtube_id: "_iAT3nXfpT0", title: "Máximo Mussini plays \"Divagando (Choro)\" by Domingos Semenzato")
36 |
--------------------------------------------------------------------------------
/playground/vanilla/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sample_app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "tsc": "vue-tsc --noEmit --project app/frontend"
7 | },
8 | "dependencies": {
9 | "@inertiajs/inertia": "^0.11.1",
10 | "@inertiajs/inertia-vue3": "^0.6.0",
11 | "@js-from-routes/core": "^1.0.2",
12 | "@js-from-routes/inertia": "^1.10.2",
13 | "@vueuse/core": "^9.13",
14 | "lodash-es": "^4.17.21",
15 | "vue": "*"
16 | },
17 | "devDependencies": {
18 | "@types/lodash-es": "^4.17.7",
19 | "@vitejs/plugin-vue": "^4.1.0",
20 | "typescript": "^5.0.3",
21 | "unplugin-vue-components": "^0.24.1",
22 | "vite": "^4.2.1",
23 | "vite-plugin-full-reload": "^1.0.5",
24 | "vite-plugin-ruby": "^3.2.1",
25 | "vite-plugin-windicss": "^1.8.10",
26 | "vue-tsc": "^1.2.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/playground/vanilla/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: 5.4
2 |
3 | importers:
4 |
5 | .:
6 | specifiers:
7 | '@inertiajs/inertia': ^0.11.0
8 | '@inertiajs/inertia-vue3': ^0.6.0
9 | '@js-from-routes/core': ^1.0
10 | '@js-from-routes/inertia': ^1.10
11 | '@types/lodash-es': ^4.17.6
12 | '@vitejs/plugin-vue': ^2.3
13 | '@vueuse/core': ^8.9.1
14 | lodash-es: ^4.17.21
15 | typescript: ^4
16 | unplugin-vue-components: ^0.21.1
17 | vite: ^2.9.6
18 | vite-plugin-full-reload: ^1.0.1
19 | vite-plugin-ruby: ^3.0.12
20 | vite-plugin-windicss: ^1.8
21 | vue: ^3.2.36
22 | vue-tsc: ^0.38.5
23 | dependencies:
24 | '@inertiajs/inertia': 0.11.0
25 | '@inertiajs/inertia-vue3': 0.6.0_lgz5zmprnxmfmyvgtmvu2blgda
26 | '@js-from-routes/core': 1.0.2
27 | '@js-from-routes/inertia': 1.10.2_533xi4bmpzky5rb45mm5wj4unm
28 | '@vueuse/core': 8.9.1_vue@3.2.37
29 | lodash-es: 4.17.21
30 | vue: 3.2.37
31 | devDependencies:
32 | '@types/lodash-es': 4.17.6
33 | '@vitejs/plugin-vue': 2.3.3_vite@2.9.14+vue@3.2.37
34 | typescript: 4.7.4
35 | unplugin-vue-components: 0.21.1_vite@2.9.14+vue@3.2.37
36 | vite: 2.9.14
37 | vite-plugin-full-reload: 1.0.1
38 | vite-plugin-ruby: 3.0.12_vite@2.9.14
39 | vite-plugin-windicss: 1.8.6_vite@2.9.14
40 | vue-tsc: 0.38.5_typescript@4.7.4
41 |
42 | packages:
43 |
44 | /@antfu/utils/0.5.2:
45 | resolution: {integrity: sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==}
46 | dev: true
47 |
48 | /@babel/helper-validator-identifier/7.18.6:
49 | resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
50 | engines: {node: '>=6.9.0'}
51 |
52 | /@babel/parser/7.18.8:
53 | resolution: {integrity: sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==}
54 | engines: {node: '>=6.0.0'}
55 | hasBin: true
56 | dependencies:
57 | '@babel/types': 7.18.8
58 |
59 | /@babel/types/7.18.8:
60 | resolution: {integrity: sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==}
61 | engines: {node: '>=6.9.0'}
62 | dependencies:
63 | '@babel/helper-validator-identifier': 7.18.6
64 | to-fast-properties: 2.0.0
65 |
66 | /@inertiajs/inertia-vue3/0.6.0_lgz5zmprnxmfmyvgtmvu2blgda:
67 | resolution: {integrity: sha512-qhPBtd/G0VS7vVVbYw1rrqKB6JqRusxqt+5ec2GLmK6t7fTlBBnZ3KsakmGZLSM1m1OGkNcfn4ifmCk3zfA8RQ==}
68 | peerDependencies:
69 | '@inertiajs/inertia': ^0.11.0
70 | vue: ^3.0.0
71 | dependencies:
72 | '@inertiajs/inertia': 0.11.0
73 | lodash.clonedeep: 4.5.0
74 | lodash.isequal: 4.5.0
75 | vue: 3.2.37
76 | dev: false
77 |
78 | /@inertiajs/inertia/0.11.0:
79 | resolution: {integrity: sha512-QF4hctgFC+B/t/WClCwfOla+WoDE9iTltQJ0u+DCfjl0KdGoCvIxYiNtuH8h8oM+RQMb8orjbpW3pHapjYI5Vw==}
80 | dependencies:
81 | axios: 0.21.4
82 | deepmerge: 4.2.2
83 | qs: 6.11.0
84 | transitivePeerDependencies:
85 | - debug
86 | dev: false
87 |
88 | /@js-from-routes/core/1.0.2:
89 | resolution: {integrity: sha512-7VEYGz4VfkgFb7MDtGEIaGYvn/9oleBaRqtSREFuV9hPooeyrI+ghsP8U4WJlCHFndtTbatUTzXRljt1zJH4Gw==}
90 | dev: false
91 |
92 | /@js-from-routes/inertia/1.10.2_533xi4bmpzky5rb45mm5wj4unm:
93 | resolution: {integrity: sha512-4Lv016v6ir7Ayp8ZsWzlMZfOEHaajd53ZwiueUzrtzkkZ5+HmUW9AmtEK29jom4qmBLVksVyreQL6BjxdEx6/w==}
94 | peerDependencies:
95 | '@inertiajs/inertia': ^0.11
96 | '@js-from-routes/core': ^1.0.0
97 | dependencies:
98 | '@inertiajs/inertia': 0.11.0
99 | '@js-from-routes/core': 1.0.2
100 | dev: false
101 |
102 | /@nodelib/fs.scandir/2.1.5:
103 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
104 | engines: {node: '>= 8'}
105 | dependencies:
106 | '@nodelib/fs.stat': 2.0.5
107 | run-parallel: 1.2.0
108 | dev: true
109 |
110 | /@nodelib/fs.stat/2.0.5:
111 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
112 | engines: {node: '>= 8'}
113 | dev: true
114 |
115 | /@nodelib/fs.walk/1.2.8:
116 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
117 | engines: {node: '>= 8'}
118 | dependencies:
119 | '@nodelib/fs.scandir': 2.1.5
120 | fastq: 1.13.0
121 | dev: true
122 |
123 | /@rollup/pluginutils/4.2.1:
124 | resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
125 | engines: {node: '>= 8.0.0'}
126 | dependencies:
127 | estree-walker: 2.0.2
128 | picomatch: 2.3.1
129 | dev: true
130 |
131 | /@types/lodash-es/4.17.6:
132 | resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
133 | dependencies:
134 | '@types/lodash': 4.14.182
135 | dev: true
136 |
137 | /@types/lodash/4.14.182:
138 | resolution: {integrity: sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==}
139 | dev: true
140 |
141 | /@types/web-bluetooth/0.0.14:
142 | resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==}
143 | dev: false
144 |
145 | /@vitejs/plugin-vue/2.3.3_vite@2.9.14+vue@3.2.37:
146 | resolution: {integrity: sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==}
147 | engines: {node: '>=12.0.0'}
148 | peerDependencies:
149 | vite: ^2.5.10
150 | vue: ^3.2.25
151 | dependencies:
152 | vite: 2.9.14
153 | vue: 3.2.37
154 | dev: true
155 |
156 | /@volar/code-gen/0.38.5:
157 | resolution: {integrity: sha512-GRGhPKoNtRwZyn9M0b2buobeMR1Aj9zxZI0osanLG9vB9YCnJov1myxKU8EJV5NobpyspLIv1X6/BEHLZNsKig==}
158 | dependencies:
159 | '@volar/source-map': 0.38.5
160 | dev: true
161 |
162 | /@volar/source-map/0.38.5:
163 | resolution: {integrity: sha512-TyTLkOtAW/7qnl4Gabt4W4vcKPBPCBdaPLKwkMglKcaX70lPH2CIwZcPMJo6PAilbUVXcuX86xfgdncWDKKaZQ==}
164 | dev: true
165 |
166 | /@volar/vue-code-gen/0.38.5:
167 | resolution: {integrity: sha512-4t2bX2bCmmlyYwPLqfH3AJXj9Km79uRmCy81recc8LB8ZT2Z9hOFNBAnlGNcCeZAtVWtmHVV7sXJtQAJQxkWeg==}
168 | dependencies:
169 | '@volar/code-gen': 0.38.5
170 | '@volar/source-map': 0.38.5
171 | '@vue/compiler-core': 3.2.37
172 | '@vue/compiler-dom': 3.2.37
173 | '@vue/shared': 3.2.37
174 | dev: true
175 |
176 | /@volar/vue-typescript/0.38.5:
177 | resolution: {integrity: sha512-Gobtg5gxR3bf/l1h300OWCWkvDQnOINgnxkDYiF8qdTBalW6qGsgGmq0uLBBDLaoahrfM7rqNd5QfJwGBgFXZg==}
178 | dependencies:
179 | '@volar/code-gen': 0.38.5
180 | '@volar/source-map': 0.38.5
181 | '@volar/vue-code-gen': 0.38.5
182 | '@vue/compiler-sfc': 3.2.37
183 | '@vue/reactivity': 3.2.37
184 | dev: true
185 |
186 | /@vue/compiler-core/3.2.37:
187 | resolution: {integrity: sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==}
188 | dependencies:
189 | '@babel/parser': 7.18.8
190 | '@vue/shared': 3.2.37
191 | estree-walker: 2.0.2
192 | source-map: 0.6.1
193 |
194 | /@vue/compiler-dom/3.2.37:
195 | resolution: {integrity: sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==}
196 | dependencies:
197 | '@vue/compiler-core': 3.2.37
198 | '@vue/shared': 3.2.37
199 |
200 | /@vue/compiler-sfc/3.2.37:
201 | resolution: {integrity: sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==}
202 | dependencies:
203 | '@babel/parser': 7.18.8
204 | '@vue/compiler-core': 3.2.37
205 | '@vue/compiler-dom': 3.2.37
206 | '@vue/compiler-ssr': 3.2.37
207 | '@vue/reactivity-transform': 3.2.37
208 | '@vue/shared': 3.2.37
209 | estree-walker: 2.0.2
210 | magic-string: 0.25.9
211 | postcss: 8.4.14
212 | source-map: 0.6.1
213 |
214 | /@vue/compiler-ssr/3.2.37:
215 | resolution: {integrity: sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==}
216 | dependencies:
217 | '@vue/compiler-dom': 3.2.37
218 | '@vue/shared': 3.2.37
219 |
220 | /@vue/reactivity-transform/3.2.37:
221 | resolution: {integrity: sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==}
222 | dependencies:
223 | '@babel/parser': 7.18.8
224 | '@vue/compiler-core': 3.2.37
225 | '@vue/shared': 3.2.37
226 | estree-walker: 2.0.2
227 | magic-string: 0.25.9
228 |
229 | /@vue/reactivity/3.2.37:
230 | resolution: {integrity: sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==}
231 | dependencies:
232 | '@vue/shared': 3.2.37
233 |
234 | /@vue/runtime-core/3.2.37:
235 | resolution: {integrity: sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==}
236 | dependencies:
237 | '@vue/reactivity': 3.2.37
238 | '@vue/shared': 3.2.37
239 |
240 | /@vue/runtime-dom/3.2.37:
241 | resolution: {integrity: sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==}
242 | dependencies:
243 | '@vue/runtime-core': 3.2.37
244 | '@vue/shared': 3.2.37
245 | csstype: 2.6.20
246 |
247 | /@vue/server-renderer/3.2.37_vue@3.2.37:
248 | resolution: {integrity: sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==}
249 | peerDependencies:
250 | vue: 3.2.37
251 | dependencies:
252 | '@vue/compiler-ssr': 3.2.37
253 | '@vue/shared': 3.2.37
254 | vue: 3.2.37
255 |
256 | /@vue/shared/3.2.37:
257 | resolution: {integrity: sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==}
258 |
259 | /@vueuse/core/8.9.1_vue@3.2.37:
260 | resolution: {integrity: sha512-a7goYb/gJxjXRBw4Fr/jEACiN33ghwM1ohJVu+Zwsr3lNL4qCQ1nU+ogta98lNg5hXJxWj7mYEmQDjjyWOu5nA==}
261 | peerDependencies:
262 | '@vue/composition-api': ^1.1.0
263 | vue: ^2.6.0 || ^3.2.0
264 | peerDependenciesMeta:
265 | '@vue/composition-api':
266 | optional: true
267 | vue:
268 | optional: true
269 | dependencies:
270 | '@types/web-bluetooth': 0.0.14
271 | '@vueuse/metadata': 8.9.1
272 | '@vueuse/shared': 8.9.1_vue@3.2.37
273 | vue: 3.2.37
274 | vue-demi: 0.13.2_vue@3.2.37
275 | dev: false
276 |
277 | /@vueuse/metadata/8.9.1:
278 | resolution: {integrity: sha512-6LADOlyl3oENHa9dsoY7LXjU1Mh14DnpM6ztETI3hpm5ZffOMIG5CB2Q6aEZfIvYr1lkJVmG2L82wFKk7VRfIA==}
279 | dev: false
280 |
281 | /@vueuse/shared/8.9.1_vue@3.2.37:
282 | resolution: {integrity: sha512-klZfn7ijI3juqVgpfQVrrlBh4uTFajwSCWm8Cdt45Kg26b1LZ9jn9n7J6GhmkFay5016GnjjivQoekQSMeJNUg==}
283 | peerDependencies:
284 | '@vue/composition-api': ^1.1.0
285 | vue: ^2.6.0 || ^3.2.0
286 | peerDependenciesMeta:
287 | '@vue/composition-api':
288 | optional: true
289 | vue:
290 | optional: true
291 | dependencies:
292 | vue: 3.2.37
293 | vue-demi: 0.13.2_vue@3.2.37
294 | dev: false
295 |
296 | /@windicss/config/1.8.6:
297 | resolution: {integrity: sha512-WVS41qUJtd44g2iWzTAE8tpgk8gD0yAr1RwwaWi7FAECKm3LVNMLOoToum9R/QKFE2n64EUVJpIvSUNby8rlhg==}
298 | dependencies:
299 | debug: 4.3.4
300 | jiti: 1.14.0
301 | windicss: 3.5.6
302 | transitivePeerDependencies:
303 | - supports-color
304 | dev: true
305 |
306 | /@windicss/plugin-utils/1.8.6:
307 | resolution: {integrity: sha512-YY6EcUsgkosaQkIseFiIoHfU1H5boOAs/l74QWLI6ryNeHLMq2e04QVsFz+Rt+U8b8PRNxXPC8ADbxE05X7I7g==}
308 | dependencies:
309 | '@antfu/utils': 0.5.2
310 | '@windicss/config': 1.8.6
311 | debug: 4.3.4
312 | fast-glob: 3.2.11
313 | magic-string: 0.26.2
314 | micromatch: 4.0.5
315 | windicss: 3.5.6
316 | transitivePeerDependencies:
317 | - supports-color
318 | dev: true
319 |
320 | /acorn/8.7.1:
321 | resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==}
322 | engines: {node: '>=0.4.0'}
323 | hasBin: true
324 | dev: true
325 |
326 | /anymatch/3.1.2:
327 | resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
328 | engines: {node: '>= 8'}
329 | dependencies:
330 | normalize-path: 3.0.0
331 | picomatch: 2.3.1
332 | dev: true
333 |
334 | /axios/0.21.4:
335 | resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
336 | dependencies:
337 | follow-redirects: 1.15.1
338 | transitivePeerDependencies:
339 | - debug
340 | dev: false
341 |
342 | /balanced-match/1.0.2:
343 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
344 | dev: true
345 |
346 | /binary-extensions/2.2.0:
347 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
348 | engines: {node: '>=8'}
349 | dev: true
350 |
351 | /brace-expansion/2.0.1:
352 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
353 | dependencies:
354 | balanced-match: 1.0.2
355 | dev: true
356 |
357 | /braces/3.0.2:
358 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
359 | engines: {node: '>=8'}
360 | dependencies:
361 | fill-range: 7.0.1
362 | dev: true
363 |
364 | /call-bind/1.0.2:
365 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
366 | dependencies:
367 | function-bind: 1.1.1
368 | get-intrinsic: 1.1.2
369 | dev: false
370 |
371 | /chokidar/3.5.3:
372 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
373 | engines: {node: '>= 8.10.0'}
374 | dependencies:
375 | anymatch: 3.1.2
376 | braces: 3.0.2
377 | glob-parent: 5.1.2
378 | is-binary-path: 2.1.0
379 | is-glob: 4.0.3
380 | normalize-path: 3.0.0
381 | readdirp: 3.6.0
382 | optionalDependencies:
383 | fsevents: 2.3.2
384 | dev: true
385 |
386 | /csstype/2.6.20:
387 | resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==}
388 |
389 | /debug/4.3.4:
390 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
391 | engines: {node: '>=6.0'}
392 | peerDependencies:
393 | supports-color: '*'
394 | peerDependenciesMeta:
395 | supports-color:
396 | optional: true
397 | dependencies:
398 | ms: 2.1.2
399 | dev: true
400 |
401 | /deepmerge/4.2.2:
402 | resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
403 | engines: {node: '>=0.10.0'}
404 | dev: false
405 |
406 | /esbuild-android-64/0.14.48:
407 | resolution: {integrity: sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==}
408 | engines: {node: '>=12'}
409 | cpu: [x64]
410 | os: [android]
411 | requiresBuild: true
412 | dev: true
413 | optional: true
414 |
415 | /esbuild-android-arm64/0.14.48:
416 | resolution: {integrity: sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==}
417 | engines: {node: '>=12'}
418 | cpu: [arm64]
419 | os: [android]
420 | requiresBuild: true
421 | dev: true
422 | optional: true
423 |
424 | /esbuild-darwin-64/0.14.48:
425 | resolution: {integrity: sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==}
426 | engines: {node: '>=12'}
427 | cpu: [x64]
428 | os: [darwin]
429 | requiresBuild: true
430 | dev: true
431 | optional: true
432 |
433 | /esbuild-darwin-arm64/0.14.48:
434 | resolution: {integrity: sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==}
435 | engines: {node: '>=12'}
436 | cpu: [arm64]
437 | os: [darwin]
438 | requiresBuild: true
439 | dev: true
440 | optional: true
441 |
442 | /esbuild-freebsd-64/0.14.48:
443 | resolution: {integrity: sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==}
444 | engines: {node: '>=12'}
445 | cpu: [x64]
446 | os: [freebsd]
447 | requiresBuild: true
448 | dev: true
449 | optional: true
450 |
451 | /esbuild-freebsd-arm64/0.14.48:
452 | resolution: {integrity: sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==}
453 | engines: {node: '>=12'}
454 | cpu: [arm64]
455 | os: [freebsd]
456 | requiresBuild: true
457 | dev: true
458 | optional: true
459 |
460 | /esbuild-linux-32/0.14.48:
461 | resolution: {integrity: sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==}
462 | engines: {node: '>=12'}
463 | cpu: [ia32]
464 | os: [linux]
465 | requiresBuild: true
466 | dev: true
467 | optional: true
468 |
469 | /esbuild-linux-64/0.14.48:
470 | resolution: {integrity: sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==}
471 | engines: {node: '>=12'}
472 | cpu: [x64]
473 | os: [linux]
474 | requiresBuild: true
475 | dev: true
476 | optional: true
477 |
478 | /esbuild-linux-arm/0.14.48:
479 | resolution: {integrity: sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==}
480 | engines: {node: '>=12'}
481 | cpu: [arm]
482 | os: [linux]
483 | requiresBuild: true
484 | dev: true
485 | optional: true
486 |
487 | /esbuild-linux-arm64/0.14.48:
488 | resolution: {integrity: sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==}
489 | engines: {node: '>=12'}
490 | cpu: [arm64]
491 | os: [linux]
492 | requiresBuild: true
493 | dev: true
494 | optional: true
495 |
496 | /esbuild-linux-mips64le/0.14.48:
497 | resolution: {integrity: sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==}
498 | engines: {node: '>=12'}
499 | cpu: [mips64el]
500 | os: [linux]
501 | requiresBuild: true
502 | dev: true
503 | optional: true
504 |
505 | /esbuild-linux-ppc64le/0.14.48:
506 | resolution: {integrity: sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==}
507 | engines: {node: '>=12'}
508 | cpu: [ppc64]
509 | os: [linux]
510 | requiresBuild: true
511 | dev: true
512 | optional: true
513 |
514 | /esbuild-linux-riscv64/0.14.48:
515 | resolution: {integrity: sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==}
516 | engines: {node: '>=12'}
517 | cpu: [riscv64]
518 | os: [linux]
519 | requiresBuild: true
520 | dev: true
521 | optional: true
522 |
523 | /esbuild-linux-s390x/0.14.48:
524 | resolution: {integrity: sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==}
525 | engines: {node: '>=12'}
526 | cpu: [s390x]
527 | os: [linux]
528 | requiresBuild: true
529 | dev: true
530 | optional: true
531 |
532 | /esbuild-netbsd-64/0.14.48:
533 | resolution: {integrity: sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==}
534 | engines: {node: '>=12'}
535 | cpu: [x64]
536 | os: [netbsd]
537 | requiresBuild: true
538 | dev: true
539 | optional: true
540 |
541 | /esbuild-openbsd-64/0.14.48:
542 | resolution: {integrity: sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==}
543 | engines: {node: '>=12'}
544 | cpu: [x64]
545 | os: [openbsd]
546 | requiresBuild: true
547 | dev: true
548 | optional: true
549 |
550 | /esbuild-sunos-64/0.14.48:
551 | resolution: {integrity: sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==}
552 | engines: {node: '>=12'}
553 | cpu: [x64]
554 | os: [sunos]
555 | requiresBuild: true
556 | dev: true
557 | optional: true
558 |
559 | /esbuild-windows-32/0.14.48:
560 | resolution: {integrity: sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==}
561 | engines: {node: '>=12'}
562 | cpu: [ia32]
563 | os: [win32]
564 | requiresBuild: true
565 | dev: true
566 | optional: true
567 |
568 | /esbuild-windows-64/0.14.48:
569 | resolution: {integrity: sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==}
570 | engines: {node: '>=12'}
571 | cpu: [x64]
572 | os: [win32]
573 | requiresBuild: true
574 | dev: true
575 | optional: true
576 |
577 | /esbuild-windows-arm64/0.14.48:
578 | resolution: {integrity: sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==}
579 | engines: {node: '>=12'}
580 | cpu: [arm64]
581 | os: [win32]
582 | requiresBuild: true
583 | dev: true
584 | optional: true
585 |
586 | /esbuild/0.14.48:
587 | resolution: {integrity: sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==}
588 | engines: {node: '>=12'}
589 | hasBin: true
590 | requiresBuild: true
591 | optionalDependencies:
592 | esbuild-android-64: 0.14.48
593 | esbuild-android-arm64: 0.14.48
594 | esbuild-darwin-64: 0.14.48
595 | esbuild-darwin-arm64: 0.14.48
596 | esbuild-freebsd-64: 0.14.48
597 | esbuild-freebsd-arm64: 0.14.48
598 | esbuild-linux-32: 0.14.48
599 | esbuild-linux-64: 0.14.48
600 | esbuild-linux-arm: 0.14.48
601 | esbuild-linux-arm64: 0.14.48
602 | esbuild-linux-mips64le: 0.14.48
603 | esbuild-linux-ppc64le: 0.14.48
604 | esbuild-linux-riscv64: 0.14.48
605 | esbuild-linux-s390x: 0.14.48
606 | esbuild-netbsd-64: 0.14.48
607 | esbuild-openbsd-64: 0.14.48
608 | esbuild-sunos-64: 0.14.48
609 | esbuild-windows-32: 0.14.48
610 | esbuild-windows-64: 0.14.48
611 | esbuild-windows-arm64: 0.14.48
612 | dev: true
613 |
614 | /estree-walker/2.0.2:
615 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
616 |
617 | /fast-glob/3.2.11:
618 | resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
619 | engines: {node: '>=8.6.0'}
620 | dependencies:
621 | '@nodelib/fs.stat': 2.0.5
622 | '@nodelib/fs.walk': 1.2.8
623 | glob-parent: 5.1.2
624 | merge2: 1.4.1
625 | micromatch: 4.0.5
626 | dev: true
627 |
628 | /fastq/1.13.0:
629 | resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
630 | dependencies:
631 | reusify: 1.0.4
632 | dev: true
633 |
634 | /fill-range/7.0.1:
635 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
636 | engines: {node: '>=8'}
637 | dependencies:
638 | to-regex-range: 5.0.1
639 | dev: true
640 |
641 | /follow-redirects/1.15.1:
642 | resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
643 | engines: {node: '>=4.0'}
644 | peerDependencies:
645 | debug: '*'
646 | peerDependenciesMeta:
647 | debug:
648 | optional: true
649 | dev: false
650 |
651 | /fsevents/2.3.2:
652 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
653 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
654 | os: [darwin]
655 | requiresBuild: true
656 | dev: true
657 | optional: true
658 |
659 | /function-bind/1.1.1:
660 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
661 |
662 | /get-intrinsic/1.1.2:
663 | resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==}
664 | dependencies:
665 | function-bind: 1.1.1
666 | has: 1.0.3
667 | has-symbols: 1.0.3
668 | dev: false
669 |
670 | /glob-parent/5.1.2:
671 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
672 | engines: {node: '>= 6'}
673 | dependencies:
674 | is-glob: 4.0.3
675 | dev: true
676 |
677 | /has-symbols/1.0.3:
678 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
679 | engines: {node: '>= 0.4'}
680 | dev: false
681 |
682 | /has/1.0.3:
683 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
684 | engines: {node: '>= 0.4.0'}
685 | dependencies:
686 | function-bind: 1.1.1
687 |
688 | /is-binary-path/2.1.0:
689 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
690 | engines: {node: '>=8'}
691 | dependencies:
692 | binary-extensions: 2.2.0
693 | dev: true
694 |
695 | /is-core-module/2.9.0:
696 | resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==}
697 | dependencies:
698 | has: 1.0.3
699 | dev: true
700 |
701 | /is-extglob/2.1.1:
702 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
703 | engines: {node: '>=0.10.0'}
704 | dev: true
705 |
706 | /is-glob/4.0.3:
707 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
708 | engines: {node: '>=0.10.0'}
709 | dependencies:
710 | is-extglob: 2.1.1
711 | dev: true
712 |
713 | /is-number/7.0.0:
714 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
715 | engines: {node: '>=0.12.0'}
716 | dev: true
717 |
718 | /jiti/1.14.0:
719 | resolution: {integrity: sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==}
720 | hasBin: true
721 | dev: true
722 |
723 | /kolorist/1.5.1:
724 | resolution: {integrity: sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==}
725 | dev: true
726 |
727 | /local-pkg/0.4.1:
728 | resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==}
729 | engines: {node: '>=14'}
730 | dev: true
731 |
732 | /lodash-es/4.17.21:
733 | resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
734 | dev: false
735 |
736 | /lodash.clonedeep/4.5.0:
737 | resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
738 | dev: false
739 |
740 | /lodash.isequal/4.5.0:
741 | resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
742 | dev: false
743 |
744 | /magic-string/0.25.9:
745 | resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
746 | dependencies:
747 | sourcemap-codec: 1.4.8
748 |
749 | /magic-string/0.26.2:
750 | resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==}
751 | engines: {node: '>=12'}
752 | dependencies:
753 | sourcemap-codec: 1.4.8
754 | dev: true
755 |
756 | /merge2/1.4.1:
757 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
758 | engines: {node: '>= 8'}
759 | dev: true
760 |
761 | /micromatch/4.0.5:
762 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
763 | engines: {node: '>=8.6'}
764 | dependencies:
765 | braces: 3.0.2
766 | picomatch: 2.3.1
767 | dev: true
768 |
769 | /minimatch/5.1.0:
770 | resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
771 | engines: {node: '>=10'}
772 | dependencies:
773 | brace-expansion: 2.0.1
774 | dev: true
775 |
776 | /ms/2.1.2:
777 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
778 | dev: true
779 |
780 | /nanoid/3.3.4:
781 | resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
782 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
783 | hasBin: true
784 |
785 | /normalize-path/3.0.0:
786 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
787 | engines: {node: '>=0.10.0'}
788 | dev: true
789 |
790 | /object-inspect/1.12.2:
791 | resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
792 | dev: false
793 |
794 | /path-parse/1.0.7:
795 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
796 | dev: true
797 |
798 | /picocolors/1.0.0:
799 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
800 |
801 | /picomatch/2.3.1:
802 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
803 | engines: {node: '>=8.6'}
804 | dev: true
805 |
806 | /postcss/8.4.14:
807 | resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
808 | engines: {node: ^10 || ^12 || >=14}
809 | dependencies:
810 | nanoid: 3.3.4
811 | picocolors: 1.0.0
812 | source-map-js: 1.0.2
813 |
814 | /qs/6.11.0:
815 | resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
816 | engines: {node: '>=0.6'}
817 | dependencies:
818 | side-channel: 1.0.4
819 | dev: false
820 |
821 | /queue-microtask/1.2.3:
822 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
823 | dev: true
824 |
825 | /readdirp/3.6.0:
826 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
827 | engines: {node: '>=8.10.0'}
828 | dependencies:
829 | picomatch: 2.3.1
830 | dev: true
831 |
832 | /resolve/1.22.1:
833 | resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
834 | hasBin: true
835 | dependencies:
836 | is-core-module: 2.9.0
837 | path-parse: 1.0.7
838 | supports-preserve-symlinks-flag: 1.0.0
839 | dev: true
840 |
841 | /reusify/1.0.4:
842 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
843 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
844 | dev: true
845 |
846 | /rollup/2.76.0:
847 | resolution: {integrity: sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==}
848 | engines: {node: '>=10.0.0'}
849 | hasBin: true
850 | optionalDependencies:
851 | fsevents: 2.3.2
852 | dev: true
853 |
854 | /run-parallel/1.2.0:
855 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
856 | dependencies:
857 | queue-microtask: 1.2.3
858 | dev: true
859 |
860 | /side-channel/1.0.4:
861 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
862 | dependencies:
863 | call-bind: 1.0.2
864 | get-intrinsic: 1.1.2
865 | object-inspect: 1.12.2
866 | dev: false
867 |
868 | /source-map-js/1.0.2:
869 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
870 | engines: {node: '>=0.10.0'}
871 |
872 | /source-map/0.6.1:
873 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
874 | engines: {node: '>=0.10.0'}
875 |
876 | /sourcemap-codec/1.4.8:
877 | resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
878 |
879 | /supports-preserve-symlinks-flag/1.0.0:
880 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
881 | engines: {node: '>= 0.4'}
882 | dev: true
883 |
884 | /to-fast-properties/2.0.0:
885 | resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
886 | engines: {node: '>=4'}
887 |
888 | /to-regex-range/5.0.1:
889 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
890 | engines: {node: '>=8.0'}
891 | dependencies:
892 | is-number: 7.0.0
893 | dev: true
894 |
895 | /typescript/4.7.4:
896 | resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
897 | engines: {node: '>=4.2.0'}
898 | hasBin: true
899 | dev: true
900 |
901 | /unplugin-vue-components/0.21.1_vite@2.9.14+vue@3.2.37:
902 | resolution: {integrity: sha512-8MhIT323q1EUu7rz6NfQeiHqDrZKtygy6s9jzcQAuuZUM2T38SHlPT5YJjBOZmM0Bau6YuNTKfBBX4iHzeusaQ==}
903 | engines: {node: '>=14'}
904 | peerDependencies:
905 | '@babel/parser': ^7.15.8
906 | vue: 2 || 3
907 | peerDependenciesMeta:
908 | '@babel/parser':
909 | optional: true
910 | dependencies:
911 | '@antfu/utils': 0.5.2
912 | '@rollup/pluginutils': 4.2.1
913 | chokidar: 3.5.3
914 | debug: 4.3.4
915 | fast-glob: 3.2.11
916 | local-pkg: 0.4.1
917 | magic-string: 0.26.2
918 | minimatch: 5.1.0
919 | resolve: 1.22.1
920 | unplugin: 0.7.1_vite@2.9.14
921 | vue: 3.2.37
922 | transitivePeerDependencies:
923 | - esbuild
924 | - rollup
925 | - supports-color
926 | - vite
927 | - webpack
928 | dev: true
929 |
930 | /unplugin/0.7.1_vite@2.9.14:
931 | resolution: {integrity: sha512-Z6hNDXDNh9aimMkPU1mEjtk+2ova8gh0y7rJeJdGH1vWZOHwF2lLQiQ/R97rv9ymmzEQXsR2fyMet72T8jy6ew==}
932 | peerDependencies:
933 | esbuild: '>=0.13'
934 | rollup: ^2.50.0
935 | vite: ^2.3.0 || ^3.0.0-0
936 | webpack: 4 || 5
937 | peerDependenciesMeta:
938 | esbuild:
939 | optional: true
940 | rollup:
941 | optional: true
942 | vite:
943 | optional: true
944 | webpack:
945 | optional: true
946 | dependencies:
947 | acorn: 8.7.1
948 | chokidar: 3.5.3
949 | vite: 2.9.14
950 | webpack-sources: 3.2.3
951 | webpack-virtual-modules: 0.4.4
952 | dev: true
953 |
954 | /vite-plugin-full-reload/1.0.1:
955 | resolution: {integrity: sha512-G/IXcDjuhfDAK4zEzWTM/kL0DiAVv0+Dox+zGZeJyUWhwtg34Q9XtJkNXveku+mTSKE20TMp3TF8D7bFEhmvfA==}
956 | dependencies:
957 | picocolors: 1.0.0
958 | picomatch: 2.3.1
959 | dev: true
960 |
961 | /vite-plugin-ruby/3.0.12_vite@2.9.14:
962 | resolution: {integrity: sha512-QosYDK6qOV1UeeFXlcH+A4pyoIs2lU9R2gWm+AJZ6gZ2yiiiLShYF6juYV8GeDsaXcOfVbPetkuPfnQCzE4mPQ==}
963 | peerDependencies:
964 | vite: '>=2.5.0'
965 | dependencies:
966 | debug: 4.3.4
967 | fast-glob: 3.2.11
968 | vite: 2.9.14
969 | transitivePeerDependencies:
970 | - supports-color
971 | dev: true
972 |
973 | /vite-plugin-windicss/1.8.6_vite@2.9.14:
974 | resolution: {integrity: sha512-D4G4qmumgklPiPrq/ZALqq8Mby6krskFVShbmb5c+0VCSsLUN96qyFRTwi81rNIHwFvlbpqflgh+BpUM/9VjQg==}
975 | peerDependencies:
976 | vite: ^2.0.1
977 | dependencies:
978 | '@windicss/plugin-utils': 1.8.6
979 | debug: 4.3.4
980 | kolorist: 1.5.1
981 | vite: 2.9.14
982 | windicss: 3.5.6
983 | transitivePeerDependencies:
984 | - supports-color
985 | dev: true
986 |
987 | /vite/2.9.14:
988 | resolution: {integrity: sha512-P/UCjSpSMcE54r4mPak55hWAZPlyfS369svib/gpmz8/01L822lMPOJ/RYW6tLCe1RPvMvOsJ17erf55bKp4Hw==}
989 | engines: {node: '>=12.2.0'}
990 | hasBin: true
991 | peerDependencies:
992 | less: '*'
993 | sass: '*'
994 | stylus: '*'
995 | peerDependenciesMeta:
996 | less:
997 | optional: true
998 | sass:
999 | optional: true
1000 | stylus:
1001 | optional: true
1002 | dependencies:
1003 | esbuild: 0.14.48
1004 | postcss: 8.4.14
1005 | resolve: 1.22.1
1006 | rollup: 2.76.0
1007 | optionalDependencies:
1008 | fsevents: 2.3.2
1009 | dev: true
1010 |
1011 | /vue-demi/0.13.2_vue@3.2.37:
1012 | resolution: {integrity: sha512-41ukrclEbMddAyP7PvxMSYqnOSzPV6r7GNnyTSKSCNTaz19GehxmTiXyP9kwHSUv2+Dr6hHqiUiF7L1VAw2KdQ==}
1013 | engines: {node: '>=12'}
1014 | hasBin: true
1015 | requiresBuild: true
1016 | peerDependencies:
1017 | '@vue/composition-api': ^1.0.0-rc.1
1018 | vue: ^3.0.0-0 || ^2.6.0
1019 | peerDependenciesMeta:
1020 | '@vue/composition-api':
1021 | optional: true
1022 | dependencies:
1023 | vue: 3.2.37
1024 | dev: false
1025 |
1026 | /vue-tsc/0.38.5_typescript@4.7.4:
1027 | resolution: {integrity: sha512-AFlqvwpENOTTJxS1Gl1uG0ZVsvtGCbgv9d0i4fXk5Pnao/ETVwWewPEsj+4cPlwa5htO9WhK6qfWwoqcCqg0yQ==}
1028 | hasBin: true
1029 | peerDependencies:
1030 | typescript: '*'
1031 | dependencies:
1032 | '@volar/vue-typescript': 0.38.5
1033 | typescript: 4.7.4
1034 | dev: true
1035 |
1036 | /vue/3.2.37:
1037 | resolution: {integrity: sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==}
1038 | dependencies:
1039 | '@vue/compiler-dom': 3.2.37
1040 | '@vue/compiler-sfc': 3.2.37
1041 | '@vue/runtime-dom': 3.2.37
1042 | '@vue/server-renderer': 3.2.37_vue@3.2.37
1043 | '@vue/shared': 3.2.37
1044 |
1045 | /webpack-sources/3.2.3:
1046 | resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
1047 | engines: {node: '>=10.13.0'}
1048 | dev: true
1049 |
1050 | /webpack-virtual-modules/0.4.4:
1051 | resolution: {integrity: sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA==}
1052 | dev: true
1053 |
1054 | /windicss/3.5.6:
1055 | resolution: {integrity: sha512-P1mzPEjgFMZLX0ZqfFht4fhV/FX8DTG7ERG1fBLiWvd34pTLVReS5CVsewKn9PApSgXnVfPWwvq+qUsRwpnwFA==}
1056 | engines: {node: '>= 12'}
1057 | hasBin: true
1058 | dev: true
1059 |
--------------------------------------------------------------------------------
/playground/vanilla/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/playground/vanilla/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/playground/vanilla/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/playground/vanilla/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/types_from_serializers/b47ac0f88d7254e43d922d02546d07a6ed829fb1/playground/vanilla/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/playground/vanilla/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/types_from_serializers/b47ac0f88d7254e43d922d02546d07a6ed829fb1/playground/vanilla/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/playground/vanilla/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElMassimo/types_from_serializers/b47ac0f88d7254e43d922d02546d07a6ed829fb1/playground/vanilla/public/favicon.ico
--------------------------------------------------------------------------------
/playground/vanilla/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/playground/vanilla/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path'
2 | import { defineConfig } from 'vite'
3 | import ruby from 'vite-plugin-ruby'
4 | import vue from '@vitejs/plugin-vue'
5 | import components from 'unplugin-vue-components/vite'
6 | import windicss from 'vite-plugin-windicss'
7 | import reloadOnChange from 'vite-plugin-full-reload'
8 |
9 | export default defineConfig({
10 | plugins: [
11 | ruby(),
12 | vue({
13 | template: {
14 | compilerOptions: {
15 | isCustomElement: name => name === 'lite-youtube',
16 | },
17 | },
18 | reactivityTransform: true,
19 | }),
20 | components({
21 | dirs: ['components'],
22 | extensions: ['vue', 'ts'],
23 | }),
24 | windicss({
25 | root: resolve(__dirname, 'app/frontend'),
26 | }),
27 | reloadOnChange([
28 | 'config/routes.rb',
29 | 'app/serializers/**/*.rb',
30 | ], { delay: 100 }),
31 | ],
32 | })
33 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - playground/vanilla
3 |
--------------------------------------------------------------------------------
/scripts/changelog.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 | const args = require('minimist')(process.argv.slice(2))
4 | const execa = require('execa')
5 | const chalk = require('chalk')
6 |
7 | const name = args._[0]?.trim() || 'types_from_serializers'
8 |
9 | if (!name) {
10 | console.error(chalk.red(`Expected library name as an argument, received ${name}`))
11 | process.exit(1)
12 | }
13 |
14 | const isRubyPackage = name === 'types_from_serializers'
15 | const packagePath = isRubyPackage ? name : `packages/${name}`
16 |
17 | /**
18 | * @param {string} bin
19 | * @param {string[]} args
20 | * @param {object} opts
21 | */
22 | const run = (bin, args, opts = {}) =>
23 | execa(bin, args, { stdio: 'inherit', ...opts })
24 |
25 | /**
26 | * @param {string} paths
27 | */
28 | const resolve = paths => path.resolve(__dirname, `../${packagePath}/${paths}`)
29 |
30 | /**
31 | * @param {string} name
32 | */
33 | function writePackageJson (name) {
34 | const versionRegex = /VERSION = "([\d.]+)"/
35 | const versionFile = fs.readFileSync(resolve(`lib/${name}/version.rb`), 'utf-8')
36 | const versionCaptures = versionFile.match(versionRegex)
37 | const version = versionCaptures && versionCaptures[1]
38 | if (!version) {
39 | console.error(chalk.red(`Could not infer version for ${name}.`))
40 | process.exit(1)
41 | }
42 | fs.writeFileSync(resolve('package.json'), `{ "version": "${version}" }`)
43 | }
44 |
45 | async function main () {
46 | if (isRubyPackage)
47 | writePackageJson(name)
48 |
49 | await run('npx', [
50 | 'conventional-changelog',
51 | '-p', 'angular',
52 | '-i', `${packagePath}/CHANGELOG.md`,
53 | '-s',
54 | '-t', `${name}@`,
55 | '--pkg', `./${packagePath}/package.json`,
56 | '--commit-path', `./${packagePath}`,
57 | ])
58 |
59 | if (isRubyPackage)
60 | fs.rmSync(resolve('package.json'))
61 | }
62 |
63 | main().catch((err) => {
64 | console.error(err)
65 | })
66 |
--------------------------------------------------------------------------------
/scripts/release.js:
--------------------------------------------------------------------------------
1 | /**
2 | * modified from https://github.com/vuejs/vue-next/blob/master/scripts/release.js
3 | */
4 | const path = require('path')
5 | const fs = require('fs')
6 | const execa = require('execa')
7 | const args = require('minimist')(process.argv.slice(2))
8 | const semver = require('semver')
9 | const chalk = require('chalk')
10 | const { prompt } = require('enquirer')
11 |
12 | const name = args._[0]?.trim() || 'types_from_serializers'
13 | let targetVersion = args._[1]
14 | if (!name) {
15 | console.error(chalk.red(`Expected library name as an argument, received ${name}`))
16 | process.exit(1)
17 | }
18 |
19 | const isRubyLibrary = name === 'types_from_serializers'
20 | const packagePath = isRubyLibrary ? name : `packages/${name}`
21 | const pkg = isRubyLibrary ? rubyPackage() : jsPackage()
22 |
23 | /**
24 | * @type {boolean}
25 | */
26 | const isDryRun = args.dry
27 | /**
28 | * @type {boolean}
29 | */
30 | const skipBuild = args.skipBuild || isRubyLibrary
31 |
32 | /**
33 | * @type {import('semver').ReleaseType[]}
34 | */
35 | const versionIncrements = [
36 | 'patch',
37 | 'minor',
38 | 'major',
39 | 'prepatch',
40 | 'preminor',
41 | 'premajor',
42 | 'prerelease',
43 | ]
44 |
45 | /**
46 | * @param {import('semver').ReleaseType} i
47 | */
48 | function inc (i) {
49 | return semver.inc(pkg.version, i)
50 | }
51 |
52 | /**
53 | * @param {string} bin
54 | * @param {string[]} args
55 | * @param {object} opts
56 | */
57 | function run (bin, args, opts = {}) {
58 | return execa(bin, args, { stdio: 'inherit', ...opts })
59 | }
60 |
61 | /**
62 | * @param {string} bin
63 | * @param {string[]} args
64 | * @param {object} opts
65 | */
66 | function dryRun (bin, args, opts = {}) {
67 | console.log(chalk.blue(`[dryrun] ${bin} ${args.join(' ')}`), opts)
68 | }
69 |
70 | /**
71 | * @param {string} msg
72 | */
73 | function step (msg) {
74 | console.log(chalk.cyan(msg))
75 | }
76 |
77 | /**
78 | * @param {string} paths
79 | */
80 | function resolve (paths) {
81 | return path.resolve(__dirname, `../${packagePath}/${paths}`)
82 | }
83 |
84 | function rubyPackage () {
85 | const versionRegex = /VERSION = "([\d.]+)"/
86 | const path = resolve(`lib/${name}/version.rb`)
87 | const content = fs.readFileSync(path, 'utf-8')
88 | const versionCaptures = content.match(versionRegex)
89 | const version = versionCaptures && versionCaptures[1]
90 | if (!version) {
91 | console.error(chalk.red(`Could not infer version for ${name}.`))
92 | process.exit(1)
93 | }
94 | return {
95 | type: 'gem',
96 | path,
97 | content,
98 | version,
99 | updateVersion (version) {
100 | const newContent = content.replace(versionRegex, `VERSION = "${version}"`)
101 | fs.writeFileSync(path, `${newContent}`)
102 | },
103 | }
104 | }
105 |
106 | function jsPackage () {
107 | const path = resolve('package.json')
108 | const content = fs.readFileSync(path, 'utf-8')
109 | return {
110 | type: 'package',
111 | path,
112 | content,
113 | ...require(path),
114 | updateVersion (version) {
115 | const newContent = { ...JSON.parse(content), version }
116 | fs.writeFileSync(path, `${JSON.stringify(newContent, null, 2)}\n`)
117 | },
118 | }
119 | }
120 |
121 | async function main () {
122 | const runIfNotDry = isDryRun ? dryRun : run
123 |
124 | if (!targetVersion) {
125 | // no explicit version, offer suggestions
126 | /**
127 | * @type {{ release: string }}
128 | */
129 | const { release } = await prompt({
130 | type: 'select',
131 | name: 'release',
132 | message: 'Select release type',
133 | choices: versionIncrements
134 | .map(i => `${i} (${inc(i)})`)
135 | .concat(['custom']),
136 | })
137 |
138 | if (release === 'custom') {
139 | /**
140 | * @type {{ version: string }}
141 | */
142 | const res = await prompt({
143 | type: 'input',
144 | name: 'version',
145 | message: 'Input custom version',
146 | initial: pkg.version,
147 | })
148 | targetVersion = res.version
149 | }
150 | else {
151 | targetVersion = release.match(/\((.*)\)/)[1]
152 | }
153 | }
154 |
155 | if (!semver.valid(targetVersion))
156 | throw new Error(`invalid target version: ${targetVersion}`)
157 |
158 | const tag = `${name}@${targetVersion}`
159 |
160 | /**
161 | * @type {{ yes: boolean }}
162 | */
163 | const { yes } = await prompt({
164 | type: 'confirm',
165 | name: 'yes',
166 | message: `Releasing ${tag}. Confirm?`,
167 | })
168 |
169 | if (!yes)
170 | return
171 |
172 | step(`\nUpdating ${pkg.type} version...`)
173 | pkg.updateVersion(targetVersion)
174 |
175 | step(`\nBuilding ${pkg.type}...`)
176 | if (!skipBuild && !isDryRun)
177 | await run('pnpm', ['build'], { cwd: resolve('.') })
178 | else
179 | console.log('(skipped)')
180 |
181 | if (isRubyLibrary)
182 | await run('bundle install && bin/standardrb', { shell: true })
183 |
184 | step('\nGenerating changelog...')
185 | await run('pnpm', ['changelog', name])
186 |
187 | const { stdout } = await run('git', ['diff'], { stdio: 'pipe' })
188 | if (stdout) {
189 | step('\nCommitting changes...')
190 | await runIfNotDry('git', ['add', '-A'])
191 | await runIfNotDry('git', ['commit', '-m', `release: ${tag}`])
192 | }
193 | else {
194 | console.log('No changes to commit.')
195 | }
196 |
197 | step(`\nPublishing ${pkg.type}...`)
198 | if (isRubyLibrary)
199 | await publishGem(targetVersion)
200 | else
201 | await publishPackage(targetVersion, runIfNotDry)
202 |
203 | step('\nPushing to GitHub...')
204 | await runIfNotDry('git', ['push'])
205 |
206 | if (isDryRun)
207 | console.log(`\nDry run finished - run git diff to see ${pkg.type} changes.`)
208 |
209 | console.log()
210 | }
211 |
212 | /**
213 | * @param {string} version
214 | * @param {Function} runIfNotDry
215 | */
216 | async function publishGem (version) {
217 | try {
218 | const runIfNotDry = isDryRun ? dryRun : execa.commandSync
219 | await runIfNotDry('bundle exec rake release', {
220 | stdio: 'inherit',
221 | cwd: resolve('.'),
222 | })
223 | console.log(chalk.green(`Successfully published ${name}@${version}`))
224 | }
225 | catch (e) {
226 | if (e.stderr.match(/previously/))
227 | console.log(chalk.red(`Skipping already published: ${name}`))
228 | else
229 | throw e
230 | }
231 | }
232 |
233 | /**
234 | * @param {string} version
235 | * @param {Function} runIfNotDry
236 | */
237 | async function publishPackage (version, runIfNotDry) {
238 | try {
239 | await runIfNotDry('npm', ['publish', '--access', 'public'], {
240 | stdio: 'inherit',
241 | cwd: resolve('.'),
242 | })
243 | console.log(chalk.green(`Successfully published ${name}@${version}`))
244 | }
245 | catch (e) {
246 | if (e.stderr.match(/previously published/))
247 | console.log(chalk.red(`Skipping already published: ${name}`))
248 | else
249 | throw e
250 | }
251 | }
252 |
253 | main().catch((err) => {
254 | console.error(err)
255 | })
256 |
--------------------------------------------------------------------------------
/scripts/verifyCommit.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/order */
2 | const args = require('minimist')(process.argv.slice(2))
3 | const msgPath = args._[0]
4 | const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()
5 |
6 | const releaseRE = /^v\d/
7 | const commitRE = /^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|lint|types|wip|release|deps)(\(.+\))?: .{1,50}/
8 |
9 | if (!releaseRE.test(msg) && !commitRE.test(msg)) {
10 | console.log()
11 | const chalk = require('chalk')
12 | console.error(
13 | ` ${chalk.bgRed.white(' ERROR ')} ${chalk.red(
14 | 'invalid commit message format.',
15 | )}\n\n${
16 | chalk.red(
17 | ' Proper commit message format is required for automated changelog generation. Examples:\n\n',
18 | )
19 | } ${chalk.green('feat: add \'comments\' option')}\n`
20 | + ` ${chalk.green('fix: handle events on blur (close #28)')}\n\n${
21 | chalk.red(' See .github/commit-convention.md for more details.\n')}`,
22 | )
23 | process.exit(1)
24 | }
25 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "simplecov"
2 | SimpleCov.start {
3 | add_filter "/spec/"
4 | add_filter "/playground/"
5 | }
6 |
7 | ENV["RACK_ENV"] = "development"
8 | require "rails"
9 | require "oj_serializers"
10 | require "types_from_serializers"
11 | require "rspec/given"
12 |
13 | begin
14 | require "pry-byebug"
15 | rescue LoadError
16 | end
17 |
18 | $LOAD_PATH.push File.expand_path("../playground", __dir__)
19 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_ComposerSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey b379726b6fef2dadbfd614384b868746
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface Composer {
6 | id: number
7 | firstName?: string
8 | lastName?: string
9 | name: string
10 | }
11 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_ComposerWithSongsSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 9111c6d881174b0013582fa6366868db
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type ComposerWithSongsSong from './ComposerWithSongs/Song'
5 |
6 | export default interface ComposerWithSongs {
7 | id: number
8 | firstName?: string
9 | lastName?: string
10 | name: string
11 | songs: ComposerWithSongsSong[]
12 | }
13 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_ComposerWithSongsSerializer__SongSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey a69c0dbcc904740e5de89181ef4b2837
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface ComposerWithSongsSong {
6 | id: number
7 | title?: string
8 | }
9 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_ModelSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey ed8ff6fbc986e6b666d559749c666dc5
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type AnyModel from '../AnyModel'
5 |
6 | export default interface Model {
7 | id: AnyModel['id']
8 | title: AnyModel['title']
9 | }
10 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_Nested__AlbumSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 58897b61d86838c60d1f12700f326896
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type AnyModel from '../../AnyModel'
5 | import type Song from '../Song'
6 |
7 | export default interface NestedAlbum {
8 | id: AnyModel['id']
9 | title: AnyModel['title']
10 | tracks: Song[]
11 | }
12 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_SnakeComposerSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 89e5760366d01f7f21244d341e1af2d4
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface SnakeComposer {
6 | id: number
7 | first_name?: string
8 | last_name?: string
9 | name: string
10 | }
11 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_SongSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey f4f9e398dc092747d13607e2bdcb0846
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Composer from './Composer'
5 |
6 | export default interface Song {
7 | id: number
8 | composer: Composer
9 | genre: "fingerstyle" | "rock" | "classical"
10 | tempo: "slow" | "medium" | "fast"
11 | title?: string
12 | }
13 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_SongWithVideosSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey c2d167325e7342eba01d0a78482d6d76
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Composer from './Composer'
5 | import type Video from './Video'
6 |
7 | export default interface SongWithVideos {
8 | id: number
9 | composer: Composer
10 | genre: "fingerstyle" | "rock" | "classical"
11 | tempo: "slow" | "medium" | "fast"
12 | title?: string
13 | videos: Video[]
14 | }
15 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_VideoSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 45d6b515fb6118eabbd39d586269cdd0
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 |
5 | export default interface Video {
6 | id: number
7 | createdAt: string | Date
8 | title?: string
9 | untypedFieldExample: any
10 | youtubeId?: string
11 | youtubeUrl?: string
12 | }
13 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_VideoWithSongSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 4042db0fa9ebdf8668c7c5f0ec172e6d
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Song from './Song'
5 |
6 | export default interface VideoWithSong {
7 | id: number
8 | createdAt: string | Date
9 | song: Song
10 | title?: string
11 | untypedFieldExample: any
12 | youtubeId?: string
13 | youtubeUrl?: string
14 | }
15 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/interfaces_index.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 6e82f9b05515cf2d2fd0ea698478f2de
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | export type { default as Composer } from './Composer'
5 | export type { default as ComposerWithSongs } from './ComposerWithSongs'
6 | export type { default as Model } from './Model'
7 | export type { default as NestedAlbum } from './Nested/Album'
8 | export type { default as SnakeComposer } from './SnakeComposer'
9 | export type { default as Song } from './Song'
10 | export type { default as SongWithVideos } from './SongWithVideos'
11 | export type { default as Video } from './Video'
12 | export type { default as VideoWithSong } from './VideoWithSong'
13 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_ComposerSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey b379726b6fef2dadbfd614384b868746
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | export {}
5 |
6 | declare global {
7 | namespace Schema {
8 | interface Composer {
9 | id: number
10 | firstName?: string
11 | lastName?: string
12 | name: string
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_ComposerWithSongsSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 9111c6d881174b0013582fa6366868db
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type ComposerWithSongsSong from './ComposerWithSongs/Song'
5 |
6 | declare global {
7 | namespace Schema {
8 | interface ComposerWithSongs {
9 | id: number
10 | firstName?: string
11 | lastName?: string
12 | name: string
13 | songs: ComposerWithSongsSong[]
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_ComposerWithSongsSerializer__SongSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey a69c0dbcc904740e5de89181ef4b2837
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | export {}
5 |
6 | declare global {
7 | namespace Schema {
8 | interface ComposerWithSongsSong {
9 | id: number
10 | title?: string
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_ModelSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey ed8ff6fbc986e6b666d559749c666dc5
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type AnyModel from '../AnyModel'
5 |
6 | declare global {
7 | namespace Schema {
8 | interface Model {
9 | id: AnyModel['id']
10 | title: AnyModel['title']
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_Nested__AlbumSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 58897b61d86838c60d1f12700f326896
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type AnyModel from '../../AnyModel'
5 | import type Song from '../Song'
6 |
7 | declare global {
8 | namespace Schema {
9 | interface NestedAlbum {
10 | id: AnyModel['id']
11 | title: AnyModel['title']
12 | tracks: Song[]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_SnakeComposerSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 89e5760366d01f7f21244d341e1af2d4
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | export {}
5 |
6 | declare global {
7 | namespace Schema {
8 | interface SnakeComposer {
9 | id: number
10 | first_name?: string
11 | last_name?: string
12 | name: string
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_SongSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey f4f9e398dc092747d13607e2bdcb0846
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Composer from './Composer'
5 |
6 | declare global {
7 | namespace Schema {
8 | interface Song {
9 | id: number
10 | composer: Composer
11 | genre: "fingerstyle" | "rock" | "classical"
12 | tempo: "slow" | "medium" | "fast"
13 | title?: string
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_SongWithVideosSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey c2d167325e7342eba01d0a78482d6d76
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Composer from './Composer'
5 | import type Video from './Video'
6 |
7 | declare global {
8 | namespace Schema {
9 | interface SongWithVideos {
10 | id: number
11 | composer: Composer
12 | genre: "fingerstyle" | "rock" | "classical"
13 | tempo: "slow" | "medium" | "fast"
14 | title?: string
15 | videos: Video[]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_VideoSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 45d6b515fb6118eabbd39d586269cdd0
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | export {}
5 |
6 | declare global {
7 | namespace Schema {
8 | interface Video {
9 | id: number
10 | createdAt: string | Date
11 | title?: string
12 | untypedFieldExample: any
13 | youtubeId?: string
14 | youtubeUrl?: string
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/__snapshots__/namespace_interfaces_VideoWithSongSerializer.snap:
--------------------------------------------------------------------------------
1 | // TypesFromSerializers CacheKey 4042db0fa9ebdf8668c7c5f0ec172e6d
2 | //
3 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
4 | import type Song from './Song'
5 |
6 | declare global {
7 | namespace Schema {
8 | interface VideoWithSong {
9 | id: number
10 | createdAt: string | Date
11 | song: Song
12 | title?: string
13 | untypedFieldExample: any
14 | youtubeId?: string
15 | youtubeUrl?: string
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/spec/types_from_serializers/generator_spec.rb:
--------------------------------------------------------------------------------
1 | require "vanilla/config/boot"
2 | require "vanilla/config/environment"
3 |
4 | describe "Generator" do
5 | let(:output_dir) { Pathname.new File.expand_path("../support/generated", __dir__) }
6 | let(:sample_dir) { Rails.root.join("app/frontend/types/serializers") }
7 | let(:serializers) {
8 | %w[
9 | Nested::AlbumSerializer
10 | VideoWithSongSerializer
11 | VideoSerializer
12 | SongSerializer
13 | SongWithVideosSerializer
14 | ModelSerializer
15 | ComposerWithSongsSerializer
16 | ComposerWithSongsSerializer::SongSerializer
17 | ComposerSerializer
18 | SnakeComposerSerializer
19 | ]
20 | }
21 |
22 | def file_for(dir, name, ext)
23 | dir.join("#{TypesFromSerializers.config.name_from_serializer.call(name).gsub("::", "/")}.#{ext}")
24 | end
25 |
26 | def app_file_for(name, ext = "ts")
27 | file_for(sample_dir, name, ext)
28 | end
29 |
30 | def output_file_for(name, ext = "ts")
31 | file_for(output_dir, name, ext)
32 | end
33 |
34 | def expect_generator
35 | expect(TypesFromSerializers)
36 | end
37 |
38 | def generate_serializers
39 | receive(:serializer_interface_content).and_call_original
40 | end
41 |
42 | original_config = TypesFromSerializers::Config.new TypesFromSerializers.config.clone.to_h.transform_values(&:clone)
43 |
44 | before do
45 | TypesFromSerializers.instance_variable_set(:@config, original_config)
46 |
47 | # Change the configuration to use a different directory.
48 | TypesFromSerializers.config do |config|
49 | config.output_dir = output_dir
50 | end
51 |
52 | output_dir.rmtree if output_dir.exist?
53 | end
54 |
55 | context "with default config options" do
56 | # NOTE: We do a manual snapshot test for now, more tests coming in the future.
57 | it "generates the files as expected" do
58 | expect_generator.to generate_serializers.exactly(serializers.size).times
59 | TypesFromSerializers.generate
60 |
61 | # It does not generate routes that don't have `export: true`.
62 | expect(output_file_for("BaseSerializer").exist?).to be false
63 |
64 | # It generates one file per serializer.
65 | serializers.each do |name|
66 | output_file = output_file_for(name)
67 | expect(output_file.read).to match_snapshot("interfaces_#{name.gsub("::", "__")}") # UPDATE_SNAPSHOTS="1" bin/rspec
68 | end
69 |
70 | # It generates an file that exports all interfaces.
71 | index_file = output_dir.join("index.ts")
72 | expect(index_file.exist?).to be true
73 | expect(index_file.read).to match_snapshot("interfaces_index") # UPDATE_SNAPSHOTS="1" bin/rspec
74 |
75 | # It does not render if generating again.
76 | TypesFromSerializers.generate
77 | end
78 | end
79 |
80 | context "with namespace config option" do
81 | it "generates the files as expected" do
82 | TypesFromSerializers.config do |config|
83 | config.namespace = "Schema"
84 | end
85 |
86 | expect_generator.to generate_serializers.exactly(serializers.size).times
87 | TypesFromSerializers.generate
88 |
89 | # It does not generate routes that don't have `export: true`.
90 | expect(output_file_for("BaseSerializer", "d.ts").exist?).to be false
91 |
92 | # It does not generate an index file
93 | index_file = output_dir.join("index.ts")
94 | expect(index_file.exist?).to be false
95 |
96 | # It generates one file per serializer.
97 | serializers.each do |name|
98 | output_file = output_file_for(name, "d.ts")
99 | expect(output_file.read).to match_snapshot("namespace_interfaces_#{name.gsub("::", "__")}") # UPDATE_SNAPSHOTS="1" bin/rspec
100 | end
101 | end
102 | end
103 |
104 | it "has a rake task available" do
105 | Rails.application.load_tasks
106 | expect_generator.to generate_serializers.exactly(serializers.size).times
107 | expect { Rake::Task["types_from_serializers:generate"].invoke }.not_to raise_error
108 | end
109 |
110 | describe "types mapping" do
111 | it "maps citext type from SQL to string type in TypeScript" do
112 | db_type = :citext
113 |
114 | ts_type = TypesFromSerializers.config.sql_to_typescript_type_mapping[db_type]
115 |
116 | expect(ts_type).to eq(:string)
117 | end
118 |
119 | it "maps uuid type from SQL to string type in TypeScript" do
120 | db_type = :uuid
121 |
122 | ts_type = TypesFromSerializers.config.sql_to_typescript_type_mapping[db_type]
123 |
124 | expect(ts_type).to eq(:string)
125 | end
126 |
127 | it "maps json type from SQL to object type in TypeScript" do
128 | db_type = :json
129 |
130 | ts_type = TypesFromSerializers.config.sql_to_typescript_type_mapping[db_type]
131 |
132 | expect(ts_type).to eq("Record")
133 | end
134 |
135 | it "maps jsonb type from SQL to object type in TypeScript" do
136 | db_type = :jsonb
137 |
138 | ts_type = TypesFromSerializers.config.sql_to_typescript_type_mapping[db_type]
139 |
140 | expect(ts_type).to eq("Record")
141 | end
142 | end
143 | end
144 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "target": "es2020",
5 | "lib": ["ESNext", "DOM"],
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "strictNullChecks": true,
9 | "moduleResolution": "Node",
10 | "resolveJsonModule": true,
11 | "skipLibCheck": true
12 | },
13 | "exclude": [
14 | "**/dist",
15 | "**/node_modules",
16 | "**/test"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/types_from_serializers/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [2.4.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.3.0...types_from_serializers@2.4.0) (2025-04-08)
2 |
3 |
4 | ### Features
5 |
6 | * add support for json and jsonb fields ([#25](https://github.com/ElMassimo/types_from_serializers/issues/25)) ([34e747f](https://github.com/ElMassimo/types_from_serializers/commit/34e747f11ebdd032484d10679240701eb2548ed3))
7 | * convert uuid fields to string type ([#24](https://github.com/ElMassimo/types_from_serializers/issues/24)) ([35c87a9](https://github.com/ElMassimo/types_from_serializers/commit/35c87a96365d32d40adce5ba9fddd3d380f29169))
8 |
9 |
10 |
11 | # [2.3.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.2.0...types_from_serializers@2.3.0) (2024-08-23)
12 |
13 |
14 | ### Features
15 |
16 | * generate types for inline serializers, exclude them from the index ([1c3657c](https://github.com/ElMassimo/types_from_serializers/commit/1c3657c61a1bc891f3219f6eaf8557cd3cd6344a)), closes [#19](https://github.com/ElMassimo/types_from_serializers/issues/19)
17 |
18 |
19 |
20 | # [2.2.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.1.0...types_from_serializers@2.2.0) (2024-08-23)
21 |
22 |
23 | ### Bug Fixes
24 |
25 | * be more accurate regarding decimal serialization ([cd63653](https://github.com/ElMassimo/types_from_serializers/commit/cd636530a5710112a14746cc7d0e3f15016cd5e1))
26 |
27 |
28 | ### Features
29 |
30 | * infer type from enums ([#20](https://github.com/ElMassimo/types_from_serializers/issues/20)) ([49dc61d](https://github.com/ElMassimo/types_from_serializers/commit/49dc61da2718256e9b5f5743b5a65c4746d64c2f))
31 |
32 |
33 |
34 | # [2.1.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.0.2...types_from_serializers@2.1.0) (2023-07-19)
35 |
36 |
37 | ### Features
38 |
39 | * add `namespace` option to generate `.d.ts` files ([#9](https://github.com/ElMassimo/types_from_serializers/issues/9)) ([6f67b1a](https://github.com/ElMassimo/types_from_serializers/commit/6f67b1ad9283868e8e3325042645bceccc85b047))
40 |
41 |
42 |
43 | ## [2.0.2](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.0.1...types_from_serializers@2.0.2) (2023-04-05)
44 |
45 |
46 | ### Features
47 |
48 | * map citext from PostgreSQL to string ([#7](https://github.com/ElMassimo/types_from_serializers/issues/7)) ([d8c6848](https://github.com/ElMassimo/types_from_serializers/commit/d8c6848b99b0f4ba3770871f491755c229a2c4b0))
49 |
50 |
51 |
52 | ## [2.0.1](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@2.0.0...types_from_serializers@2.0.1) (2023-04-03)
53 |
54 |
55 | ### Bug Fixes
56 |
57 | * `add_attribute` now expects keyword arguments ([154b49e](https://github.com/ElMassimo/types_from_serializers/commit/154b49e463e3e6533b21520b7f0d699e6f0f47ba))
58 |
59 |
60 |
61 | ## [2.0.0](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@0.1.2...types_from_serializers@2.0.0) (2023-04-02)
62 |
63 | This version adds support for `oj_serializers-2.0.2`, supporting all changes in:
64 |
65 | - https://github.com/ElMassimo/oj_serializers/pull/9
66 |
67 | ### Features ✨
68 |
69 | - Now keys will match the [`transform_keys`](https://github.com/ElMassimo/oj_serializers#transforming-attribute-keys-) configuration instead of always being camelized
70 | - Support for [`flat_one`](https://github.com/ElMassimo/oj_serializers#composing-serializers-)
71 | - Use relative paths for imports to make the output configuration more flexible
72 | - Define the order of properties in the interface with `sort_properties_by`
73 |
74 | ## [0.1.3](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@0.1.2...types_from_serializers@0.1.3) (2022-07-12)
75 |
76 |
77 | ### Features
78 |
79 | * apply the sql mapping fallback as the default ([64898c4](https://github.com/ElMassimo/types_from_serializers/commit/64898c4e3a3f83ea67294f2200f253cd2a64aea9))
80 |
81 |
82 |
83 | ## [0.1.2](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@0.1.1...types_from_serializers@0.1.2) (2022-07-12)
84 |
85 |
86 | ### Bug Fixes
87 |
88 | * avoid having the full file path in the cache key ([556f8f6](https://github.com/ElMassimo/types_from_serializers/commit/556f8f667608fa950a3ad0647540055b1b5f1dc8))
89 |
90 |
91 |
92 | ## [0.1.1](https://github.com/ElMassimo/types_from_serializers/compare/types_from_serializers@0.1.0...types_from_serializers@0.1.1) (2022-07-12)
93 |
94 |
95 |
96 | # 0.1.0 (2022-07-12)
97 |
98 |
99 | ### Features
100 |
101 | - Start simple, no additional syntax required
102 | - Infers types from a related `ActiveRecord` model, using the SQL schema
103 | - Understands JS native types and how to map SQL columns: `string`, `boolean`, etc
104 | - Automatically types [associations](https://github.com/ElMassimo/oj_serializers#associations-dsl-), importing the generated types for the referenced serializers
105 | - Detects [conditional attributes](https://github.com/ElMassimo/oj_serializers#rendering-an-attribute-conditionally) and marks them as optional: `name?: string`
106 | - Fallback to a custom interface using `type_from`
107 | - Supports custom types and automatically adds the necessary imports
108 | - handle non-ActiveRecord models and extract types from unions ([ea9b2a7](https://github.com/ElMassimo/types_from_serializers/commit/ea9b2a71cb85503ff691e5ef115ab73f89b005af))
109 | - support specifying base serializers and additional dirs to scan ([164cfe1](https://github.com/ElMassimo/types_from_serializers/commit/164cfe17bb0527c59cf95441381aef7bf797a568))
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/types_from_serializers/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Máximo Mussini
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/types_from_serializers/README.md:
--------------------------------------------------------------------------------
1 |
2 | Types From Serializers
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | [aliases]: https://vite-ruby.netlify.app/guide/development.html#import-aliases-%F0%9F%91%89
14 | [config options]: https://github.com/ElMassimo/types_from_serializers/blob/main/lib/types_from_serializers/generator.rb#L82-L85
15 | [readme]: https://github.com/ElMassimo/types_from_serializers
16 |
17 | For more information, check the main [README].
18 |
19 | ### Installation 💿
20 |
21 | Add this line to your application's Gemfile:
22 |
23 | ```ruby
24 | gem 'types_from_serializers'
25 | ```
26 |
27 | And then execute:
28 |
29 | $ bundle
30 |
31 | Or install it yourself as:
32 |
33 | $ gem install types_from_serializers
34 |
--------------------------------------------------------------------------------
/types_from_serializers/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 |
5 | class Bundler::GemHelper
6 | def version_tag
7 | "types_from_serializers@#{version}"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/types_from_serializers/bin/release:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd .. && pnpm release types_from_serializers "$@"
3 |
--------------------------------------------------------------------------------
/types_from_serializers/lib/types_from_serializers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "types_from_serializers/version"
4 | require_relative "types_from_serializers/dsl"
5 | require_relative "types_from_serializers/railtie"
6 |
--------------------------------------------------------------------------------
/types_from_serializers/lib/types_from_serializers/dsl.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "active_support/concern"
4 |
5 | # Internal: A DSL to specify types for serializer attributes.
6 | module TypesFromSerializers
7 | module DSL
8 | extend ActiveSupport::Concern
9 |
10 | module ClassMethods
11 | # Override: Capture the name of the model related to the serializer.
12 | #
13 | # name - An alias for the internal object in the serializer.
14 | # model - The name of an ActiveRecord model to infer types from the schema.
15 | # types_from - The name of a TypeScript interface to infer types from.
16 | def object_as(name, model: nil, types_from: nil)
17 | # NOTE: Avoid taking memory for type information that won't be used.
18 | if Rails.env.development?
19 | model ||= name.is_a?(Symbol) ? name : try(:_serializer_model_name) || name
20 | define_singleton_method(:_serializer_model_name) { model }
21 | define_singleton_method(:_serializer_types_from) { types_from } if types_from
22 | end
23 |
24 | super(name)
25 | end
26 |
27 | # Public: Shortcut for typing a serializer attribute.
28 | #
29 | # It specifies the type for a serializer method that will be defined
30 | # immediately after calling this method.
31 | def type(type, **options)
32 | attribute type: type, **options
33 | end
34 |
35 | private
36 |
37 | # Override: Remove unnecessary options in production, types are only
38 | # used when generating code in development.
39 | unless Rails.env.development?
40 | def add_attribute(name, type: nil, optional: nil, **options)
41 | super(name, **options)
42 | end
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/types_from_serializers/lib/types_from_serializers/generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "digest"
4 | require "fileutils"
5 | require "pathname"
6 |
7 | # Public: Automatically generates TypeScript interfaces for Ruby serializers.
8 | module TypesFromSerializers
9 | DEFAULT_TRANSFORM_KEYS = ->(key) { key.camelize(:lower).chomp("?") }
10 |
11 | # Internal: Extensions that simplify the implementation of the generator.
12 | module SerializerRefinements
13 | refine String do
14 | # Internal: Converts a name such as :user to the User constant.
15 | def to_model
16 | classify.safe_constantize
17 | end
18 | end
19 |
20 | refine Symbol do
21 | def safe_constantize
22 | to_s.classify.safe_constantize
23 | end
24 |
25 | def to_model
26 | to_s.to_model
27 | end
28 | end
29 |
30 | refine Class do
31 | # Internal: Name of the TypeScript interface.
32 | def ts_name
33 | TypesFromSerializers.config.name_from_serializer.call(name).tr_s(":", "")
34 | end
35 |
36 | # Internal: The base name of the TypeScript file to be written.
37 | def ts_filename
38 | TypesFromSerializers.config.name_from_serializer.call(name).gsub("::", "/")
39 | end
40 |
41 | # Internal: If the serializer was defined inside a file.
42 | def inline_serializer?
43 | name.include?("Serializer::")
44 | end
45 |
46 | # Internal: The TypeScript properties of the serialzeir interface.
47 | def ts_properties
48 | @ts_properties ||= begin
49 | model_class = _serializer_model_name&.to_model
50 | model_columns = model_class.try(:columns_hash) || {}
51 | model_enums = model_class.try(:defined_enums) || {}
52 | types_from = try(:_serializer_types_from)
53 |
54 | prepare_attributes(
55 | sort_by: TypesFromSerializers.config.sort_properties_by,
56 | transform_keys: TypesFromSerializers.config.transform_keys || try(:_transform_keys) || DEFAULT_TRANSFORM_KEYS,
57 | )
58 | .flat_map { |key, options|
59 | if options[:association] == :flat
60 | options.fetch(:serializer).ts_properties
61 | else
62 | Property.new(
63 | name: key,
64 | type: options[:serializer] || options[:type],
65 | optional: options[:optional] || options.key?(:if),
66 | multi: options[:association] == :many,
67 | column_name: options.fetch(:value_from),
68 | ).tap do |property|
69 | property.infer_type_from(model_columns, model_enums, types_from)
70 | end
71 | end
72 | }
73 | end
74 | end
75 |
76 | # Internal: A first pass of gathering types for the serializer attributes.
77 | def ts_interface
78 | @ts_interface ||= Interface.new(
79 | name: ts_name,
80 | filename: ts_filename,
81 | properties: ts_properties,
82 | )
83 | end
84 | end
85 | end
86 |
87 | # Internal: The configuration for TypeScript generation.
88 | Config = Struct.new(
89 | :base_serializers,
90 | :serializers_dirs,
91 | :output_dir,
92 | :custom_types_dir,
93 | :name_from_serializer,
94 | :global_types,
95 | :sort_properties_by,
96 | :sql_to_typescript_type_mapping,
97 | :skip_serializer_if,
98 | :transform_keys,
99 | :namespace,
100 | keyword_init: true,
101 | ) do
102 | def relative_custom_types_dir
103 | @relative_custom_types_dir ||= (custom_types_dir || output_dir.parent).relative_path_from(output_dir)
104 | end
105 |
106 | def unknown_type
107 | sql_to_typescript_type_mapping.default
108 | end
109 | end
110 |
111 | # Internal: Information to generate a TypeScript interface for a serializer.
112 | Interface = Struct.new(
113 | :name,
114 | :filename,
115 | :properties,
116 | keyword_init: true,
117 | ) do
118 | using SerializerRefinements
119 |
120 | def inspect
121 | to_h.inspect
122 | end
123 |
124 | # Internal: Returns a list of imports for types used in this interface.
125 | def used_imports
126 | association_serializers, attribute_types = properties.map(&:type).compact.uniq
127 | .partition { |type| type.respond_to?(:ts_interface) }
128 |
129 | serializer_type_imports = association_serializers.map(&:ts_interface)
130 | .map { |type| [type.name, relative_path(type.pathname, pathname)] }
131 |
132 | custom_type_imports = attribute_types
133 | .flat_map { |type| extract_typescript_types(type.to_s) }
134 | .uniq
135 | .reject { |type| global_type?(type) }
136 | .map { |type|
137 | type_path = TypesFromSerializers.config.relative_custom_types_dir.join(type)
138 | [type, relative_path(type_path, pathname)]
139 | }
140 |
141 | (custom_type_imports + serializer_type_imports)
142 | .map { |interface, filename| "import type #{interface} from '#{filename}'\n" }
143 | end
144 |
145 | def as_typescript
146 | indent = TypesFromSerializers.config.namespace ? 3 : 1
147 | <<~TS.gsub(/\n$/, "")
148 | interface #{name} {
149 | #{" " * indent}#{properties.index_by(&:name).values.map(&:as_typescript).join("\n#{" " * indent}")}
150 | #{" " * (indent - 1)}}
151 | TS
152 | end
153 |
154 | protected
155 |
156 | def pathname
157 | @pathname ||= Pathname.new(filename)
158 | end
159 |
160 | # Internal: Calculates a relative path that can be used in an import.
161 | def relative_path(target_path, importer_path)
162 | path = target_path.relative_path_from(importer_path.parent).to_s
163 | path.start_with?(".") ? path : "./#{path}"
164 | end
165 |
166 | # Internal: Extracts any types inside generics or array types.
167 | def extract_typescript_types(type)
168 | type.split(/[<>\[\],\s|]+/)
169 | end
170 |
171 | # NOTE: Treat uppercase names as custom types.
172 | # Lowercase names would be native types, such as :string and :boolean.
173 | def global_type?(type)
174 | type[0] == type[0].downcase || TypesFromSerializers.config.global_types.include?(type)
175 | end
176 | end
177 |
178 | # Internal: The type metadata for a serializer attribute.
179 | Property = Struct.new(
180 | :name,
181 | :type,
182 | :optional,
183 | :multi,
184 | :column_name,
185 | keyword_init: true,
186 | ) do
187 | using SerializerRefinements
188 |
189 | def inspect
190 | to_h.inspect
191 | end
192 |
193 | # Internal: Infers the property's type by checking a corresponding SQL
194 | # column, or falling back to a TypeScript interface if provided.
195 | def infer_type_from(columns_hash, defined_enums, ts_interface)
196 | if type
197 | type
198 | elsif (enum = defined_enums[column_name.to_s])
199 | self.type = enum.keys.map(&:inspect).join(" | ")
200 | elsif (column = columns_hash[column_name.to_s])
201 | self.multi = true if column.try(:array)
202 | self.optional = true if column.null && !column.default
203 | self.type = TypesFromSerializers.config.sql_to_typescript_type_mapping[column.type]
204 | elsif ts_interface
205 | self.type = "#{ts_interface}['#{name}']"
206 | end
207 | end
208 |
209 | def as_typescript
210 | type_str = if type.respond_to?(:ts_name)
211 | type.ts_name
212 | else
213 | type || TypesFromSerializers.config.unknown_type
214 | end
215 |
216 | "#{name}#{"?" if optional}: #{type_str}#{"[]" if multi}"
217 | end
218 | end
219 |
220 | # Internal: Structure to keep track of changed files.
221 | class Changes
222 | def initialize(dirs)
223 | @added = Set.new
224 | @removed = Set.new
225 | @modified = Set.new
226 | track_changes(dirs)
227 | end
228 |
229 | def updated?
230 | @modified.any? || @added.any? || @removed.any?
231 | end
232 |
233 | def any_removed?
234 | @removed.any?
235 | end
236 |
237 | def modified_files
238 | @modified
239 | end
240 |
241 | def only_modified?
242 | @added.empty? && @removed.empty?
243 | end
244 |
245 | def clear
246 | @added.clear
247 | @removed.clear
248 | @modified.clear
249 | end
250 |
251 | private
252 |
253 | def track_changes(dirs)
254 | Listen.to(*dirs, only: %r{.rb$}) do |modified, added, removed|
255 | modified.each { |file| @modified.add(file) }
256 | added.each { |file| @added.add(file) }
257 | removed.each { |file| @removed.add(file) }
258 | end.start
259 | end
260 | end
261 |
262 | class << self
263 | using SerializerRefinements
264 |
265 | attr_reader :force_generation
266 |
267 | # Public: Configuration of the code generator.
268 | def config
269 | (@config ||= default_config(root)).tap do |config|
270 | yield(config) if block_given?
271 | end
272 | end
273 |
274 | # Public: Generates code for all serializers in the app.
275 | def generate(force: ENV["SERIALIZER_TYPES_FORCE"])
276 | @force_generation = force
277 | config.output_dir.rmtree if force && config.output_dir.exist?
278 |
279 | if config.namespace
280 | load_serializers(all_serializer_files) if force
281 | else
282 | generate_index_file
283 | end
284 |
285 | loaded_serializers.each do |serializer|
286 | generate_interface_for(serializer)
287 | end
288 | end
289 |
290 | def generate_changed
291 | if changes.updated?
292 | config.output_dir.rmtree if changes.any_removed?
293 | load_serializers(changes.modified_files)
294 | generate
295 | changes.clear
296 | end
297 | end
298 |
299 | # Internal: Defines a TypeScript interface for the serializer.
300 | def generate_interface_for(serializer)
301 | interface = serializer.ts_interface
302 |
303 | write_if_changed(filename: interface.filename, cache_key: interface.inspect, extension: config.namespace ? "d.ts" : "ts") {
304 | serializer_interface_content(interface)
305 | }
306 | end
307 |
308 | # Internal: Allows to import all serializer types from a single file.
309 | def generate_index_file
310 | cache_key = all_serializer_files.map { |file| file.delete_prefix(root.to_s) }.join
311 | write_if_changed(filename: "index", cache_key: cache_key) {
312 | load_serializers(all_serializer_files)
313 | serializers_index_content(loaded_serializers)
314 | }
315 | end
316 |
317 | # Internal: Checks if it should avoid generating an interface.
318 | def skip_serializer?(serializer)
319 | serializer.name.in?(config.base_serializers) ||
320 | config.skip_serializer_if.call(serializer)
321 | end
322 |
323 | # Internal: Returns an object compatible with FileUpdateChecker.
324 | def track_changes
325 | changes
326 | end
327 |
328 | private
329 |
330 | def root
331 | defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd)
332 | end
333 |
334 | def changes
335 | @changes ||= Changes.new(config.serializers_dirs)
336 | end
337 |
338 | def all_serializer_files
339 | config.serializers_dirs.flat_map { |dir| Dir["#{dir}/**/*.rb"] }.sort
340 | end
341 |
342 | def load_serializers(files)
343 | files.each { |file| require file }
344 | end
345 |
346 | def loaded_serializers
347 | config.base_serializers.map(&:constantize)
348 | .flat_map(&:descendants)
349 | .uniq
350 | .sort_by(&:name)
351 | .reject { |s| skip_serializer?(s) }
352 | rescue NameError
353 | raise ArgumentError, "Please ensure all your serializers extend BaseSerializer, or configure `config.base_serializers`."
354 | end
355 |
356 | def default_config(root)
357 | Config.new(
358 | # The base serializers that all other serializers extend.
359 | base_serializers: ["BaseSerializer"],
360 |
361 | # The dirs where the serializer files are located.
362 | serializers_dirs: [root.join("app/serializers").to_s],
363 |
364 | # The dir where interface files are placed.
365 | output_dir: root.join(defined?(ViteRuby) ? ViteRuby.config.source_code_dir : "app/frontend").join("types/serializers"),
366 |
367 | # Remove the serializer suffix from the class name.
368 | name_from_serializer: ->(name) {
369 | name.split("::").map { |n| n.delete_suffix("Serializer") }.join("::")
370 | },
371 |
372 | # Types that don't need to be imported in TypeScript.
373 | global_types: [
374 | "Array",
375 | "Record",
376 | "Date",
377 | ].to_set,
378 |
379 | # Allows to choose a different sort order, alphabetical by default.
380 | sort_properties_by: :name,
381 |
382 | # Allows to avoid generating a serializer.
383 | skip_serializer_if: ->(serializer) { false },
384 |
385 | # Maps SQL column types to TypeScript native and custom types.
386 | sql_to_typescript_type_mapping: {
387 | boolean: :boolean,
388 | date: "string | Date",
389 | datetime: "string | Date",
390 | decimal: "string | number",
391 | integer: :number,
392 | string: :string,
393 | text: :string,
394 | citext: :string,
395 | uuid: :string,
396 | json: "Record",
397 | jsonb: "Record",
398 | }.tap do |types|
399 | types.default = :unknown
400 | end,
401 |
402 | # Allows to transform keys, useful when converting objects client-side.
403 | transform_keys: nil,
404 |
405 | # Allows scoping typescript definitions to a namespace
406 | namespace: nil,
407 | )
408 | end
409 |
410 | # Internal: Writes if the file does not exist or the cache key has changed.
411 | # The cache strategy consists of a comment on the first line of the file.
412 | #
413 | # Yields to receive the rendered file content when it needs to.
414 | def write_if_changed(filename:, cache_key:, extension: "ts")
415 | filename = config.output_dir.join("#{filename}.#{extension}")
416 | FileUtils.mkdir_p(filename.dirname)
417 | cache_key_comment = "// TypesFromSerializers CacheKey #{Digest::MD5.hexdigest(cache_key)}\n"
418 | File.open(filename, "a+") { |file|
419 | if stale?(file, cache_key_comment)
420 | file.truncate(0)
421 | file.write(cache_key_comment)
422 | file.write(yield)
423 | end
424 | }
425 | end
426 |
427 | def serializers_index_content(serializers)
428 | <<~TS
429 | //
430 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
431 | #{serializers.reject(&:inline_serializer?).map { |s|
432 | "export type { default as #{s.ts_name} } from './#{s.ts_filename}'"
433 | }.join("\n")}
434 | TS
435 | end
436 |
437 | def serializer_interface_content(interface)
438 | config.namespace ? declaration_interface_definition(interface) : standard_interface_definition(interface)
439 | end
440 |
441 | def standard_interface_definition(interface)
442 | <<~TS
443 | //
444 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
445 | #{interface.used_imports.join}
446 | export default #{interface.as_typescript}
447 | TS
448 | end
449 |
450 | def declaration_interface_definition(interface)
451 | <<~TS
452 | //
453 | // DO NOT MODIFY: This file was automatically generated by TypesFromSerializers.
454 | #{interface.used_imports.empty? ? "export {}\n" : interface.used_imports.join}
455 | declare global {
456 | namespace #{config.namespace} {
457 | #{interface.as_typescript}
458 | }
459 | }
460 | TS
461 | end
462 |
463 | # Internal: Returns true if the cache key has changed since the last codegen.
464 | def stale?(file, cache_key_comment)
465 | @force_generation || file.gets != cache_key_comment
466 | end
467 | end
468 | end
469 |
--------------------------------------------------------------------------------
/types_from_serializers/lib/types_from_serializers/railtie.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails/railtie"
4 |
5 | class TypesFromSerializers::Railtie < Rails::Railtie
6 | railtie_name :types_from_serializers
7 |
8 | # Automatically generates code whenever a serializer is loaded.
9 | if defined?(Rails.env) && Rails.env.development?
10 | require_relative "generator"
11 |
12 | initializer "types_from_serializers.reloader" do |app|
13 | if Gem.loaded_specs["listen"]
14 | require "listen"
15 |
16 | app.config.after_initialize do
17 | app.reloaders << TypesFromSerializers.track_changes
18 | end
19 |
20 | app.config.to_prepare do
21 | TypesFromSerializers.generate_changed
22 | end
23 | else
24 | app.config.to_prepare do
25 | TypesFromSerializers.generate
26 | end
27 |
28 | Rails.logger.warn "Add 'listen' to your Gemfile to automatically generate code on serializer changes."
29 | end
30 | end
31 | end
32 |
33 | # Suitable when triggering code generation manually.
34 | rake_tasks do |app|
35 | namespace :types_from_serializers do
36 | desc "Generates TypeScript interfaces for each serializer in the app."
37 | task generate: :environment do
38 | require_relative "generator"
39 | start_time = Time.zone.now
40 | print "Generating TypeScript interfaces..."
41 | serializers = TypesFromSerializers.generate(force: true)
42 | puts "completed in #{(Time.zone.now - start_time).round(2)} seconds.\n"
43 | puts "Found #{serializers.size} serializers:"
44 | puts serializers.map { |s| "\t#{s.name}" }.join("\n")
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/types_from_serializers/lib/types_from_serializers/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module TypesFromSerializers
4 | # Public: This library adheres to semantic versioning.
5 | VERSION = "2.4.0"
6 | end
7 |
--------------------------------------------------------------------------------
/types_from_serializers/types_from_serializers.gemspec:
--------------------------------------------------------------------------------
1 | require File.expand_path("../lib/types_from_serializers/version", __FILE__)
2 |
3 | Gem::Specification.new do |s|
4 | s.name = "types_from_serializers"
5 | s.version = TypesFromSerializers::VERSION
6 | s.authors = ["Máximo Mussini"]
7 | s.email = ["maximomussini@gmail.com"]
8 | s.summary = "Generate TypeScript interfaces from your JSON serializers."
9 | s.description = "types_from_serializers helps you by automatically generating TypeScript interfaces for your JSON serializers, allowing you typecheck your frontend code to ship fast and with confidence."
10 | s.homepage = "https://github.com/ElMassimo/types_from_serializers"
11 | s.license = "MIT"
12 | s.extra_rdoc_files = ["README.md"]
13 | s.files = Dir.glob("{lib,exe,templates}/**/*") + %w[README.md CHANGELOG.md LICENSE.txt]
14 | s.require_path = "lib"
15 |
16 | s.add_dependency "railties", ">= 5.1"
17 | s.add_dependency "oj_serializers", ">= 2.0.2", "~> 2.0"
18 | s.add_dependency "listen", "~> 3.2"
19 |
20 | s.add_development_dependency "bundler", "~> 2"
21 | s.add_development_dependency "rake", "~> 13"
22 | s.add_development_dependency "rspec-given", "~> 3.8"
23 | s.add_development_dependency "rspec-snapshot"
24 | s.add_development_dependency "simplecov", "< 0.18"
25 | s.add_development_dependency "standard", "~> 1.0"
26 | s.add_development_dependency "activerecord"
27 | s.add_development_dependency "js_from_routes"
28 | s.add_development_dependency "sqlite3"
29 | s.add_development_dependency "rubocop"
30 | s.add_development_dependency "rubocop-rails"
31 | s.add_development_dependency "rubocop-rspec"
32 | s.add_development_dependency "rubocop-performance"
33 | end
34 |
--------------------------------------------------------------------------------