-
-
Save jkolyer/d2577a524f632953f5ff409aa3ec721f to your computer and use it in GitHub Desktop.
Utility for post-processing MST models to workaround circular dependencies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Ruby script to post-process mst-gql models. | |
# This is a stop-gap to remove circular dependencies and other artifacts. | |
# The plan is to update [this fork](https://github.com/jkolyer/mst-gql) with the | |
# post-processing code and other fixes. | |
# Usage: | |
# add these scripts to the package.json in the examples/5-nextjs folder: | |
# "scaffold": "../../generator/mst-gql-scaffold.js", | |
# "did:scaffold": "ruby base-model-imports.rb ./src/models ./mst-gql.config.js", | |
# update the examples/5-nextjs/mst-gql.config.js with your root models | |
# start your gql server and run these on command line: | |
# `npm run scaffold` | |
# `npm run did:scaffold` | |
# then copy the generated models into your project | |
# This script post-processes the model and base model files to remove | |
# TS circular dependencies. | |
# It joins all the base models into AllBaseModels.ts, except RootStore.base | |
# which remains separate. | |
# Root model type are defined in the rootModelTypes.ts file. | |
# | |
# expected arguments are the model source directory, and | |
# comma-separated list of root domains (same as in the generator config) | |
dir = ARGV[0] + '/*.ts' | |
configFile = ARGV[1] | |
File.read(configFile) =~ /roots: \[(.*)\]/ | |
rootModels = $1.gsub('"','').split(',').map(&:strip) | |
base_imports = { | |
withTypedRefs: "import { withTypedRefs } from 'mst-gql'\n", | |
MSTGQLRef: "import { MSTGQLRef } from 'mst-gql'\n", | |
ModelBase: "import { ModelBase } from 'mst-gql'\n", | |
} | |
all_base_models = '' | |
root_store_base = '' | |
base_files = [] | |
root_store_base_file = '' | |
rootModelTypeRegex = Regexp.union(rootModels.collect { |rm| ", #{rm}ModelType" }) | |
lateTypeModels = {} | |
# iterate over all generated files | |
Dir.glob(dir) do |fpath| | |
# process base model files first | |
if fpath =~ /\.base\.ts$/ | |
# save root store base file name for delayed processing | |
if fpath =~ /RootStore\.base/ | |
root_store_base_file = fpath | |
next | |
end | |
base_files.append(fpath) | |
File.open(fpath).each do |line| | |
if line =~ /^import/ | |
from = line.split(' ')[-1] | |
next if from.end_with?('.base"') | |
next if from.end_with?('Model"') | |
# next if from.end_with?('Enum"') | |
next if from.end_with?('ModelBase"') | |
if not base_imports[from] | |
# if line =~ /from "mst-gql"/ | |
# line.gsub!('from "mst-gql"', 'from "../lib"') | |
# end | |
base_imports[from] = line | |
end | |
next | |
end | |
next if line.start_with?('/* eslint') || | |
line.start_with?('/* tslint') || | |
line =~ /mst-gql generated file/ | |
if line =~ /types.late\(\(\): any \=\> ([^\)]+)/ | |
lower = "#{$1[0].downcase}#{$1[1..-1]}" | |
lateTypeModels[$1] = lower | |
code = "types.late((): any => #{$1})" | |
replace = "types.late((): any => #{lower}())" | |
line.sub!(code, replace) | |
end | |
all_base_models += line | |
end | |
all_base_models += "\n/******END CLASS******/" | |
next | |
end | |
# process regular model files | |
# update imports as needed | |
fname = fpath.split('/')[-1].split('.')[0] | |
file_stuff = '' | |
File.open(fpath).each do |line| | |
# if line =~ /from "mst-gql"/ | |
# file_stuff += line.gsub(/from "mst-gql"/, 'from "../lib"') | |
# next | |
# end | |
if fname != 'RootStore' | |
file_stuff += line.gsub(/\.\/.*\.base/, './AllBaseModels') | |
else | |
file_stuff += line | |
end | |
end | |
if fname == 'index' | |
file_stuff += "export * from './rootModelTypes'\n" | |
end | |
# write out new model file with new imports | |
puts 'Rewriting '+fpath | |
File.open(fpath, 'w') do |ff| | |
ff.write(file_stuff) | |
end | |
end | |
# rewrite RootStore.base with new imports | |
# and custom header | |
puts "Building RootStore.base.ts" | |
importModelTypes = '' | |
rootModels.each do |rootModel| | |
importModelTypes += "#{rootModel}ModelType, " | |
end | |
root_store_base += "import { #{importModelTypes}} from './rootModelTypes'\n" | |
File.open(root_store_base_file).each do |line| | |
if line =~ /^import/ | |
if line =~ /from "mst-gql"/ | |
# line.gsub!('from "mst-gql"', 'from "../lib"') | |
elsif line =~ /Model\.base/ | |
line.gsub!(/from "\.\/.*Model\.base"/, 'from "./AllBaseModels"') | |
end | |
else | |
next if line =~ /lint.disable/ | |
next if line =~ /mst-gql generated file/ | |
end | |
if line =~ /^import/ | |
line.gsub!(/#{rootModelTypeRegex}/, '') | |
end | |
root_store_base += line | |
end | |
base_hdr = "/* *** AUTOGENERATED FILES BY mst-gql *** DO NOT EDIT *** */\n" | |
base_hdr += "/* *** POST-PROCESSED WITH #{File.basename(__FILE__)} *** */\n" | |
base_hdr += "/* *** #{Time.now.utc.strftime('%FT%TZ')} *** */\n\n" | |
base_hdr += "/* eslint-disable */\n" | |
base_hdr += "/* tslint-disable */\n\n" | |
no_edit_hdr = base_hdr | |
rootModelTypeImports = 'import { ' | |
rootModels.each do |rootModel| | |
rootModelTypeImports += "#{rootModel}ModelType, " | |
end | |
rootModelTypeImports += '} from "./rootModelTypes"' | |
base_imports['rootModel'] = rootModelTypeImports | |
base_imports.each do |from, import| | |
next if import =~ /RootStoreType/ | |
base_hdr += import | |
end | |
base_hdr += "\nimport { \n" | |
lateTypeModels.each do |lateModel, lowercase| | |
base_hdr += " #{lowercase},\n" | |
end | |
base_hdr += "} from './lateModels'\n" | |
all_base_models = base_hdr + all_base_models | |
puts "Writing AllBaseModels.ts" | |
File.open(ARGV[0]+'/AllBaseModels.ts','w') do |ff| | |
ff.write(all_base_models) | |
end | |
# rootModelTypes.ts is used to remove circular dependencies | |
# between RootStore and the leaf model files | |
puts "Writing rootModelTypes.ts" | |
File.open(ARGV[0]+'/rootModelTypes.ts','w') do |ff| | |
ff.write(no_edit_hdr) | |
ff.write("import { Instance } from 'mobx-state-tree'\n") | |
exports = [] | |
rootModels.each do |rootModel| | |
ff.write("import { #{rootModel}Model } from './#{rootModel}Model'\n") | |
exp = "export type #{rootModel}ModelType = Instance<typeof #{rootModel}Model>\n" | |
exports.append(exp) | |
end | |
ff.write("\n") | |
exports.each do |exp| | |
ff.write(exp) | |
end | |
end | |
puts "Writing lateModels.ts" | |
File.open(ARGV[0]+'/lateModels.ts','w') do |ff| | |
ff.write(no_edit_hdr) | |
ff.write("/**** These functions embed requires for each model with a type.late reference to avoid circular depdencies from AllBaseModels ****/\n\n") | |
lateTypeModels.each do |lateModel, lowercase| | |
fcnName = lowercase | |
lateRequire = "let _#{fcnName} = undefined\n" | |
lateRequire += "export function #{fcnName}() {\n" | |
lateRequire += " if (!_#{fcnName}) {\n" | |
lateRequire += " _#{fcnName} = require('./#{lateModel}')\n" | |
lateRequire += " }\n" | |
lateRequire += " return (_#{fcnName} as any).#{lateModel}\n" | |
lateRequire += "}\n" | |
ff.write(lateRequire) | |
end | |
end | |
puts "Writing RootStore.base.ts file" | |
root_store_base = "#{no_edit_hdr}\n#{root_store_base}" | |
File.open(root_store_base_file,'w') do |ff| | |
ff.write(root_store_base) | |
end | |
puts 'Deleting base files...' | |
base_files.each do |fpath| | |
File.delete(fpath) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment