@lenne.tech/cli
Version:
lenne.Tech CLI: lt
181 lines (180 loc) • 7.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tools = void 0;
const fs_1 = require("fs");
const singleComment = Symbol('singleComment');
const multiComment = Symbol('multiComment');
const stripWithoutWhitespace = () => '';
const stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, ' ');
const isEscaped = (jsonString, quotePosition) => {
let index = quotePosition - 1;
let backslashCount = 0;
while (jsonString[index] === '\\') {
index -= 1;
backslashCount += 1;
}
return Boolean(backslashCount % 2);
};
class Tools {
/**
* Constructor for integration of toolbox
*/
constructor(toolbox) {
this.toolbox = toolbox;
this.hintShown = false;
}
/**
* Check if --help-json flag is set; if so, print the command definition as JSON and return true.
* Commands should call this early and return immediately when it returns true.
*
* @param definition - The command's help definition (name, description, options, etc.)
* @returns true if --help-json was handled (caller should return), false otherwise
*/
helpJson(definition) {
const { parameters } = this.toolbox;
if (!parameters.options['help-json'] && !parameters.options.helpJson) {
return false;
}
console.debug(JSON.stringify(definition, null, 2));
return true;
}
/**
* Show a hint when running in non-interactive mode (no TTY)
* Suggests using CLI parameters instead of interactive prompts.
* Only shows once per session.
*
* Skipped when the caller already passed `--noConfirm` — they're
* deliberately running headless and don't need a hint to repeat the
* command, which would just be confusing noise (the friction-log
* complaint that triggered this guard).
*
* @param usage - Example command with parameters, e.g. "lt fullstack init --name <name> --frontend <nuxt|angular> --noConfirm"
*/
nonInteractiveHint(usage) {
var _a;
if (this.hintShown || process.stdin.isTTY) {
return;
}
const { parameters, print } = this.toolbox;
const noConfirm = (_a = parameters === null || parameters === void 0 ? void 0 : parameters.options) === null || _a === void 0 ? void 0 : _a.noConfirm;
if (noConfirm === true || noConfirm === 'true') {
this.hintShown = true;
return;
}
this.hintShown = true;
print.info(print.colors.yellow(`Hint: Non-interactive mode detected. Use parameters to skip prompts:`));
print.info(print.colors.yellow(` ${usage}`));
print.info('');
}
/**
* Strip and save JSON file
*/
stripAndSaveJsonFile(path) {
const content = this.stripJsonComments((0, fs_1.readFileSync)(path, 'utf8'));
(0, fs_1.writeFileSync)(path, content);
return content;
}
/**
* Strip JSON comments from a string
* Inspired by https://github.com/sindresorhus/strip-json-comments/blob/main/index.js
*/
stripJsonComments(jsonString, { trailingCommas = false, whitespace = true } = {}) {
if (typeof jsonString !== 'string') {
throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``);
}
const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
let isInsideString = false;
let isInsideComment = false;
let offset = 0;
let buffer = '';
let result = '';
let commaIndex = -1;
for (let index = 0; index < jsonString.length; index++) {
const currentCharacter = jsonString[index];
const nextCharacter = jsonString[index + 1];
if (!isInsideComment && currentCharacter === '"') {
// Enter or exit string
const escaped = isEscaped(jsonString, index);
if (!escaped) {
isInsideString = !isInsideString;
}
}
if (isInsideString) {
continue;
}
if (!isInsideComment && currentCharacter + nextCharacter === '//') {
// Enter single-line comment
buffer += jsonString.slice(offset, index);
offset = index;
isInsideComment = singleComment;
index++;
}
else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') {
// Exit single-line comment via \r\n
index++;
isInsideComment = false;
buffer += strip(jsonString, offset, index);
offset = index;
continue;
}
else if (isInsideComment === singleComment && currentCharacter === '\n') {
// Exit single-line comment via \n
isInsideComment = false;
buffer += strip(jsonString, offset, index);
offset = index;
}
else if (!isInsideComment && currentCharacter + nextCharacter === '/*') {
// Enter multiline comment
buffer += jsonString.slice(offset, index);
offset = index;
isInsideComment = multiComment;
index++;
continue;
}
else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') {
// Exit multiline comment
index++;
isInsideComment = false;
buffer += strip(jsonString, offset, index + 1);
offset = index + 1;
continue;
}
else if (trailingCommas && !isInsideComment) {
if (commaIndex !== -1) {
if (currentCharacter === '}' || currentCharacter === ']') {
// Strip trailing comma
buffer += jsonString.slice(offset, index);
result += strip(buffer, 0, 1) + buffer.slice(1);
buffer = '';
offset = index;
commaIndex = -1;
}
else if (currentCharacter !== ' ' &&
currentCharacter !== '\t' &&
currentCharacter !== '\r' &&
currentCharacter !== '\n') {
// Hit non-whitespace following a comma; comma is not trailing
buffer += jsonString.slice(offset, index);
offset = index;
commaIndex = -1;
}
}
else if (currentCharacter === ',') {
// Flush buffer prior to this point, and save new comma index
result += buffer + jsonString.slice(offset, index);
buffer = '';
offset = index;
commaIndex = index;
}
}
}
return result + buffer + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
}
}
exports.Tools = Tools;
/**
* Extend toolbox
*/
exports.default = (toolbox) => {
toolbox.tools = new Tools(toolbox);
};