generator-luau
Version:
A Yeoman generator for Luau projects
333 lines (287 loc) • 8.85 kB
JavaScript
import Generator from 'yeoman-generator';
import path from 'path';
import { format } from 'prettier-package-json';
import axios from 'axios';
class PackageJsonBuilder {
constructor(name) {
this._base = {
name,
keywords: ['luau'],
};
this._dependencies = {};
this._devDependencies = {};
this._scripts = {};
}
static from(packageJson) {
const { name, ...definitions } = packageJson;
return new PackageJsonBuilder(name).merge(definitions)
}
merge({ scripts, dependencies, devDependencies, ...definitions }) {
Object.assign(this._base, definitions);
if (scripts) {
this.addScripts(scripts);
}
if (devDependencies) {
this.addDevDependencies(devDependencies);
}
if (dependencies) {
this.addDependencies(dependencies);
}
return this
}
addScripts(scripts) {
Object.assign(this._scripts, scripts);
return this
}
addDevDependencies(dependencies) {
Object.assign(this._devDependencies, dependencies);
return this
}
addDependencies(dependencies) {
Object.assign(this._dependencies, dependencies);
return this
}
build() {
return Object.assign(
{},
{
scripts: this._scripts,
dependencies: this._dependencies,
devDependencies: this._devDependencies,
},
this._base
)
}
}
const VERSION_CACHE = {
'@jsdotlua/jest': '3.10.0',
'@jsdotlua/jest-globals': '3.10.0',
npmluau: '0.1.2',
};
function fetchScopedPackageVersion(packageName) {
const scope = packageName.substring(1, packageName.indexOf('/'));
return axios
.get(`https://registry.npmjs.com/-/v1/search`, {
params: {
text: `scope:${scope} ${packageName}`,
},
})
.then((res) => res.data?.objects[0]?.package?.version)
}
function fetchPackageVersion(packageName) {
return axios
.get(`https://registry.npmjs.org/${packageName}/latest`)
.then((res) => res.data?.version)
}
async function fetchLatestNpmPackageVersion(packageName) {
const version = VERSION_CACHE[packageName];
if (version !== undefined) {
return version
}
const fetchedVersion = (
packageName.startsWith('@')
? fetchScopedPackageVersion(packageName)
: fetchPackageVersion(packageName)
).catch((err) => {
console.warn(`fetching '${packageName}' version on npm errored: ${err}`);
return null
});
if (fetchedVersion) {
VERSION_CACHE[packageName] = fetchedVersion;
}
return fetchedVersion
}
async function createDependencies(...packages) {
const versions = await Promise.all(
packages.map(async (name) => ({
name,
version: await fetchLatestNpmPackageVersion(name),
}))
);
return versions.reduce((dependencies, { name, version }) => {
if (version) {
dependencies[name] = '^' + version;
} else {
console.warn(
`skip dependency '${name}'. You can manually add it with ` +
`'npm install ${name}' or 'yarn add ${name}'`
);
}
return dependencies
}, {})
}
const extractPackageName = (packageName) => {
const firstSlash = packageName.indexOf('/');
if (firstSlash) {
return {
name: packageName.slice(firstSlash + 1),
scope: packageName.slice(0, firstSlash),
full: packageName,
}
}
return {
name: packageName,
scope: null,
full: packageName,
}
};
class LuauPackageGenerator extends Generator {
constructor(args, opts) {
super(args, opts);
this._name = extractPackageName(opts.name);
this._authorName = opts.authorName;
this._authorEmail = opts.authorEmail;
this._githubOwner = opts.githubOwner;
this._luaExtension = opts.luaExtension;
this._useJest = opts.useJest;
this._luaEnvironment = opts.luaEnvironment;
this._licenseFileName = opts.licenseFileName;
this._location = opts.location;
this._isWorkspace = opts.location != null;
this._canSetupMultipleProjects = this._isWorkspace;
}
async prompting() {
this._projects = [
await this._setupProject(
this._name.full,
path.basename(this.destinationRoot())
),
];
let askAgain = this._canSetupMultipleProjects;
while (askAgain) {
const { setupNewProject } = await this.prompt([
{
type: 'confirm',
name: 'setupNewProject',
message: 'Would you like to setup another project?',
default: false,
},
]);
if (setupNewProject) {
this._projects.push(await this._setupProject());
} else {
askAgain = false;
}
}
this.packages = this._projects.map(({ projectName }) => projectName);
}
async _setupProject(name, defaultName) {
const { projectName } = name
? { projectName: name }
: await this.prompt([
{
type: 'input',
name: 'projectName',
message: 'What is the project name?',
default: defaultName,
},
]);
const { useReact } = await this.prompt([
{
type: 'confirm',
name: 'useReact',
message: 'Would you like to use React Lua?',
when: this._luaEnvironment === 'roblox',
default: false,
},
]);
return {
projectName,
useReact,
}
}
async writing() {
const luaExtension = this._luaExtension;
const luaEnvironment = this._luaEnvironment;
const licenseName = this.licenseName ?? 'UNLICENSED';
await Promise.all(
this._projects.map(async ({ projectName, useReact }) => {
const projectNameInfo = extractPackageName(projectName);
const getLocation = this._location
? (...subpath) =>
this.destinationPath(
this._location,
projectNameInfo.name,
...subpath
)
: (...subpath) => this.destinationPath(...subpath);
const packageJsonLocation = getLocation('package.json');
const packageBuilder = this.fs.exists(packageJsonLocation)
? PackageJsonBuilder.from(this.fs.readJSON(packageJsonLocation))
: new PackageJsonBuilder(projectNameInfo.full);
const repositoryBaseUrl = this._isWorkspace
? `https://github.com/${this._githubOwner}/${this._name.name}`
: `https://github.com/${this._githubOwner}/${projectNameInfo.name}`;
const gitRepoUrl = `git+${repositoryBaseUrl}.git`;
const entryPoint = `src/init.${luaExtension}`;
packageBuilder.merge({
version: '0.1.0',
main: entryPoint,
description: '',
author: `${this._authorName} <${this._authorEmail}>`,
repository: this._isWorkspace
? {
type: 'git',
url: gitRepoUrl,
directory: `${this._location}/${projectNameInfo.name}`,
}
: { type: 'git', url: gitRepoUrl },
homepage: `${repositoryBaseUrl}#readme`,
});
this.fs.write(getLocation(entryPoint), 'return {}\n');
this.fs.copyTpl(
this.templatePath('README.md'),
getLocation('README.md'),
{
projectName: projectNameInfo.name,
fullProjectName: projectNameInfo.full,
repoName: this._name.name,
licenseName,
licenseFileName: this._licenseFileName,
licensePath: `${this._location ? '../../' : ''}${this._licenseFileName}`,
githubOwner: this._githubOwner,
}
);
const copyFiles = {
'CHANGELOG.md': 'CHANGELOG.md',
npm_ignore: '.npmignore',
};
Object.entries(copyFiles).forEach(([templateName, destinationName]) => {
this.fs.copyTpl(
this.templatePath(templateName),
this.destinationPath(destinationName)
);
});
if (useReact) {
if (luaEnvironment === 'roblox') {
packageBuilder.addDependencies(
await createDependencies(
'@jsdotlua/react',
'@jsdotlua/react-roblox'
)
);
} else {
packageBuilder.addDependencies(
await createDependencies('@jsdotlua/react')
);
}
}
if (this._useJest) {
packageBuilder.addDevDependencies(
await createDependencies('@jsdotlua/jest', '@jsdotlua/jest-globals')
);
const testFilePath = `init.test.${luaExtension}`;
this.fs.copyTpl(
this.templatePath('init.test.lua'),
getLocation(`src/__tests__/${testFilePath}`)
);
}
this.fs.write(
packageJsonLocation,
format(packageBuilder.build())
);
})
);
}
}
export { LuauPackageGenerator as default };