Skip to content

Commit

Permalink
Merge pull request #1529 from polywrap/kris/create-template-from-url
Browse files Browse the repository at this point in the history
`polywrap create template` command
  • Loading branch information
krisbitney authored Mar 2, 2023
2 parents 8b38653 + 4e83149 commit dc85b44
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 184 deletions.
14 changes: 8 additions & 6 deletions packages/cli/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,24 @@
"commands_create_error_outputDirMissingPath": "{option} option missing {argument} argument",
"commands_create_error_unrecognizedCommand": "Unrecognized command",
"commands_create_error_unrecognizedLanguage": "Unrecognized language",
"commands_create_error_badUrl": "URL {url} uses an invalid format. Valid URL formats: {formats}",
"commands_create_options_command": "command",
"commands_create_options_commands": "Commands",
"commands_create_options_createApp": "Create a Polywrap application",
"commands_create_options_createPlugin": "Create a Polywrap plugin",
"commands_create_options_createProject": "Create a Polywrap wasm wrapper",
"commands_create_options_createApp": "Create a Polywrap application.",
"commands_create_options_createPlugin": "Create a Polywrap plugin.",
"commands_create_options_createProject": "Create a Polywrap wasm wrapper.",
"commands_create_options_h": "Show usage information",
"commands_create_options_t": "Download template from a URL.",
"commands_create_options_t_url": "URL",
"commands_create_options_formats": "formats",
"commands_create_options_lang": "lang",
"commands_create_options_langs": "langs",
"commands_create_options_o": "Output directory for the new project",
"commands_create_options_o_path": "path",
"commands_create_options_projectName": "project-name",
"commands_create_overwritePrompt": "Do you want to overwrite this directory?",
"commands_create_overwriting": "Overwriting {dir}...",
"commands_create_readyApp": "You are ready to build an app using Polywrap",
"commands_create_readyPlugin": "You are ready to build a plugin into a Polywrap",
"commands_create_readyProtocol": "You are ready to turn your protocol into a Polywrap",
"commands_create_ready": "You are ready to build with Polywrap",
"commands_create_settingUp": "Setting everything up...",
"commands_test_options_workflow": "workflow",
"commands_test_options_workflowScript": "Path to workflow script",
Expand Down
8 changes: 5 additions & 3 deletions packages/cli/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,24 @@
"commands_create_error_outputDirMissingPath": "{option} option missing {argument} argument",
"commands_create_error_unrecognizedCommand": "Unrecognized command",
"commands_create_error_unrecognizedLanguage": "Unrecognized language",
"commands_create_error_badUrl": "URL {url} uses an invalid format. Valid URL formats: {formats}",
"commands_create_options_command": "command",
"commands_create_options_commands": "Commands",
"commands_create_options_createApp": "Create a Polywrap application",
"commands_create_options_createPlugin": "Create a Polywrap plugin",
"commands_create_options_createProject": "Create a Polywrap wasm wrapper",
"commands_create_options_h": "Show usage information",
"commands_create_options_t": "Download template from a .git URL",
"commands_create_options_t_url": "URL",
"commands_create_options_formats": "formats",
"commands_create_options_lang": "lang",
"commands_create_options_langs": "langs",
"commands_create_options_o": "Output directory for the new project",
"commands_create_options_o_path": "path",
"commands_create_options_projectName": "project-name",
"commands_create_overwritePrompt": "Do you want to overwrite this directory?",
"commands_create_overwriting": "Overwriting {dir}...",
"commands_create_readyApp": "You are ready to build an app using Polywrap",
"commands_create_readyPlugin": "You are ready to build a plugin into a Polywrap",
"commands_create_readyProtocol": "You are ready to turn your protocol into a Polywrap",
"commands_create_ready": "You are ready to build with Polywrap",
"commands_create_settingUp": "Setting everything up...",
"commands_test_options_workflow": "workflow",
"commands_test_options_workflowScript": "Path to workflow script",
Expand Down
96 changes: 92 additions & 4 deletions packages/cli/src/__tests__/e2e/create.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { clearStyle, polywrapCli } from "./utils";
import { runCLI } from "@polywrap/test-env-js";
import rimraf from "rimraf";
import { ProjectType, supportedLangs } from "../../commands";
import { UrlFormat } from "../../lib";

