re-natal/main.coffee

259 lines
7.5 KiB
CoffeeScript
Raw Normal View History

2015-08-26 03:48:43 +00:00
# Natal
# Bootstrap ClojureScript React Native apps
# Dan Motzenbecker
# http://oxism.com
# MIT License
2015-08-22 03:45:42 +00:00
fs = require 'fs'
crypto = require 'crypto'
{execSync} = require 'child_process'
2015-09-19 17:51:11 +00:00
cli = require 'commander'
2015-08-26 01:28:58 +00:00
chalk = require 'chalk'
2015-08-31 02:02:21 +00:00
semver = require 'semver'
reactInit = require 'react-native/local-cli/init'
rnVersion = require(__dirname + '/package.json').dependencies['react-native']
2015-08-22 03:45:42 +00:00
resources = __dirname + '/resources/'
camelRx = /([a-z])([A-Z])/g
projNameRx = /\$PROJECT_NAME\$/g
projNameHyphRx = /\$PROJECT_NAME_HYPHENATED\$/g
projNameUnderRx = /\$PROJECT_NAME_UNDERSCORED\$/g
2015-09-19 18:19:54 +00:00
podMinVersion = '0.38.2'
2015-09-19 02:55:59 +00:00
2015-08-22 03:45:42 +00:00
2015-08-26 01:28:58 +00:00
log = (s, color = 'green') ->
console.log chalk[color] s
2015-08-22 03:45:42 +00:00
2015-08-26 01:28:58 +00:00
logErr = (err, color = 'red') ->
console.error chalk[color] err
2015-09-19 18:47:07 +00:00
process.exit 1
2015-08-22 03:45:42 +00:00
editSync = (path, pairs) ->
fs.writeFileSync path, pairs.reduce (contents, [rx, replacement]) ->
contents.replace rx, replacement
, fs.readFileSync path, encoding: 'ascii'
2015-09-19 18:02:28 +00:00
writeConfig = (config) ->
try
fs.writeFileSync '.natal', JSON.stringify config, null, 2
catch {message}
logErr \
if message.match /EACCES/i
'Invalid write permissions for creating .natal config file'
else
message
2015-09-19 18:21:19 +00:00
readConfig = ->
try
JSON.parse fs.readFileSync '.natal'
catch {message}
logErr \
if message.match /ENOENT/i
'No Natal config was found in this directory (.natal)'
else if message.match /EACCES/i
'No read permissions for .natal'
else if message.match /Unexpected/i
'.natal contains malformed JSON'
else
message
2015-08-22 03:45:42 +00:00
init = (projName) ->
projNameHyph = projName.replace(camelRx, '$1-$2').toLowerCase()
projNameUs = projName.replace(camelRx, '$1_$2').toLowerCase()
try
2015-09-19 03:10:03 +00:00
log "Creating #{projName}", 'bgMagenta'
2015-08-26 01:44:11 +00:00
log ''
if fs.existsSync projNameHyph
2015-09-19 03:10:03 +00:00
throw new Error "Directory #{projNameHyph} already exists"
execSync 'type lein'
execSync 'type pod'
2015-08-31 02:02:21 +00:00
podVersion = execSync('pod --version').toString().trim()
2015-09-19 03:10:03 +00:00
unless semver.satisfies podVersion, ">=#{podMinVersion}"
2015-09-19 02:55:59 +00:00
throw new Error """
2015-09-19 03:10:03 +00:00
Natal requires CocoaPods #{podMinVersion} or higher (you have #{podVersion}).
2015-09-19 02:55:59 +00:00
Run [sudo] gem update cocoapods and try again.
"""
2015-08-22 15:06:30 +00:00
log 'Creating Leiningen project'
2015-09-19 17:48:07 +00:00
execSync "lein new #{projNameHyph}", stdio: 'ignore'
2015-08-22 15:06:30 +00:00
log 'Updating Leiningen project'
process.chdir projNameHyph
2015-09-19 03:10:03 +00:00
execSync "cp #{resources}project.clj project.clj"
2015-08-22 15:06:30 +00:00
editSync 'project.clj', [[projNameHyphRx, projNameHyph]]
2015-09-19 03:10:03 +00:00
corePath = "src/#{projNameUs}/core.clj"
2015-08-22 15:06:30 +00:00
fs.unlinkSync corePath
corePath += 's'
2015-09-19 03:10:03 +00:00
execSync "cp #{resources}core.cljs #{corePath}"
2015-08-22 15:06:30 +00:00
editSync corePath, [[projNameHyphRx, projNameHyph], [projNameRx, projName]]
2015-09-19 03:10:03 +00:00
execSync "cp #{resources}ambly.sh start.sh"
2015-08-22 15:07:06 +00:00
editSync 'start.sh', [[projNameUnderRx, projNameUs]]
2015-08-22 17:30:12 +00:00
log 'Compiling ClojureScript'
2015-09-19 17:48:07 +00:00
execSync 'lein cljsbuild once dev', stdio: 'ignore'
2015-08-22 17:30:12 +00:00
2015-08-22 17:30:42 +00:00
log 'Creating React Native skeleton'
fs.mkdirSync 'iOS'
process.chdir 'iOS'
_log = console.log
global.console.log = ->
reactInit '.', projName
global.console.log = _log
fs.writeFileSync 'package.json', JSON.stringify
name: projName
version: '0.0.1'
private: true
scripts:
start: 'node_modules/react-native/packager/packager.sh'
dependencies:
'react-native': rnVersion
, null, 2
execSync 'npm i', stdio: 'ignore'
2015-08-22 17:30:42 +00:00
2015-08-22 17:32:06 +00:00
log 'Installing Pod dependencies'
process.chdir 'iOS'
2015-09-19 03:10:03 +00:00
execSync "cp #{resources}Podfile ."
execSync 'pod install', stdio: 'ignore'
2015-08-22 17:32:06 +00:00
2015-08-22 17:32:53 +00:00
log 'Updating Xcode project'
for ext in ['m', 'h']
2015-09-19 03:10:03 +00:00
path = "#{projName}/AppDelegate.#{ext}"
execSync "cp #{resources}AppDelegate.#{ext} #{path}"
2015-08-22 17:32:53 +00:00
editSync path, [[projNameRx, projName], [projNameHyphRx, projNameHyph]]
uuid1 = crypto
.createHash 'md5'
.update projName, 'utf8'
.digest('hex')[...24]
.toUpperCase()
2015-08-29 05:46:58 +00:00
uuid2 = uuid1.split ''
uuid2.splice 7, 1, ((parseInt(uuid1[7], 16) + 1) % 16).toString(16).toUpperCase()
uuid2 = uuid2.join ''
2015-08-22 17:32:53 +00:00
editSync \
2015-09-19 03:10:03 +00:00
"#{projName}.xcodeproj/project.pbxproj",
2015-08-22 17:32:53 +00:00
[
[
/OTHER_LDFLAGS = "-ObjC";/g
'OTHER_LDFLAGS = "${inherited}";'
]
[
/\/\* End PBXBuildFile section \*\//
2015-09-19 03:10:03 +00:00
"\t\t#{uuid2} /* out in Resources */ =
{isa = PBXBuildFile; fileRef = #{uuid1} /* out */; };
2015-08-22 17:32:53 +00:00
\n/* End PBXBuildFile section */"
]
[
/\/\* End PBXFileReference section \*\//
2015-09-19 03:10:03 +00:00
"\t\t#{uuid1} /* out */ = {isa = PBXFileReference; lastKnownFileType
= folder; name = out; path = ../../../target/out;
2015-08-22 17:32:53 +00:00
sourceTree = \"<group>\"; };\n/* End PBXFileReference section */"
]
[
/main.jsbundle \*\/\,/
2015-09-19 03:10:03 +00:00
"main.jsbundle */,\n\t\t\t\t#{uuid1} /* out */,"
2015-08-22 17:32:53 +00:00
]
[
/\/\* LaunchScreen.xib in Resources \*\/\,/
"/* LaunchScreen.xib in Resources */,
2015-09-19 03:10:03 +00:00
\n\t\t\t\t#{uuid2} /* out in Resources */,"
2015-08-22 17:32:53 +00:00
]
]
2015-09-19 18:02:28 +00:00
log 'Creating Natal config'
2015-09-19 03:10:03 +00:00
process.chdir '../..'
2015-09-19 18:02:28 +00:00
writeConfig name: projName
2015-09-19 03:10:03 +00:00
2015-08-26 01:44:11 +00:00
log '\nWhen Xcode appears, click the play button to run the app on the simulator.', 'yellow'
log 'Then run the following for an interactive workflow:', 'yellow'
2015-09-19 03:10:03 +00:00
log "cd #{projNameHyph}", 'inverse'
2015-08-26 01:44:11 +00:00
log './start.sh', 'inverse'
log 'First, choose the correct device (Probably [1]).', 'yellow'
log 'At the REPL prompt type this:', 'yellow'
2015-09-19 03:10:03 +00:00
log "(in-ns '#{projNameHyph}.core)", 'inverse'
2015-08-26 01:44:11 +00:00
log 'Changes you make via the REPL or by changing your .cljs files should appear live.', 'yellow'
log 'Try this command as an example:', 'yellow'
log '(swap! app-state assoc :text "Hello Native World")', 'inverse'
log ''
2015-08-26 03:48:43 +00:00
log '✔ Done', 'bgMagenta'
log ''
2015-08-26 01:44:11 +00:00
2015-09-19 03:05:39 +00:00
catch {message}
logErr \
if message.match /type\:.+lein/i
'Leiningen is required (http://leiningen.org/)'
else if message.match /type\:.+pod/i
'CocoaPods is required (https://cocoapods.org/)'
else
message
2015-09-19 18:47:28 +00:00
openXcode = (name) ->
try
execSync "open iOS/iOS/#{name}.xcworkspace", stdio: 'ignore'
catch {message}
logErr \
if message.match /ENOENT/i
"""
Cannot find #{name}.xcworkspace in iOS/iOS.
Run this command from your project's root directory.
"""
else if message.match /EACCES/i
"Invalid permissions for opening #{name}.xcworkspace in iOS/iOS"
else
message
2015-08-26 03:48:43 +00:00
2015-09-19 19:35:00 +00:00
getDeviceList = ->
try
2015-09-19 21:28:59 +00:00
execSync 'xcrun instruments -s devices'
2015-09-19 19:35:00 +00:00
.toString()
.split '\n'
.filter (line) -> /^i/.test line
catch {message}
logErr 'Device listing failed: ' + message
2015-09-19 17:51:11 +00:00
cli.version '0.0.4'
2015-08-26 03:48:43 +00:00
2015-09-19 17:51:11 +00:00
cli.command 'init <name>'
.description 'Create a new ClojureScript React Native project'
.action (name) ->
if typeof name isnt 'string'
logErr '''
natal init requires a project name as the first argument.
e.g.
natal init HelloWorld
'''
2015-08-26 03:48:43 +00:00
2015-09-19 17:51:11 +00:00
init name
2015-09-19 19:35:00 +00:00
cli.command 'launch'
.description 'Run project in simulator and start REPL'
.action ->
launch readConfig()
2015-09-19 18:47:28 +00:00
cli.command 'xcode'
.description 'Open Xcode project'
.action ->
openXcode readConfig().name
2015-09-19 19:35:00 +00:00
cli.command 'listdevices'
.description 'List available simulator devices by index'
.action ->
console.log (getDeviceList()
.map (line, i) -> "#{i}\t#{line}"
.join '\n')
2015-09-19 17:51:11 +00:00
cli.parse process.argv