@ts-defold/create
Version:
@ts-defold scaffolding and cli utility
1,003 lines (976 loc) • 37.3 kB
JavaScript
;
var path = require('path');
var yargs = require('yargs');
var React = require('react');
var reactRouter = require('react-router');
var ink = require('ink');
var Gradient = require('ink-gradient');
var BigText = require('ink-big-text');
var InkMarkdown = require('ink-markdown');
var chalk = require('chalk');
var Spinner = require('ink-spinner');
var fs = require('fs');
var reactFinalForm = require('react-final-form');
var semver = require('semver');
var TextInput = require('ink-text-input');
var crossSpawn = require('cross-spawn');
var fetch = require('node-fetch');
var tmp = require('tmp-promise');
var rimraf = require('rimraf');
var Zip = require('adm-zip');
var simpleGit = require('simple-git');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
var yargs__default = /*#__PURE__*/_interopDefaultLegacy(yargs);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var Gradient__default = /*#__PURE__*/_interopDefaultLegacy(Gradient);
var BigText__default = /*#__PURE__*/_interopDefaultLegacy(BigText);
var InkMarkdown__default = /*#__PURE__*/_interopDefaultLegacy(InkMarkdown);
var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
var Spinner__default = /*#__PURE__*/_interopDefaultLegacy(Spinner);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var semver__default = /*#__PURE__*/_interopDefaultLegacy(semver);
var TextInput__default = /*#__PURE__*/_interopDefaultLegacy(TextInput);
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
var tmp__default = /*#__PURE__*/_interopDefaultLegacy(tmp);
var rimraf__default = /*#__PURE__*/_interopDefaultLegacy(rimraf);
var Zip__default = /*#__PURE__*/_interopDefaultLegacy(Zip);
var simpleGit__default = /*#__PURE__*/_interopDefaultLegacy(simpleGit);
function Markdown({ children, ...props }) {
return /* @__PURE__ */ React__default["default"].createElement(InkMarkdown__default["default"], {
html: chalk__default["default"].cyan,
link: chalk__default["default"].blueBright,
strong: chalk__default["default"].green,
em: chalk__default["default"].yellowBright,
codespan: chalk__default["default"].dim,
...props
}, children);
}
const makeGradient = () => {
const GRADIENT = [
{ h: 0, s: 1, v: 1, a: 1 },
{ h: 90, s: 1, v: 1, a: 1 },
{ h: 180, s: 1, v: 1, a: 1 },
{ h: 270, s: 1, v: 1, a: 1 },
{ h: 360, s: 1, v: 1, a: 1 }
];
const theta = new Date().getSeconds() * 6;
return GRADIENT.map((stop, index) => ({
...stop,
h: (theta + index * 90) % 360
}));
};
function Help({ text }) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
alignItems: "center",
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(Gradient__default["default"], {
colors: makeGradient()
}, /* @__PURE__ */ React__default["default"].createElement(BigText__default["default"], {
text: "TS-DEFOLD",
font: "simple3d",
letterSpacing: 0,
space: false
})), /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column",
paddingTop: 1
}, /* @__PURE__ */ React__default["default"].createElement(Markdown, null, text)));
}
function useProjectApply(dir, config) {
const [info, setInfo] = React.useState({
isLoading: true,
complete: false,
error: ""
});
React.useEffect(() => {
let pending = true;
(async () => {
try {
const packageJson = JSON.parse(await fs__default["default"].promises.readFile(path__default["default"].join(dir, "package.json"), "utf8"));
packageJson.private = true;
packageJson.name = config.name;
packageJson.version = config.version;
packageJson.description = config.description;
packageJson.author = config.author;
delete packageJson.repository;
delete packageJson.license;
const keyOrder = [
"private",
"name",
"version",
"description",
"author"
];
const keys = keyOrder.reduce((acc, key) => ({ ...acc, key }), {});
JSON.stringify(packageJson, (key, value) => {
if (!(key in keys))
keyOrder.push(key);
return value;
});
await fs__default["default"].promises.writeFile(path__default["default"].join(dir, "package.json"), JSON.stringify(packageJson, keyOrder, 2));
if (pending) {
setInfo((i) => ({ ...i, isLoading: false, complete: true }));
}
} catch (e) {
const error = e;
if (pending) {
setInfo((i) => ({
...i,
isLoading: false,
error: error.message
}));
}
}
})();
return () => {
pending = false;
};
}, [dir, config]);
return info;
}
function ProjectApply({
dir,
config,
active,
onCompletion
}) {
const apply = useProjectApply(dir, config);
React.useEffect(() => {
if (active && !apply.isLoading) {
if (apply.error) {
onCompletion == null ? void 0 : onCompletion(false);
} else if (apply.complete) {
onCompletion == null ? void 0 : onCompletion(true);
}
}
}, [apply, active, onCompletion]);
if (apply.isLoading) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "cyanBright"
}, /* @__PURE__ */ React__default["default"].createElement(Spinner__default["default"], {
type: "hamburger"
})), " ", "Applying project configuration"));
} else if (apply.error) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u{10102}"), " Project configuration failed!"), /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, " ", apply.error));
}
return /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, "\u2713"), " Applied project configuration");
}
function FormInput({
onFocus,
onBlur,
...props
}) {
React.useEffect(() => {
onFocus == null ? void 0 : onFocus();
return onBlur == null ? void 0 : onBlur();
}, [onFocus, onBlur]);
return /* @__PURE__ */ React__default["default"].createElement(TextInput__default["default"], {
...props,
showCursor: true
});
}
function FormError({ children }) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, children));
}
function ProjectConfig({
author,
email,
project,
active,
onConfigured,
onCompletion
}) {
const [activeField, setActiveField] = React.useState(0);
const fields = React.useMemo(() => {
return [
{
name: "name",
label: "Project Name",
validate: (value) => {
if (!value) {
return "Required";
}
},
format: (value) => value ? value.toLowerCase().replace(/[^a-z \\-]/g, "").replace(/ /g, "-") : "",
placeholder: project.name || "my-awesome-game"
},
{
name: "version",
label: "Version",
placeholder: "0.1.0",
format: (value) => value === void 0 ? "" : value.replace(/[^0-9.]/g, ""),
validate: (value) => !value ? "Required" : semver__default["default"].valid(value) ? void 0 : "Invalid semantic version"
},
{
name: "description",
label: "Description",
placeholder: `My Awesome ${games[Math.trunc(Math.random() * games.length - 1)]} Game!`,
format: (value) => value === void 0 ? "" : value,
validate: (value) => !value ? "Required" : void 0
},
{
name: "author",
label: "Author",
placeholder: author + (email ? ` <${email}>` : ""),
format: (value) => value === void 0 ? "" : value,
validate: (value) => !value ? "Required" : void 0
}
];
}, [author, email, project]);
const onSubmit = (values) => {
onConfigured == null ? void 0 : onConfigured(values);
if (active)
onCompletion == null ? void 0 : onCompletion(true);
};
return /* @__PURE__ */ React__default["default"].createElement(reactFinalForm.Form, {
onSubmit,
initialValues: {
...fields.reduce((acc, f) => ({ ...acc, [f.name]: "" }), {})
},
render: ({ handleSubmit }) => /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, fields.map(({ name, label, placeholder, format, validate }, index) => /* @__PURE__ */ React__default["default"].createElement(reactFinalForm.Field, {
name,
key: name,
format,
validate
}, ({ input, meta }) => /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Box, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
bold: activeField === index
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: meta.error && !meta.pristine ? "red" : meta.valid ? "green" : "cyanBright"
}, activeField === index ? "\u276F " : meta.touched ? "\u2713 " : " "), `${label}: `), activeField === index ? /* @__PURE__ */ React__default["default"].createElement(FormInput, {
...input,
tabComplete: true,
placeholder,
onSubmit: () => {
if (meta.valid && !meta.validating) {
setActiveField((value) => value + 1);
if (activeField === fields.length - 1) {
handleSubmit();
}
}
}
}) : /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: !meta.touched || !input.value ? "gray" : void 0
}, input.value || placeholder), meta.validating && name === "name" && /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
marginLeft: 1
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "yellow"
}, /* @__PURE__ */ React__default["default"].createElement(Spinner__default["default"], {
type: "dots"
})))), meta.error && meta.touched && !meta.pristine && /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
paddingLeft: 2
}, /* @__PURE__ */ React__default["default"].createElement(FormError, null, meta.error))))))
});
}
const games = [
"RPG",
"SHMUP",
"FPS",
"Platformer",
"Tower Defense",
"Puzzle",
"Racing",
"Action",
"Adventure",
"Strategy",
"Simulation",
"MMORPG",
"Battle Royal"
];
function ProjectDir({
project,
active,
onCompletion
}) {
React.useEffect(() => {
if (active && !project.isLoading) {
if (project.exists || !project.isEmpty) {
onCompletion == null ? void 0 : onCompletion(false);
} else {
onCompletion == null ? void 0 : onCompletion(true);
}
}
}, [project, active, onCompletion]);
if (project.isLoading) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "cyanBright"
}, /* @__PURE__ */ React__default["default"].createElement(Spinner__default["default"], {
type: "hamburger"
})), " ", "Checking project directory"));
} else if (project.exists || !project.isEmpty) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u{10102}"), " Project directory at", " ", /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, project.relativePath || "."), " already exists!"), /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "grey"
}, "Please choose an empty or new directory for your project."));
}
return /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, "\u2713"), " Creating new project in", " ", /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, project.relativePath));
}
function useDependencyInstall(dir) {
const [install, setInstall] = React.useState({
isLoading: true,
complete: false,
progress: "",
errors: []
});
React.useEffect(() => {
var _a;
let output;
const child = crossSpawn.spawn("npm", ["ci", "--no-audit", "--no-progress", "--verbose"], {
cwd: dir,
stdio: "pipe"
});
function process(data) {
var _a2;
output += data.toString();
const lines = output.split("\n");
if (!output.endsWith("\n"))
output = (_a2 = lines.pop()) != null ? _a2 : "";
const module = lines.map((l) => l.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "")).map((line) => {
var _a3, _b;
return (_b = (_a3 = /.*reifyNode:node_modules\/(?<module>[^\s]+)/.exec(line)) == null ? void 0 : _a3.groups) == null ? void 0 : _b.module;
}).filter((l) => l).pop();
if (module) {
setInstall((i) => ({
...i,
progress: module
}));
}
}
(_a = child.stderr) == null ? void 0 : _a.on("data", process);
child.on("close", (code) => {
if (code === 0) {
setInstall((i) => ({
...i,
isLoading: false,
complete: true
}));
} else {
setInstall((i) => ({
...i,
isLoading: false,
complete: false,
errors: [
"Failed to install dependencies",
"run npm install for details"
]
}));
}
});
return () => {
child.kill();
};
}, [dir]);
return install;
}
function ProjectInstall({
dir,
active,
onCompletion
}) {
const install = useDependencyInstall(dir);
React.useEffect(() => {
if (active) {
if (install.complete) {
onCompletion == null ? void 0 : onCompletion(true);
} else if (install.errors.length > 0) {
onCompletion == null ? void 0 : onCompletion(false);
}
}
}, [install, active, onCompletion]);
if (install.isLoading) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "row"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "cyanBright"
}, /* @__PURE__ */ React__default["default"].createElement(Spinner__default["default"], {
type: "hamburger"
})), " ", "Installing dependencies", " ", install.progress && /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "grey"
}, install.progress)));
} else if (install.errors.length > 0) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u{10102}"), " Dependency installation failed!"), /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, " ", install.errors.join("\n ")));
}
return /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, "\u2713"), " Installed dependencies");
}
function ProjectReady({
project,
active,
onCompletion
}) {
React.useEffect(() => {
if (active)
onCompletion == null ? void 0 : onCompletion(true);
}, [active, onCompletion]);
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
paddingTop: 1,
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, "\u{1F680} Your project is ready to go!"), /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
paddingTop: 1,
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, "Next Steps:"), /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
paddingLeft: 2,
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(Markdown, null, `cd *${project}*`), /* @__PURE__ */ React__default["default"].createElement(Markdown, null, "**npm** run dev"), /* @__PURE__ */ React__default["default"].createElement(Markdown, null, "Open *app/game.project* in the Defold editor.")), /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
width: 46,
alignItems: "center",
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
paddingTop: 1,
flexDirection: "row"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, "TypeScript "), /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u2764\uFE0F "), /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, " Defold")))));
}
function TemplateInfo({
template,
active,
onCompletion
}) {
React.useEffect(() => {
if (active && !template.isLoading) {
onCompletion == null ? void 0 : onCompletion(template.found);
}
}, [template, active, onCompletion]);
if (template.isLoading) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "cyanBright"
}, /* @__PURE__ */ React__default["default"].createElement(Spinner__default["default"], {
type: "hamburger"
})), " ", "Getting Template Info"));
} else if (!template.found) {
if (template.rate) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u{10102}"), " The api is rate limited, try again in", " ", /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "yellow"
}, Math.ceil(template.rate / 60)), " ", Math.ceil(template.rate / 60) > 1 ? "minutes" : "minute");
} else {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u{10102}"), " The template", " ", /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, template.name), " was not found!"), template.match ? /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "grey"
}, "Are you looking for ", /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "yellow"
}, template.match), " ", "instead?") : /* @__PURE__ */ React__default["default"].createElement(Markdown, null, "`Try:` https://github.com/search?q=tsd-template&type=repositories"));
}
}
return /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, "\u2713"), " Using project template", " ", /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, template.name));
}
function useFileDownload(url, file = "") {
const [download, setDownload] = React.useState({
isLoading: true,
complete: false,
error: "",
progress: 0,
total: 0,
url,
file
});
React.useEffect(() => {
let pending = true;
(async () => {
const req = await fetch__default["default"](url);
if (req.status === 200) {
const dest = file ? file : (await tmp__default["default"].file({ postfix: ".zip", keep: true })).path;
const stream = fs__default["default"].createWriteStream(dest);
if (pending) {
setDownload((a) => ({
...a,
file: dest,
total: Number(req.headers.get("content-length") || 1)
}));
}
req.body.on("data", (chunk) => {
stream.write(chunk);
if (pending) {
setDownload((a) => ({
...a,
progress: a.progress + chunk.length
}));
}
});
req.body.on("end", () => {
stream.end(() => {
if (pending) {
setDownload((a) => ({
...a,
isLoading: false,
complete: true,
progress: a.total > 0 ? a.total : a.progress
}));
}
});
});
} else {
if (pending) {
setDownload((a) => ({
...a,
isLoading: false,
error: `${req.status} ${req.statusText}`
}));
}
}
})();
return () => {
pending = false;
};
}, [url, file]);
return download;
}
function TemplateDownload({
url,
active,
onDownloaded,
onCompletion
}) {
const download = useFileDownload(url);
const label = "Downloading template ";
React.useEffect(() => {
if (active) {
if (download.complete) {
onDownloaded == null ? void 0 : onDownloaded(download.file);
onCompletion == null ? void 0 : onCompletion(true);
} else if (download.error) {
onCompletion == null ? void 0 : onCompletion(false);
}
}
}, [download, active, onCompletion, onDownloaded]);
if (download.isLoading) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "row"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "cyanBright"
}, /* @__PURE__ */ React__default["default"].createElement(Spinner__default["default"], {
type: "hamburger"
})), " ", label, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "grey"
}, `${Number(download.progress / 1024).toFixed(2)} KB`)));
} else if (download.error) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u{10102}"), " Template download failed!"), /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, " ", download.error), /* @__PURE__ */ React__default["default"].createElement(Markdown, null, `\`URL\`: ${url}`));
}
return /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, "\u2713"), " Downloaded template archive");
}
function useUnzip(src, dest = "", cleanup = false) {
const [unzip, setUnzip] = React.useState({
isLoading: true,
complete: false,
error: "",
src,
dest,
progress: ""
});
React.useEffect(() => {
let pending = true;
(async () => {
const zip = new Zip__default["default"](src);
const entries = zip.getEntries();
const packageEntry = entries.find((entry) => entry.name === "package.json");
if (packageEntry) {
const parts = packageEntry.entryName.split("/");
const root = (parts.length > 1 ? parts.slice(0, parts.length - 1).join("/") : "") + "/";
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.entryName.startsWith(root) && entry.entryName !== root && !entry.entryName.endsWith("/")) {
const relEntry = entry.entryName.replace(root, "");
const destPath = path__default["default"].join(dest, path__default["default"].dirname(relEntry), "/");
fs.mkdir(destPath, { recursive: true }, () => null);
if (pending) {
setUnzip((z) => ({ ...z, progress: relEntry }));
await new Promise((resolve) => setTimeout(resolve, 1));
}
zip.extractEntryTo(entry.entryName, destPath, false, false);
}
}
if (cleanup)
rimraf__default["default"](src, () => null);
if (pending) {
setUnzip((z) => ({ ...z, isLoading: false, complete: true }));
}
} else {
if (cleanup) {
rimraf__default["default"](src, () => null);
rimraf__default["default"](dest, () => null);
}
if (pending) {
setUnzip((z) => ({
...z,
isLoading: false,
error: "No package.json"
}));
}
}
})();
return () => {
pending = false;
};
}, [src, dest, cleanup]);
return unzip;
}
function TemplateUnzip({
src,
dest,
remove,
active,
onCompletion
}) {
const unzip = useUnzip(src, dest, remove);
React.useEffect(() => {
if (active) {
if (unzip.complete) {
onCompletion == null ? void 0 : onCompletion(true);
} else if (unzip.error) {
onCompletion == null ? void 0 : onCompletion(false);
}
}
}, [unzip, active, onCompletion]);
if (unzip.isLoading) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "row"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "cyanBright"
}, /* @__PURE__ */ React__default["default"].createElement(Spinner__default["default"], {
type: "hamburger"
})), " ", "Extracting template", " ", unzip.progress && /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "grey"
}, unzip.progress)));
} else if (unzip.error) {
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, "\u{10102}"), " Template extraction failed!"), /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "red"
}, " ", unzip.error));
}
return /* @__PURE__ */ React__default["default"].createElement(ink.Text, null, /* @__PURE__ */ React__default["default"].createElement(ink.Text, {
color: "green"
}, "\u2713"), " Extracted template archive");
}
function Wizard({
children,
step,
exclusive
}) {
const matches = [];
React__default["default"].Children.forEach(children, (child) => {
var _a;
if (React__default["default"].isValidElement(child)) {
const tl = (_a = child.props.index) != null ? _a : Number.MAX_VALUE;
const match = exclusive ? tl == step ? child : null : tl <= step ? child : null;
if (match)
matches.push(match);
}
});
if (exclusive) {
return matches.length > 0 ? React__default["default"].cloneElement(matches[0], { step, active: true }) : null;
}
return /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, matches.map((match, index) => React__default["default"].cloneElement(match, {
step,
key: index,
active: match.props.index == step,
complete: match.props.index < step
})));
}
function Step({
children,
index,
active,
complete
}) {
const childrenWithProps = React__default["default"].Children.map(children, (child) => {
if (React__default["default"].isValidElement(child)) {
return React__default["default"].cloneElement(child, {
step: index,
active,
complete
});
}
return child;
});
return /* @__PURE__ */ React__default["default"].createElement(React__default["default"].Fragment, null, childrenWithProps);
}
function useGitConfig() {
const [config, setConfig] = React.useState({
user: "",
email: ""
});
React.useEffect(() => {
let pending = true;
(async () => {
try {
const git = simpleGit__default["default"]();
const cfg = {
user: (await git.getConfig("user.name")).value || "",
email: (await git.getConfig("user.email")).value || ""
};
if (pending)
setConfig(cfg);
} catch (e) {
//! ignore
}
})();
return () => {
pending = false;
};
}, []);
return config;
}
function useProjectDir(dir) {
const [results, setResults] = React.useState({
isLoading: true,
exists: false,
isEmpty: false,
relativePath: "",
name: ""
});
React.useEffect(() => {
let pending = true;
(async () => {
var _a;
const exists = !!await new Promise((r) => fs__default["default"].access(dir, fs__default["default"].constants.F_OK, (e) => r(!e)));
const stats = {
isLoading: false,
exists,
isEmpty: exists ? await (async () => {
const itr = await fs__default["default"].promises.opendir(dir);
const { done } = await itr[Symbol.asyncIterator]().next();
if (!done)
itr.close();
return !!done;
})() : true,
relativePath: path__default["default"].relative((_a = process.env.INIT_CWD) != null ? _a : process.cwd(), dir),
name: path__default["default"].basename(dir)
};
if (pending)
setResults(stats);
})();
return () => {
pending = false;
};
}, [dir]);
return results;
}
function useFetchTemplate(template) {
const [archive, setArchive] = React.useState({
isLoading: true,
found: false,
match: "",
url: "",
name: ""
});
React.useEffect(() => {
let pending = true;
(async () => {
const slug = template && (template.startsWith("tsd-template-") ? template : `tsd-template-${template}`) || "tsd-template";
const payload = { isLoading: false };
const req = await fetch__default["default"](`https://api.github.com/search/repositories?q=${slug}&sort=stars`);
if (req.status === 200) {
const json = await req.json();
const results = json.items.filter((item) => item.name.startsWith("tsd-template"));
const exact = results.find((item) => item.name === slug);
const match = results.length && results[0] || void 0;
const url = exact ? exact.archive_url.replace("{archive_format}", "zipball").replace("{/ref}", `/${exact.default_branch}`) : "";
payload.found = !!exact;
payload.match = !exact && match ? match.name.replace("tsd-template-", "") : "";
payload.url = url;
payload.name = exact ? exact.name : template;
} else if (req.status === 403) {
const reset = req.headers.get("X-RateLimit-Reset");
if (reset) {
payload.found = false;
payload.rate = parseInt(reset) - Date.now();
}
}
if (pending)
setArchive((a) => ({ ...a, ...payload }));
})();
return () => {
pending = false;
};
}, [template]);
return archive;
}
function Project({ dir, template }) {
const [config, setConfig] = React.useState();
const [archivePath, setArchivePath] = React.useState();
const history = reactRouter.useHistory();
const { exit } = ink.useApp();
const { step } = reactRouter.useParams();
const currentStep = step ? parseInt(step) : 0;
const { user: author, email } = useGitConfig();
const project = useProjectDir(dir);
const templateInfo = useFetchTemplate(template);
const onWizardNext = (success) => {
if (currentStep == 7)
exit();
if (success) {
history.push(`/project/${currentStep + 1}`);
} else {
exit(new Error());
}
};
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(Wizard, {
step: currentStep
}, /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 0,
name: "Project Directory"
}, /* @__PURE__ */ React__default["default"].createElement(ProjectDir, {
project,
onCompletion: onWizardNext
})), /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 1,
name: "Template Info"
}, /* @__PURE__ */ React__default["default"].createElement(TemplateInfo, {
template: templateInfo,
onCompletion: onWizardNext
})), /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 2,
name: "Project Configuration"
}, /* @__PURE__ */ React__default["default"].createElement(ProjectConfig, {
author,
email,
project,
onConfigured: (v) => setConfig(v),
onCompletion: onWizardNext
})), /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 3,
name: "Download Template"
}, /* @__PURE__ */ React__default["default"].createElement(TemplateDownload, {
url: templateInfo.url,
onDownloaded: (f) => setArchivePath(f),
onCompletion: onWizardNext
})), /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 4,
name: "Extract Template"
}, archivePath && /* @__PURE__ */ React__default["default"].createElement(TemplateUnzip, {
src: archivePath,
dest: dir,
onCompletion: onWizardNext
})), /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 5,
name: "Apply Config"
}, config && /* @__PURE__ */ React__default["default"].createElement(ProjectApply, {
dir,
config,
onCompletion: onWizardNext
})), /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 6,
name: "Install Dependencies"
}, /* @__PURE__ */ React__default["default"].createElement(ProjectInstall, {
dir,
onCompletion: onWizardNext
})), /* @__PURE__ */ React__default["default"].createElement(Step, {
index: 7,
name: "Project Ready"
}, /* @__PURE__ */ React__default["default"].createElement(ProjectReady, {
project: project.relativePath,
onCompletion: onWizardNext
}))));
}
const CLI = ({ path, data }) => {
const { help, dir, template } = React.useMemo(() => {
const { help: help2, dir: dir2, template: template2 } = data;
return { help: help2 != null ? help2 : "", dir: dir2 != null ? dir2 : "", template: template2 != null ? template2 : "" };
}, [data]);
return /* @__PURE__ */ React__default["default"].createElement(ink.Box, {
width: 80,
flexDirection: "column"
}, /* @__PURE__ */ React__default["default"].createElement(reactRouter.MemoryRouter, {
initialEntries: [path],
initialIndex: 0
}, /* @__PURE__ */ React__default["default"].createElement(reactRouter.Switch, null, /* @__PURE__ */ React__default["default"].createElement(reactRouter.Route, {
path: "/help"
}, /* @__PURE__ */ React__default["default"].createElement(Help, {
text: help
})), /* @__PURE__ */ React__default["default"].createElement(reactRouter.Route, {
path: "/project/:step?"
}, /* @__PURE__ */ React__default["default"].createElement(Project, {
dir,
template
})))));
};
function App(path, data) {
const instance = ink.render(/* @__PURE__ */ React__default["default"].createElement(CLI, {
path,
data
}));
return path === "/help" ? Promise.resolve() : instance.waitUntilExit();
}
yargs__default["default"].scriptName("").usage("Usage: **npm** init @ts-defold <project-directory> -- `[options]`").positional("project-directory", {
describe: "Empty directory to initialize project in",
type: "string"
}).options({
template: {
describe: "Starting template for project",
type: "string"
}
}).command("*", "Generate a new project", () => null, async (argv) => {
var _a;
try {
if (argv._.length == 1) {
await App(`/project`, {
dir: path__default["default"].resolve((_a = process.env.INIT_CWD) != null ? _a : process.cwd(), argv._[0].toString()),
template: argv.template
});
} else {
await App("/help", { help: await yargs__default["default"].getHelp() });
}
} catch (e) {
}
process.exit(0);
}).version().epilogue("For more information on `templates` see:\nhttps://github.com/ts-defold/create#readme\n").help().argv;
//# sourceMappingURL=index.js.map