src/saofile.ts
import {
concatExtend,
extendBase,
getPluginsArray,
get_potential_package_managers,
GitHelper,
handleIgnore,
mergeBabel,
mergeJSONFiles,
mergePackages,
mergePluginData,
tips,
} from "@Helper";
import { ProjectPrompt } from "@Helper/lucky";
import { formatFiles } from "@Helper/prettier";
import chalk from "chalk";
import { exec } from "child_process";
import { prompt } from "enquirer";
import fetch from "node-fetch";
import path from "path";
import { promisify } from "util";
import { v4 as uuidv4 } from "uuid";
import validate from "validate-npm-package-name";
import { Action, GeneratorConfig } from "../@types/sao";
const saoConfig: GeneratorConfig = {
prompts(sao) {
const { apiMode } = sao.opts.extras;
if (apiMode) return [];
const {
appName,
extras: { paths, presetAnswers },
} = sao.opts;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const sourcePrompts = require(path.resolve(
paths.sourcePath,
"prompt.js",
));
const packageManagerChoices = get_potential_package_managers();
return [
{
type: "input",
name: "name",
message: "What would you like to name your project?:",
default: appName,
validate: (name: string) => {
const appNameValidation = validate(name);
if (appNameValidation.errors) {
return false;
}
return true;
},
},
...(sourcePrompts?.prompts ?? []).map((el: ProjectPrompt) => ({
...el,
default: presetAnswers?.[el.name] ?? el.default,
})),
{
type: "select",
name: "npmClient",
message: "Choose a package manager:",
choices: packageManagerChoices,
default:
packageManagerChoices.length === 1
? packageManagerChoices[0].name
: undefined,
skip: () => packageManagerChoices.length === 1,
},
];
},
data(sao) {
/**
* Package Manager
*/
const answers = {
...sao.opts.extras.presetAnswers,
...sao.answers,
};
const { npmClient } = answers;
let pmRun = "npm run";
if (npmClient === "yarn") {
pmRun = "yarn";
} else if (npmClient === "pnpm") {
pmRun = "pnpm";
}
/**
* Extend.js data
*/
const { sourcePath } = sao.opts.extras.paths;
const { projectType } = sao.opts.extras;
const pluginAnswers = { ...answers };
delete pluginAnswers.name;
delete pluginAnswers.svg;
delete pluginAnswers.title;
delete pluginAnswers.icon;
const selectedPlugins = getPluginsArray(pluginAnswers);
const extendData = concatExtend(
extendBase,
["_base", ...selectedPlugins],
sourcePath,
answers,
);
/**
* Plugins meta data
*/
const pluginsData = mergePluginData(
{},
sourcePath,
selectedPlugins,
"meta.json",
).plugins;
const metaJSONPath =
projectType === "react" ? "src/meta.json" : "public/meta.json";
/**
* Return
*/
return {
...answers,
projectType,
answers,
selectedPlugins,
pm: npmClient,
pmRun,
pluginsData,
metaJSONPath,
...extendData,
};
},
async actions(sao) {
const answers = {
...sao.opts.extras.presetAnswers,
...sao.answers,
};
if (answers.name.length === 0) {
const error = sao.createError("App name is required!");
throw error;
}
const appName = answers.name;
const appNameValidation = validate(appName);
if (appNameValidation.warnings) {
appNameValidation.warnings.forEach((warn) =>
this.logger.warn(warn),
);
}
if (appNameValidation.errors) {
appNameValidation.errors.forEach((warn) => this.logger.error(warn));
process.exit(1);
}
const emailPromptResult: { userEmail: string } = await prompt({
type: "input",
name: "userEmail",
message:
"Mind sharing your email? (We reach out to developers for free priority support, events, and SWAG kits. We never spam.)",
required: false,
skip: () =>
sao.opts.extras.apiMode ||
!sao.opts.extras.projectType?.includes?.("refine"),
});
let projectId = "";
try {
const createProjectResponse = await fetch(
"https://telemetry.refine.dev/projects",
{
method: "POST",
body: JSON.stringify({
email: emailPromptResult.userEmail || answers.userEmail,
answers: {
...answers,
projectType: sao.opts.extras.projectType,
},
source:
sao.opts.extras.apiMode === false
? "CREATE_REFINE_APP"
: "REFINE_NEW",
}),
headers: { "Content-Type": "application/json" },
},
);
const createProjectResponseData =
await createProjectResponse.json();
projectId = createProjectResponseData.projectId;
} catch (_e) {
//
}
sao.opts.outDir = sao.opts.outDir + "/" + appName;
sao.opts.appName = appName;
const { sourcePath } = sao.opts.extras.paths;
const actionsArray: Action[] = [
{
type: "add",
files: "**",
templateDir: path.join(sourcePath, "template"),
data() {
return { ...sao.data, projectId };
},
},
{
type: "move",
templateDir: path.join(sourcePath, "template"),
patterns: {
gitignore: ".gitignore",
"_package.json": "package.json",
"_next-env.d.ts": "next-env.d.ts",
"_tsconfig.json": "tsconfig.json",
babelrc: ".babelrc",
},
data() {
return sao.data;
},
},
] as Action[];
const pluginAnswers = {
...sao.opts.extras.presetAnswers,
...sao.answers,
};
delete pluginAnswers.name;
delete pluginAnswers.svg;
delete pluginAnswers.title;
delete pluginAnswers.icon;
const selectedPlugins = getPluginsArray(pluginAnswers);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const sourcePrompts = require(path.resolve(sourcePath, "prompt.js"));
actionsArray.push(
...["_base", ...selectedPlugins].map((plugin: string) => {
const customFilters = handleIgnore(
sourcePrompts?.ignores ?? [],
{
...sao.opts.extras.presetAnswers,
...sao.answers,
},
plugin,
);
return {
type: "add" as const,
files: "**",
templateDir: path.join(sourcePath, "plugins", plugin),
filters: {
"extend.js": false,
"package.json": false,
"package.js": false,
"tsconfig.json": false,
".babelrc": false,
"meta.json": false,
...customFilters,
},
data() {
return sao.data;
},
};
}),
);
/**
* eslintrc handler
*/
actionsArray.push({
type: "move" as const,
patterns: {
"_.eslintrc": ".eslintrc",
},
data() {
return sao.data;
},
} as Action);
/**
* meta.json handler
*/
actionsArray.push({
type: "modify" as const,
files: sao.data.metaJSONPath,
handler(data: Record<string, unknown>) {
return mergePluginData(
data,
sourcePath,
selectedPlugins,
"meta.json",
);
},
});
/**
* package.json handler
*/
actionsArray.push({
type: "modify" as const,
files: "package.json",
handler(data: Record<string, unknown>): Record<string, unknown> {
return mergePackages(
data,
sourcePath,
selectedPlugins,
sao.answers,
);
},
});
/**
* tsconfig.json handler
*/
actionsArray.push({
type: "modify" as const,
files: "tsconfig.json",
handler(data: Record<string, unknown>) {
return mergeJSONFiles(
data,
sourcePath,
selectedPlugins,
"tsconfig.json",
);
},
});
/**
* .babelrc handler
*/
actionsArray.push({
type: "modify" as const,
files: ".babelrc",
async handler(data: string) {
const merged = await mergeBabel(
JSON.parse(data),
sourcePath,
selectedPlugins,
);
return JSON.stringify(merged);
},
});
if (!sao.opts.extras.apiMode && !sao.opts.extras.disableTelemetry) {
try {
await fetch("https://telemetry.refine.dev/superplate", {
method: "POST",
body: JSON.stringify({
event: "generate",
properties: {
...sao.answers,
type: sao.opts.extras.projectType,
},
anonymousId: uuidv4(),
originalTimestamp: new Date(),
}),
headers: { "Content-Type": "application/json" },
});
} catch (error) {
//
}
}
return actionsArray;
},
async prepare() {
tips.preInstall();
},
async completed(saoInstance) {
const { debug, apiMode } = saoInstance.opts.extras;
/**
* Format generated project
*/
formatFiles(saoInstance.outDir);
if (apiMode) return;
const { npmClient } = saoInstance.answers;
/**
* Git init and install packages
*/
if (!debug) {
const canGitInit = await GitHelper.CanGitInit(saoInstance.outDir);
if (canGitInit) {
saoInstance.gitInit();
}
await saoInstance.npmInstall({
npmClient: npmClient,
installArgs: ["--silent"],
});
}
/**
* Create an initial commit
*/
if (!debug) {
try {
// add
await promisify(exec)(
`git --git-dir="${saoInstance.outDir}"/.git/ --work-tree="${saoInstance.outDir}"/ add -A`,
);
// commit
const message =
saoInstance.opts.extras.commitMessage ??
"initial commit with superplate";
await promisify(exec)(
`git --git-dir="${saoInstance.outDir}"/.git/ --work-tree="${saoInstance.outDir}"/ commit -m "${message}"`,
);
saoInstance.logger.info("created an initial commit.");
} catch (_) {
console.log(
chalk.yellow`An error occured while creating git commit.`,
);
}
}
/**
* Show messages after completion
*/
tips.postInstall({
name: saoInstance.opts.appName ?? "",
dir: saoInstance.outDir,
pm: npmClient,
});
},
};
module.exports = saoConfig;