Skip to content

Instantly share code, notes, and snippets.

@jkolyer
Created March 19, 2022 17:30
Show Gist options
  • Save jkolyer/d2577a524f632953f5ff409aa3ec721f to your computer and use it in GitHub Desktop.
Save jkolyer/d2577a524f632953f5ff409aa3ec721f to your computer and use it in GitHub Desktop.
Utility for post-processing MST models to workaround circular dependencies
# 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