UNPKG

servicestack-cli

Version:

Simple CLI utils for ServiceStack projects

308 lines (248 loc) 9.86 kB
import * as fs from "fs"; import * as url from 'url'; import * as request from "request"; var packageConf = require('../package.json'); const ALIAS = { "cs": "csharp", "ts": "typescript", "tsd": "typescript.d", "typescriptd": "typescript.d", "kt": "kotlin", "vb": "vbnet", "fs": "fsharp", } const REF_EXT = { "csharp": "dtos.cs", "typescript": "dtos.ts", "typescript.d": "dtos.d.ts", "swift": "dtos.swift", "java": "dtos.java", "kotlin": "dtos.kt", "vbnet": "dtos.vb", "fsharp": "dtos.fs", }; export function cli(args: string[]) { const nodeExe = args[0]; const cliPath = args[1]; const scriptNameExt = splitOnLast(cliPath.replace(/\\/g, '/'), '/')[1]; const scriptName = splitOnLast(scriptNameExt, '.')[0]; const cliLang = splitOnLast(scriptName, '-')[0]; const lang = ALIAS[cliLang] || cliLang; const cwd = process.cwd(); const cmdArgs = args.slice(2); const dtosExt = REF_EXT[lang]; // console.log({ cliPath, scriptNameExt, cliLang, lang, cmdArgs, dtosExt }); // console.log(packageConf.version); // process.exit(0); const isDefault = cmdArgs.length == 0; if (isDefault) { execDefault(lang, cwd, dtosExt); return; } const arg1 = normalizeSwitches(cmdArgs[0]); const isHelp = ["/h", "/?", "/help"].indexOf(arg1) >= 0; if (isHelp) { execHelp(lang, scriptName, dtosExt); return; } const isVersion = ["/v", "/version"].indexOf(arg1) >= 0; if (isVersion) { console.log(`Version: ${packageConf.version}`); return; } if (["/"].indexOf(arg1[0]) === -1 && cmdArgs.length <= 2) { try { const target = arg1; if (target.indexOf("://") >= 0) { var typesUrl = target.indexOf(`/types/${lang}`) == -1 ? combinePaths(target, `/types/${lang}`) : target; var fileName = `Reference.${dtosExt}`; if (cmdArgs.length >= 2 && cmdArgs[1]) { fileName = cmdArgs[1]; } else { const parts = url.parse(typesUrl).host.split('.'); fileName = parts.length >= 2 ? parts[parts.length - 2] : parts[0]; } if (!fileName.endsWith(`.${dtosExt}`)) { fileName = fileName + `.${dtosExt}`; } saveReference(lang, typesUrl, cwd, fileName); } else { updateReference(lang, cwd, target); } } catch (e) { handleError(e); } return; } console.log(`Unknown Command: ${scriptName} ${cmdArgs.join(' ')}\n`); execHelp(lang, scriptName, dtosExt); return -1; } function handleError(e, msg:string=null) { if (msg) { console.error(msg); } console.error(e.message || e); process.exit(-1); } export function updateReference(lang: string, cwd: string, target:string) { const targetExt = splitOnLast(target, '.')[1]; const langExt = splitOnLast(REF_EXT[lang], '.')[1]; if (targetExt != langExt) throw new Error(`Invalid file type: '${target}', expected '.${langExt}' source file`); const existingRefPath = combinePaths(cwd, target); if (!fs.existsSync(existingRefPath)) throw new Error(`File does not exist: ${existingRefPath.replace(/\\/g, '/')}`); const existingRefSrc = fs.readFileSync(existingRefPath, 'utf8'); if (existingRefSrc.indexOf("Options:") === -1) throw new Error(`ERROR: ${target} is not an existing Swift ServiceStack Reference`); var options = {}; var baseUrl = ""; var lines = existingRefSrc.split(/\r?\n/); for (var line of lines) { if (line.startsWith("*/")) break; if (lang === "vbnet"){ if (line.trim().length === 0) break; if (line[0] === "'") line = line.substring(1); } if (line.startsWith("BaseUrl: ")) { baseUrl = line.substring("BaseUrl: ".length); } else if (baseUrl) { if (line.indexOf("//") === -1 && line.indexOf("'") === -1) { var parts = splitOnFirst(line, ":"); if (parts.length === 2) { var key = parts[0].trim(); var val = parts[1].trim(); options[key] = val; } } } } if (!baseUrl) throw new Error(`ERROR: Could not find baseUrl in ${target}`); var qs = ""; for (var key in options) { qs += qs.length > 0 ? "&" : "?"; qs += `${key}=${encodeURIComponent(options[key])}`; } const typesUrl = combinePaths(baseUrl, `/types/${lang}`) + qs; saveReference(lang, typesUrl, cwd, target); } export function saveReference(lang: string, typesUrl: string, cwd: string, fileName: string) { const filePath = combinePaths(cwd, fileName); request(typesUrl, (err, res, dtos) => { if (err) handleError(err); try { if (dtos.indexOf("Options:") === -1) throw new Error(`ERROR: Invalid Response from ${typesUrl}`); const filePathExists = fs.existsSync(filePath); fs.writeFileSync(filePath, dtos, 'utf8'); console.log(filePathExists ? `Updated: ${fileName}` : `Saved to: ${fileName}`); if (lang == "swift") { importSwiftClientSources(cwd); } if (process.env.SERVICESTACK_TELEMETRY_OPTOUT != "1") { var cmdType = filePathExists ? "updateref" : "addref"; const statsUrl = `https://servicestack.net/stats/${cmdType}/record?name=${lang}&source=cli&version=${packageConf.version}`; try { request(statsUrl); } catch(ignore){} } } catch (e) { handleError(e, `ERROR: Could not write DTOs to: ${fileName}`); } }); } export function execDefault(lang: string, cwd: string, dtosExt:string) { var matchingFiles = []; fs.readdirSync(cwd).forEach(entry => { if (entry.endsWith(dtosExt)) { matchingFiles.push(entry); } }); if (matchingFiles.length === 0) { console.error(`No '.${dtosExt}' files found`); process.exit(-1); } else { matchingFiles.forEach(target => { try { updateReference(lang, cwd, target); } catch(e) { console.error(e.message || e); } }); } } export function execHelp(lang: string, scriptName: string, dtosExt: string) { const USAGE = `Version: ${packageConf.version} Syntax: ${scriptName} [options] [BaseUrl|File] Add a new ServiceStack Reference: ${scriptName} {BaseUrl} ${scriptName} {BaseUrl} {File} Update all *.${dtosExt} ServiceStack References in Current Directory: ${scriptName} Update an existing ServiceStack Reference: ${scriptName} {File}.${dtosExt} Options: -h, --help Print this message -v, --version Print this version This tool collects anonymous usage to determine the most used languages to improve your experience. To disable set SERVICESTACK_TELEMETRY_OPTOUT=1 environment variable to 1 using your favorite shell.`; console.log(USAGE); } export function importSwiftClientSources(cwd:string) { const clientSrcPath = combinePaths(cwd, "JsonServiceClient.swift"); if (!fs.existsSync(clientSrcPath)) { const clientSrcUrl = "https://servicestack.net/dist/swiftref/JsonServiceClient.swift"; request(clientSrcUrl, (err, res, clientSrc) => { if (err) handleError(err); try { if (clientSrc.indexOf("JsonServiceClient") === -1) throw new Error(`ERROR: Invalid Response from ${clientSrcUrl}\n${clientSrc}`); fs.writeFileSync(clientSrcPath, clientSrc, 'utf8'); console.log("Imported: JsonServiceClient.swift"); } catch (e) { handleError(e, `ERROR: Could not import: JsonServiceClient.swift`); } }); } } export const normalizeSwitches = (cmd:string) => cmd.replace(/^-+/,'/'); //utils export const splitOnFirst = (s: string, c: string): string[] => { if (!s) return [s]; var pos = s.indexOf(c); return pos >= 0 ? [s.substring(0, pos), s.substring(pos + 1)] : [s]; }; export const splitOnLast = (s: string, c: string): string[] => { if (!s) return [s]; var pos = s.lastIndexOf(c); return pos >= 0 ? [s.substring(0, pos), s.substring(pos + 1)] : [s]; }; export const combinePaths = (...paths: string[]): string => { var parts = [], i, l; for (i = 0, l = paths.length; i < l; i++) { var arg = paths[i]; parts = arg.indexOf("://") === -1 ? parts.concat(arg.split("/")) : parts.concat(arg.lastIndexOf("/") === arg.length - 1 ? arg.substring(0, arg.length - 1) : arg); } var combinedPaths = []; for (i = 0, l = parts.length; i < l; i++) { var part = parts[i]; if (!part || part === ".") continue; if (part === "..") combinedPaths.pop(); else combinedPaths.push(part); } if (parts[0] === "") combinedPaths.unshift(""); return combinedPaths.join("/") || (combinedPaths.length ? "/" : "."); };