Skip to content

Commit

Permalink
feat: add dico init command
Browse files Browse the repository at this point in the history
  • Loading branch information
lihbr committed Jun 1, 2021
1 parent e9d989a commit 6a61930
Show file tree
Hide file tree
Showing 22 changed files with 346 additions and 46 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"chalk": "4.1.1",
"consola": "2.15.3",
"debug": "4.3.1",
"detect-indent": "6.1.0",
"exit": "0.1.2",
"inquirer": "8.1.0",
"latest-version": "5.1.0",
Expand Down
6 changes: 6 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import semver from "semver";
import * as commands from "./commands";
import { NAME, PACKAGE, VERSION } from "./const";
import { logger, ucFirst } from "./lib";
import * as middlewares from "./middlewares";

const cli = cac(NAME);

Expand All @@ -25,6 +26,11 @@ cli.command("whoami", "Display current user").action(async options => {
await commands.whoami(cli, options);
});

cli.command("init", "Init a dico in your project").action(async options => {
await middlewares.signedInOnly();
await commands.init(cli, options);
});

cli.version(VERSION);
cli.help(commands.help);

Expand Down
7 changes: 2 additions & 5 deletions src/commands/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ import { CAC } from "cac";
import exit from "exit";
import { NAME } from "../const";
import { logger } from "../lib";
import * as messages from "../messages";

