mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 04:55:06 +09:00
Add guided flow using prompts for cli (#2252)
## Description This PR adds prompts, when the cli is ran using `webstudio` and without any commands. It requests the users if they want to create a folder. If yes, it will create a folder and changes the directory to the newly created ones. This PR even allows `webstudio link` command to be executed without prompts using ```sh webstudio link -l <link> ``` ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - test it on preview - [ ] hi @TrySound , I need you to do - conceptual review (architecture, feature-correctness) ## Before requesting a review - [x] made a self-review
This commit is contained in:
1 parent
87e69b2e85
commit
81781ee199
7 files changed
+112
-10
No files matched your search
@@ -34,11 +34,13 @@
|
||||
"env-paths": "^3.0.0",
|
||||
"ora": "^7.0.1",
|
||||
"p-limit": "^4.0.0",
|
||||
"prompts": "^2.4.2",
|
||||
"strip-indent": "^4.0.0",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.13.0",
|
||||
"@types/prompts": "^2.4.4",
|
||||
"@webstudio-is/form-handlers": "workspace:^",
|
||||
"@webstudio-is/scripts": "workspace:^",
|
||||
"@webstudio-is/tsconfig": "workspace:^",
|
||||
|
||||
+11
-5
@@ -3,9 +3,10 @@ import { exit, argv } from "node:process";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { GLOBAL_CONFIG_FILE } from "./config";
|
||||
import { ensureFileInPath } from "./fs-utils";
|
||||
import { link } from "./commands/link";
|
||||
import { link, linkOptions } from "./commands/link";
|
||||
import { sync } from "./commands/sync";
|
||||
import { build, buildOptions } from "./commands/build";
|
||||
import { initFlow } from "./commands/init-flow";
|
||||
import makeCLI from "yargs";
|
||||
import packageJson from "../package.json" assert { type: "json" };
|
||||
import type { CommonYargsArgv } from "./commands/yargs-types";
|
||||
@@ -34,18 +35,23 @@ export const main = async () => {
|
||||
type: "boolean",
|
||||
})
|
||||
.option("h", {
|
||||
describe: "Show version number",
|
||||
describe: "Show all commands",
|
||||
alias: "help",
|
||||
type: "boolean",
|
||||
})
|
||||
.scriptName("webstudio")
|
||||
.demandCommand(1, "Command is required");
|
||||
.scriptName("webstudio");
|
||||
|
||||
cmd.version(packageJson.version).alias("v", "version");
|
||||
|
||||
cmd.command(["build"], "build the project", buildOptions, build);
|
||||
cmd.command(["link"], "link the project from your workspace", {}, link);
|
||||
cmd.command(
|
||||
["link"],
|
||||
"link the project from your workspace",
|
||||
linkOptions,
|
||||
link
|
||||
);
|
||||
cmd.command(["sync"], "sync your project", {}, sync);
|
||||
cmd.command("$0", "setup the project", {}, initFlow);
|
||||
|
||||
await cmd.parse();
|
||||
} catch (error) {
|
||||
|
||||
@@ -15,7 +15,9 @@ export const buildOptions = (yargs: CommonYargsArgv) =>
|
||||
|
||||
// @todo: use options.assets to define if we need to download assets
|
||||
export const build = async (
|
||||
options: StrictYargsOptionsToInterface<typeof buildOptions>
|
||||
options:
|
||||
| StrictYargsOptionsToInterface<typeof buildOptions>
|
||||
| { assets?: boolean }
|
||||
) => {
|
||||
try {
|
||||
await access(LOCAL_DATA_FILE);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import prompts, { type PromptObject } from "prompts";
|
||||
import { ensureFolderExists, isFileExists } from "../fs-utils";
|
||||
import { chdir, cwd } from "node:process";
|
||||
import { join } from "node:path";
|
||||
import { link } from "./link";
|
||||
import { sync } from "./sync";
|
||||
import { build } from "./build";
|
||||
|
||||
export const initFlow = async () => {
|
||||
const isProjectConfigured = await isFileExists(".webstudio/config.json");
|
||||
const prompsList: PromptObject[] = [];
|
||||
|
||||
if (isProjectConfigured === false) {
|
||||
prompsList.push(
|
||||
{
|
||||
type: "confirm",
|
||||
name: "folder",
|
||||
message: "Do you want to create a folder",
|
||||
initial: true,
|
||||
},
|
||||
{
|
||||
type: (prev) => (prev === true ? "text" : null),
|
||||
name: "folderName",
|
||||
message: "Enter a project name",
|
||||
},
|
||||
{
|
||||
type: (prev) => (prev === undefined ? null : "text"),
|
||||
name: "projectLink",
|
||||
message: "Enter a project link",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const response = await prompts(prompsList);
|
||||
|
||||
/*
|
||||
If users wanted to create a folder, we need to create it and link the project.
|
||||
*/
|
||||
if (response.folderName) {
|
||||
await ensureFolderExists(join(cwd(), response.folderName));
|
||||
chdir(join(cwd(), response.folderName));
|
||||
await link({ link: response.projectLink });
|
||||
}
|
||||
|
||||
/*
|
||||
If the project is already set up, we can sync and build it.
|
||||
*/
|
||||
await sync();
|
||||
await build({ assets: true });
|
||||
};
|
||||
@@ -4,10 +4,31 @@ import * as readline from "node:readline/promises";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { GLOBAL_CONFIG_FILE, LOCAL_CONFIG_FILE } from "../config";
|
||||
import { ensureFileInPath } from "../fs-utils";
|
||||
import type {
|
||||
CommonYargsArgv,
|
||||
StrictYargsOptionsToInterface,
|
||||
} from "./yargs-types";
|
||||
|
||||
export const link = async () => {
|
||||
const rl = readline.createInterface({ input: stdin, output: stdout });
|
||||
const shareLink = await rl.question(`Paste share link (with build access): `);
|
||||
export const linkOptions = (yargs: CommonYargsArgv) =>
|
||||
yargs.option("link", {
|
||||
alias: "l",
|
||||
type: "string",
|
||||
describe: "Link to a webstudio project",
|
||||
});
|
||||
|
||||
export const link = async (
|
||||
options: StrictYargsOptionsToInterface<typeof linkOptions> | { link?: string }
|
||||
) => {
|
||||
let shareLink: string;
|
||||
if (options.link) {
|
||||
shareLink = options.link;
|
||||
} else {
|
||||
const rl = readline.createInterface({ input: stdin, output: stdout });
|
||||
shareLink = await rl.question(
|
||||
`Please paste a link from the Share Dialog in the builder: `
|
||||
);
|
||||
rl.close();
|
||||
}
|
||||
|
||||
const shareLinkUrl = new URL(shareLink);
|
||||
const host = shareLinkUrl.origin;
|
||||
@@ -34,7 +55,6 @@ export const link = async () => {
|
||||
};
|
||||
|
||||
await writeFile(GLOBAL_CONFIG_FILE, JSON.stringify(newConfig, null, 2));
|
||||
rl.close();
|
||||
console.info(`Saved credentials for project ${projectId}.
|
||||
You can find your config at ${GLOBAL_CONFIG_FILE}
|
||||
`);
|
||||
|
||||
@@ -9,6 +9,15 @@ import {
|
||||
import { join } from "node:path";
|
||||
import type { Folder } from "./args";
|
||||
|
||||
export const isFileExists = async (filePath: string) => {
|
||||
try {
|
||||
await access(filePath, constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const ensureFileInPath = async (filePath: string, content?: string) => {
|
||||
const dir = dirname(filePath);
|
||||
|
||||
|
||||
Generated
+13
@@ -480,6 +480,9 @@ importers:
|
||||
p-limit:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
strip-indent:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
@@ -490,6 +493,9 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^18.13.0
|
||||
version: 18.13.0
|
||||
'@types/prompts':
|
||||
specifier: ^2.4.4
|
||||
version: 2.4.4
|
||||
'@webstudio-is/form-handlers':
|
||||
specifier: workspace:^
|
||||
version: link:../form-handlers
|
||||
@@ -7579,6 +7585,13 @@ packages:
|
||||
resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==}
|
||||
dev: true
|
||||
|
||||
/@types/prompts@2.4.4:
|
||||
resolution: {integrity: sha512-p5N9uoTH76lLvSAaYSZtBCdEXzpOOufsRjnhjVSrZGXikVGHX9+cc9ERtHRV4hvBKHyZb1bg4K+56Bd2TqUn4A==}
|
||||
dependencies:
|
||||
'@types/node': 18.17.1
|
||||
kleur: 3.0.3
|
||||
dev: true
|
||||
|
||||
/@types/prop-types@15.7.5:
|
||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
||||
|
||||
|
||||
Reference in new issue
Block a user