Skip to content

Commit

Permalink
feat: add sequential scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
Qu4k committed Jun 18, 2020
1 parent d314ca5 commit a4568fe
Show file tree
Hide file tree
Showing 13 changed files with 223 additions and 120 deletions.
25 changes: 16 additions & 9 deletions denon.config.ts
@@ -1,15 +1,22 @@
import { DenonConfig } from "https://deno.land/x/denon/mod.ts";
import { DenonConfig } from "./mod.ts";

const config: DenonConfig = {
scripts: {
test: {
cmd: "deno test",
desc: "run denon test",
allow: [
"read",
],
unstable: true,
},
test: [
{
cmd: "deno fmt",
desc: "run denon format",
},
{
cmd: "deno test",
desc: "run denon format",
allow: [
"all",
],
unstable: true,
watch: false,
},
],
},
};

Expand Down
4 changes: 2 additions & 2 deletions denon.ts
Expand Up @@ -127,7 +127,7 @@ if (import.meta.main) {
}

// create configuration file.
// TODO: should be made interactive.
// TODO(@qu4k): should be made interactive.
if (args.init) {
await initializeConfig(args.init);
Deno.exit(0);
Expand All @@ -151,7 +151,7 @@ if (import.meta.main) {
log.info(`watching extensions: ${config.watcher.exts.join(",")}`);
}

// TODO(qu4k): events
// TODO(@qu4k): events
for await (let event of denon.run(script)) {
if (event.type === "reload") {
if (
Expand Down
2 changes: 1 addition & 1 deletion examples/oak.ts
Expand Up @@ -3,7 +3,7 @@ import { green } from "https://deno.land/std/fmt/colors.ts";

const app = new Application();

let PORT: number = 8000;
let PORT = 8000;

let port = Deno.env.get("PORT");
if (port) {
Expand Down
2 changes: 1 addition & 1 deletion examples/simple.ts
Expand Up @@ -22,6 +22,6 @@ while (i > 0) {
console.log(Deno.pid, red(`Execution #${i}!`));

if (--i % 10 === 0) {
throw "Oh no.";
throw new Error("Oh no.");
}
}
5 changes: 5 additions & 0 deletions schema.json
Expand Up @@ -62,6 +62,11 @@
"type": "boolean",
"default": false
},
"watch": {
"description": "Enable file watching.",
"type": "boolean",
"default": true
},
"inspect": {
"description": "Activate inspector on host:port",
"type": "string",
Expand Down
44 changes: 26 additions & 18 deletions src/cli.ts
Expand Up @@ -11,6 +11,7 @@ import {
grant,
exists,
omelette,
red,
} from "../deps.ts";

import {
Expand Down Expand Up @@ -55,7 +56,7 @@ export const PERMISSION_OPTIONAL: {
upgradeExe: [{ name: "net" }],
};

export async function grantPermissions() {
export async function grantPermissions(): Promise<void> {
// @see PERMISSIONS .
let permissions = await grant([...PERMISSIONS]);
if (!permissions || permissions.length < PERMISSIONS.length) {
Expand All @@ -68,7 +69,7 @@ export async function grantPermissions() {
* Create configuration file in the root of current work directory.
* // TODO: make it interactive
*/
export async function initializeConfig(template: string) {
export async function initializeConfig(template: string): Promise<void> {
let permissions = await grant(PERMISSION_OPTIONAL.initializeConfig);
if (
!permissions ||
Expand All @@ -87,7 +88,7 @@ export async function initializeConfig(template: string) {
/**
* Grab a fresh copy of denon
*/
export async function upgrade(version?: string) {
export async function upgrade(version?: string): Promise<void> {
const url = `https://deno.land/x/denon${
version ? `@${version}` : ""
}/denon.ts`;
Expand Down Expand Up @@ -134,21 +135,21 @@ export async function upgrade(version?: string) {
/**
* Generate autocomplete suggestions
*/
export function autocomplete(config: CompleteDenonConfig) {
export function autocomplete(config: CompleteDenonConfig): void {
// Write your CLI template.
const completion = omelette.default(`denon <script>`);

// Bind events for every template part.
completion.on("script", function ({ reply }: { reply: Function }) {
const watcher = new Watcher(config.watcher);
completion.on("script", function ({ reply }: { reply: Function }): void {
// const watcher = new Watcher(config.watcher);
const auto = Object.keys(config.scripts);
for (const file of Deno.readDirSync(Deno.cwd())) {
if (file.isFile && watcher.isWatched(file.name)) {
auto.push(file.name);
} else {
// auto.push(file.name);
}
}
// for (const file of Deno.readDirSync(Deno.cwd())) {
// if (file.isFile && watcher.isWatched(file.name)) {
// auto.push(file.name);
// } else {
// // auto.push(file.name);
// }
// }
reply(auto);
});

Expand All @@ -157,9 +158,9 @@ export function autocomplete(config: CompleteDenonConfig) {

/**
* List all available scripts declared in the config file.
* // TODO: make it interactive
* // TODO(@qu4k): make it interactive
*/
export function printAvailableScripts(config: CompleteDenonConfig) {
export function printAvailableScripts(config: CompleteDenonConfig): void {
if (Object.keys(config.scripts).length) {
log.info("available scripts:");
const runner = new Runner(config);
Expand All @@ -168,11 +169,18 @@ export function printAvailableScripts(config: CompleteDenonConfig) {
console.log();
console.log(` - ${yellow(bold(name))}`);

if (typeof script === "object" && script.desc) {
if (typeof script === "object" && !Array.isArray(script) && script.desc) {
console.log(` ${script.desc}`);
}

console.log(gray(` $ ${runner.build(name).cmd.join(" ")}`));
let commands = runner
.build(name)
.map((command) => command.cmd.join(" "))
.join(bold(" && "));

console.log(
gray(` $ ${commands}`),
);
}
console.log();
console.log(
Expand All @@ -199,7 +207,7 @@ export function printAvailableScripts(config: CompleteDenonConfig) {
* Help message to be shown if `denon`
* is run with `--help` flag.
*/
export function printHelp(version: string) {
export function printHelp(version: string): void {
setColorEnabled(true);
console.log(
`${blue("DENON")} - ${version}
Expand Down
4 changes: 2 additions & 2 deletions src/config.ts
Expand Up @@ -68,7 +68,7 @@ export type DenonConfig = RunnerConfig & Partial<CompleteDenonConfig>;
* Parameters are not optional
*/
export interface CompleteDenonConfig extends RunnerConfig {
[key: string]: any;
[key: string]: unknown;
watcher: WatcherConfig;
logger: LogConfig;
args?: Args;
Expand Down Expand Up @@ -202,7 +202,7 @@ export async function readConfig(
/**
* Reads the denon config from a file
*/
export async function writeConfigTemplate(template: string) {
export async function writeConfigTemplate(template: string): Promise<void> {
const templates = `https://deno.land/x/denon@${BRANCH}/templates`;
const url = `${templates}/${template}`;
log.info(`fetching template from ${url}`);
Expand Down
63 changes: 45 additions & 18 deletions src/daemon.ts
Expand Up @@ -3,7 +3,8 @@
import { log } from "../deps.ts";

import { Denon, DenonEvent } from "../denon.ts";
import { CompleteDenonConfig, DenonConfig } from "./config.ts";
import { CompleteDenonConfig } from "./config.ts";
import { ScriptOptions } from "./scripts.ts";

/**
* Daemon instance.
Expand All @@ -26,7 +27,7 @@ export class Daemon implements AsyncIterable<DenonEvent> {
/**
* Restart current process.
*/
private async reload() {
private async reload(): Promise<void> {
if (this.#config.logger && this.#config.logger.fullscreen) {
log.debug("clearing screen");
console.clear();
Expand All @@ -45,16 +46,31 @@ export class Daemon implements AsyncIterable<DenonEvent> {
await this.start();
}

private start() {
const command = this.#denon.runner.build(this.#script);
const process = command.exe();
log.debug(`S: starting process with pid ${process.pid}`);
this.#processes[process.pid] = (process);
this.monitor(process);
return command;
private async start(): Promise<ScriptOptions> {
const commands = this.#denon.runner.build(this.#script);

// Sequential execution, one process after another is executed,
// *sequentially*, the last process is named `main` and is the
// one that will actually be demonized.
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
let process = command.exe();
log.debug(`S: starting process with pid ${process.pid}`);

if (i === commands.length - 1) {
log.warning(`starting main \`${command.cmd.join(" ")}\``);
this.#processes[process.pid] = process;
this.monitor(process, command.options);
return command.options;
} else {
log.info(`starting sequential \`${command.cmd.join(" ")}\``);
await process.status();
}
}
return {};
}

private killAll() {
private killAll(): void {
log.debug(`K: killing ${Object.keys(this.#processes).length} process[es]`);
// kill all processes spawned
let pcopy = Object.assign({}, this.#processes);
Expand All @@ -71,7 +87,10 @@ export class Daemon implements AsyncIterable<DenonEvent> {
}
}

private async monitor(process: Deno.Process) {
private async monitor(
process: Deno.Process,
options: ScriptOptions,
): Promise<void> {
log.debug(`M: monitoring status of process with pid ${process.pid}`);
const pid = process.pid;
let s: Deno.ProcessStatus | undefined;
Expand All @@ -91,19 +110,27 @@ export class Daemon implements AsyncIterable<DenonEvent> {
if (s) {
// log status status
if (s.success) {
log.info("clean exit - waiting for changes before restart");
if (options.watch) {
log.info("clean exit - waiting for changes before restart");
} else {
log.info("clean exit - denon is exiting ...");
}
} else {
log.info(
"app crashed - waiting for file changes before starting ...",
);
if (options.watch) {
log.error(
"app crashed - waiting for file changes before starting ...",
);
} else {
log.error("app crashed - denon is exiting ...");
}
}
}
} else {
log.debug(`M: process with pid ${process.pid} was killed`);
}
}

private async onExit() {
private async onExit(): Promise<void> {
if (Deno.build.os !== "windows") {
const signs = [
Deno.Signal.SIGHUP,
Expand All @@ -125,9 +152,9 @@ export class Daemon implements AsyncIterable<DenonEvent> {
yield {
type: "start",
};
const command = this.start();
const options = await this.start();
this.onExit();
if (command.options.watch) {
if (options.watch) {
for await (const watchE of this.#denon.watcher) {
if (watchE.some((_) => _.type.includes("modify"))) {
log.debug(
Expand Down
12 changes: 7 additions & 5 deletions src/merge.ts
@@ -1,18 +1,20 @@
// Copyright 2020-present the denosaurs team. All rights reserved. MIT license.

export type Indexable = Record<string, any>;

/**
* Performs a deep merge of `source` into `target`.
* Mutates `target` only but not its objects and arrays.
*/
export function merge<T extends Record<string, any>>(
target: T,
source: any,
source: Indexable,
): T {
const t = target as Record<string, any>;
const isObject = (obj: any) => obj && typeof obj === "object";
const t = target as Record<string, unknown>;
const isObject = (obj: unknown) => obj && typeof obj === "object";

if (!isObject(target) || !isObject(source)) {
return source;
return source as T;
}

for (const key of Object.keys(source)) {
Expand All @@ -22,7 +24,7 @@ export function merge<T extends Record<string, any>>(
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
t[key] = sourceValue;
} else if (isObject(targetValue) && isObject(sourceValue)) {
t[key] = merge(Object.assign({}, targetValue), sourceValue);
t[key] = merge(Object.assign({}, targetValue), sourceValue as Indexable);
} else {
t[key] = sourceValue;
}
Expand Down

0 comments on commit a4568fe

Please sign in to comment.