Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-updating development mode on file changes #19

Merged
merged 10 commits into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ zeplin connect [options]
|--------------|---------------------------------------------------|-------------------------|
| -f, --file | Path to components configuration file | .zeplin/components.json |
| -d, --dev | Activate development mode | false |
| --no-watch | Disable watching file changes on development mode | false |
| -p, --plugin | npm package name of a Zeplin CLI `connect` plugin | |
| -h, --help | Output usage information | |
| --verbose | Enable verbose logs | |
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@hapi/joi": "^16.1.7",
"axios": "^0.19.0",
"chalk": "^3.0.0",
"chokidar": "^3.3.1",
"ci-info": "^2.0.0",
"commander": "^4.1.0",
"endent": "^1.3.0",
Expand Down
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ const connectCommand = program.command("connect")
.description("Connect components to code")
.option("-f, --file <file>", "Full path to components file", createCollector(), defaults.commands.connect.filePaths)
.option("-d, --dev", "Activate development mode", defaults.commands.connect.devMode)
.option("--no-watch", "Disable watch files on development mode", defaults.commands.connect.devModeWatch)
.option("-p, --plugin <plugin>", "npm package name of a Zeplin CLI connect plugin", createCollector(), [])
.action(commandRunner(async options => {
const connectOptions: ConnectOptions = {
configFiles: options.file,
devMode: options.dev,
devModePort: defaults.commands.connect.port,
devModeWatch: options.watch,
plugins: options.plugin
};

Expand Down
119 changes: 87 additions & 32 deletions src/commands/connect/index.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,110 @@
import chalk from "chalk";
import chokidar from "chokidar";
import dedent from "ts-dedent";

import logger from "../../util/logger";
import { indent } from "../../util/text";
import { getComponentConfigFiles } from "./config";
import { ConnectedBarrelComponents } from "./interfaces/api";
import { connectComponentConfigFiles } from "./plugin";
import { ConnectDevServer } from "./server";
import { ConnectedComponentsService } from "./service";
import { indent } from "../../util/text";
import logger from "../../util/logger";

export interface ConnectOptions {
configFiles: string[];
devMode: boolean;
devModePort: number;
plugins: string[];
}
const connectComponents = async (options: ConnectOptions): Promise<ConnectedBarrelComponents[]> => {
yuqu marked this conversation as resolved.
Show resolved Hide resolved
const {
configFiles,
plugins
} = options;

export async function connect(options: ConnectOptions): Promise<void> {
try {
logger.debug(`connect options: ${JSON.stringify(options)}`);
const componentConfigFiles = await getComponentConfigFiles(configFiles, plugins);

const {
configFiles,
plugins,
devMode,
devModePort
} = options;
logger.debug(`component config files: ${JSON.stringify(componentConfigFiles)}`);

const componentConfigFiles = await getComponentConfigFiles(configFiles, plugins);
const connectedBarrels = await connectComponentConfigFiles(componentConfigFiles);

logger.debug(`component config files: ${JSON.stringify(componentConfigFiles)}`);
logger.debug(`connected barrels output: ${JSON.stringify(connectedBarrels)}`);

const connectedBarrels = await connectComponentConfigFiles(componentConfigFiles);
return connectedBarrels;
};

logger.debug(`connected barrels output: ${JSON.stringify(connectedBarrels)}`);
const startDevServer = async (
options: ConnectOptions,
yuqu marked this conversation as resolved.
Show resolved Hide resolved
connectedBarrels: ConnectedBarrelComponents[]
): Promise<void> => {
const {
configFiles,
devModePort,
devModeWatch
} = options;

if (devMode) {
logger.info("Starting development server…");
logger.info("Starting development server…");

const devServer = new ConnectDevServer(connectedBarrels);
const devServer = new ConnectDevServer(connectedBarrels);

await devServer.start(devModePort);
await devServer.start(devModePort);

logger.info(`Development server is started on port ${devModePort}!`);
} else {
logger.info("Connecting all connected components into Zeplin…");
logger.info(chalk.green(`Development server is started on port ${devModePort}.`));
yuqu marked this conversation as resolved.
Show resolved Hide resolved

const service = new ConnectedComponentsService();
if (devModeWatch) {
const componentFiles = connectedBarrels?.map(f =>
yuqu marked this conversation as resolved.
Show resolved Hide resolved
f.connectedComponents.map(c => c.path)
).reduce((a, b) => [...a, ...b], []);

await service.uploadConnectedBarrels(connectedBarrels);
const watcher = chokidar.watch(
[...configFiles, ...componentFiles],
yuqu marked this conversation as resolved.
Show resolved Hide resolved
{
cwd: process.cwd(),
persistent: true,
awaitWriteFinish: true
}
);

logger.info("🦄 Components successfully connected to components in Zeplin.");
watcher.on("change", async path => {
logger.info((chalk.yellow(`\nFile change detected ${path}.\n`)));

try {
const updatedConnectedBarrels = await connectComponents(options);

await devServer.stop();
yuqu marked this conversation as resolved.
Show resolved Hide resolved

watcher.close().then(() => startDevServer(options, updatedConnectedBarrels));
} catch (error) {
logger.error(chalk.red(dedent`
Could not restart development server.
${error}
`));
}
});
}
};

const upload = async (connectedBarrels: ConnectedBarrelComponents[]): Promise<void> => {
logger.info("Connecting all connected components into Zeplin…");

const service = new ConnectedComponentsService();

await service.uploadConnectedBarrels(connectedBarrels);

logger.info("🦄 Components successfully connected to components in Zeplin.");
};

export interface ConnectOptions {
configFiles: string[];
devMode: boolean;
devModePort: number;
devModeWatch: boolean;
plugins: string[];
}

export async function connect(options: ConnectOptions): Promise<void> {
try {
logger.debug(`connect options: ${JSON.stringify(options)}`);

const connectedBarrels = await connectComponents(options);

if (options.devMode) {
await startDevServer(options, connectedBarrels);
} else {
await upload(connectedBarrels);
}
} catch (error) {
error.message = dedent`
Expand Down
5 changes: 5 additions & 0 deletions src/commands/connect/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const connectComponentConfig = async (
const pluginPromises = plugins.map(async plugin => {
try {
if (plugin.supports(component)) {
logger.debug(`${plugin.name} supports ${component.path}. Processing…`);
const componentData = await plugin.process(component);

data.push({
Expand All @@ -110,6 +111,10 @@ const connectComponentConfig = async (
componentData.links?.forEach(link =>
urlPaths.push(processLink(link))
);

logger.debug(`${plugin.name} processed ${component.path}: ${componentData}`);
} else {
logger.debug(`${plugin.name} does not support ${component.path}.`);
}
} catch (err) {
throw new CLIError(dedent`
Expand Down
45 changes: 38 additions & 7 deletions src/commands/connect/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import express from "express";
import { ConnectedBarrelComponents, ConnectedComponent } from "../interfaces/api";
import { CLIError } from "../../../errors";
import { Server } from "http";
import { OK } from "http-status-codes";
import { Socket } from "net";
import { CLIError } from "../../../errors";
import logger from "../../../util/logger";
import { ConnectedBarrelComponents, ConnectedComponent } from "../interfaces/api";

export class ConnectDevServer {
connectedBarrels: ConnectedBarrelComponents[] = [];
stopped = false;
server: Server | undefined;
connections: Socket[] = [];

constructor(connectedBarrels: ConnectedBarrelComponents[]) {
this.connectedBarrels = connectedBarrels;
Expand All @@ -18,7 +24,11 @@ export class ConnectDevServer {
return found ? found.connectedComponents : null;
}

start(port: number): Promise<void> {
start(port: number): Promise<Server> {
if (this.server && this.server.listening) {
return Promise.resolve(this.server);
}

const app = express();

// CORS
Expand All @@ -37,16 +47,37 @@ export class ConnectDevServer {
return res.status(OK).json({ connectedComponents });
});

const promise = new Promise<void>((resolve, reject): void => {
app.listen(port, resolve)
return new Promise<Server>((resolve, reject): void => {
this.server = app.listen(port)
.on("listening", () => {
logger.debug(`Started dev server on port ${port}`);
resolve(this.server);
})
.on("error", (err: NodeJS.ErrnoException) => {
if (err.code === "EADDRINUSE") {
reject(new CLIError(`Port ${port} is already in use.`));
}
})
.on("connection", connection => {
this.connections.push(connection);
connection.on("close", () =>
(this.connections = this.connections.filter(curr => curr !== connection))
);
});
});
}

stop(): Promise<void> {
logger.debug("Stopping dev server.");

this.stopped = true;
this.connections.forEach(conn => conn.end());

return promise;
return new Promise((resolve): void => {
this.server?.close(() => {
logger.debug("Stopped dev server.");
resolve();
});
});
}
}

1 change: 1 addition & 0 deletions src/config/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const defaults = {
connect: {
filePaths: [".zeplin/components.json"],
devMode: false,
devModeWatch: true,
port: 9756
}
},
Expand Down