const HELP = `Usage: polywrap create|c [options] [command]
Expand All @@ -12,15 +13,24 @@ Options:
-h, --help display help for command
Commands:
wasm [options] <language> <name> Create a Polywrap wasm wrapper langs:
wasm [options] <language> <name> Create a Polywrap wasm wrapper. langs:
assemblyscript, rust, interface
app [options] <language> <name> Create a Polywrap application langs:
app [options] <language> <name> Create a Polywrap application. langs:
typescript
plugin [options] <language> <name> Create a Polywrap plugin langs:
plugin [options] <language> <name> Create a Polywrap plugin. langs:
typescript
template [options] <url> <name> Download template from a URL. formats:
.git
help [command] display help for command
`;

const urlExamples = (format: UrlFormat): string => {
if (format === UrlFormat.git) {
return "https://github.com/polywrap/logging.git";
}
throw Error("This should never happen");
}

describe("e2e tests for create command", () => {
it("Should show help text", async () => {
const { exitCode: code, stdout: output, stderr: error } = await runCLI({
Expand Down Expand Up @@ -64,7 +74,7 @@ describe("e2e tests for create command", () => {
});

expect(code).toEqual(1);
expect(error).toContain("error: missing required argument 'language");
expect(error).toContain("error: missing required argument 'language'");
expect(output).toBe("");
});

Expand Down Expand Up @@ -134,4 +144,82 @@ describe("e2e tests for create command", () => {
}
});
}

describe("template", () => {
it("Should throw error for missing required argument - url", async () => {
const { exitCode: code, stdout: output, stderr: error } = await runCLI({
args: ["create", "template"],
cli: polywrapCli,
});

expect(code).toEqual(1);
expect(error).toContain("error: missing required argument 'url'");
expect(output).toBe("");
});

it("Should throw error for missing required argument - name", async () => {
const { exitCode: code, stdout: output, stderr: error } = await runCLI({
args: ["create", "template", "lang"],
cli: polywrapCli,
});

expect(code).toEqual(1);
expect(error).toContain("error: missing required argument 'name'");
expect(output).toBe("");
});

it("Should throw error for invalid url parameter", async () => {
const { exitCode: code, stdout: output, stderr: error } = await runCLI({
args: ["create", "template", "lang", "demo"],
cli: polywrapCli,
});

expect(code).toEqual(1);
expect(error).toContain(`URL 'lang' uses an invalid format. Valid URL formats: ${Object.values(UrlFormat).join(", ")}`);
expect(output).toBe("");
});

for (const format of Object.values(UrlFormat)) {
const url = urlExamples(format);

describe(format, () => {
it("Should throw error for missing path argument for --output-dir option", async () => {
const { exitCode: code, stdout: output, stderr: error } = await runCLI({
args: ["create", "template", url, "name", "-o"],
cli: polywrapCli,
});

expect(code).toEqual(1);
expect(error).toContain(
"error: option '-o, --output-dir <path>' argument missing"
);
expect(output).toBe("");
});

it("Should successfully generate project", async () => {
rimraf.sync(`${__dirname}/test`);

const { exitCode: code, stdout: output } = await runCLI({
args: [
"create",
"template",
url,
"test",
"-o",
`${__dirname}/test`,
],
cwd: __dirname,
cli: polywrapCli,
});

expect(code).toEqual(0);
expect(clearStyle(output)).toContain(
"🔥 You are ready "
);

rimraf.sync(`${__dirname}/test`);
}, 60000);
})
}
});
});
74 changes: 62 additions & 12 deletions packages/cli/src/commands/create.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Command, Program, BaseCommandOptions } from "./types";
import { createLogger } from "./utils/createLogger";
import { generateProjectTemplate, intlMsg, parseLogFileOption } from "../lib";
import {
downloadProjectTemplate,
generateProjectTemplate,
intlMsg,
parseLogFileOption,
parseUrlFormat,
UrlFormat,
} from "../lib";

import fse from "fs-extra";
import path from "path";
Expand All @@ -11,10 +18,13 @@ import { Argument } from "commander";
const nameStr = intlMsg.commands_create_options_projectName();
const langStr = intlMsg.commands_create_options_lang();
const langsStr = intlMsg.commands_create_options_langs();
const formatsStr = intlMsg.commands_create_options_formats();
const createProjStr = intlMsg.commands_create_options_createProject();
const createAppStr = intlMsg.commands_create_options_createApp();
const createPluginStr = intlMsg.commands_create_options_createPlugin();
const createTemplateStr = intlMsg.commands_create_options_t();
const pathStr = intlMsg.commands_create_options_o_path();
const urlStr = intlMsg.commands_create_options_t_url();

