Skip to content

Commit 7d8aaa4

Browse files
committed
feat!: multifile improvements
1 parent 4cb9330 commit 7d8aaa4

File tree

8 files changed

+151
-77
lines changed

8 files changed

+151
-77
lines changed

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
bundler-cache: true
3131
- name: Install dependencies
3232
run:
33-
npm i -g semantic-release-rubygem @semantic-release/changelog @semantic-release/git @semantic-release/release-notes-generator @semantic-release/github @semantic-release/commit-analyzer semantic-release
33+
npm i -g semantic-release-rubygem conventional-changelog-conventionalcommits @semantic-release/changelog @semantic-release/git @semantic-release/release-notes-generator @semantic-release/github @semantic-release/commit-analyzer semantic-release
3434
- name: Release
3535
env:
3636
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.releaserc.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
{
22
"branches": ["main"],
33
"plugins": [
4-
"@semantic-release/commit-analyzer",
4+
[
5+
"@semantic-release/commit-analyzer",
6+
{
7+
"preset": "conventionalcommits"
8+
}
9+
],
510
"@semantic-release/release-notes-generator",
611
"@semantic-release/changelog",
712
[
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
module Anchor::TypeScript
2+
class MultifileSaveService
3+
START_MARKER = "// START AUTOGEN\n"
4+
END_MARKER = "\n// END AUTOGEN\n"
5+
REGEX = /#{Regexp.escape(START_MARKER)}(.*?)#{Regexp.escape(END_MARKER)}/m
6+
7+
def self.call(...)
8+
new(...).call
9+
end
10+
11+
def initialize(generator:, folder_path:, force: false, resource_file_extension: ".ts")
12+
@generator = generator
13+
@folder_path = folder_path
14+
@force = force
15+
@resource_file_extension = "." + resource_file_extension.sub(/^\./, "")
16+
end
17+
18+
def call
19+
FileUtils.mkdir_p(@folder_path)
20+
results = @generator.call
21+
results.each { |result| save_result(result) }
22+
end
23+
24+
private
25+
26+
def save_result(result)
27+
filename = if result.type == MultifileSchemaGenerator::FileType::RESOURCE
28+
resource_file_name(result.name)
29+
else
30+
result.name
31+
end
32+
33+
path = Rails.root.join(@folder_path, filename)
34+
35+
if @force || !File.exist?(path)
36+
File.open(path, "w") { |f| f.write(result.text) }
37+
return
38+
end
39+
40+
existing_content = File.read(path)
41+
42+
new_content = if manually_editable?(existing_content)
43+
replace_between(
44+
existing: existing_content,
45+
new: result.text,
46+
)
47+
else
48+
result.text
49+
end
50+
51+
File.open(path, "w") { |f| f.write(new_content) }
52+
end
53+
54+
def resource_file_name(name)
55+
File.basename(name, ".ts") + @resource_file_extension
56+
end
57+
58+
def manually_editable?(text)
59+
!text.match(REGEX).nil?
60+
end
61+
62+
def replace_between(existing:, new:)
63+
new_content = extract_between(new)
64+
raise "Was @generator initialized with manually_editable: false?" unless new_content
65+
66+
existing.gsub(REGEX, "#{START_MARKER}\n#{new_content}\n#{END_MARKER}")
67+
end
68+
69+
def extract_between(text)
70+
match = text.match(REGEX)
71+
match[1].strip
72+
end
73+
end
74+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
module Anchor::TypeScript
2+
class MultifileSchemaGenerator < Anchor::SchemaGenerator
3+
Result = Struct.new(:name, :text, :type, keyword_init: true)
4+
5+
module FileType
6+
RESOURCE = "resource"
7+
UTIL = "util"
8+
end
9+
10+
def initialize(register:, context: {}, include_all_fields: false, exclude_fields: nil, manually_editable: true) # rubocop:disable Lint/MissingSuper
11+
@register = register
12+
@context = context
13+
@include_all_fields = include_all_fields
14+
@exclude_fields = exclude_fields
15+
@manually_editable = manually_editable
16+
end
17+
18+
def call
19+
[shared_file] + resource_files
20+
end
21+
22+
private
23+
24+
def shared_file
25+
maybe_type = "export type Maybe<T> = T | null;"
26+
27+
enum_expressions = enums.map(&:express)
28+
text = ([maybe_type] + enum_expressions).join("\n\n") + "\n"
29+
Result.new(name: "shared.ts", text:, type: FileType::UTIL)
30+
end
31+
32+
def resource_files
33+
resources.map do |r|
34+
definition = r.definition(
35+
context: @context,
36+
include_all_fields: @include_all_fields,
37+
exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
38+
)
39+
40+
file_structure = ::Anchor::TypeScript::FileStructure.new(definition)
41+
text = file_structure.to_code(manually_editable: @manually_editable)
42+
name = file_structure.name
43+
Result.new(name:, text:, type: FileType::RESOURCE)
44+
end
45+
end
46+
47+
def resources
48+
@resources ||= @register.resources.map { |r| Anchor::TypeScript::Resource.new(r) }
49+
end
50+
51+
def enums
52+
@enums ||= @register.enums.map { |e| Anchor::TypeScript::Types::Enum.new(e) }
53+
end
54+
end
55+
end

lib/anchor/type_script/schema_generator.rb

-47
Original file line numberDiff line numberDiff line change
@@ -32,51 +32,4 @@ def enums
3232
@enums ||= @register.enums.map { |e| Anchor::TypeScript::Types::Enum.new(e) }
3333
end
3434
end
35-
36-
class MultifileSchemaGenerator < Anchor::SchemaGenerator
37-
def initialize(register:, context: {}, include_all_fields: false, exclude_fields: nil, manually_editable: true) # rubocop:disable Lint/MissingSuper
38-
@register = register
39-
@context = context
40-
@include_all_fields = include_all_fields
41-
@exclude_fields = exclude_fields
42-
@manually_editable = manually_editable
43-
end
44-
45-
def call
46-
[shared_file] + resource_files
47-
end
48-
49-
private
50-
51-
def shared_file
52-
maybe_type = "export type Maybe<T> = T | null;"
53-
54-
enum_expressions = enums.map(&:express)
55-
content = ([maybe_type] + enum_expressions).join("\n\n") + "\n"
56-
{ name: "shared.ts", content: }
57-
end
58-
59-
def resource_files
60-
resources.map do |r|
61-
definition = r.definition(
62-
context: @context,
63-
include_all_fields: @include_all_fields,
64-
exclude_fields: @exclude_fields.nil? ? [] : @exclude_fields[r.anchor_schema_name.to_sym],
65-
)
66-
67-
file_structure = ::Anchor::TypeScript::FileStructure.new(definition)
68-
content = file_structure.to_code(manually_editable: @manually_editable)
69-
name = file_structure.name
70-
{ name:, content: }
71-
end
72-
end
73-
74-
def resources
75-
@resources ||= @register.resources.map { |r| Anchor::TypeScript::Resource.new(r) }
76-
end
77-
78-
def enums
79-
@enums ||= @register.enums.map { |e| Anchor::TypeScript::Types::Enum.new(e) }
80-
end
81-
end
8235
end

lib/jsonapi-resources-anchor.rb

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
require "anchor/type_script/file_structure"
1616
require "anchor/type_script/types"
1717
require "anchor/type_script/schema_generator"
18+
require "anchor/type_script/multifile_save_service"
19+
require "anchor/type_script/multifile_schema_generator"
1820
require "anchor/type_script/serializer"
1921
require "anchor/type_script/resource"
2022
require "anchor/json_schema/serializer"

spec/anchor/example_multifile_schema_snapshot_spec.rb

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
RSpec.describe "Example" do
55
def self.multifile_snapshot_test(filename, generate)
66
it "generates correct #{filename} schema" do
7-
result = generate.call
8-
result.each do |res|
9-
filename = res[:name]
7+
results = generate.call
8+
results.each do |res|
9+
filename = res.name
10+
1011
path = Rails.root.join("test/files/multifile", filename)
11-
schema = res[:content]
12+
schema = res.text
13+
1214
unless File.file?(path)
1315
File.open(path, "w") { |file| file.write(schema) }
1416
end

spec/example/lib/tasks/anchor.rake

+7-24
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,9 @@ namespace :anchor do
99
puts "✅ #{File.basename(path)}"
1010
end
1111

12-
def write_to_multi(folder, force, generate)
13-
FileUtils.mkdir_p("test/files/#{folder}")
14-
result = generate.call
15-
result.each do |res|
16-
path = Rails.root.join("test/files/#{folder}", res[:name])
17-
if force || !File.exist?(path)
18-
File.open(path, "w") { |f| f.write(res[:content]) }
19-
next
20-
end
21-
22-
existing_content = File.read(path)
23-
new_content =
24-
if existing_content.starts_with?("// START AUTOGEN\n") && existing_content.include?("// END AUTOGEN\n")
25-
after_end = existing_content.split("// END AUTOGEN\n").second
26-
[res[:content].split("\n// END AUTOGEN\n").first, "// END AUTOGEN", after_end].join("\n")
27-
else
28-
res[:content]
29-
end
30-
31-
File.open(path, "w") { |f| f.write(new_content) }
32-
end
12+
def write_to_multi(folder, force, generator)
13+
folder_path = "test/files/#{folder}"
14+
Anchor::TypeScript::MultifileSaveService.call(generator:, folder_path:, force:)
3315
puts "✅ #{folder}"
3416
end
3517

@@ -43,15 +25,16 @@ namespace :anchor do
4325
write_to "excluded_fields_schema.ts", -> {
4426
Anchor::TypeScript::SchemaGenerator.call(register: Schema.register, exclude_fields: { User: [:name, :posts] })
4527
}
46-
write_to_multi "multifile", false, -> {
47-
Anchor::TypeScript::MultifileSchemaGenerator.call(
28+
write_to_multi "multifile",
29+
false,
30+
Anchor::TypeScript::MultifileSchemaGenerator.new(
4831
register: Schema.register,
4932
context: {},
5033
include_all_fields: true,
5134
exclude_fields: nil,
5235
manually_editable: true,
5336
)
54-
}
37+
5538
write_to "json_schema.json", -> {
5639
Anchor::JSONSchema::SchemaGenerator.call(register: Schema.register, include_all_fields: true)
5740
}

0 commit comments

Comments
 (0)