1 | #!/usr/bin/env node
|
2 |
|
3 | const fs = require('fs')
|
4 | const path = require('path')
|
5 | const dotenv = require('dotenv')
|
6 | const in$ = require('incredibles')
|
7 | const argv = require('minimist')(process.argv.slice(3))
|
8 | const {spawn, exec} = require('child_process')
|
9 | const dot_env = '.env'
|
10 | const package_js = 'package.js'
|
11 | const package_json = 'package.json'
|
12 | const packages_dir = 'packages'
|
13 |
|
14 | require.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 |
|
25 | in$.ductMethod('pj', 'thru', path.join)
|
26 |
|
27 | let 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 ) )
|
30 |
|
31 | const home = in$(process.env.HOME).cut()
|
32 | home.pj(dot_env).if(fs.existsSync).then( v => dotenv.config( {path: v.value} ) )
|
33 | const 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 | )
|
37 | const dot_cubesat = '.cubesat'
|
38 | const cubesat_path = findDir(dot_cubesat).is('').then(v=>home).else(v=>v).self(v=>v.result).pj(dot_cubesat).cut()
|
39 | const global_settings = cubesat_path.pj('settings.js').cut()
|
40 | const workspace = in$(process.env.WORKSPACE || '~/workspace').cut()
|
41 | const test_path = workspace.pj('test').cut()
|
42 | const packages_path = test_path.pj(packages_dir).cut()
|
43 | const node_modules = in$(process.env.NODE_MODULES) || findDir('node_modules') || home
|
44 | const paths2test = 'client server imports lib public private resources'.split(' ')
|
45 |
|
46 |
|
47 |
|
48 | let taskBook = in$({})
|
49 | let tasks, options
|
50 |
|
51 | class Task {
|
52 | constructor (name, fn, options) {
|
53 | this.name = name
|
54 | this.fn = fn
|
55 | this.options = options || {}
|
56 | taskBook.set(name, this) } }
|
57 |
|
58 | let 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 |
|
66 | const isCommand = f => require.main === module && f()
|
67 |
|
68 | isCommand( main )
|
69 | let command = process.argv[2] || 'help'
|
70 |
|
71 | const taskTogo = in$(taskBook.get(command))
|
72 | const arg0 = argv._[0]
|
73 | const error = e => e && (console.error(e) || true)
|
74 | const error_quit = e => console.error(e) || process.exit(1)
|
75 |
|
76 | isCommand( handleErrors )
|
77 |
|
78 | const cd = d => process.chdir(d)
|
79 | const mkdir = (dir, path, f) => cd(path) && fs.mkdir(dir, e => e || f(dir, path))
|
80 | const cp = (s, t) => fs.createReadStream(s).pipe(fs.createWriteStream(t))
|
81 | const 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 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 | github_url = function(repo) {
|
106 | return 'https://github.com/' + repo + '.git' }
|
107 |
|
108 | create = function() {
|
109 | var site;
|
110 |
|
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 |
|
114 | deploy = () => 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 |
|
127 | install_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 |
|
135 | isCommand( taskBook.get(command, 'fn') )
|
136 |
|
137 | function 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 |
|
144 | function main () {
|
145 |
|
146 | paths = 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 |
|
165 | let v, param = argv._[0]
|
166 |
|
167 | const getVersion = p => in$(p).thru(require).value.version
|
168 | const version = () => paths.pickAt(param, 'path').pj('package.json').thru(getVersion).value
|
169 | const addVersionNumber = s => s.split('.').map( (v,i,a)=>(i != a.length - 1) ? v : (parseInt(v) + 1).toString() ).join('.')
|
170 | const addVersion = (file, data) =>
|
171 | data.replace(new RegExp('"version":\\s*"' + (v = getVersion(file)) + '"'), '"version": "' + addVersionNumber(v) + '"')
|
172 |
|
173 | const 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 |
|
181 | const 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) ) ) )
|
184 | const npmPublish = (path, after) => (spawn_command('npm', 'publish', ['.'], path)).on('exit', after ? after : () => {} )
|
185 | const meteorPublish = (path, after) => (spawn_command('meteor', 'publish', [], path)).on('exit', after ? after : () => {} )
|
186 |
|
187 | const 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) : () => {} ) ) }
|
196 | const meteorRun = (path, port) => spawn_command( 'meteor', 'run', argv._.concat(['--port', port || '3000', '--settings', deploy_settings]), path || site_path )
|
197 |
|
198 | const 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 |
|
207 | const 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 |
|
216 | const 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 |
|
219 | const 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 |
|
224 | const 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 |
|
230 | const 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 |
|
235 | const 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 |
|
242 | new 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 |
|
248 | new 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 |
|
252 | new Task( 'args', () =>
|
253 | console.log(' arguments: ', argv)
|
254 | , { dotsat: 0, test: 0, description: 'Show arguments.' } )
|
255 |
|
256 | new Task( 'help', () =>
|
257 | taskBook.forEach( (v, k) => in$(' ', k.padEnd(16), v.options.description).print() )
|
258 | , { dotsat: 0, test: 0, description: 'Help message.' } )
|
259 |
|
260 | new 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 |
|
266 | new Task( 'version', () => console.log(version())
|
267 | , { dotsat: 0, test: 0, description: 'Print sat version.', arg0: 1 } )
|
268 |
|
269 | new 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 |
|
275 | const npmMeteor = () => paths.filter( v=>v.npm || v.meteor ).map(v=>v).into$
|
276 | const select = p => paths.filter( v=>v[p] )
|
277 |
|
278 | new Task( 'update', () => paths.filter( v=>v.npm || v.meteor ).self(update)
|
279 | , { dotsat: 1, test: 0, description: 'Update packages.', thirdCommand: 1 } )
|
280 |
|
281 |
|
282 | new Task( 'npm-update', () =>
|
283 | select('npm').self([npmList, npmUpdate])
|
284 | , { dotsat: 1, test: 0, description: 'Update npm modules.', thirdCommand: 1 })
|
285 |
|
286 | new 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 |
|
291 | new Task( 'npm-link', () =>
|
292 | select('npmLink').self(npmLink)
|
293 | , { dotsat: 0, test: 0, description: 'Link local npm modules.' } )
|
294 |
|
295 | const testList = a => a.map( v =>
|
296 | ({name: v.npmName || v.name, prefix: test_path.value, path: v.path.value}) ).into$
|
297 | new 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 |
|
301 | const 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 |
|
305 | new Task( 'alias', alias,
|
306 | { dotsat: 0, test: 0, description: 'Print alias' } )
|
307 |
|
308 | new 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 |
|
316 | new 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 |
|
320 | new Task( 'run', () => meteorRun()
|
321 | , { dotsat: 1, test: 0, description: 'Run meteor server.', settings: 1 })
|
322 |
|
323 | new 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 |
|
340 | new Task( 'publish', () => npmMeteor().carry(publish)
|
341 | , { dotsat: 0, test: 0, description: 'Publish Meteor packages.' })
|
342 |
|
343 | new 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 |
|
348 | new Task( 'settings', () => Settings.log()
|
349 | , {dotsat: 1, test: 0, description: 'Settings', settings: 1})
|
350 |
|
351 | new Task( 'settings.json', () =>
|
352 | fs.readFile( site_settings, 'utf-8', (e, data) =>
|
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 |
|
360 | new Task( 'global-settings', () =>
|
361 | in$(process.env.GLOBAL_SETTINGS).thru(JSON.parse).log()
|
362 | , {dotsat: 1, test: 0, description: 'Settings', settings: 1})
|
363 |
|
364 | new 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 |
|
368 | new Task( 'geo', () =>
|
369 | spawn_command( 'node', 'geo.js', [], workspace.pj('dev') )
|
370 | , {dotsat: 0, test: 0, description: 'Deploy meteor', settings: 0})
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | tasks = 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 |
|
382 | options = in$({})
|
383 | }
|