export const _default = (cli: CAC, _: { [key: string]: never }): void => {
const command = cli.args.join(" ");

if (command) {
logger.error(
"Invalid command: `%s`, run `%s --help` for all commands",
command,
NAME
);
logger.error(messages.InvalidCommand, command, NAME);
exit(1);
} else {
cli.outputHelp();
Expand Down
8 changes: 7 additions & 1 deletion src/commands/help.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chalk from "chalk";
import { PACKAGE, VERSION } from "../const";
import * as dicorc from "../core/dicorc";

interface HelpSection {
title?: string;
Expand All @@ -14,9 +15,14 @@ export const help = (sections: HelpSection[]): void => {
}\n`;

// Add header
const config = dicorc.read();
sections.unshift({
body: `\n📚 Dico CLI\n ${chalk.cyanBright(
"Read the docs:"
)} https://docs.dico.app/cli`
)} https://docs.dico.app/cli${
config.endpoint
? `\n ${chalk.cyan("Endpoint override:")} ${config.endpoint}`
: ""
}`
});
};
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ export * from "./signin";
export * from "./signout";
export * from "./whoami";

export * from "./init";

export * from "./help";
export * from "./default";
128 changes: 128 additions & 0 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { CAC } from "cac";
// @ts-expect-error it's our fork and we don't care
import UpdateRenderer from "@lihbr/listr-update-renderer";
import Listr, { ListrTaskWrapper } from "listr";
import { Observable } from "rxjs";
import inquirer from "inquirer";

import { lineBreak, logger } from "../lib";
import * as client from "../core/client";
import * as dicojson from "../core/dicojson";
import { Dico, Role } from "../types";
import * as messages from "../messages";
import { DEFAULT_SOURCES_PATTERN, DEFAULT_TIMEOUT } from "../const";
import exit from "exit";

class InitError extends Error {}

const getDicos = new Listr(
[
{
title: "Looking for dicos...",
// @ts-expect-error listr types are broken
task: (ctx: { dicos: Dico[] }, task: ListrTaskWrapper) =>
new Observable(observer => {
Promise.all([
client.dico.select.all.run(),
new Promise(resolve => setTimeout(resolve, DEFAULT_TIMEOUT))
])
.then(([dicos, _]) => {
const filteredDicos = dicos.filter(dico =>
[Role.Admin, Role.Developer].includes(dico.current_user_role)
);

if (!filteredDicos.length) {
observer.error(new InitError(messages.NoDicoFoundDeveloper));
}

if (dicos.length === 1) {
task.title = "One dico found";
} else {
task.title = `${dicos.length} dicos found`;
}

ctx.dicos = dicos;
observer.complete();
})
.catch(error => {
observer.error(error);
});
})
}
],
{ renderer: UpdateRenderer }
);

const pickDico = {
run: async (dicos: Dico[]) =>
await inquirer.prompt<{ dico: Dico }>([
{
type: "list",
name: "dico",
message: "Pick the dico you wish to init in current working direcotry:",
choices: dicos.map(dico => ({
name: `${dico.slug} (${dico.name})`,
value: dico
})),
loop: false,
pageSize: 12
}
])
};

const initConfig = {
run: async (dico: Dico) =>
await new Listr(
[
{
title: "Creating `dico.config.json`...",
// @ts-expect-error listr types are broken
task: (ctx: { config: ConfigJSON }, task: ListrTaskWrapper) =>
new Observable(observer => {
Promise.all([
client.structure.select.byDicoSlug.run(dico.slug),
new Promise(resolve => setTimeout(resolve, DEFAULT_TIMEOUT))
])
.then(([structure, _]) => {
const config = {
dico: dico.slug,
sources: DEFAULT_SOURCES_PATTERN,
schema: structure.schema,
updated_at: structure.updated_at
};

try {
dicojson.write(config);
} catch (error) {
observer.error(error);
}

task.title = "`dico.config.json` created!";
ctx.config = config;
observer.complete();
})
.catch(error => {
observer.error(error);
});
})
}
],
{ renderer: UpdateRenderer }
).run()
};

export const init = async (
_: CAC,
__: { [key: string]: never }
): Promise<void> => {
if (dicojson.exists()) {
logger.error(messages.DicoJSONAlreadyExists, dicojson.getPath());
exit(1);
}

lineBreak();
const { dicos } = await getDicos.run();
const { dico } = await pickDico.run(dicos);
await initConfig.run(dico);
lineBreak();
};
12 changes: 7 additions & 5 deletions src/commands/signin.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import { CAC } from "cac";
import { logger } from "../lib";
import { lineBreak, logger } from "../lib";
import * as user from "../core/user";
import exit from "exit";
import * as messages from "../messages";

export const signin = async (_: CAC, token: string): Promise<void> => {
if (token.length !== 64) {
logger.error("Invalid token format, make sure you copied it correctly!");
logger.error(messages.InvalidTokenFormat);
exit(1);
}

try {
const signedInUser = await user.signin(token);
console.log("");
lineBreak();
logger.success(
"Logged in as `%s <%s>`\n",
messages.SignedInAs,
signedInUser?.fullname,
signedInUser?.email
);
lineBreak();
} catch (error) {
if (error.status === 401) {
logger.error("Invalid token, make sure you copied it correctly!");
logger.error(messages.InvalidToken);
exit(1);
} else {
throw error;
Expand Down
4 changes: 2 additions & 2 deletions src/commands/signout.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CAC } from "cac";
import { logger } from "../lib";
import { lineBreak, logger } from "../lib";
import * as user from "../core/user";

export const signout = (_: CAC, __: { [key: string]: never }): void => {
user.signout();
console.log("");
lineBreak();
logger.success("Logged out\n");
};
13 changes: 8 additions & 5 deletions src/commands/whoami.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { CAC } from "cac";
import * as dicorc from "../core/dicorc";
import * as user from "../core/user";
import { logger } from "../lib";
import { lineBreak, logger } from "../lib";
import * as messages from "../messages";

export const whoami = async (
_: CAC,
__: { [key: string]: never }
): Promise<void> => {
if (await user.isSignedIn()) {
const { user: signedInUser } = dicorc.read();
console.log("");
lineBreak();
logger.info(
"Logged in as `%s <%s>`\n",
messages.SignedInAs,
signedInUser?.fullname,
signedInUser?.email
);
lineBreak();
} else {
console.log("");
logger.info("Not logged in! Log in with command `login <token>`\n");
lineBreak();
logger.info(`${messages.NotSignedIn}`);
lineBreak();
}
};
7 changes: 7 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,11 @@ export const PACKAGE = pkg.name;
export const DESCRIPTION = pkg.description;
export const VERSION = pkg.version;
export const RC_FILE = ".dicorc";
export const JSON_FILE = "dico.config.json";
export const API_ENDPOINT = "https://api.dico.app/v1/cli";
export const DEFAULT_SOURCES_PATTERN = [
"src/**/*.(js|jsx)",
"src/**/*.(ts|tsx)",
"src/**/*.vue"
];
export const DEFAULT_TIMEOUT = 1000;
66 changes: 52 additions & 14 deletions src/core/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import _fetch from "node-fetch";
import { API_ENDPOINT } from "../const";
import * as dicorc from "./dicorc";
import * as messages from "../messages";
import { ConfigRC, Dico, Structure } from "../types";

export const fetch = async <R>(
class ClientError extends Error {}

export const fetch = async <R = { [key: string]: unknown }>(
endpoint: string,
token: string,
token?: string,
options?: { [key: string]: unknown }
): Promise<{ status: number; msg: string; data: R }> => {
if (!token) {
token = dicorc.read().user?.token;

if (!token) {
throw new ClientError(messages.NotSignedIn);
}
}

const headers: { [key: string]: string } = {
authorization: `Bearer ${token}`
};
Expand Down Expand Up @@ -36,16 +48,42 @@ export const fetch = async <R>(
return json;
};

export const whoami = async (
token: string
): Promise<Required<dicorc.Config>["user"]> => {
const {
data: { fullname, email }
} = await fetch<Required<dicorc.Config>["user"]>("/whoami", token);

return {
token,
fullname,
email
};
export const user = {
whoami: {
run: async (token: string): Promise<Required<ConfigRC>["user"]> => {
const {
data: { fullname, email }
} = await fetch<Required<ConfigRC>["user"]>("/whoami", token);

return {
token,
fullname,
email
};
}
}
};

export const dico = {
select: {
all: {
run: async (token?: string): Promise<Dico[]> => {
const { data } = await fetch<Dico[]>("/dico", token);

return data;
}
}
}
};

export const structure = {
select: {
byDicoSlug: {
run: async (slug: string, token?: string): Promise<Structure> => {
const { data } = await fetch<Structure>(`/structure/${slug}`, token);

return data;
}
}
}
};
Loading

0 comments on commit 6a61930

Please sign in to comment.