Skip to content

Commit

Permalink
feat: "sqd run" command
Browse files Browse the repository at this point in the history
  • Loading branch information
mo4islona committed Jul 5, 2023
1 parent 07cf340 commit 6c89e8c
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 52 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@subsquid/cli",
"description": "squid cli tool",
"version": "2.4.2",
"version": "2.5.0-beta.0",
"license": "GPL-3.0-or-later",
"repository": "git@github.com:subsquid/squid-cli.git",
"publishConfig": {
Expand Down
64 changes: 20 additions & 44 deletions src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import targz from 'targz';
import { deploySquid, uploadFile, promptOrganization } from '../api';
import { DeployCommand } from '../deploy-command';
import { Manifest } from '../manifest';
import { loadManifestFile } from '../manifest/loadManifestFile';

const compressAsync = promisify(targz.compress);

Expand All @@ -23,50 +24,25 @@ const SQUID_PATH_DESC = [

export function resolveManifest(
localPath: string,
manifest: string,
): { error: string } | { buildDir: string; squidDir: string; manifestValue: Manifest } {
const squidDir = path.resolve(localPath);
if (!fs.statSync(squidDir).isDirectory()) {
return {
error: `The path ${squidDir} is a not a squid directory. Please provide a path to a squid root directory`,
};
}

const manifestPath = path.resolve(path.join(localPath, manifest));
if (fs.statSync(manifestPath).isDirectory()) {
return {
error: `The path ${manifestPath} is a directory, not a manifest file. Please provide a path to a valid manifest file inside squid directory`,
};
}

const buildDir = path.join(squidDir, 'builds');

fs.mkdirSync(buildDir, { recursive: true, mode: 0o777 });

manifestPath: string,
): { error: string } | { buildDir: string; squidDir: string; manifest: Manifest } {
try {
const manifestValue = yaml.load(fs.readFileSync(manifestPath).toString()) as Manifest;

if (!manifestValue.name) {
return { error: `A Squid ${chalk.bold('name')} must be specified in the manifest` };
} else if (manifestValue.version < 1) {
return { error: `A Squid ${chalk.bold('version')} must be greater than 0` };
} else if (!manifestValue.version) {
return { error: `A Squid ${chalk.bold('version')} must be specified in the manifest` };
}
const { squidDir, manifest } = loadManifestFile(localPath, manifestPath);

const buildDir = path.join(squidDir, 'builds');
fs.mkdirSync(buildDir, { recursive: true, mode: 0o777 });

return {
squidDir,
buildDir,
manifestValue,
manifest,
};
} catch (e: any) {
return { error: `The manifest file on ${manifestPath} can not be parsed: ${e.message}` };
return { error: e.message };
}
}

export default class Deploy extends DeployCommand {
static aliases = ['squid:deploy'];

static description = 'Deploy a new or update an existing squid version';
static args = [
{
Expand Down Expand Up @@ -108,7 +84,7 @@ export default class Deploy extends DeployCommand {
async run(): Promise<void> {
const {
args: { source },
flags: { manifest, 'hard-reset': hardReset, update, 'no-stream-logs': disableStreamLogs, org },
flags: { manifest: manifestPath, 'hard-reset': hardReset, update, 'no-stream-logs': disableStreamLogs, org },
} = await this.parse(Deploy);

const isUrl = source.startsWith('http://') || source.startsWith('https://');
Expand All @@ -117,21 +93,21 @@ export default class Deploy extends DeployCommand {
let organization = org;

if (!isUrl) {
const res = resolveManifest(source, manifest);
const res = resolveManifest(source, manifestPath);
if ('error' in res) return this.error(res.error);

const { buildDir, squidDir, manifestValue } = res;
const { buildDir, squidDir, manifest } = res;

const archiveName = `${manifestValue.name}-v${manifestValue.version}.tar.gz`;
const archiveName = `${manifest.name}-v${manifest.version}.tar.gz`;
const squidArtifact = path.join(buildDir, archiveName);

this.log(chalk.dim(`Squid directory: ${squidDir}`));
this.log(chalk.dim(`Build directory: ${buildDir}`));
this.log(chalk.dim(`Manifest: ${manifest}`));
this.log(chalk.dim(`Manifest: ${manifestPath}`));

const squid = await this.findSquid({ squidName: manifestValue.name });
const squid = await this.findSquid({ squidName: manifest.name });
if (squid) {
const version = squid.versions.find((v) => v.name === `v${manifestValue.version}`);
const version = squid.versions.find((v) => v.name === `v${manifest.version}`);

if (version) {
/**
Expand All @@ -147,7 +123,7 @@ export default class Deploy extends DeployCommand {
{
name: 'confirm',
type: 'confirm',
message: `Version "v${manifestValue.version}" of Squid "${manifestValue.name}" belongs to "${squid.organization?.code}". Update a squid in "${squid.organization?.code}" project?`,
message: `Version "v${manifest.version}" of Squid "${manifest.name}" belongs to "${squid.organization?.code}". Update a squid in "${squid.organization?.code}" project?`,
},
]);
if (!confirm) return;
Expand All @@ -161,7 +137,7 @@ export default class Deploy extends DeployCommand {
{
name: 'confirm',
type: 'confirm',
message: `Version "v${manifestValue.version}" of Squid "${manifestValue.name}" will be updated. Are you sure?`,
message: `Version "v${manifest.version}" of Squid "${manifest.name}" will be updated. Are you sure?`,
},
]);
if (!confirm) return;
Expand Down Expand Up @@ -218,7 +194,7 @@ export default class Deploy extends DeployCommand {
deploy = await deploySquid({
hardReset,
artifactUrl,
manifestPath: manifest,
manifestPath,
organization,
});
} else {
Expand All @@ -228,7 +204,7 @@ export default class Deploy extends DeployCommand {
deploy = await deploySquid({
hardReset,
artifactUrl: source,
manifestPath: manifest,
manifestPath,
organization,
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export default class Init extends CliCommand {

const manifestPath = path.resolve(localDir, 'squid.yaml');
try {
const manifest = readManifest(manifestPath);
const manifest = readManifest(manifestPath, false);

/** Override name in squid manifest **/
manifest.name = name;
Expand Down
135 changes: 135 additions & 0 deletions src/commands/run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { spawn } from 'child_process';
import path from 'path';
import * as readline from 'readline';
import { Writable } from 'stream';

import { Flags } from '@oclif/core';
import chalk from 'chalk';
import dotenv from 'dotenv';

import { CliCommand } from '../command';
import { loadManifestFile } from '../manifest/loadManifestFile';

function runProcess(
{ cwd, output }: { cwd: string; output: Writable },
{ name, cmd, env }: { name: string; cmd: string[]; env: Record<string, string> },
) {
const [command, ...args] = cmd;
const { PROCESSOR_PROMETHEUS_PORT, ...childEnv } = process.env;

const child = spawn(command, args, {
env: {
...childEnv,
...env,
FORCE_PRETTY_LOGGER: 'true',
},
cwd,
});

const prefix = chalk.magenta(`[${name}] `);

readline
.createInterface({
input: child.stderr,
})
.on('line', (line) => {
output.write(`${prefix}${line}\n`);
});
readline
.createInterface({
input: child.stdout,
})
.on('line', (line) => {
output.write(`${prefix}${line}\n`);
});

return child;
}

function isSkipped({ include, exclude }: { include?: string[]; exclude?: string[] }, haystack: string) {
if (exclude?.length && exclude.includes(haystack)) return true;
else if (include?.length && !include.includes(haystack)) return true;

return false;
}

export default class Run extends CliCommand {
static description = 'Run a squid';

static flags = {
manifest: Flags.string({
char: 'm',
description: 'Relative path to a squid manifest file in squid source',
required: false,
default: 'squid.yaml',
}),
envFile: Flags.string({
char: 'f',
description: 'Relative path to an additional environment file in squid source',
required: false,
default: '.env',
}),
exclude: Flags.string({
char: 'e',
description: 'Do not run specified services',
required: false,
multiple: true,
}),
include: Flags.string({
char: 'i',
description: 'Run only specified services',
required: false,
multiple: true,
exclusive: ['exclude'],
}),
};

static args = [
{
name: 'path',
required: true,
hidden: true,
default: '.',
},
];

async run(): Promise<void> {
const {
flags: { manifest: manifestPath, envFile, exclude, include },
args: { path: squidPath },
} = await this.parse(Run);

try {
const { squidDir, manifest } = loadManifestFile(squidPath, manifestPath);
const runner = { cwd: squidDir, output: process.stdout };

if (envFile) {
const { error } = dotenv.config({
path: path.join(squidDir, '/', envFile),
});
if (error) {
this.error(error);
}
}

if (manifest.deploy?.api && !isSkipped({ include, exclude }, 'api')) {
runProcess(runner, {
name: 'api',
...manifest.deploy.api,
});
}

if (manifest.deploy?.processor) {
for (const processor of manifest.deploy?.processor) {
if (isSkipped({ include, exclude }, processor.name)) {
continue;
}

runProcess(runner, processor);
}
}
} catch (e: any) {
this.error(e.message);
}
}
}
42 changes: 42 additions & 0 deletions src/manifest/loadManifestFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import fs from 'fs';
import path from 'path';

import chalk from 'chalk';
import yaml from 'js-yaml';

import { Manifest } from './manifest';

export function loadManifestFile(localPath: string, manifestPath: string): { squidDir: string; manifest: Manifest } {
const squidDir = path.resolve(localPath);

if (!fs.statSync(squidDir).isDirectory()) {
throw new Error(`The path ${squidDir} is a not a squid directory. Please provide a path to a squid root directory`);
}

const manifestFullPath = path.resolve(path.join(localPath, manifestPath));
if (fs.statSync(manifestFullPath).isDirectory()) {
throw new Error(
`The path ${manifestFullPath} is a directory, not a manifest file. Please provide a path to a valid manifest file inside squid directory`,
);
}

let manifestValue;
try {
manifestValue = yaml.load(fs.readFileSync(manifestFullPath).toString()) as Manifest;
} catch (e: any) {
throw new Error(`The manifest file on ${manifestFullPath} can not be parsed: ${e.message}`);
}

if (!manifestValue.name) {
throw new Error(`A Squid ${chalk.bold('name')} must be specified in the manifest`);
} else if (manifestValue.version < 1) {
throw new Error(`A Squid ${chalk.bold('version')} must be greater than 0`);
} else if (!manifestValue.version) {
throw new Error(`A Squid ${chalk.bold('version')} must be specified in the manifest`);
}

return {
squidDir,
manifest: manifestValue,
};
}
43 changes: 37 additions & 6 deletions src/manifest/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,56 @@
import fs from 'fs';

import yaml from 'js-yaml';
import { isPlainObject } from 'lodash';

export type Manifest = {
type ManifestApi = {
cmd: string[];
env: Record<string, string>;
};

type ManifestProcessor = {
name: string;
cmd: string[];
env: Record<string, string>;
};

export interface RawManifest {
name: string;
version: number;
build: null;
};
deploy?: {
processor?: ManifestProcessor | ManifestProcessor[];
api?: ManifestApi;
};
}

export interface Manifest extends RawManifest {
deploy?: {
api?: ManifestApi;
processor?: ManifestProcessor[];
};
}

export function readManifest(path: string, transform = true) {
const manifest = yaml.load(fs.readFileSync(path).toString()) as RawManifest;

if (transform) {
if (manifest.deploy?.processor && isPlainObject(manifest.deploy.processor)) {
manifest.deploy.processor = [manifest.deploy.processor as ManifestProcessor];
}
}

export function readManifest(path: string) {
return yaml.load(fs.readFileSync(path).toString()) as Manifest;
return manifest;
}

export function formatManifest(manifest: Manifest): string {
export function formatManifest(manifest: RawManifest): string {
return yaml.dump(manifest, {
styles: {
'tag:yaml.org,2002:null': 'empty',
},
});
}

export function saveManifest(path: string, manifest: Manifest) {
export function saveManifest(path: string, manifest: RawManifest) {
fs.writeFileSync(path, formatManifest(manifest));
}

0 comments on commit 6c89e8c

Please sign in to comment.