@knapsack/app
Version:
Build Design Systems on top of knapsack, by Basalt
283 lines (256 loc) • 7.17 kB
text/typescript
/**
* Copyright (C) 2018 Basalt
This file is part of Knapsack.
Knapsack is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
Knapsack is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along
with Knapsack; if not, see <https://www.gnu.org/licenses>.
*/
import fs from 'fs-extra';
import os from 'os';
import qs from 'qs';
import yaml from 'js-yaml';
import resolve from 'resolve';
import { execSync } from 'child_process';
import prettier from 'prettier';
import { relative, join, isAbsolute } from 'path';
import * as log from '../cli/log';
/**
* @param fileName - path to where JSON file should be written
* @param object - data to turn to JSON
*/
export function writeJson(fileName: string, object: object): Promise<void> {
return fs.writeFile(fileName, JSON.stringify(object, null, 2) + os.EOL);
}
/**
* @param fileName - path to where JSON file should be read
*/
export function readJson(fileName: string): Promise<{ [k: string]: any }> {
return fs.readFile(fileName, 'utf8').then(file => JSON.parse(file));
}
export function readJsonSync(fileName: string): object {
return JSON.parse(fs.readFileSync(fileName, 'utf8'));
}
export function writeYaml(fileName: string, object: object): Promise<void> {
return fs.writeFile(fileName, yaml.safeDump(object, { noRefs: true }));
}
export function readYaml(fileName: string): Promise<{ [k: string]: any }> {
return fs
.readFile(fileName, 'utf8')
.then(file => yaml.safeLoad(file, { filename: fileName }));
}
export function readYamlSync(fileName: string): object {
return yaml.safeLoad(fs.readFileSync(fileName, 'utf8'), {
filename: fileName,
});
}
export function isRemoteUrl(url: string): boolean {
return url.startsWith('http') || url.startsWith('//');
}
/**
* Get a NPM package's package.json as object
*/
export function getPkg(pkg: string): object {
const pkgPath = require.resolve(`${pkg}/package.json`);
return readJsonSync(pkgPath);
}
export function fileExists(filePath: string): boolean {
try {
return fs.statSync(filePath).isFile();
} catch (err) {
return false;
}
}
export function dirExists(dirPath: string): boolean {
try {
return fs.statSync(dirPath).isDirectory();
} catch (err) {
return false;
}
}
export function fileExistsOrExit(filePath: string, msg?: string): void {
if (fileExists(filePath)) return;
log.error(msg || `This file does not exist! ${filePath}`);
process.exit(1);
}
export function dirExistsOrExit(dirPath: string, msg?: string): void {
if (dirExists(dirPath)) return;
log.error(msg || `This folder does not exist! ${dirPath}`);
process.exit(1);
}
export function getGitBranch(): string {
try {
const branch = execSync('git symbolic-ref --short HEAD', {
encoding: 'utf8',
});
return branch?.trim();
} catch (err) {
console.error(`Uh oh! error thrown in getGitBranch: ${err.message}`);
return '';
}
}
export function resolvePath({
path,
resolveFromDirs = [],
}: {
path: string;
resolveFromDirs?: string[];
}): {
exists: boolean;
absolutePath?: string;
relativePathFromCwd?: string;
originalPath: string;
type: 'absolute' | 'relative' | 'package' | 'unknown';
} {
if (isAbsolute(path)) {
return {
originalPath: path,
absolutePath: path,
exists: fileExists(path),
relativePathFromCwd: relative(process.cwd(), path),
type: 'absolute',
};
}
let absolutePath: string;
try {
absolutePath = resolve.sync(path, {
basedir: process.cwd(),
});
if (absolutePath) {
return {
originalPath: path,
exists: true,
absolutePath,
relativePathFromCwd: relative(process.cwd(), absolutePath),
type: 'package',
};
}
} catch (e) {
// oh well
}
let result: {
exists: boolean;
absolutePath?: string;
relativePathFromCwd?: string;
originalPath: string;
type: 'relative';
};
resolveFromDirs.forEach(base => {
const x = join(process.cwd(), join(base, path));
if (fileExists(x)) {
result = {
exists: true,
absolutePath: x,
originalPath: path,
relativePathFromCwd: relative(process.cwd(), x),
type: 'relative',
};
}
});
if (result?.exists) return result;
return {
exists: false,
originalPath: path,
type: 'unknown',
};
}
/**
* Parse QueryString, decode non-strings
* Changes strings like `'true'` to `true` among others like numbers
* @see qsStringify
*/
export function qsParse(querystring: string): object {
return qs.parse(querystring, {
// This custom decoder is for turning values like `foo: "true"` into `foo: true`, along with Integers, null, and undefined.
// https://github.com/ljharb/qs/issues/91#issuecomment-437926409
decoder(str) {
const strWithoutPlus = str.replace(/\+/g, ' ');
if (/^(\d+|\d*\.\d+)$/.test(str)) {
return parseFloat(str);
}
const keywords = {
true: true,
false: false,
null: null,
undefined,
};
if (str in keywords) {
return keywords[str];
}
// utf8
try {
return decodeURIComponent(strWithoutPlus);
} catch (e) {
return strWithoutPlus;
}
},
});
}
/**
* Turn object of data into query string
* @see qsParse
*/
export function qsStringify(data: object): string {
return qs.stringify(data);
}
export function base64ToString(b64: string): string {
return Buffer.from(b64, 'base64').toString();
}
export function stringToBase64(string: string): string {
return Buffer.from(string).toString('base64');
}
/**
* Format code with Prettier
* If it can't format, it just returns original code
* @link https://prettier.io/docs/en/options.html#parser
*/
export function formatCode({
code,
language,
}: {
code: string;
language: string;
}): string {
if (!code) return code;
try {
const format = parser =>
prettier.format(code?.trim(), {
trailingComma: 'all',
singleQuote: true,
semi: true,
parser,
htmlWhitespaceSensitivity: 'ignore',
});
switch (language) {
case 'html':
return format('html');
case 'react':
case 'js':
case 'jsx':
return format('babel');
case 'ts':
case 'tsx':
case 'react-typescript':
return format('typescript');
case 'json':
return format('json');
case 'yml':
case 'yaml':
return format('yaml');
case 'md':
case 'markdown':
return format('markdown');
default:
return code?.trim();
}
} catch (error) {
console.error(error);
return code;
}
}