UNPKG

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