export const supportedLangs = {
wasm: ["assemblyscript", "rust", "interface"] as const,
Expand Down Expand Up @@ -137,18 +147,57 @@ export const create: Command = {
});
}
);

createCommand
.command("template")
.description(
`${createTemplateStr} ${formatsStr}: ${Object.values(UrlFormat).join(
", "
)}`
)
.addArgument(new Argument("<url>", urlStr).argRequired())
.addArgument(new Argument("<name>", nameStr))
.option(
`-o, --output-dir <${pathStr}>`,
`${intlMsg.commands_create_options_o()}`
)
.option("-v, --verbose", intlMsg.commands_common_options_verbose())
.option("-q, --quiet", intlMsg.commands_common_options_quiet())
.option(
`-l, --log-file [${pathStr}]`,
`${intlMsg.commands_build_options_l()}`
)
.action(async (url, name, options: Partial<CreateCommandOptions>) => {
await run("template", url, name, {
outputDir: options.outputDir || false,
verbose: options.verbose || false,
quiet: options.quiet || false,
logFile: parseLogFileOption(options.logFile),
});
});
},
};

async function run(
command: ProjectType,
language: SupportedLangs,
command: ProjectType | "template",
languageOrUrl: SupportedLangs | string,
name: string,
options: Required<CreateCommandOptions>
) {
const { outputDir, verbose, quiet, logFile } = options;
const logger = createLogger({ verbose, quiet, logFile });

// if using custom template, check url validity before creating project dir
let urlFormat: UrlFormat | undefined;
if (command === "template") {
try {
urlFormat = parseUrlFormat(languageOrUrl);
} catch (e) {
logger.error(e.message);
process.exit(1);
}
}

const projectDir = path.resolve(outputDir ? `${outputDir}/${name}` : name);

// check if project already exists
Expand All @@ -175,16 +224,17 @@ async function run(
}

try {
await generateProjectTemplate(command, language, projectDir);
let readyMessage;
if (command === "wasm") {
readyMessage = intlMsg.commands_create_readyProtocol();
} else if (command === "app") {
readyMessage = intlMsg.commands_create_readyApp();
} else if (command === "plugin") {
readyMessage = intlMsg.commands_create_readyPlugin();
if (command === "template") {
await downloadProjectTemplate(
languageOrUrl,
projectDir,
logger,
urlFormat
);
} else {
await generateProjectTemplate(command, languageOrUrl, projectDir);
}
logger.info(`🔥 ${readyMessage} 🔥`);
logger.info(`🔥 ${intlMsg.commands_create_ready()} 🔥`);
process.exit(0);
} catch (err) {
const commandFailError = intlMsg.commands_create_error_commandFail({
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export interface CommandTypings {
options: CreateCommandOptions;
arguments: [language: SupportedWasmLangs, name: string];
};
template: {
options: CreateCommandOptions;
arguments: [url: string, name: string];
};
};
deploy: DeployCommandOptions;
docgen: {
Expand Down
29 changes: 29 additions & 0 deletions packages/cli/src/lib/CacheDirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import path from "path";
import rimraf from "rimraf";
import copyfiles from "copyfiles";
import { writeFileSync } from "@polywrap/os-js";
import fse from "fs-extra";

export const globalCacheRoot: string =
process.platform == "darwin"
? process.env.HOME + "/Library/Preferences/polywrap/cache"
: process.env.HOME + "/.local/share/polywrap/cache";

export interface CacheDirectoryConfig {
rootDir: string;
Expand All @@ -23,6 +29,14 @@ export class CacheDirectory {
);
}

public initCache(): this {
const cacheDir = this.getCacheDir();
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
return this;
}

public resetCache(): void {
rimraf.sync(this.getCacheDir());
}
Expand Down Expand Up @@ -83,4 +97,19 @@ export class CacheDirectory {
});
});
}

public async copyFromCache(
sourceSubDir: string,
destDir: string
): Promise<void> {
const source = this.getCachePath(sourceSubDir);

if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}

await fse.copy(source, destDir, {
overwrite: true,
});
}
}
Loading

0 comments on commit dc85b44

Please sign in to comment.