Skip to content

Commit

Permalink
feat: add dico push command
Browse files Browse the repository at this point in the history
  • Loading branch information
lihbr committed Jun 3, 2021
1 parent b67a43f commit 3b2d94f
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 20 deletions.
43 changes: 39 additions & 4 deletions playground/dico.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,46 @@
"src/**/*.vue"
],
"schema": {
"foo": {
"js": {
"foo": "",
"bar": "",
"baz": "",
"qux": {}
"nested": {
"foo": "",
"bar": ""
}
},
"jsx": {
"foo": "",
"bar": "",
"nested": {
"foo": "",
"bar": ""
}
},
"ts": {
"foo": "",
"bar": "",
"nested": {
"foo": "",
"bar": ""
}
},
"tsx": {
"foo": "",
"bar": "",
"nested": {
"foo": "",
"bar": ""
}
},
"vue": {
"foo": "",
"bar": "",
"nested": {
"foo": "",
"bar": ""
}
}
},
"updated_at": "2021-06-01T10:00:08.413473+00:00"
"updated_at": "2021-06-03T08:19:40.516881+00:00"
}
14 changes: 14 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ cli
.command("init", "Init a dico in your project")
.option("-f, --force", "Override existing `dico.config.json`")
.action(async options => {
// TODO: Check role? / handle 401
await middlewares.signedInOnly();
await commands.init(cli, options);
});
Expand All @@ -39,6 +40,19 @@ cli.command("build", "Build current project dico").action(async options => {
await commands.build(cli, options);
});

cli
.command("push", "Push current dico to Dico.app")
.option(
"-b, --build",
"Also build current project dico (performs `dico build` before pushing)"
)
.option("-f, --force", "Force push, even if not in sync (not recommended)")
.action(async options => {
// TODO: Check role? / handle 401
await middlewares.signedInOnly();
await commands.push(cli, options);
});

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

