@haxtheweb/create
Version:
CLI for all things HAX the web
832 lines (806 loc) ⢠35.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.HAXWiring = void 0;
exports.getInputMethodFromType = getInputMethodFromType;
exports.webcomponentActions = webcomponentActions;
exports.webcomponentCommandDetected = webcomponentCommandDetected;
exports.webcomponentGenerateHAXSchema = webcomponentGenerateHAXSchema;
exports.webcomponentProcess = webcomponentProcess;
exports.webcomponentRename = webcomponentRename;
var fs = _interopRequireWildcard(require("node:fs"));
var path = _interopRequireWildcard(require("node:path"));
var _promises = require("node:timers/promises");
var ejs = _interopRequireWildcard(require("ejs"));
var p = _interopRequireWildcard(require("@clack/prompts"));
var _picocolors = _interopRequireDefault(require("picocolors"));
var _statements = require("../statements.js");
var _logging = require("../logging.js");
var _utils = require("../utils.js");
var haxcmsLib = _interopRequireWildcard(require("@haxtheweb/haxcms-nodejs/dist/lib/HAXCMS.js"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const HAXCMS = haxcmsLib.HAXCMS;
let sysGit = true;
(0, _utils.exec)('which git', error => {
if (error) {
sysGit = false;
}
});
class HAXWiring {
/**
* Return a haxProperties prototype / example structure
*/
prototypeHaxProperties = () => {
// example properties valid for HAX context menu.
let props = {
api: "1",
type: "element",
editingElement: "core",
hideDefaultSettings: false,
canScale: true,
canEditSource: true,
contentEditable: false,
gizmo: {
title: "Tag name",
description: "",
icon: "icons:android",
color: "purple",
tags: ["Other"],
handles: [{
type: "data",
type_exclusive: false,
url: "src"
}],
meta: {
author: "auto"
},
requiresChildren: false,
requiresParent: false
},
settings: {
configure: [{
slot: "",
title: "Inner content",
description: "The slotted content that lives inside the tag",
inputMethod: "textfield",
icon: "android",
required: true,
validationType: "text"
}, {
slot: "button",
title: "Button content",
description: "The content that can override the button",
inputMethod: "textfield",
icon: "android",
required: true,
validationType: "text"
}, {
property: "title",
title: "Title",
description: "",
inputMethod: "textfield",
icon: "android",
required: true,
validationType: "text"
}, {
property: "primaryColor",
title: "Title",
description: "",
inputMethod: "textfield",
icon: "android",
required: false,
validation: ".*",
validationType: "text"
}],
advanced: [{
property: "secondaryColor",
title: "Secondary color",
description: "An optional secondary color used in certain edge cases.",
inputMethod: "colorpicker",
icon: "color"
}, {
property: "endPoint",
title: "API endpoint",
description: "An optional endpoint to hit and load in more data dymaically.",
inputMethod: "textfield",
icon: "android",
validation: "[a-z0-9]",
validationType: "url"
}],
developer: []
},
saveOptions: {
wipeSlot: false,
unsetAttributes: ["end-point", "secondary-color"]
},
documentation: {
howTo: "https://haxtheweb.org/welcome",
purpose: "https://haxtheweb.org/welcome"
},
demoSchema: [{
tag: "my-tag",
content: "<p>inner html</p>",
properties: {
endPoint: "https://cdn2.thecatapi.com/images/9j5.jpg",
primaryColor: "yellow",
title: "A cat"
}
}]
};
return props;
};
}
// processing an element
exports.HAXWiring = HAXWiring;
async function webcomponentProcess(commandRun, project, port = "8000") {
// Security (M-2): defense-in-depth re-check of --npm-client at the point it
// is interpolated into exec() shell strings. create.js validates earlier,
// but the site/webcomponent subcommand option paths can bypass that check.
if (commandRun.options.npmClient) {
commandRun.options.npmClient = (0, _utils.validateNpmClient)(commandRun.options.npmClient);
}
// auto select operations to perform if requested
if (!project.extras) {
console.log(commandRun.options.extras);
if (commandRun.options.extras === false) {
project.extras = [];
} else {
let extras = ['launch', 'install', 'git'];
if (!sysGit || project.isMonorepo) {
extras.pop();
}
project.extras = extras;
}
}
// values not set by user but used in templating
project.className = (0, _utils.dashToCamel)(project.name);
// option to build github repo link for the user
if (project.extras && project.extras.includes('git')) {
// @todo need to support git@ and https methods
if (commandRun.options.auto) {
project.gitRepo = `https://github.com/${project.author}/${project.name}.git`;
} else {
project.gitRepo = await p.text({
message: 'Git Repo location:',
placeholder: `https://github.com/${project.author}/${project.name}.git`,
initialValue: `https://github.com/${project.author}/${project.name}.git`
});
}
// if they supplied one and it has github in it, build a link automatically for ejs index
if (project.gitRepo && project.gitRepo.includes('github.com')) {
project.githubLink = project.gitRepo.replace('git@github.com:', 'https://github.com/').replace('.git', '');
} else {
project.githubLink = null;
}
} else {
project.githubLink = null;
}
// if we have an org, add a / at the end so file name is written correctly
if (project.org) {
project.org += '/';
} else {
project.org = '';
}
let s = p.spinner();
s.start((0, _statements.merlinSays)('Copying project files'));
// leverage this little helper from HAXcms
let templateDir = project.template || 'compliant';
await HAXCMS.recurseCopy(`${process.mainModule.path}/templates/${project.type}/${templateDir}/`, `${project.path}/${project.name}`);
// rename gitignore to improve copy cross platform compat
const renameIfExists = (src, dest) => {
if (fs.existsSync(src)) {
fs.renameSync(src, dest);
}
};
renameIfExists(`${project.path}/${project.name}/_github`, `${project.path}/${project.name}/.github`);
renameIfExists(`${project.path}/${project.name}/_vscode`, `${project.path}/${project.name}/.vscode`);
renameIfExists(`${project.path}/${project.name}/_dddignore`, `${project.path}/${project.name}/.dddignore`);
renameIfExists(`${project.path}/${project.name}/_editorconfig`, `${project.path}/${project.name}/.editorconfig`);
renameIfExists(`${project.path}/${project.name}/_gitignore`, `${project.path}/${project.name}/.gitignore`);
renameIfExists(`${project.path}/${project.name}/_nojekyll`, `${project.path}/${project.name}/.nojekyll`);
renameIfExists(`${project.path}/${project.name}/_npmignore`, `${project.path}/${project.name}/.npmignore`);
renameIfExists(`${project.path}/${project.name}/_surgeignore`, `${project.path}/${project.name}/.surgeignore`);
renameIfExists(`${project.path}/${project.name}/_travis.yml`, `${project.path}/${project.name}/.travis.yml`);
// rename paths that are of the element name in question
renameIfExists(`${project.path}/${project.name}/lib/webcomponent.haxProperties.json`, `${project.path}/${project.name}/lib/${project.name}.haxProperties.json`);
// loop through and rename all the localization files
if (fs.existsSync(`${project.path}/${project.name}/locales/`)) {
fs.readdirSync(`${project.path}/${project.name}/locales/`).forEach(async function (file, index) {
fs.renameSync(`${project.path}/${project.name}/locales/${file}`, `${project.path}/${project.name}/locales/${file.replace('webcomponent', project.name)}`);
});
}
renameIfExists(`${project.path}/${project.name}/webcomponent.js`, `${project.path}/${project.name}/${project.name}.js`);
renameIfExists(`${project.path}/${project.name}/test/webcomponent.test.js`, `${project.path}/${project.name}/test/${project.name}.test.js`);
s.stop((0, _statements.merlinSays)('Files copied'));
await (0, _promises.setTimeout)(500);
s.start((0, _statements.merlinSays)('Making files awesome'));
for (const filePath of (0, _utils.readAllFiles)(`${project.path}/${project.name}`)) {
try {
// ensure we don't try to pattern rewrite image files
if (!filePath.endsWith('.jpg') && !filePath.endsWith('.png')) {
const ejsString = ejs.fileLoader(filePath, 'utf8');
let content = ejs.render(ejsString, project);
// file written successfully
fs.writeFileSync(filePath, content);
}
} catch (err) {
console.error(filePath);
console.error(err);
}
}
s.stop('Files are now awesome!');
if (project.gitRepo && !commandRun.options.isMonorepo) {
try {
await (0, _utils.exec)(`cd ${project.path}/${project.name} && git init && git add -A && git commit -m "first commit" && git branch -M main${project.gitRepo ? ` && git remote add origin ${project.gitRepo}` : ''}`);
} catch (e) {}
}
// options for install, git and other extras
// can't launch if we didn't install first so launch implies installation
if (project.extras && (project.extras.includes('launch') || project.extras.includes('install'))) {
s.start((0, _statements.merlinSays)(`Installation magic (${commandRun.options.npmClient} install)`));
try {
// monorepos install from top but then still need to launch from local location
if (!commandRun.options.isMonorepo) {
await (0, _utils.exec)(`cd ${project.path}/${project.name} && ${commandRun.options.npmClient} install`);
}
} catch (e) {
console.warn(e);
}
s.stop((0, _statements.merlinSays)(`Everything is installed. It's go time`));
}
// autolaunch if default was selected
if (project.extras && project.extras.includes('launch')) {
let optionPath = `${project.path}/${project.name}`;
let command = `${commandRun.options.npmClient} start`;
p.note(`${(0, _statements.merlinSays)(`I have summoned a sub-process daemon š¹`)}
š Running your ${_picocolors.default.bold(project.type)} ${_picocolors.default.bold(project.name)}:
${_picocolors.default.underline(_picocolors.default.cyan(`http://localhost:${port}`))}
š Launched: ${_picocolors.default.underline(_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${optionPath}`))))}
š» Folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`cd ${optionPath}`)))}
š Open folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`open ${optionPath}`)))}
š VS Code Project: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`code ${optionPath}`)))}
š§ Launch later: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${command}`)))}
āØļø To resume š§ Merlin press: ${_picocolors.default.bold(_picocolors.default.black(_picocolors.default.bgRed(` CTRL + C or CTRL + BREAK `)))}
`);
// at least a second to see the message print at all
await (0, _promises.setTimeout)(1000);
try {
await (0, _utils.exec)(`cd ${optionPath} && ${command} && ${commandRun.options.npmClient} run analyze`);
} catch (e) {
// don't log bc output is weird
}
} else if (!commandRun.options.quiet) {
let nextSteps = `cd ${project.path}/${project.name} && ${project.extras.includes('install') ? '' : `${commandRun.options.npmClient} install && `}${commandRun.options.npmClient} start`;
p.note(`${project.name} is ready to go. Run the following to start development:`);
p.outro(nextSteps);
}
}
function webcomponentActions() {
return [{
value: 'start',
label: "Launch project"
}, {
value: 'wc:stats',
label: "Check status of web component"
}, {
value: 'wc:element',
label: "Add new Lit component to existing project"
}, {
value: 'wc:haxproperties',
label: "Write haxProperties schema"
}, {
value: 'wc:rename',
label: "Rename this web component"
}];
}
// autodetect webcomponent
async function webcomponentCommandDetected(commandRun, packageData = {}, port = "8000") {
if (!commandRun.options.quiet) {
p.intro(`${_picocolors.default.bgBlack(_picocolors.default.white(` HAXTheWeb : Webcomponent detected `))}`);
p.intro(`${_picocolors.default.bgBlue(_picocolors.default.white(` Web component name: ${packageData.name} `))}`);
}
let actions = webcomponentActions();
let actionAssigned = false;
// default to status unless already set so we don't issue a create in a create
if (!commandRun.arguments.action) {
actionAssigned = true;
commandRun.arguments.action = 'wc:status';
}
commandRun.command = "webcomponent";
let operation = {
...commandRun.arguments,
...commandRun.options
};
actions.push({
value: 'quit',
label: "šŖ Quit"
});
while (operation.action !== 'quit') {
if (!operation.action) {
commandRun = {
command: null,
arguments: {},
options: {
npmClient: `${operation.npmClient}`
}
};
operation = await p.group({
action: ({
results
}) => p.select({
message: `Actions you can take:`,
defaultValue: actions[0],
initialValue: actions[0],
options: actions
})
}, {
onCancel: () => {
if (!commandRun.options.quiet) {
p.cancel('š§ Merlin: Canceling CLI.. HAX ya later šŖ');
}
process.exit(0);
}
});
}
if (operation.action) {
p.intro(`hax wc ${_picocolors.default.bold(operation.action)}`);
}
switch (operation.action) {
case "start":
if (!commandRun.options.quiet) {
// Multi-line clack spacing
p.note(`${(0, _statements.merlinSays)(`I have summoned a sub-process daemon š¹`)}
š Running your ${_picocolors.default.bold('webcomponent')} ${_picocolors.default.bold(packageData.name)}:
${_picocolors.default.underline(_picocolors.default.cyan(`http://localhost:${port}`))}
š Launched: ${_picocolors.default.underline(_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${process.cwd()}`))))}
š» Folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`cd ${process.cwd()}`)))}
š Open folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`open ${process.cwd()}`)))}
š VS Code Project: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`code ${process.cwd()}`)))}
š§ Launch later: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${commandRun.options.npmClient} start`)))}
āØļø To exit š§ Merlin press: ${_picocolors.default.bold(_picocolors.default.black(_picocolors.default.bgRed(` CTRL + C or CTRL + BREAK `)))}
`);
}
try {
// ensure it's installed first, unless it's a monorepo. basic check for node_modules
// folder as far as if already installed so we don't double install needlessly
if (!commandRun.options.isMonorepo && !fs.existsSync("./node_modules")) {
if (!commandRun.options.quiet) {
let s = p.spinner();
s.start((0, _statements.merlinSays)(`Installation magic (${commandRun.options.npmClient} install)`));
await (0, _utils.exec)(`${commandRun.options.npmClient} install`);
s.stop((0, _statements.merlinSays)(`Everything is installed. It's go time`));
} else {
await (0, _utils.exec)(`${commandRun.options.npmClient} install`);
}
}
await (0, _utils.exec)(`${commandRun.options.npmClient} start`);
} catch (e) {
// don't log bc output is odd
}
break;
case "serve":
try {
if (!commandRun.options.quiet) {
p.intro(`Launching development server.. `);
}
if (packageData.scripts.serve) {
if (!commandRun.options.quiet) {
p.note(`${(0, _statements.merlinSays)(`Project launched in development mode`)}
š Running your ${_picocolors.default.bold('webcomponent')} ${_picocolors.default.bold(packageData.name)}:
${_picocolors.default.underline(_picocolors.default.cyan(`http://localhost:${port}`))}
š Launched: ${_picocolors.default.underline(_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${process.cwd()}`))))}
š» Folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`cd ${process.cwd()}`)))}
š Open folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`open ${process.cwd()}`)))}
š VS Code Project: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`code ${process.cwd()}`)))}
š§ Launch later: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${commandRun.options.npmClient} serve`)))}
āØļø To exit š§ Merlin press: ${_picocolors.default.bold(_picocolors.default.black(_picocolors.default.bgRed(` CTRL + C or CTRL + BREAK `)))}
`);
}
await (0, _utils.exec)(`${commandRun.options.npmClient} run serve`);
} else {
// if no serve script, run start instead
if (!commandRun.options.quiet) {
p.note(`${(0, _statements.merlinSays)(`No ${_picocolors.default.bold('serve')} script found, running ${_picocolors.default.bold(`${commandRun.options.npmClient} start`)} instead`)}
š Running your ${_picocolors.default.bold('webcomponent')} ${_picocolors.default.bold(packageData.name)}:
${_picocolors.default.underline(_picocolors.default.cyan(`http://localhost:${port}`))}
š Launched: ${_picocolors.default.underline(_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${process.cwd()}`))))}
š» Folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`cd ${process.cwd()}`)))}
š Open folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`open ${process.cwd()}`)))}
š VS Code Project: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`code ${process.cwd()}`)))}
š§ Launch later: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`${commandRun.options.npmClient} start`)))}
āØļø To exit š§ Merlin press: ${_picocolors.default.bold(_picocolors.default.black(_picocolors.default.bgRed(` CTRL + C or CTRL + BREAK `)))}
`);
}
await (0, _utils.exec)(`${commandRun.options.npmClient} start`);
}
} catch (e) {
// don't log bc output is odd
console.log("error", e);
}
break;
case "wc:status":
case "wc:stats":
case "webcomponent:status":
case "webcomponent:stats":
try {
let webcomponentStats = {};
if (packageData) {
webcomponentStats.title = packageData.name;
webcomponentStats.description = packageData.description;
webcomponentStats.git = packageData.repository.url;
}
webcomponentStats.modules = [];
webcomponentStats.superclasses = [];
if (fs.existsSync(`${process.cwd()}/custom-elements.json`)) {
let components = JSON.parse(fs.readFileSync(`${process.cwd()}/custom-elements.json`, 'utf8')).modules;
for (var i in components) {
webcomponentStats.modules.push(`${components[i].path}`);
if (components[i].declarations[0].superclass && !webcomponentStats.superclasses.includes(components[i].declarations[0].superclass.name)) {
webcomponentStats.superclasses.push(`${components[i].declarations[0].superclass.name}`);
}
}
}
if (!commandRun.options.format && !commandRun.options.quiet) {
p.intro(`${_picocolors.default.bgBlue(_picocolors.default.white(` Title: ${webcomponentStats.title} `))}`);
p.intro(`${_picocolors.default.bgBlue(_picocolors.default.white(` Description: ${webcomponentStats.description} `))}`);
if (webcomponentStats.git) {
p.intro(`${_picocolors.default.bgBlue(_picocolors.default.white(` Git: ${webcomponentStats.git} `))}`);
}
if (webcomponentStats.modules.length !== 0) {
p.intro(`${_picocolors.default.bgBlue(_picocolors.default.white(` Modules: ${webcomponentStats.modules} `))}`);
p.intro(`${_picocolors.default.bgBlue(_picocolors.default.white(` Number of modules: ${webcomponentStats.modules.length} `))}`);
}
if (webcomponentStats.superclasses.length !== 0) {
p.intro(`${_picocolors.default.bgBlue(_picocolors.default.white(` Inherited superclasses: ${webcomponentStats.superclasses} `))}`);
}
}
} catch (e) {
(0, _logging.log)(e.stderr);
}
break;
case "wc:element":
case "webcomponent:element":
try {
if (!commandRun.options.name) {
commandRun.options.name = await p.text({
message: 'Component name:',
placeholder: 'my-component',
initialValue: 'my-component',
required: true,
validate: value => {
let joint = process.cwd();
if (commandRun.options.path) {
joint = commandRun.options.path;
}
return (0, _utils.validateWebcomponentName)(value, {
force: commandRun.options.force,
joint: joint,
checkExists: true
});
}
});
} else {
let value = commandRun.options.name;
let joint = process.cwd();
if (commandRun.options.path) {
joint = commandRun.options.path;
}
const error = (0, _utils.validateWebcomponentName)(value, {
force: commandRun.options.force,
joint: joint,
checkExists: true
});
if (error) {
console.error(_picocolors.default.red(error));
process.exit(1);
}
}
const project = {
name: commandRun.options.name,
mainModule: packageData.name,
path: process.cwd(),
className: (0, _utils.dashToCamel)(commandRun.options.name),
year: new Date().getFullYear()
};
if (packageData.author) {
project.author = packageData.author.name;
}
const filePath = `${project.path}/${project.name}.js`;
await fs.copyFileSync(`${process.mainModule.path}/templates/generic/webcomponent.js`, filePath);
const ejsString = ejs.fileLoader(filePath, 'utf8');
let content = ejs.render(ejsString, project);
// file written successfully
fs.writeFileSync(filePath, content);
p.note(`š§ Add to another web component (.js): ${_picocolors.default.underline(_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`import ./${project.name}.js`))))}
š» Add to an HTML file: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(`<script type="module" src="${project.name}"></script>`)))}`);
// at least a second to see the message print at all
await (0, _promises.setTimeout)(1000);
} catch (e) {
(0, _logging.log)(e.stderr);
// Original ejs.render error checking
console.error(_picocolors.default.red(process.cwd()));
console.error(_picocolors.default.red(e));
}
break;
case "wc:haxproperties":
case "webcomponent:haxproperties":
try {
if (packageData.customElements) {
await webcomponentGenerateHAXSchema(commandRun, packageData);
}
} catch (e) {
(0, _logging.log)(e.stderr);
}
break;
case "rename":
case "wc:rename":
case "webcomponent:rename":
try {
await webcomponentRename(commandRun, packageData);
} catch (e) {
(0, _logging.log)(e.stderr);
}
break;
case "quit":
// quit
process.exit(0);
break;
}
// y or noi need to act like it ran and finish instead of looping options
if (commandRun.options.y || !commandRun.options.i || !actionAssigned) {
process.exit(0);
}
operation.action = null;
}
}
// merge the web component factory libraries the user has installed
async function webcomponentGenerateHAXSchema(commandRun, packageData) {
// run analyzer automatically if we have it so that it's up to date
if (packageData.scripts.analyze) {
await (0, _utils.exec)(`${commandRun.options.npmClient} run analyze`);
}
if (fs.existsSync(`${process.cwd()}/${packageData.customElements}`)) {
const ceFileData = fs.readFileSync(`${process.cwd()}/${packageData.customElements}`, 'utf8', (error, data) => {
if (error) {
console.warn(error);
return;
}
return data;
});
let wiring = new HAXWiring();
if (commandRun.options.debug) {
(0, _logging.log)(ceFileData, 'debug');
}
if (ceFileData) {
let ce = JSON.parse(ceFileData);
await ce.modules.forEach(async modules => {
await modules.declarations.forEach(async declarations => {
let props = wiring.prototypeHaxProperties();
props.gizmo.title = declarations.tagName.replace('-', ' ');
props.gizmo.tags = ["Other"];
props.gizmo.handles = [];
props.gizmo.meta.author = "HAXTheWeb core team";
delete props.gizmo.shortcutKey;
delete props.gizmo.requiresChildren;
delete props.gizmo.requiresParent;
props.settings.configure = [];
props.settings.advanced = [];
props.documentation = {
howTo: null,
purpose: null
};
props.saveOptions = {
unsetAttributes: []
};
props.demoSchema = [{
tag: declarations.tagName,
content: "",
properties: {}
}];
let propData = [];
if (declarations.attributes) {
propData = declarations.attributes;
}
// loop through and if props are things we can map then do it
await propData.forEach(async prop => {
if (["t", "colors", '_haxState', "elementVisible"].includes(prop.fieldName)) {
props.saveOptions.unsetAttributes.push(prop.fieldName);
} else {
let type = "textfield";
if (prop.type && prop.type.text) {
type = getInputMethodFromType(prop.type.text);
}
if (type) {
let propSchema = {
property: prop.fieldName,
title: prop.name,
description: "",
inputMethod: type
};
if (prop.default !== undefined) {
props.demoSchema[0].properties[prop.fieldName] = prop.default;
}
props.settings.configure.push(propSchema);
}
}
});
if (commandRun.options.v) {
(0, _logging.log)(JSON.stringify(props, null, 2), 'silly');
}
fs.writeFileSync(`./lib/${declarations.tagName}.haxProperties.json`, JSON.stringify(props, null, 2));
(0, _logging.log)(`schema written to: ./lib/${declarations.tagName}.haxProperties.json`);
});
});
}
}
}
function getInputMethodFromType(type) {
switch (type) {
case "string":
return "textfield";
case "number":
return "number";
case "boolean":
return "boolean";
}
return false;
}
async function webcomponentRename(commandRun, packageData) {
const oldCwd = process.cwd();
const oldDirName = path.basename(oldCwd);
const parentDir = path.dirname(oldCwd);
// monorepo guard: prevent renaming from the monorepo root
if (packageData && packageData.workspaces) {
console.error(_picocolors.default.red('Cannot rename a webcomponent from the monorepo root. Run this command inside the element directory.'));
process.exit(1);
}
// derive old name and scope from package.json or directory name
let oldName = oldDirName;
let scope = '';
if (packageData && packageData.name) {
const parts = packageData.name.split('/');
if (parts.length > 1) {
scope = parts[0] + '/';
oldName = parts[1];
} else {
oldName = parts[0];
}
}
// load wc-registry for collision checking
let wcReg = {};
try {
const regPath = path.join(__dirname, '../../lib/wc-registry.json');
if (fs.existsSync(regPath)) {
wcReg = JSON.parse(fs.readFileSync(regPath, 'utf8'));
}
} catch (e) {
// ignore missing registry
}
let newName;
if (!commandRun.options.name) {
if (commandRun.options.i === false || commandRun.options.y || commandRun.options.auto) {
console.error(_picocolors.default.red("Name is required when running non-interactively. Pass --name <value>."));
process.exit(1);
}
newName = await p.text({
message: 'New component name:',
placeholder: 'my-new-element',
required: true,
validate: value => {
return (0, _utils.validateWebcomponentName)(value, {
wcReg,
force: commandRun.options.force,
joint: parentDir,
checkExists: true
});
}
});
} else {
newName = commandRun.options.name;
const error = (0, _utils.validateWebcomponentName)(newName, {
wcReg,
force: commandRun.options.force,
joint: parentDir,
checkExists: true
});
if (error) {
console.error(_picocolors.default.red(error));
process.exit(1);
}
}
const oldClassName = (0, _utils.dashToCamel)(oldName);
const newClassName = (0, _utils.dashToCamel)(newName);
const newScopedName = scope + newName;
const newDir = path.join(parentDir, newName);
let s = p.spinner();
s.start((0, _statements.merlinSays)(`Renaming ${oldName} to ${newName}`));
// rewrite file contents
for (const filePath of (0, _utils.readAllFiles)(oldCwd)) {
// skip binary and generated directories
if (filePath.includes('/node_modules/') || filePath.includes('/dist/') || filePath.includes('/public/')) {
continue;
}
if (filePath.endsWith('.jpg') || filePath.endsWith('.png')) {
continue;
}
try {
let content = fs.readFileSync(filePath, 'utf8');
// replace class name first, then tag name
content = content.replaceAll(oldClassName, newClassName);
content = content.replaceAll(oldName, newName);
fs.writeFileSync(filePath, content);
} catch (err) {
console.warn(_picocolors.default.yellow(`Could not rewrite ${filePath}: ${err.message}`));
}
}
// rename files that contain the old name in their basename
const filesToRename = [];
for (const filePath of (0, _utils.readAllFiles)(oldCwd)) {
if (filePath.includes('/node_modules/') || filePath.includes('/dist/') || filePath.includes('/public/')) {
continue;
}
const basename = path.basename(filePath);
if (basename.includes(oldName)) {
const newBasename = basename.replaceAll(oldName, newName);
const newFilePath = path.join(path.dirname(filePath), newBasename);
filesToRename.push({
old: filePath,
new: newFilePath
});
}
}
// rename deeper files first to avoid parent rename blocking children
filesToRename.sort((a, b) => b.old.length - a.old.length);
for (const {
old: oldPath,
new: newPath
} of filesToRename) {
if (fs.existsSync(oldPath)) {
fs.renameSync(oldPath, newPath);
}
}
// explicitly rewrite package.json to ensure scope and fields are correct
const packageJsonPath = path.join(oldCwd, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
pkg.name = newScopedName;
if (pkg.main) {
pkg.main = pkg.main.replaceAll(oldName, newName);
}
if (pkg.module) {
pkg.module = pkg.module.replaceAll(oldName, newName);
}
if (pkg.repository && pkg.repository.url) {
pkg.repository.url = pkg.repository.url.replaceAll(oldName, newName);
}
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
}
// delete stale custom-elements.json
const ceJsonPath = path.join(oldCwd, 'custom-elements.json');
if (fs.existsSync(ceJsonPath)) {
fs.unlinkSync(ceJsonPath);
}
// delete stale package-lock.json to avoid confusion
const lockPath = path.join(oldCwd, 'package-lock.json');
if (fs.existsSync(lockPath)) {
fs.unlinkSync(lockPath);
}
s.stop((0, _statements.merlinSays)(`Files rewritten`));
// rename parent directory
fs.renameSync(oldCwd, newDir);
if (!commandRun.options.quiet) {
p.note(`${_picocolors.default.bold(oldName)} renamed to ${_picocolors.default.bold(newName)}\n\nš New folder: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(newDir)))}\nš Package name: ${_picocolors.default.bold(_picocolors.default.yellow(_picocolors.default.bgBlack(newScopedName)))}`);
}
// regenerate custom-elements.json
try {
let s2 = p.spinner();
s2.start((0, _statements.merlinSays)('Regenerating custom-elements.json'));
await (0, _utils.exec)(`cd ${newDir} && ${commandRun.options.npmClient || 'npm'} run analyze`);
s2.stop((0, _statements.merlinSays)('custom-elements.json regenerated'));
} catch (e) {
console.warn(_picocolors.default.yellow('Could not regenerate custom-elements.json. Run `npm run analyze` manually.'));
}
}