@chakra-ui/cli
Version:
Generate theme typings for autocomplete
186 lines (183 loc) • 6.66 kB
JavaScript
;
import * as p from '@clack/prompts';
import { boxen } from '@visulima/boxen';
import { Command } from 'commander';
import createDebug from 'debug';
import { existsSync } from 'fs';
import { writeFile } from 'fs/promises';
import { join } from 'node:path/posix';
import { getProjectContext } from '../utils/context.js';
import { convertTsxToJsx } from '../utils/convert-tsx-to-jsx.js';
import { fetchCompositions, fetchComposition } from '../utils/fetch.js';
import { getFileDependencies, findCompositionById } from '../utils/get-file-dependencies.js';
import { ensureDir } from '../utils/io.js';
import { installCommand } from '../utils/run-command.js';
import { addCommandFlagsSchema } from '../utils/schema.js';
import { uniq } from '../utils/shared.js';
import { tasks } from '../utils/tasks.js';
const debug = createDebug("chakra:snippet");
const SnippetCommand = new Command("snippet").description("Add snippets to your project for better DX").addCommand(
new Command("add").description("Add a new snippet for better DX").argument("[snippets...]", "snippets to add").option("-d, --dry-run", "Dry run").option("--outdir <dir>", "Output directory to write the snippets").option("--all", "Add all snippets").option("-f, --force", "Overwrite existing files").option("--tsx", "Convert to TSX").action(async (selectedComponents, flags) => {
const parsedFlags = addCommandFlagsSchema.parse(flags);
const { dryRun, force, all, tsx } = parsedFlags;
const ctx = await getProjectContext({
cwd: parsedFlags.outdir || process.cwd(),
tsx
});
debug("context", ctx);
const jsx = !ctx.isTypeScript;
const outdir = parsedFlags.outdir || ctx.scope.componentsDir;
ensureDir(outdir);
const items = await fetchCompositions();
const inferredComponents = getComponents({
components: selectedComponents,
all,
items
});
const components = inferredComponents.items;
debug("components", components);
p.log.info(inferredComponents.message);
const deps = uniq(
components.flatMap((id) => getFileDependencies(items, id))
);
const fileDependencies = uniq(
deps.map((dep) => dep.fileDependencies).flat()
);
const npmDependencies = uniq(
deps.map((dep) => dep.npmDependencies).flat()
);
debug("fileDependencies", fileDependencies);
debug("npmDependencies", npmDependencies);
let skippedFiles = [];
await tasks([
{
title: `Installing required dependencies...`,
enabled: !!npmDependencies.length && !dryRun,
task: () => installCommand([...npmDependencies, "--silent"], outdir)
},
{
title: "Writing file dependencies",
enabled: !!fileDependencies.length && !dryRun,
task: async () => {
await Promise.all(
fileDependencies.map(async (dep) => {
if (existsSync(join(outdir, dep)) && !force) {
skippedFiles.push(dep);
return;
}
const item = await fetchComposition(dep);
if (jsx) {
item.file.name = item.file.name.replace(".tsx", ".jsx");
await transformToJsx(item);
}
const outPath = join(outdir, item.file.name);
await writeFile(
outPath,
item.file.content.replace("compositions/ui", "."),
"utf-8"
);
})
);
}
},
{
title: "Writing selected snippets",
task: async () => {
await Promise.all(
components.map(async (id) => {
let filename = findCompositionById(items, id)?.file ?? id + ".tsx";
if (jsx) {
filename = filename.replace(".tsx", ".jsx");
}
if (existsSync(join(outdir, filename)) && !force) {
skippedFiles.push(id);
return;
}
try {
const item = await fetchComposition(id);
if (jsx) {
item.file.name = item.file.name.replace(".tsx", ".jsx");
await transformToJsx(item);
}
const outPath = join(outdir, item.file.name);
if (dryRun) {
printFileSync(item);
} else {
await writeFile(
outPath,
item.file.content.replace("compositions/ui", "."),
"utf-8"
);
}
} catch (error) {
if (error instanceof Error) {
p.log.error(error?.message);
process.exit(0);
}
}
})
);
}
}
]);
if (skippedFiles.length) {
p.log.warn(
`Skipping ${skippedFiles.length} file(s) that already exist. Use the --force flag to overwrite.`
);
}
p.outro("\u{1F389} Done!");
})
).addCommand(
new Command("list").description("List available snippets").action(async () => {
const { default: Table } = await import('cli-table');
const table = new Table({
head: ["name", "dependencies"],
colWidths: [20, 30],
style: { compact: true }
});
const items = await fetchCompositions();
if (!Array.isArray(items)) {
throw new Error("[chakra] invalid json response");
}
items.forEach((item) => {
const deps = item.npmDependencies;
const depsString = deps.length ? deps.join(", ") : "-";
table.push([item.id, depsString]);
});
p.log.info(`Found ${items.length} snippets`);
p.log.info(table.toString());
p.outro("\u{1F389} Done!");
})
);
async function transformToJsx(item) {
const content = await convertTsxToJsx(item.file.content);
item.file.content = content;
item.file.name = item.file.name.replace(".tsx", ".jsx");
}
function printFileSync(item) {
const boxText = boxen(item.file.content, {
headerText: `${item.file.name}
`,
borderStyle: "none"
});
p.log.info(boxText);
}
const RECOMMENDED_SNIPPETS = ["provider", "toaster", "tooltip"];
function getComponents(opts) {
const { components, all, items } = opts;
if (components.length === 0 && !all)
return {
message: "No component(s) selected, adding recommended snippets...",
items: RECOMMENDED_SNIPPETS
};
if (all)
return {
message: "Adding all snippets...",
items: items.map((item) => item.id)
};
return {
message: `Adding ${components.length} snippet(s)...`,
items: components
};
}
export { SnippetCommand };