Expand Down
2 changes: 1 addition & 1 deletion src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const buildDico = new Listr(

export const build = async (
_: CAC,
__: { [key: string]: never }
options: { [key: string]: unknown }
): Promise<void> => {
if (!dicojson.exists()) {
logger.error(messages.DicoJSONNotFound, dicojson.getPath());
Expand Down
5 changes: 2 additions & 3 deletions src/commands/default.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,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 => {
export const _default = (cli: CAC, _: { [key: string]: unknown }): void => {
const command = cli.args.join(" ");

if (command) {
logger.error(messages.InvalidCommand, command, NAME);
logger.error(messages.InvalidCommand, command);
exit(1);
} else {
cli.outputHelp();
Expand Down
1 change: 1 addition & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./whoami";

export * from "./init";
export * from "./build";
export * from "./push";

export * from "./help";
export * from "./default";
2 changes: 1 addition & 1 deletion src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const initConfig = {

export const init = async (
_: CAC,
options: { [key: string]: never }
options: { [key: string]: unknown }
): Promise<void> => {
if (!options.force && dicojson.exists()) {
logger.error(messages.DicoJSONAlreadyExists, dicojson.getPath());
Expand Down
130 changes: 130 additions & 0 deletions src/commands/push.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import chalk from "chalk";
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 { lineBreak, logger } from "../lib";
import * as dicojson from "../core/dicojson";
import * as client from "../core/client";
import * as messages from "../messages";
import exit from "exit";
import { DEFAULT_TIMEOUT, JSON_FILE } from "../const";
import { ConfigJSON, ProjectKey } from "../types";
import { build } from "./build";

class PushError extends Error {}

interface PushDicoContext {
config: ConfigJSON;
force: boolean;
}

const pushDico = new Listr(
[
{
title: "Checking production dico status...",
// @ts-expect-error listr types are broken
task: (ctx: PushDicoContext, task: ListrTaskWrapper) =>
new Observable(observer => {
Promise.all([
client.structure.select.byDicoSlug.run(ctx.config.dico),
new Promise(resolve => setTimeout(resolve, DEFAULT_TIMEOUT))
])
.then(([structure, _]) => {
if (structure.updated_at !== ctx.config.updated_at) {
if (!ctx.force) {
observer.error(
new PushError(messages.ProductionDicoNotInSync)
);
} else {
task.title = `${messages.ProductionDicoNotInSync}, ignoring because of \`force\` flag!`;
}
}

task.title = `Production dico is in sync with local \`${JSON_FILE}\``;

observer.complete();
})
.catch(error => {
observer.error(error);
});
})
},
{
title: "Uploading new schema to Dico.app...",
// @ts-expect-error listr types are broken
task: (ctx: PushDicoContext, task: ListrTaskWrapper) =>
new Observable(observer => {
Promise.all([
client.structure.update.byDicoSlug.schema.run(
ctx.config.dico,
ctx.config.schema
),
new Promise(resolve => setTimeout(resolve, DEFAULT_TIMEOUT))
])
.then(([structure, _]) => {
// Update config file
const config = dicojson.read();
config.updated_at = structure.updated_at;
dicojson.write(config);

task.title = "New schema uploaded";
observer.complete();
})
.catch(error => {
observer.error(error);
});
})
}
],
{ renderer: UpdateRenderer }
);

export const push = async (
cli: CAC,
options: { [key: string]: unknown }
): Promise<void> => {
if (!dicojson.exists()) {
logger.error(messages.DicoJSONNotFound, dicojson.getPath());
exit(1);
}

if (options.force) {
logger.warn(messages.CommandWithForce, "init");
} else {
lineBreak();
}

if (options.build) {
logger.info(messages.CommandWithFlagCommand, "push", "build", "build");
await build(cli, {});
logger.info(messages.NowStartingCommand, "push");
lineBreak();
}

try {
await pushDico.run({
config: dicojson.read(),
force: !!options.force
});
} catch (error) {
// Handle conflicts error
if (error instanceof PushError && error.message.includes("not in sync")) {
logger.error(`${error.message}:`);

logger.info(
`Sync errors happen when your \`${JSON_FILE}\` file is older than the one available on Dico.app\n Try merging your branch with the most up-to-date one on git before trying again\n Alternatively you can use the \`force\` flag to bypass sync errors (not recommended)`
);
lineBreak();

exit(1);
} else {
throw error;
}
}

logger.success(messages.CommandSuccessful, "push");
lineBreak();
};
2 changes: 1 addition & 1 deletion src/commands/signout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CAC } from "cac";
import { lineBreak, logger } from "../lib";
import * as user from "../core/user";

export const signout = (_: CAC, __: { [key: string]: never }): void => {
export const signout = (_: CAC, __: { [key: string]: unknown }): void => {
user.signout();
lineBreak();
logger.success("Logged out\n");
Expand Down
2 changes: 1 addition & 1 deletion src/commands/whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as messages from "../messages";

export const whoami = async (
_: CAC,
__: { [key: string]: never }
__: { [key: string]: unknown }
): Promise<void> => {
if (await user.isSignedIn()) {
const { user: signedInUser } = dicorc.read();
Expand Down
27 changes: 26 additions & 1 deletion src/core/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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";
import { ConfigJSON, ConfigRC, Dico, Structure } from "../types";

class ClientError extends Error {}

Expand All @@ -23,6 +23,7 @@ export const fetch = async <R = { [key: string]: unknown }>(
authorization: `Bearer ${token}`
};

// Populate headers with headers from options
if (options && options.headers && typeof options.headers === "object") {
Object.entries(options.headers).map(([k, v]) => {
if (typeof v === "string") {
Expand All @@ -31,6 +32,12 @@ export const fetch = async <R = { [key: string]: unknown }>(
});
}

// Ensure body is JSON
if (options && options.body) {
options.body = JSON.stringify(options.body);
headers["content-type"] = "application/json";
}

const response = await _fetch(
`${dicorc.read().endpoint || API_ENDPOINT}${endpoint}`,
{
Expand Down Expand Up @@ -85,5 +92,23 @@ export const structure = {
return data;
}
}
},
update: {
byDicoSlug: {
schema: {
run: async (
slug: string,
schema: ConfigJSON["schema"],
token?: string
): Promise<Structure> => {
const { data } = await fetch<Structure>(`/structure/${slug}`, token, {
method: "PUT",
body: { schema }
});

return data;
}
}
}
}
};
17 changes: 9 additions & 8 deletions src/messages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { NAME } from "./const";
import { JSON_FILE, NAME } from "./const";

export const InvalidCommand =
"Invalid command: `%s`, run `%s --help` for all commands";
export const InvalidCommand = `Invalid command: \`%s\`, run \`${NAME} --help\` for all commands`;

export const NotSignedIn = "Not logged in! Log in with command `login <token>`";
export const SignedInAs = "Logged in as `%s <%s>`";
Expand All @@ -13,12 +12,14 @@ export const InvalidTokenFormat =
export const NoDicoFoundDeveloper =
"No dico found where you at least have the `Developer` role";

export const DicoJSONNotFound = "`dico.config.json` not found at `%s`";
export const DicoJSONAlreadyExists =
"`dico.config.json` already exists at `%s`!";
export const DicoJSONNotFound = `\`${JSON_FILE}\` not found at \`%s\``;
export const DicoJSONAlreadyExists = `\`${JSON_FILE}\` already exists at \`%s\`!`;

export const NoSourceFilesFound =
"No source files found with current `sources` option. Double-check your `dico.config.json` and try again!";
export const NoSourceFilesFound = `No source files found with current \`sources\` option. Double-check your \`${JSON_FILE}\` and try again!`;

export const CommandSuccessful = `\`${NAME} %s\` successful!`;
export const CommandWithForce = `Running \`${NAME} %s\` with \`force\` flag`;
export const CommandWithFlagCommand = `Running \`${NAME} %s\` with \`%s\` flag, performing \`${NAME} %s\` first:`;
export const NowStartingCommand = `Now starting command \`${NAME} %s\`:`;

export const ProductionDicoNotInSync = `Production dico is not in sync with local \`${JSON_FILE}\``;

0 comments on commit 3b2d94f

Please sign in to comment.