UNPKG

18.8 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3const fs = require('fs')
4const path = require('path')
5const dotenv = require('dotenv')
6const in$ = require('incredibles')
7const argv = require('minimist')(process.argv.slice(3))
8const {spawn, exec} = require('child_process') // , spawn = ref.spawn, exec = ref.exec
9const dot_env = '.env'
10const package_js = 'package.js'
11const package_json = 'package.json'
12const packages_dir = 'packages'
13
14require.main !== module && ( () => {
15 const gulp = require('gulp')
16 const valve = (v, ...o) => require(v)(...o)
17 paths = {
18 src: "./src/*.js"
19 , doc: "./doc" }
20 gulp.task( 'doc', () => {
21 gulp.src(paths.src)
22 .pipe( valve("gulp-markdox") )
23 .pipe( gulp.dest(paths.doc) ) }) })()
24
25in$.method({pj: path.join})
26
27let findDir = d => process.cwd().split('/').concat('')
28 .map( (v,i,a) => a.slice(0, -i-1).join('/') )
29 .find( v => in$(v).pj(d).chain(fs.existsSync).value ).into$ // must be in site path
30
31const home = in$(process.env.HOME).cut()
32home.pj(dot_env).if(fs.existsSync).then( v => dotenv.config( {path: v.value} ) )
33const site_path = findDir('.sat').cut()
34// const dot_sat_path = site_path.pj('.sat')
35// const mobile_config = site_path.pj('mobile-config.js')
36const dot_cubesat = '.cubesat'
37const cubesat_path = findDir(dot_cubesat).if('').then(home).else(v=>v).result.pj(dot_cubesat).cut()
38const site_settings = site_path.pj('lib', 'settings.js').value
39const deploy_settings = site_path.pj('.settings.json').value
40const global_settings = cubesat_path.pj('settings.js').cut()
41const workspace = in$(process.env.WORKSPACE).cut() || home.pj('workspace').cut()
42const test_path = workspace.pj('test').cut()
43const packages_path = test_path.pj(packages_dir).cut()
44const node_modules = in$(process.env.NODE_MODULES) || findDir('node_modules') || home // NODE_MODULES is not standard. but
45const paths2test = 'client server lib public private resources'.split(' ') // NODE_PATH may create confusion so don't use it.
46
47let taskBook = in$({})
48let tasks, options
49
50class Task {
51 constructor (name, fn, options) {
52 this.name = name
53 this.fn = fn
54 this.options = options || {}
55 taskBook.set(name, this) } }
56
57let Settings = in$({}), prop
58__.Settings = obj => in$(obj).type()
59 .case('function')
60 .then( v=>prop = in$({}).assign( Settings, obj({}) ).value )
61 .then( v=>Settings.assign( in$(obj(prop)).invokeProperties(prop) ) )
62 .case('object')
63 .then( v=> Settings.assign( v.invokeProperties( in$({}).assign(Settings, v.value) )) )
64
65const isCommand = f => require.main === module && f()
66
67isCommand( main )
68let command = process.argv[2] || 'help'
69// console.log(command, taskBook.val, 0, taskBook.get('help').get('fn'))
70const taskTogo = in$(taskBook.get(command))
71const arg0 = argv._[0]
72const error = e => e && (console.error(e) || true)
73const error_quit = e => console.error(e) || process.exit(1)
74
75isCommand( handleErrors )
76
77const cd = d => process.chdir(d)
78const mkdir = (dir, path, f) => cd(path) && fs.mkdir(dir, e => e || f(dir, path))
79const cp = (s, t) => fs.createReadStream(s).pipe(fs.createWriteStream(t))
80const spawn_command = (bin, command, args, path) => {
81 in$([' ', bin, command]).append(args).join(' ').log()
82 ;(path = in$.strip(path)) && ( cd(path) || console.log(' ', path) )
83 return spawn(bin, [command].concat(args), {stdio: 'inherit'}) }
84
85/*
86mobile_config = function() {
87 var data, o
88 settings()
89 init_settings()
90 data = __.keys(o = Settings.app).map(function(k) {
91 var ref2, ref3
92 if ((ref2 = mcTable[k]) != null ? ref2.list : void 0)
93 return __.keys(o[k]).map( l => 'App.' + k + '("' + l + '", ' + strOrObj(o[k][l]) + ');' ).join('\n') + '\n\n'
94 else if (__.isArray(o[k])
95 return o[k].map( l => 'App.' + k + '("' + l + '");' ).join('\n') + '\n\n'
96 else
97 return 'App.' + k + '({' + (((ref3 = mcTable[k]) != null ? ref3.f : void 0) || mc_obj)(o[k]) + '\n});\n\n'
98 }).join('')
99 fs.readFile( mobile_config_js, 'utf-8', (e, d) =>
100 d === data || fs.writeFile( mobile_config_js, data, e =>
101 console.log( new Date() + ' ' + mobile_config_js + ' is written.' ) ) ) }
102*/
103
104github_url = function(repo) {
105 return 'https://github.com/' + repo + '.git' }
106
107create = function() {
108 var site;
109 //__.check('name', site = argv._[0]) || console.error("error: Not a vaild name to create. Use alphanumeric and '.', '_', '-'.", site) || process.exit(1);
110 return (spawn_command('git', 'clone', [github_url(argv.repo || 'i4han/sat-spark'), site])).on('exit', function(code) {
111 return code && (console.error('error: Git exited with an error.') || process.exit(1)); }); };
112
113deploy = () => spawn_command('meteor', 'deploy', [argv._[0] || Settings.deploy.name], site_path)
114
115__create_test = function() {
116 (test_path = argv._[0]) || console.error("error: Test directory name is missing.") || process.exit(1);
117 if (fs.existsSync(test_path)) {
118 return console.error("error: Directory already exist.");
119 } else {
120 return meteor_create(test_path, function() {
121 return mkdir(packages_dir, null, function() {
122 return mkdir(cubesat_name, packages_dir, function() {
123 return (spawn_command('git', 'clone', [github_url('i4han/cubesat'), '.'], cubesat_name)).on('exit', function() {
124 return console.info("info: cubesat package directory:", process.cwd()); }); }); }); }); } };
125
126install_mobile = () => {
127 let wt
128 !site_path && !((wt = argv['with-test']) && test_path) && console.error("error: Run in .sat working directory or specify valid test name." || process.exit(1));
129 ;(['install-sdk', 'add-platform'].reduce(((f, c) =>
130 () => spawn_command('meteor', c, ['ios'], wt ? test_path : site_path).on('exit', f)),
131 () => console.log(new Date()) ))() }
132
133// console.log(0, taskBook.val, 1, taskBook.get(command).val, 2, taskBook.get(command, 'fn'))
134isCommand( taskBook.get(command, 'fn') ) // task_command.call()
135
136function handleErrors () {
137 taskTogo.value || error_quit(`fatal: Unknown command "${command}"`)
138 taskTogo.at('options.dotsat') && (site_path.value || error_quit(`fatal: You must run it in .sat working directory or its subdirectory.`))
139 taskTogo.at('options.arg0') && (arg0 || error_quit(`error: You need to specify app or package name for the third argument.`))
140 taskTogo.at('options.test') && (fs.existsSync(test_path) || error_quit(`fatal: test path "${test_path}" does not exist. `))
141 taskTogo.at('options.settings') && require( site_settings ) }
142
143function main () {
144
145paths = in$({
146 home: { type: "user", name: "home", path: home }
147 , cubesat: { type: "user", name: "cubesat", path: cubesat_path, cd:1 }
148 , module: { type: "user", name: "module", path: node_modules, cd:1 }
149 , settings: { type: "user", name: "settings", path: global_settings }
150 , ws: { type: "user", name: "workspace", path: workspace, cd:1 }
151 , site: { type: "site", name: "site", path: site_path, git: 1, cd:0 }
152 , test: { type: "site", name: "test", path: test_path, cd:1, npmLink: 'incredibles' }
153 , package: { type: "package", name: "packages", path: packages_path }
154 , jq: { type: "package", name: "isaac:jquery-x", git: 0, cd:1 }
155 , sq: { type: "package", name: "isaac:style-query", git: 0, cd:1 }
156 , cs: { type: "package", name: "isaac:cubesat", git: 1, cd:1
157 , npm:[node_modules], meteor:[site_path], npmName: 'cubesat', npmLink: 'incredibles', npmEnv:1 }
158 , in: { type: "package", name: "isaac:incredibles", git: 1, cd:1
159 , npm:[node_modules, site_path, test_path], npmName: 'incredibles', jasmine:1, npmTest: 1, npmEnv:1 }
160 , u2: { type: "package", name: "isaac:underscore2", git: 1, cd:1
161 , npm:[node_modules], meteor:[site_path], npmName: 'underscore2', jasmine:1 } })
162.forEach( v=> v.path || (v.path = packages_path.pj(v.name).cut()) )
163
164let v, param = argv._[0]
165
166const getVersion = p => in$(p).chain(require).value.version
167const version = () => paths.pickAt(param, 'path').pj('package.json').chain(getVersion).value
168const addVersion = s => s.split('.').map( (v,i,a)=>(i != a.length - 1) ? v : (parseInt(v) + 1).toString() ).join('.')
169const increaseVersion = (file, data) =>
170 data.replace(new RegExp('"version":\\s*"' + (v = getVersion(file)) + '"'), '"version": "' + addVersion(v) + '"')
171
172const gitPush = (commit, _path) => {
173 let p = _path.shift()
174 spawn_command('git', 'add', ['.'], p).on( 'exit', code =>
175 spawn_command('git', 'commit', ['-m', commit], p).on( 'exit', code =>
176 code ? _path.length ? gitPush( commit, _path ) : undefined
177 : spawn_command('git', 'push', [], p).on( 'exit', code =>
178 _path.length ? gitPush( commit, _path ) : undefined ) ) ) }
179
180const editFile = (file, func, action) =>
181 fs.readFile( file.value, 'utf8', (e, data) => error(e) ||
182 fs.writeFile( file.value, data = func(file, data), 'utf8', e => error(e) || ( action && action(file, data) ) ) )
183const npmPublish = (path, after) => (spawn_command('npm', 'publish', ['.'], path)).on('exit', after ? after : () => {} )
184const meteorPublish = (path, after) => (spawn_command('meteor', 'publish', [], path)).on('exit', after ? after : () => {} )
185
186const publish = paths => {
187 let v = paths.shift(), path = v.path.cut()
188 editFile( path.pj( v.meteor ? package_js : package_json ),
189 (f, d) => increaseVersion( path.pj(package_json), d ), (f, d) =>
190 v.meteor ? meteorPublish( path, () =>
191 v.npm ? editFile( path.pj(package_json), increaseVersion, (f, d) =>
192 npmPublish( path, paths.length ? () => publish(paths) : () => {} ) )
193 : paths.length ? publish(paths) : {} )
194 : npmPublish( path, paths.length ? () => publish(paths) : () => {} ) ) }
195const meteorRun = (path, port) => spawn_command( 'meteor', 'run', argv._.concat(['--port', port || '3000', '--settings', deploy_settings]), path || site_path )
196
197const npmUpdate = (npms, install) => {
198 if (!npms.size()) return
199 let v = npms.shift().result
200 let name = install ? '.' : v.name
201 spawn_command( 'npm', 'remove', [v.name, '--save', '--prefix', v.prefix], v.path ).on( 'exit', () =>
202 spawn_command('npm', 'install', [name, '--save', '--prefix', v.prefix], v.path ) ).on( 'exit', () =>
203 npmUpdate(npms, install) ) }
204
205const meteorUpdate = (npms, meteors) => {
206 if (!meteors.length)
207 return npms.length ? npmUpdate(npms) : undefined
208 let v = meteors.shift()
209 spawn_command( 'meteor', 'update', [v.name], v.path).on('exit',
210 meteors.length ? () => meteorUpdate(npms, meteors) : () => npmUpdate(npms) ) }
211
212const npmList = arr => arr.reduce( (a,v) =>
213 a.append( v.npm.map( w => ({name: v.npmName || v.name, prefix:w.value, path:v.path.value }) ) ), in$([]) )
214
215const update = paths => meteorUpdate(
216 select('npm').over(npmList),
217 paths.filter(v => v.meteor)
218 .reduce( ((a,v,i) => a.concat( v.meteor.map( w => ({name:v.name, path:w}) ) )), [] ) )
219
220const npmLink = npms => {
221 if (!npms.object.size()) return
222 let v = in$(npms.object.shift()).values()[0]
223 spawn_command( 'npm', 'remove', [v.npmLink, '--save'], v.path ).on( 'exit', () =>
224 spawn_command( 'npm', 'link', [v.npmLink, '--save'], v.path ) ).on( 'exit', () => npmLink(npms) ) }
225
226const jasmine = (a, fn) => {
227 if (a.length === 0) return
228 spawn_command( 'jasmine', a.shift() ).on( 'exit', code =>
229 code === 0 ? a.length !== 0 ? jasmine(a, fn) : fn === undefined ? {} : fn() : {} ) }
230
231const jasmineSpecs = () =>
232 paths.filter(v=>v.jasmine).map(v=>v.path).reduce( (a,v) =>
233 a.concat(
234 fs.readdirSync( v.pj('spec').value )
235 .filter( w => w.match(/[sS]pec\.js$/) )
236 .map( x => v.pj('spec', x).value ) ), [] )
237
238new Task( 'env', () =>
239 fs.readFile( home.pj(dot_env).value, 'utf8', (e, data) => error(e) ||
240 data.replace( /^\s*([a-zA-Z_]{1}[a-zA-Z0-9_]*).*/mg, (m, p1) =>
241 console.log( ` $${p1.padEnd(22)} = ` + process.env[p1] ) ) )
242 , { dotsat: 0, test: 0, description: 'Show arguments and environment variables.' } )
243
244new Task( 'paths', () =>
245 paths.map( (v, k) => in$(' ', k.padEnd(16), v.type.padEnd(8), v.path.value).out() )
246 , { dotsat: 0, test: 0, description: 'Show working paths.' } )
247
248new Task( 'args', () =>
249 console.log(' arguments: ', argv)
250 , { dotsat: 0, test: 0, description: 'Show arguments.' } )
251
252new Task( 'help', () =>
253 taskBook.forEach( (v, k) => in$(' ', k.padEnd(16), v.options.description).out() )
254 , { dotsat: 0, test: 0, description: 'Help message.' } )
255
256new Task( 'add-version', () => {
257 let p = paths.pickAt(param, 'path').cut()
258 editFile( p.pj(package_js), (f, d) => increaseVersion( p.pj(package_json).value, d), () =>
259 editFile( p.pj(package_json), increaseVersion, version)) }
260 , { dotsat: 0, test: 0, description: 'Increase version in pakage.json.', arg0: 1 } )
261
262new Task( 'version', () => console.log(version())
263 , { dotsat: 0, test: 0, description: 'Print sat version.', arg0: 1 } )
264
265new Task( 'jasmine', () => {
266 jasmine( jasmineSpecs() )
267 spawn_command( 'node', 'geo.js', [], workspace.pj('dev') ).on('exit', () =>
268 spawn_command( 'diff', 'out.kml', ['out2.kml'], workspace.pj('dev') ) ) }
269 , { dotsat: 0, test: 0, description: 'Run Jasmine test framework.' } )
270
271const npmMeteor = () => paths.filter( v=>v.npm || v.meteor ).map(v=>v).into$
272const select = p => paths.filter( (v,k) => v[p] ) //.map(v=>v)
273
274new Task( 'update', () => npmMeteor().carry(update)
275 , { dotsat: 1, test: 0, description: 'Update packages.', thirdCommand: 1 } )
276
277
278new Task( 'npm-update', () =>
279 select('npm').chainOver(npmList, npmUpdate)
280 , { dotsat: 1, test: 0, description: 'Update npm modules.', thirdCommand: 1 })
281
282new Task( 'npm-install', () =>
283 param ? in$([paths.pickAt(param)]).chainOver(npmList, npmUpdate) :
284 select('npm').chainOver(npmList, npmUpdate)
285 , { dotsat: 1, test: 0, description: 'Install local npm modules.', thirdCommand: 1 } )
286
287new Task( 'npm-link', () =>
288 select('npmLink').over(npmLink)
289 , { dotsat: 0, test: 0, description: 'Link local npm modules.' } )
290
291const testList = a => a.map( v =>
292 ({name: v.npmName || v.name, prefix: test_path.value, path: v.path.value}) ).into$
293new Task( 'npm-test-install', () =>
294 select('npmTest').carry(testList).carry(npmUpdate, true)
295 , { dotsat: 0, test: 0, description: 'Install local npm modules for test site.', thirdCommand: 1 } )
296
297const alias = () => {
298 fs.readFile( home.pj('.alias').value, 'utf8', (e, data) => error(e) || in$(data).log() )
299 paths.filter( v=>v.cd ).forEach( (v,k)=> `alias cd-${k}='cd ${v.path.value}'`.into$.log() ) }
300
301new Task( 'alias', alias,
302 { dotsat: 0, test: 0, description: 'Print alias' } )
303
304new Task( 'script', () => {
305 alias()
306 home.pj(dot_env).chain([fs.readFile, 'utf8', (e, data) => error(e) ||
307 data.replace(/^\s*([a-zA-Z])/mg, "export $1").into$.log()])
308 global_settings.chain(require, JSON.stringify).log(v => `export GLOBAL_SETTINGS='${v.value}'`)
309 paths.filter( v=>v.npmEnv).forEach( v=> (`export ${v.npmName.toUpperCase()}_PATH=` + v.path.value).into$.log() ) }
310 , { dotsat: 0, test: 0, description: 'Print export .env $. <(sat script)' } )
311
312new Task( 'git-push', () =>
313 gitPush( arg0, paths.filter(v => v.git).map(v => v.path.value) )
314 , { dotsat: 1, test: 0, description: 'Git push.', arg0: 1 })
315
316new Task( 'run', () => meteorRun()
317 , { dotsat: 1, test: 0, description: 'Run meteor server.', settings: 1 })
318
319new Task( 'test', () => {
320 paths2test.forEach( d => {
321 let target, source
322 fs.unlink( target = test_path.pj(d).value, () =>
323 fs.existsSync( source = site_path.pj(d).value ) &&
324 fs.symlink( source, target, () =>
325 console.log(new Date(), source) ) ) })
326 fs.readdir( test_path.value, (e, list) => {
327 e || list.forEach( f => path.extname(f) === '.js' &&
328 fs.unlink( test_path.pj(f).value ) )
329 fs.readdir( site_path.value, (e, list) =>
330 e || list.forEach( f => path.extname(f) === '.js' &&
331 fs.link( site_path.pj(f).value, test_path.pj(f).value, () =>
332 console.log(new Date(), f) ) ) ) })
333 meteorRun(test_path, '3300') }
334 , { dotsat: 1, test: 0, description: 'Test environment.', settings: 1 })
335
336new Task( 'publish', () => npmMeteor().carry(publish)
337 , { dotsat: 0, test: 0, description: 'Publish Meteor packages.' })
338
339new Task( 'api-url', () => {
340 __._Settings = Settings.value
341 in$.meteor.queryString(__._Settings.google.maps.options).log() }
342 , {dotsat: 0, test: 0, description: 'Settings', settings: 1})
343
344new Task( 'settings', () => Settings.log()
345 , {dotsat: 1, test: 0, description: 'Settings', settings: 1})
346
347new Task( 'settings.json', () =>
348 fs.readFile( site_settings, 'utf-8', (e, data) => // search for process.env.ENVIRONMENT_VARIABLES
349 in$({
350 public: JSON.parse(process.env.GLOBAL_SETTINGS).public
351 , "galaxy.meteor.com": { env: data.match(/process\.env\.[A-Z0-9_]+/mg)
352 .map(v => v.slice(12)).reduce( (a,v) => a.set(v, process.env[v]), in$({}) ).value }
353 }).chain([JSON.stringify, null, 4]).log() )
354 , {dotsat: 1, test: 0, description: 'Settings', settings: 1})
355
356new Task( 'global-settings', () =>
357 in$(process.env.GLOBAL_SETTINGS).chain(JSON.parse).log()
358 , {dotsat: 1, test: 0, description: 'Settings', settings: 1})
359
360new Task( 'deploy', () =>
361 spawn_command( 'meteor', 'deploy', ['--settings', '.settings.json', process.env.DEPLOY_URL], site_path )
362 , {dotsat: 1, test: 0, description: 'Deploy meteor', settings: 1})
363
364new Task( 'geo', () =>
365 spawn_command( 'node', 'geo.js', [], workspace.pj('dev') )
366 , {dotsat: 0, test: 0, description: 'Deploy meteor', settings: 0})
367
368
369//meteor deploy --settings settings.json map.meteorapp.com
370//meteor deploy --settings settings.json map.meteorapp.com
371tasks = in$({
372 create: { call: () => create(), dotsat: 0, test: 0, description: 'Create a project.' },
373 deploy: { call: () => deploy(), dotsat: 1, test: 0, description: 'Deploy to meteor.com.' },
374 mobileConfig: { call: () => mobile_config(), dotsat: 0, test: 1, description: 'Create mobile-config.js' },
375 createTest: { call: () => create_test(), dotsat: 0, test: 1, description: 'Create test directory.' },
376 installMobile: { call: () => install_mobile(), dotsat: 0, test: 1, description: 'Install mobile sdk and platform.' } })
377
378options = in$({})
379}