From a076ec119d50b4199a19c4fd742ac9d59bc0f4c4 Mon Sep 17 00:00:00 2001 From: mkucharz Date: Sat, 2 Dec 2017 14:29:53 +0100 Subject: [PATCH] feat(): linking and building components --- packages/cli/src/cli-component.js | 44 +++++++ packages/cli/src/cli.js | 4 + packages/cli/src/commands/component-link.js | 25 ++++ packages/cli/src/commands/component-list.js | 45 +++++++ packages/cli/src/commands/index.js | 6 +- .../cli/src/commands/socket-deploy-hot.js | 79 ++++++++++++- packages/cli/src/utils/component/component.js | 18 +++ packages/cli/src/utils/component/index.js | 3 + packages/cli/src/utils/context.js | 2 + packages/cli/src/utils/errors.js | 9 ++ packages/cli/src/utils/sockets/sockets.js | 111 +++++++++++++++++- 11 files changed, 336 insertions(+), 10 deletions(-) create mode 100644 packages/cli/src/cli-component.js create mode 100644 packages/cli/src/commands/component-link.js create mode 100644 packages/cli/src/commands/component-list.js create mode 100644 packages/cli/src/utils/component/component.js create mode 100644 packages/cli/src/utils/component/index.js create mode 100644 packages/cli/src/utils/errors.js diff --git a/packages/cli/src/cli-component.js b/packages/cli/src/cli-component.js new file mode 100644 index 00000000..02a42c13 --- /dev/null +++ b/packages/cli/src/cli-component.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node +import program from './program' +import commands from './commands' +import session from './utils/session' +import context from './utils/context' +import validateCommands from './utils/validate-commands' +import { echo } from './utils/print-tools' + +const setup = async () => { + await context.session.load() + + program + .command('link ') + .group('Components') + .description('Link all components to your project') + .action(async (...options) => { + session.isAuthenticated() + session.hasProject() + echo() + new commands.ComponentLink(context).run(options) + }) + + program + .command('list') + .group('Components') + .description('List all components') + .action(async (...options) => { + session.isAuthenticated() + session.hasProject() + echo() + new commands.ComponentList(context).run(options) + }) + + program + .on('*', (commandsArr) => validateCommands(commandsArr)) + + if (!process.argv.slice(2).length) { + program.outputHelp() + } + + program.parse(process.argv) +} + +setup() diff --git a/packages/cli/src/cli.js b/packages/cli/src/cli.js index 54aabe56..f3d10102 100755 --- a/packages/cli/src/cli.js +++ b/packages/cli/src/cli.js @@ -272,6 +272,10 @@ const setup = async () => { .command('hosting', 'Manage your web assets and host them on Syncano') .on('*', (commandsArr) => validateCommands(commandsArr)) + program + .command('component', 'Manage your Socket components') + .on('*', (commandsArr) => validateCommands(commandsArr)) + context.session.loadPlugins(program, context) program.parse(process.argv) diff --git a/packages/cli/src/commands/component-link.js b/packages/cli/src/commands/component-link.js new file mode 100644 index 00000000..cb6b8ad5 --- /dev/null +++ b/packages/cli/src/commands/component-link.js @@ -0,0 +1,25 @@ +import path from 'path' +import format from 'chalk' +import { echo, echon } from '../utils/print-tools' + +export default class ComponentLink { + constructor (context) { + this.session = context.session + this.Socket = context.Socket + this.Component = context.Component + } + + async run ([projectPath, cmd]) { + const sockets = await this.Socket.listLocal() + const prints = sockets.map(async socket => { + const components = await this.Socket.getLocal(socket).getComponents() + return components.forEach(async component => { + echon(4)(`Linking component ${format.cyan.bold(component.packageName)}...`) + component.linkWithProject(path.join(process.cwd(), projectPath)) + echo(format.green(' Done')) + }) + }) + await Promise.all(prints) + echo() + } +} diff --git a/packages/cli/src/commands/component-list.js b/packages/cli/src/commands/component-list.js new file mode 100644 index 00000000..2e1f226a --- /dev/null +++ b/packages/cli/src/commands/component-list.js @@ -0,0 +1,45 @@ +import format from 'chalk' +import logger from '../utils/debug' +import { echo } from '../utils/print-tools' + +const { info, debug } = logger('cmd-component-list') + +export default class ComponentList { + constructor (context) { + this.session = context.session + this.Socket = context.Socket + this.Component = context.Component + } + + async run ([projetPath, cmd]) { + info('ComponentLink.run') + + const sockets = await this.Socket.listLocal() + sockets.forEach(async socket => { + const components = await this.Socket.getLocal(socket).getComponents() + components.forEach(component => { + this.printComponent(component) + }) + }) + } + + async printComponent (component) { + debug('printComponent') + const metadata = component.metadata + + echo(4)(`${format.cyan.bold('name')}: ${format.cyan.bold(component.packageName)}`) + echo(4)(`${format.dim('socket')}: ${component.socketName}`) + + if (metadata.parameters) { + echo() + Object.keys(metadata.parameters).forEach(paramName => { + const paramObj = metadata.parameters[paramName] + echo(8)(`${format.dim('name')}: ${format.cyan(paramName) || ''}`) + echo(8)(`${format.dim('description')}: ${paramObj.description}`) + echo(8)(`${format.dim('example')}: ${paramObj.example}`) + echo() + }) + } + echo() + } +} diff --git a/packages/cli/src/commands/index.js b/packages/cli/src/commands/index.js index b0ae027f..77234efc 100644 --- a/packages/cli/src/commands/index.js +++ b/packages/cli/src/commands/index.js @@ -22,6 +22,8 @@ import SocketTrace from './socket-trace' import SocketInstall from './socket-install' import SocketCreate from './socket-create' import SocketUninstall from './socket-uninstall' +import ComponentList from './component-list' +import ComponentLink from './component-link' export default { Attach, @@ -47,5 +49,7 @@ export default { SocketTrace, SocketInstall, SocketCreate, - SocketUninstall + SocketUninstall, + ComponentList, + ComponentLink } diff --git a/packages/cli/src/commands/socket-deploy-hot.js b/packages/cli/src/commands/socket-deploy-hot.js index ca5d6d72..f0748b95 100644 --- a/packages/cli/src/commands/socket-deploy-hot.js +++ b/packages/cli/src/commands/socket-deploy-hot.js @@ -145,25 +145,98 @@ export default class SocketDeployCmd { } updateEnds() spinner.stop() + } + } + + async deployComponent (component) { + const componentName = component.packageName + debug(`deployComponent: ${componentName}`) + const deployTimer = new Timer() + const msg = p(`${format.magenta('component build:')} ${currentTime()} ${format.cyan(componentName)}`) + this.mainSpinner.stop() + const spinner = new SimpleSpinner(msg).start() + + // We have to count here number of updates + if (!pendingUpdates[componentName]) { pendingUpdates[componentName] = 0 } + + pendingUpdates[componentName] += 1 + if (pendingUpdates[componentName] > 1) { + spinner.stop() + this.mainSpinner.start() + debug(`not updating, update pending: ${pendingUpdates[componentName]}`) + return + } + + const updateEnds = async () => { + this.mainSpinner.start() + // After update we have to understand if we should fire new one + pendingUpdates[componentName] -= 1 + if (pendingUpdates[componentName] > 0) { + pendingUpdates[componentName] = 0 + await this.deployComponent(component) + } + } + try { + await component.build() + + spinner.stop() + SocketDeployCmd.printUpdateSuccessful(componentName, {status: 'ok'}, deployTimer) + await updateEnds() + } catch (err) { + spinner.stop() + if (err instanceof CompileError) { + const status = format.red(' build error:') + echo(2)(`${status} ${currentTime()} ${format.cyan(componentName)}\n\n${err.traceback.split('\n').map(line => p(8)(line)).join('\n')}`) + } else { + const status = format.red('build error:') + echo(2)(`${status} ${currentTime()} ${format.cyan(componentName)} ${format.red(err.message)}`) + } + + if (this.cmd.bail) { + SocketDeployCmd.bail() + } + updateEnds() + spinner.stop() } } getSocketToUpdate (fileName) { - if (fileName.match(/\/test\//)) { + if (fileName.match(/\/test\//) || fileName.match(/\/components\//)) { return false } return this.localSockets.find((socket) => socket.isSocketFile(fileName)) } + async getComponentToUpdate (fileName) { + const sockets = await this.Socket.listLocal() + const componentsList = [] + await Promise.all(sockets.map(async socket => { + const components = await this.Socket.getLocal(socket).getComponents() + components.forEach(component => { + componentsList.push(component) + }) + })) + let componentFound = null + componentsList.some(component => { + if (component.isComponentFile(fileName)) { + componentFound = component + } + }) + return componentFound + } + runStalker () { // Stalking files debug('watching:', this.session.projectPath) this.stalker = watchr.create(this.session.projectPath) - this.stalker.on('change', (changeType, fileName) => { + this.stalker.on('change', async (changeType, fileName) => { timer.reset() const socketToUpdate = this.getSocketToUpdate(fileName) - if (socketToUpdate) { + const componentToUpdate = await this.getComponentToUpdate(fileName) + if (componentToUpdate) { + this.deployComponent(componentToUpdate) + } else if (socketToUpdate) { this.deploySocket(socketToUpdate) } }) diff --git a/packages/cli/src/utils/component/component.js b/packages/cli/src/utils/component/component.js new file mode 100644 index 00000000..63eb74a0 --- /dev/null +++ b/packages/cli/src/utils/component/component.js @@ -0,0 +1,18 @@ +import session from '../session' +import Socket from '../sockets' + +class Component { + constructor () { + this.session = session + } + + static async list () { + const sockets = await Socket.list() + sockets.forEach(async socket => { + const components = await socket.getComponents() + console.log(socket.name, components) + }) + } +} + +export default Component diff --git a/packages/cli/src/utils/component/index.js b/packages/cli/src/utils/component/index.js new file mode 100644 index 00000000..781c9ba8 --- /dev/null +++ b/packages/cli/src/utils/component/index.js @@ -0,0 +1,3 @@ +import Component from './component' + +export default Component diff --git a/packages/cli/src/utils/context.js b/packages/cli/src/utils/context.js index eaed7cf0..ccb5a373 100644 --- a/packages/cli/src/utils/context.js +++ b/packages/cli/src/utils/context.js @@ -1,6 +1,7 @@ import Init from './init' import Hosting from './hosting' import Socket from './sockets' +import Component from './component' import Registry from './registry' import session from './session' @@ -8,6 +9,7 @@ export default { Init, Hosting, Socket, + Component, Registry, session } diff --git a/packages/cli/src/utils/errors.js b/packages/cli/src/utils/errors.js new file mode 100644 index 00000000..46f02b11 --- /dev/null +++ b/packages/cli/src/utils/errors.js @@ -0,0 +1,9 @@ +class CompileError { + constructor (traceback) { + this.traceback = traceback + } +} + +export { + CompileError +} diff --git a/packages/cli/src/utils/sockets/sockets.js b/packages/cli/src/utils/sockets/sockets.js index a63a1163..4d084da7 100644 --- a/packages/cli/src/utils/sockets/sockets.js +++ b/packages/cli/src/utils/sockets/sockets.js @@ -25,7 +25,6 @@ import { p, echo } from '../print-tools' import { getTemplate } from '../templates' import { CompileError } from '../errors' - const { debug } = logger('utils-sockets') class MetadataObject { @@ -77,6 +76,70 @@ class Handler extends MetadataObject {} class Event extends MetadataObject {} +class Component extends MetadataObject { + constructor (name, metadata, socketName) { + super(name, metadata, socketName) + this.componentPath = path.join(Socket.getLocal(this.socketName).socketPath, this.metadata.path) + this.packageName = this.getRealName() + } + getRealName () { + debug('getRealComponentName') + return JSON.parse(fs.readFileSync(path.join(this.componentPath, 'package.json'))).name + } + + linkWithProject (projectPath) { + debug('linkWithProject') + child.spawnSync( + 'yarn', + ['link'], + { + cwd: this.componentPath, + maxBuffer: 2048 * 1024, + stdio: [process.stdio, 'pipe', 'pipe'] + } + ) + child.spawnSync( + 'yarn', + ['link', this.packageName], + { + cwd: projectPath, + maxBuffer: 2048 * 1024, + stdio: [process.stdio, 'pipe', 'pipe'] + } + ) + } + + isComponentFile (filePath) { + return filePath.includes(path.join(this.componentPath, 'src')) + } + + build () { + debug(`component build: ${this.packageName}`) + + return new Promise(async (resolve, reject) => { + const command = 'npm' + const args = 'run build -s' + + process.env.FORCE_COLOR = true + const out = child.spawnSync( + command, + args.split(' '), + { + cwd: this.componentPath, + maxBuffer: 2048 * 1024, + stdio: [process.stdio, 'pipe', 'pipe'] + } + ) + + if (out.status !== 0) { + reject(new CompileError(out.stderr.toString())) + } else { + resolve() + } + }) + } +} + class Socket { constructor (socketName, socketPath) { debug('Sockets.constructor', socketName) @@ -95,8 +158,24 @@ class Socket { this.dependencyOf = [] // that looks stupid - this.remote = { spec: { endpoints: {}, event_handlers: {}, events: {} }, metadata: {} } - this.spec = { spec: { endpoints: {}, event_handlers: {}, events: {} } } + this.remote = { + spec: { + endpoints: {}, + event_handlers: {}, + events: {}, + components: {} + }, + metadata: {} + } + + this.spec = { + spec: { + endpoints: {}, + event_handlers: {}, + events: {}, + components: {} + } + } this.loadLocal() @@ -499,6 +578,21 @@ class Socket { }) } + composeComponentsFromSpec (objectType, ObjectClass) { + debug('composeComponentsFromSpec', objectType, ObjectClass) + const objects = Object.assign({}, this.spec[objectType]) + Object.assign(objects, this.spec[objectType]) + + debug('objects to process', objects) + return Object.keys(objects).map(objectName => { + debug(`checking ${objectName}`) + const objectMetadata = objects[objectName] + debug('objectMetadata', objectMetadata) + const object = new ObjectClass(objectName, objectMetadata, this.name) + return object + }) + } + getEndpoints () { debug('getEndpoints') return this.composeFromSpec('endpoints', Endpoint) @@ -557,8 +651,8 @@ class Socket { }) } - static getEndpointTraceByUrl (url) { - const resp = axios.request({ + static async getEndpointTraceByUrl (url) { + const resp = await axios.request({ url: `https://${session.getHost()}${url}`, method: 'GET', headers: { @@ -568,6 +662,12 @@ class Socket { return resp.data } + async getComponents () { + debug('getComponents') + debug('getEndpoints') + return this.composeComponentsFromSpec('components', Component) + } + async createZip ({ plainSources = false } = {}) { debug('createZip') return new Promise((resolve, reject) => { @@ -881,7 +981,6 @@ class Socket { debug(`compile socketPath: ${this.getSocketPath()}`) return new Promise(async (resolve, reject) => { - if (this.isDependencySocket || this.isProjectRegistryDependency) { await Registry.getSocket(this) const fileName = path.join(session.getBuildPath(), `${this.name}.zip`)