feat: provide toolkit tools

This commit is contained in:
noratu
2024-05-28 02:26:44 +00:00
committed by haochengkuo
parent 682fa7aa99
commit e0a0244241
8 changed files with 1299 additions and 0 deletions

248
tool/GenerateJSDepend.php Executable file
View File

@ -0,0 +1,248 @@
#!/usr/bin/php
<?php
/* Decode json file to php object */
function DecodeJsonFile($file)
{
$content = @file_get_contents($file);
if (FALSE === $content) {
echo $file . ": Read error.\n";
return NULL;
}
$result = json_decode($content, true);
if (!is_array($result)) {
echo $file . ": Json parse failed.\n";
}
return $result;
}
/* Get all dependency from one js file. e.g. */
/*
fn = "js/abc.js": {
"SYNO.AAA": {
"depend" : [
"SYNO.Parent"
]
},
"SYNO.BBB" : {
"depend" : [
"SYNO.Father"
]
}
}
input -->
fn["js/abc.js"]
output -->
["SYNO.Parent", "SYNO.Father"]
*/
function MergeDependency($fnList)
{
$depend = array();
foreach ($fnList as $fnName => $fnConfig) {
if (is_array($fnConfig['depend'])) {
$depend = array_merge($depend, $fnConfig['depend']);
}
}
return array_unique($depend);
}
function FindSrcFileForFn($fnName)
{
global $ConfigDebug;
foreach ($ConfigDebug as $srcFile => $fileConfig) {
if (isSet($fileConfig[$fnName])) {
return $srcFile;
}
}
return FALSE;
}
function PostTraversalDependency($file, $depend, &$queue)
{
if (FALSE !== array_search($file, $queue)) {
return;
}
if (is_array($depend[$file])) {
foreach ($depend[$file] as $depFn) {
$depFile = FindSrcFileForFn($depFn);
if (FALSE === $depFile || $file === $depFile) {
continue;
}
PostTraversalDependency($depFile, $depend, $queue);
}
}
if (isSet($depend[$file])) {
$queue[] = $file;
}
}
function Usage()
{
global $argv;
echo <<<EOD
Usage: {$argv[0]} module_dir
EOD;
}
/* ----------------------- */
/* ----------------------- */
/* main access entry point */
/* ----------------------- */
/* ----------------------- */
if (!is_dir($argv[1])) {
echo "Error: {$argv[1]} is not a directory.\n";
Usage();
exit(1);
}
$CWD = getcwd();
chdir($argv[1]);
$InputFiles = array(
'debug' => 'config.debug',
'define' => 'config.define',
'auto_debug' => 'auto_config.debug'
);
$OutputFiles = array(
'config' => 'config',
'depend' => 'config.depends'
);
$JSInfo = array();
$Config = array();
$define_path = $InputFiles['define'];
if (file_exists($define_path)) {
$ConfigDefine = DecodeJsonFile($define_path);
} else {
echo "Skip parsing: config.define not exist in config.depends\n";
$ConfigDefine = array();
}
/* try to read from auto config path */
if (file_exists($InputFiles['debug'])) {
$ConfigDebug = DecodeJsonFile($InputFiles['debug']);
} else if (file_exists($InputFiles['auto_debug'])) {
$ConfigDebug = DecodeJsonFile($InputFiles['auto_debug']);
} else {
echo "Skip parsing: config.debug and auto_config.debug not exist\n";
$ConfigDebug = array();
}
if (!is_array($ConfigDebug) || !is_array($ConfigDefine)) {
exit(1);
}
/* merge config, parse fn and dependency info */
foreach ($ConfigDefine as $dstFile => $srcFiles) {
$Config[$dstFile] = array();
$JSInfo[$dstFile] = array('jsFiles' => array(), 'fnList' => array(), 'params' => null);
$srcJsFiles = array();
if (isSet($srcFiles['JSfiles'])) { /* with parameters */
$srcJsFiles = $srcFiles['JSfiles'];
} else {
$srcJsFiles = $srcFiles;
}
foreach ($srcJsFiles as $files) {
foreach(glob($files) as $file) {
if (!isSet($ConfigDebug[$file])) {
$ConfigDebug[$file] = array();
}
$Config[$dstFile] = array_merge($Config[$dstFile], $ConfigDebug[$file]);
$JSInfo[$dstFile]['jsFiles'][$file] = MergeDependency($ConfigDebug[$file]);
$JSInfo[$dstFile]['fnList'] = array_merge($JSInfo[$dstFile]['fnList'], array_keys($ConfigDebug[$file]));
if (isSet($srcFiles['params'])) {
$JSInfo[$dstFile]['params'] = $srcFiles['params'];
}
}
}
}
/* copy virtual config */
if (isSet($ConfigDebug['.url'])) {
$Config['.url'] = $ConfigDebug['.url'];
}
/* Resolve JS compress dependency */
foreach ($JSInfo as $dstFile => &$dstConfig) {
$queue = array();
foreach (array_keys($dstConfig['jsFiles']) as $file) {
PostTraversalDependency($file, $dstConfig['jsFiles'], $queue);
}
if(isSet($JSInfo[$dstFile]['params'])) {
$JSInfo[$dstFile] = array('files' => $queue, 'params' => $JSInfo[$dstFile]['params']);
} else {
$JSInfo[$dstFile] = $queue;
}
}
function getFlattenDepList($dstConfig, $comp) {
$dependList = array();
if (array_key_exists('depend', $comp)) {
foreach ($comp['depend'] as $item) {
if (array_key_exists($item, $dstConfig)) {
// internal depend => get its depend list
$flatDep = getFlattenDepList($dstConfig, $dstConfig[$item]);
$dependList = array_merge($flatDep, $dependList);
$dependList = array_values(array_unique($dependList));
} else {
$dependList[] = $item;
}
}
}
return $dependList;
}
/* flatten dependency */
if ($argv[2] != "") {
echo "Flatten Dependency...\n";
foreach ($Config as $dstFile => &$dstConfig) {
foreach ($dstConfig as $comp => &$compConfig) {
if (array_key_exists('depend', $compConfig)) {
$dependList = getFlattenDepList($dstConfig, $compConfig);
if (count($dependList) === 0) {
unset($compConfig['depend']);
} else {
$compConfig['depend'] = $dependList;
}
}
}
}
echo "Flatten Dependency Done...\n";
}
function GenerateConfig()
{
global $Config;
$result = json_encode($Config);
if( (function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()) ||
(ini_get('magic_quotes_sybase') && (strtolower(ini_get('magic_quotes_sybase')) != "off" )) ) {
$result = stripslashes($result);
}
return $result;
}
file_put_contents($OutputFiles['depend'], json_encode($JSInfo));
if (count($Config) > 0) {
file_put_contents($OutputFiles['config'], GenerateConfig());
}
exit(0);
?>

340
tool/GenerateModuleFiles.php Executable file
View File

@ -0,0 +1,340 @@
#!/usr/bin/php
<?php
/* Decode json file to php object */
function DecodeJsonFile($file)
{
$content = @file_get_contents($file);
if (FALSE === $content) {
echo $file . ": Read error.\n";
return NULL;
}
$result = json_decode($content, true);
if (!is_array($result)) {
echo $file . ": Json parse failed.\n";
}
return $result;
}
function HasExternalDepend(&$dstConfig, &$comp)
{
if (array_key_exists($comp, $dstConfig)) {
$compConfig = $dstConfig[$comp];
if (array_key_exists('depend', $compConfig)) {
foreach ($compConfig['depend'] as $item) {
if (HasExternalDepend($dstConfig, $item)) {
return true;
}
}
}
return false;
} else {
return true;
}
}
function GenerateCompassConfig()
{
$compassConfig = <<<EOD
# Get the directory that this configuration file exists in
dir = File.dirname(__FILE__)
load File.join(dir, '../../../resources/scss/src')
ext_dir = "/usr/syno/synoman/scripts/ext-3.4/ux/scss/src"
# compatibility with 6.0
ext_dir = "/source/synojslib/ext-3.4/ux/scss/src" unless File.directory?(ext_dir)
add_import_path ext_dir
syno_ext_dir = "/usr/syno/synoman/synoSDSjslib/scss"
# compatibility with 6.0
syno_ext_dir="/source/synoSDSjslib/scss" unless File.directory?(syno_ext_dir)
add_import_path syno_ext_dir
# Compass configurations
sass_path = dir
css_path = File.join(dir, "..")
images_path = "/usr/syno/synoman/scripts/ext-3.4/ux"
# compatibility with 6.0
images_path = "/source/synojslib/ext-3.4/ux" unless File.directory?(images_path)
environment = :production
output_style = :compressed
sprite_load_path = File.join(dir, "../images")
http_path = "./"
generated_images_dir = "../images"
module Sass::Script::Functions
def real_path(image_file)
if Compass.configuration.images_path
File.join(Compass.configuration.images_path, image_file)
else
File.join(Compass.configuration.project_path, image_file)
end
end
def md5(filename)
if filename && File.exist?(filename)
digest = Digest::MD5.new()
digest << File.read(filename)
unquoted_string(digest.hexdigest)
else
Compass::Util.compass_warn(
"File not found: #{filename}, cannot do prevent cache signature."
)
unquoted_string('no_image_found')
end
end
def syno_md5(image_file)
image_file = image_file.respond_to?(:value) ? image_file.value : image_file
if File.exists?(image_file)
md5(image_file)
else
md5(real_path(image_file))
end
end
end
EOD;
return $compassConfig;
}
function GenerateMakefile($compress)
{
global $ConfigDefine;
if (true === $compress) {
echo "Compress to Gzip: [{$compress}]\n";
}
$JSTarget = array();
foreach ($ConfigDefine as $dstFile => $srcFiles) {
array_push($JSTarget, $dstFile);
}
$CompressedFiles = implode(' ', array_keys($ConfigDefine));
$Makefile = <<<EOD
include /env.mak
JS_COMPILER=/usr/local/tool/shrinksafe.php $(JS_COMPILER_ARGV)
SASS_COMPILER=/usr/bin/compass $(SASS_COMPILER_ARGV)
CONFIG_PARSER=/usr/syno/bin/GenerateModuleFiles.php
JS_DEPENDER=/usr/syno/bin/GenerateJSDepend.php
AUTO_CONFIG_TOOL=/usr/local/tool/parse_requires.py
CONFIGFILE=config
STYLEFILE=style.css
SCSS_FILE=scss/style.scss
SASS_CACHE=scss/.sass-cache
COMPASS_CONFIG=scss/config.rb
JSFILES={$CompressedFiles}
JS_INPUT=$(shell /usr/bin/find $(JS_DIR) -name "*.js")
TARGET_JSCompress=CSSCompress config.depends \$(JSFILES)
TARGET_install=install_CSSCompress
TARGET_clean=clean_CSSCompress
ifneq ("$(wildcard app.config)","")
DEBUGFILE:=auto_config.debug
else
DEBUGFILE:=config.debug
endif
USE_COMPRESS_GZIP={$compress}
ifeq ("\$(SYNOCOMPRESS)", "")
USE_COMPRESS_GZIP=no
endif
ifeq ("\$(USE_COMPRESS_GZIP)", "yes")
STYLEFILE_GZIP:=$(addsuffix .gz, $(STYLEFILE))
JSFILES_GZIP:=$(addsuffix .gz, $(JSFILES))
TARGET_install+=install_GzipCompress
TARGET_clean+=clean_GzipCompress
endif
JSCompress: $(TARGET_JSCompress)
JSCompress_AutoConfig: $(TARGET_JSCompress)
CSSCompress:
if [ -f \$(SCSS_FILE) ]; then \$(SASS_COMPILER) compile scss; fi
clean_JSCompress: $(TARGET_clean)
if [ -n "\$(JSFILES)" ]; then rm -f \$(JSFILES); fi
if [ -n "auto_config.debug" ]; then rm -f auto_config.debug; fi
if [ -n "config.depends" ]; then rm -f config.depends; fi
if [ -n "config" ]; then rm -f config; fi
clean_CSSCompress:
if [ -f \$(SCSS_FILE) ]; then rm -f \$(STYLEFILE); fi
if [ -d \$(SASS_CACHE) ]; then rm -rf \$(SASS_CACHE); fi
clean_GzipCompress:
if [ -n "\$(JSFILES_GZIP)" ]; then rm -f \$(JSFILES_GZIP); fi
if [ -f "\$(STYLEFILE_GZIP)" ]; then rm -f \$(STYLEFILE_GZIP); fi
install_JSCompress: $(TARGET_install)
[ -d \$(INSTALLDIR) ] || install -d \$(INSTALLDIR)
if [ -f \$(CONFIGFILE) ]; then cp \$(CONFIGFILE) \$(INSTALLDIR); fi
if [ -n "\$(JSFILES)" ]; then cp \$(JSFILES) \$(INSTALLDIR); fi
if [ -f \$(STYLEFILE) ]; then cp -f \$(STYLEFILE) \$(INSTALLDIR); fi
install_CSSCompress:
# already install style.css install_JSCompress
install_GzipCompress: \$(JSFILES) CSSCompress
if [ -f "\$(STYLEFILE)" ] && [ -s "\$(STYLEFILE)" ]; then \$(SYNOCOMPRESS) \$(STYLEFILE); fi
if [ -n "\$(JSFILES)" ]; then \$(SYNOCOMPRESS) \$(JSFILES); fi
[ -d "\$(INSTALLDIR)" ] || install -d \$(INSTALLDIR)
if [ -f "\$(STYLEFILE_GZIP)" ]; then cp -f \$(STYLEFILE_GZIP) \$(INSTALLDIR); fi
for JSFILE in $(JSFILES_GZIP); do \
if [ -f "\$\$JSFILE" ]; then cp -f \$\$JSFILE $(INSTALLDIR); fi \
done
auto_config.debug: app.config config.define \$(JS_INPUT)
\$(AUTO_CONFIG_TOOL) --makegoal=\$(MAKECMDGOALS) \$(JS_DIR) \$(JS_NAMESPACE)
config.depends: $(DEBUGFILE) config.define
\$(JS_DEPENDER) \$(shell pwd)
EOD;
foreach ($ConfigDefine as $dstFile => $dstConfig) {
if(isSet($ConfigDefine[$dstFile]['JSfiles']) && isSet($ConfigDefine[$dstFile]['params'])) {
$Makefile .= $dstFile . ': config.depends ' . implode(' ', $ConfigDefine[$dstFile]['JSfiles']) . "\n";
$Makefile .= "\t$(JS_COMPILER) " . $ConfigDefine[$dstFile]['params'] . " -d ". $dstFile ." \"\$(shell pwd)/config.depends\"\n\n";
} else {
$Makefile .= $dstFile . ': config.depends ' . implode(' ', $dstConfig) . "\n";
$Makefile .= "\t$(JS_COMPILER) -d ". $dstFile ." \$(shell pwd)/config.depends\n\n";
}
}
return $Makefile;
}
function Usage()
{
global $argv;
echo <<<EOD
Usage: {$argv[0]} [-c yes|no] moduleDir
-c yes|no:
yes: Compress .css and .js to .gz
no: Don't compress to .gz
EOD;
}
/* ----------------------- */
/* ----------------------- */
/* main access entry point */
/* ----------------------- */
/* ----------------------- */
function GetOptions()
{
global $argv, $argc;
$options = "c:";
$opts = getopt($options);
foreach( $opts as $o => $a )
{
while ( $k = array_search( "-" . $o, $argv ) ) {
if ( $k ) {
unset( $argv[$k] );
$argc--;
}
if ( preg_match( "/^.*".$o.":.*$/i", $options ) ) {
unset( $argv[$k+1] );
$argc--;
}
}
}
$argv = array_merge( $argv );
return $opts;
}
$opts = GetOptions();
if (FALSE === $opts || $argc < 2 || $argc > 3) {
Usage();
exit(1);
}
/* Compress package's js, css to gzip */
$UseCompressGzip="yes";
if (file_exists("/lnxscripts")) {
$UseCompressGzip = "no";
} else if (isSet($opts['c'])) {
switch ($opts['c']) {
case "yes":
$UseCompressGzip = "yes";
break;
case "no":
$UseCompressGzip = "no";
break;
default:
echo "Error: Invalid Transpiler Option.\n";
Usage();
exit(1);
}
}
if (!is_dir($argv[1])) {
echo "Error: {$argv[1]} is not a directory.\n";
exit(1);
}
$CWD = getcwd();
chdir($argv[1]);
$InputFiles = array(
'debug' => 'config.debug',
'define' => 'config.define',
'auto_debug'=> 'auto_config.debug',
'depend' => 'config.depends'
);
$OutputFiles = array(
'config' => 'config',
'makefile' => 'Makefile.js.inc',
'style' => 'style.css',
'scss' => 'scss/style.scss',
'compass_config' => 'scss/config.rb'
);
if (file_exists("app.config") && file_exists("config.debug")) {
echo "Error > app.config and config.debug should not both exist\n";
exit(1);
}
$define_path = $InputFiles['define'];
if (file_exists($define_path)) {
$ConfigDefine = DecodeJsonFile($define_path);
} else {
echo "Skip parsing: config.define not exist\n";
$ConfigDefine = array();
}
if (!is_array($ConfigDefine)) {
exit(1);
}
/* Generate files */
file_put_contents($OutputFiles['makefile'], GenerateMakefile($UseCompressGzip));
// generate config.rb for compass if scss file exists
if (file_exists($OutputFiles['scss'])) {
file_put_contents($OutputFiles['compass_config'], GenerateCompassConfig());
}
chdir($CWD);
?>

10
tool/_babel.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
"presets": [
[
"/usr/lib/node_modules/@babel/preset-env",
{
modules: false
}
]
]
}

39
tool/_eslintrc.json Executable file
View File

@ -0,0 +1,39 @@
{
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2020
},
"env": {
"browser": true,
"es6": true
},
"rules": {
"no-unused-vars": ["off", {
"vars": "all",
"args": "none"
}],
"no-mixed-spaces-and-tabs": "off",
"no-empty": "off",
"no-prototype-builtins": "off",
"yoda": "off"
},
"globals": {
"SYNO": true,
"Ext": true,
"Vue": true,
"_S": true,
"_D": true,
"_JSLIBSTR": true,
"_WFT": false,
"_AST": true,
"_VCT": true,
"_VST": true,
"PhotoStation": true,
"Main": true,
"_PST": true,
"_T": true,
"_TT": true,
"Promise": true,
"synowebapi": true
}
}

142
tool/parse_requires.py Executable file
View File

@ -0,0 +1,142 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import os
import json
import re
import copy
from argparse import ArgumentParser
class Parser:
def __init__ (self, jsPath, makeGoal, appPrefix):
self.jsPath = jsPath
self.makeGoal = makeGoal
appPrefixOr = re.sub(r'\s+', '|', appPrefix)
print(appPrefixOr)
rePatterns = [
'(?://\s*@require\s*:\s*([\w\-_.]+))', # require
'(?:Ext\.define\s*\(\s*[\'\"]([\w\-_.]+))', # define
'(?:extend\s*:\s*\(*[\'\"]([\w\-_.]+))', # extend
'(?:([\w\-_.]+)\s*=\s*Ext.extend\s*\(\s*([\w\-_.]+))', # define2, extend2
'(?:((?:{})[\w\-_.]+)\s*=[^=])'.format(appPrefixOr), # define3
'(?://\s*@define\s*:\s*([\w\-_.]+))' # define4
]
self.matchRe = re.compile('|'.join(rePatterns))
self.replaceRe = re.compile('\/([^\/]+)$')
prefixNames = appPrefix.split(" ")
self.ourRe = re.compile('^({})'.format(appPrefixOr))
def run (self):
if len(self.makeGoal) == 0 or self.makeGoal == 'all' or self.makeGoal == 'JSCompress' or self.makeGoal == 'JSCompress_AutoConfig':
self.make()
if self.makeGoal == 'clean':
self.clean()
def make (self):
self.clean()
jsFiles = self.getJSFiles()
print("Auto config js files:")
print(jsFiles)
self.parseRequire(jsFiles)
def clean (self):
return os.system('/usr/bin/find -L ' + self.jsPath + ' -name auto_config.debug -exec rm -f {} \\;')
def getJSFiles (self):
return os.popen('/usr/bin/find -L {} -name "*.js"'.format(self.jsPath)).read().strip().split('\n')
def parseRequire (self, jsFiles):
generatedPath = 'auto_config.debug'
debugConfig = {}
for jsFile in jsFiles:
with open(jsFile) as file:
fnDefs = self.getFnDefs(file.read())
debugConfig[jsFile] = self.mixAppConfig(jsFile, fnDefs)
self.jsonToFile(debugConfig, generatedPath)
def getMatchResult(self, js):
matchRec = self.matchRe.findall(js)
result = []
for require, define, extend, define2, extend2, define3, define4 in matchRec:
if len(require) > 0:
result.append(('require', None, require))
elif len(define) > 0:
result.append(('define', define, None))
elif len(extend) > 0:
last = result.pop()
result.append(('define', last[1], extend))
elif len(define2) > 0:
result.append(('define', define2, extend2))
elif len(define3) > 0:
result.append(('define', define3, None))
elif len(define4) > 0:
result.append(('define', define4, None))
return result
def getFnDefs (self, js):
matches = self.getMatchResult(js)
fnDefs = {}
requires = []
for rule, fn, depend in matches:
if rule == 'require':
requires.append(depend)
elif rule == 'define':
fnDefs[fn] = requires
requires = []
if (depend):
self.prependExtends(fnDefs[fn], depend)
return fnDefs
def prependExtends (self, requires, extends):
if self.isOurExtends(extends):
requires.insert(0, extends)
def isOurExtends (self, extends):
return self.ourRe.search(extends)
def mixAppConfig (self, jsFile, fnDefs):
appConfPath = 'app.config'
conf = {}
try:
appConf = self.fileToJson(appConfPath)
except:
print('[Warning] no {} file or failed to parse into json'.format(appConfPath))
appConf = {}
for fnName, dependencies in fnDefs.iteritems():
conf[fnName] = {}
if fnName in appConf:
conf[fnName] = copy.deepcopy(appConf[fnName])
conf[fnName]['depend'] = dependencies
return conf;
def fileToJson (self, path):
with open(path) as file:
return json.load(file)
def jsonToFile (self, js, path):
with open(path, 'w') as file:
json.dump(js, file, indent=2)
def Main ():
parser = ArgumentParser(description='Chat @require to Config debug tool')
parser.add_argument('--makegoal', metavar='makegoals', type=str, help='your MAKECMDGOALS')
parser.add_argument('jspath', metavar='js-path', type=str, help='yout js path')
parser.add_argument('appprefix', metavar='app-prefix', type=str, help='application prefix (e.g. SYNO.SDS.Chat)')
options = parser.parse_args()
parser = Parser(jsPath=options.jspath, makeGoal=options.makegoal, appPrefix=options.appprefix)
parser.run()
Main()

449
tool/shrinksafe.php Executable file
View File

@ -0,0 +1,449 @@
#!/usr/bin/php
<?php
$JRE = "/usr/lib/jvm/java-6-jdk/bin/java";
$JSLINT = "/usr/local/tool/jslint+rhino.jar";
$RhinoCompressor = "/usr/local/tool/custom_rhino.jar";
$ClosureCompiler = "/usr/local/tool/closurecompiler.jar";
$YUICompressor = "/usr/local/tool/yuicompressor-2.4.2.jar";
$VerboseMsg = false;
$COPYRIGHT = "/* Copyright (c) " . date("Y") . " Synology Inc. All rights reserved. */\n\n";
$JSLINT_IGNORE = array("/variable .* declared in a block/",
"/.* is better written in dot notation/",
"/Use .* to compare with/",
"/Line breaking error/",
"/A constructor name should start with an uppercase letter/",
"/Missing 'new' prefix when invoking a constructor/",
"/Use the array literal notation*/",
"/JavaScript URL./",
"/document.write/",
"/eval/");
function ShouldIgnoreJSLintError($desc)
{
global $JSLINT_IGNORE;
foreach($JSLINT_IGNORE as $pattern) {
if (preg_match($pattern, $desc)) {
return true;
}
}
return false;
}
function FindJSHintComments($fileList) {
echo "== begin of JSHint comments ==\n";
$cmd = "grep -n ' jshint ' $fileList";
system($cmd);
$cmd = "grep -n ' global ' $fileList";
system($cmd);
echo "== end of JSHint comments ==\n";
}
function ValidateJSFiles($linter, $jsFiles) {
global $JRE, $JSLINT, $VerboseMsg;
$errors = 0;
$fileList = implode(' ', $jsFiles);
if ($linter === "rhino-jslint") {
$cmd = sprintf("%s -jar %s %s", $JRE, $JSLINT, $fileList);
} elseif ($linter === "jslint") {
$cmd = sprintf("/usr/bin/jslint --white --terse %s", $fileList);
} elseif ($linter === "jshint") {
$cmd = sprintf("/usr/bin/jshint %s", $fileList);
if ($VerboseMsg) {
FindJSHintComments($fileList);
}
} elseif ($linter === "eslint") {
$cmd = sprintf("/usr/bin/eslint --color %s", $fileList);
}
$handle = popen($cmd, "r");
if (false === $handle) {
echo "Failed to execute [".$cmd."]\n";
return 1;
}
while (!feof($handle)) {
$line = fgets($handle);
$line = trim($line);
if (!$line) {
continue;
}
if ($linter === "rhino-jslint") {
list($prog, $file, $lineno, $err, $desc) = explode(":", $line);
$invalidLine = !ShouldIgnoreJSLintError($desc);
} else {
$invalidLine = true;
}
if ($invalidLine) {
$errors++;
}
if ($invalidLine || $VerboseMsg) {
echo $line ."\n";
}
}
pclose($handle);
return $errors;
}
function CombineJSFiles($outFile, $jsFiles)
{
foreach ($jsFiles as $file) {
$fileContent = file_get_contents($file);
/* append newline to end of file in case last line is comment */
if ($fileContent !== false && substr($fileContent, -1) !== "\n") {
$fileContent .= "\n";
}
file_put_contents($outFile, $fileContent, FILE_APPEND);
}
}
function RhinoCompress($outFile, $jsFile)
{
global $JRE, $RhinoCompressor;
$cmd = sprintf("%s -jar %s -c %s",
escapeshellcmd($JRE), escapeshellarg($RhinoCompressor),
escapeshellarg($jsFile));
$handle = popen($cmd, "r");
/* custom_rhino.jar won't trim line-break */
$fp = fopen($outFile, "a");
while (!feof($handle)) {
fwrite($fp, trim(fgets($handle)));
}
fclose($fp);
pclose($handle);
}
/* Transpiler */
function TranspileJSFiles($transpiler, $outFile, $jsFiles)
{
$retCode = -1;
$tmpFile = getcwd()."/.jsTmp_" . strtr($outFile, "/", "_") . ".js";
CombineJSFiles($tmpFile, $jsFiles);
$retCode = $transpiler($outFile, $tmpFile);
@unlink($tmpFile);
return $retCode;
}
function BabelTranspile($outFile, $jsFile)
{
$retCode = -1;
$cmd = sprintf("babel %s -o %s", escapeshellarg($jsFile), escapeshellarg($outFile));
system($cmd, $retCode);
return $retCode;
}
function SkipTranspile($outFile, $jsFile)
{
$retCode = -1;
$cmd = sprintf("mv %s %s", escapeshellarg($jsFile), escapeshellarg($outFile));
system($cmd, $retCode);
return $retCode;
}
/* Compressor */
function ClosureCompile($outFile, $jsFile)
{
global $JRE, $ClosureCompiler;
$cmd = sprintf("%s -jar %s --warning_level QUIET --js %s >>%s",
escapeshellcmd($JRE), escapeshellarg($ClosureCompiler),
escapeshellarg($jsFile), escapeshellarg($outFile));
system($cmd);
}
function YUICompress($outFile, $jsFile)
{
global $JRE, $YUICompressor;
$cmd = sprintf("%s -jar %s %s >>%s",
escapeshellcmd($JRE), escapeshellarg($YUICompressor),
escapeshellarg($jsFile), escapeshellarg($outFile));
system($cmd);
}
function UglifyjsCompress($outFile, $jsFile)
{
$cmd = sprintf("/usr/bin/uglifyjs --comments '/license/i' -m -c drop_console,warnings=false %s >> %s",
escapeshellarg($jsFile), escapeshellarg($outFile));
system($cmd);
}
function TerserCompress($outFile, $jsFile)
{
$cmd = sprintf("/usr/bin/terser --comments '/license/i' -m -c drop_console,warnings=false %s >> %s",
escapeshellarg($jsFile), escapeshellarg($outFile));
system($cmd);
}
function SkipCompress($outFile, $jsFile)
{
$cmd = sprintf("mv %s %s",
escapeshellarg($jsFile), escapeshellarg($outFile));
system($cmd);
}
function CompressJSFiles($compressor, $outFile, $transpiledFile)
{
global $COPYRIGHT;
file_put_contents($outFile, $COPYRIGHT);
$compressor($outFile, $transpiledFile);
@unlink($transpiledFile);
}
function GetOptions()
{
global $argv;
$options = "svdc:l:t:";
$opts = getopt($options);
foreach( $opts as $o => $a )
{
while ( $k = array_search( "-" . $o, $argv ) ) {
if ( $k )
unset( $argv[$k] );
if ( preg_match( "/^.*".$o.":.*$/i", $options ) )
unset( $argv[$k+1] );
}
}
$argv = array_merge( $argv );
return $opts;
}
function ShowMessage($msg)
{
global $VerboseMsg;
if (!$VerboseMsg) {
return;
}
echo $msg . "\n";
}
function Usage()
{
global $argv;
echo <<<EOD
USAGE:
{$argv[0]} [-d] output config.depends
{$argv[0]} [-s|-l] [-tc] [-v] output input [...]
OPTIONS:
-d: Read JS dependency from config depend file
-s: Skip JavaScript Validation (will ignore -l)
-t Transpiler:
babel: Convert ES6+ code into a backwards compatible version
skip: Don't do any transpile (default)
-c Compressor:
rhino: old compressor
closure: Google Closure Compiler
uglifyjs: UglifyJs Compressor
yui: Yahoo YUI Compressor (default)
terser: JavaScript parser, mangler, optimizer and beautifier toolkit for ES6+
skip: do not use any compressor
-l Linter:
rhino-jslint: old linter
jslint: node-jslint
jshint: node-jshint (default)
eslint: eslint for ES6/JSX syntax
skip: skip JavaScript validation
-v:
show verbose message, including full JSLint messages.
EOD;
}
/* Decode json file to php object */
function DecodeJsonFile($file)
{
print($file."\n");
$content = @file_get_contents($file);
if (FALSE === $content) {
echo $file . ": Read error.\n";
return NULL;
}
$result = json_decode($content, true);
if (!is_array($result)) {
echo $file . ": Json parse failed.\n";
}
return $result;
}
function SetTranspiler($opts)
{
$transpiler = SkipTranspile;
if (isSet($opts['t'])) {
switch ($opts['t']) {
case 'babel':
if (!file_exists("/usr/bin/babel")) {
echo "Error: Babel not found, available after DSM6.2\n";
exit(1);
}
$transpiler = BabelTranspile;
break;
case 'skip':
$transpiler = SkipTranspile;
break;
default:
echo "Error: Invalid Transpiler Option.\n";
Usage();
exit(1);
}
if ('skip' != $opts['t']) {
echo "Transpile using [".$opts['t']."]\n";
}
}
return $transpiler;
}
function SetCompressor($opts)
{
$compressor = YUICompress;
if (isSet($opts['c'])) {
switch ($opts['c']) {
case 'rhino':
$compressor = RhinoCompress;
break;
case 'closure':
$compressor = ClosureCompile;
break;
case 'yui':
$compressor = YUICompress;
break;
case 'uglifyjs':
$compressor = UglifyjsCompress;
break;
case 'terser':
$compressor = TerserCompress;
break;
case 'skip':
$compressor = SkipCompress;
break;
default:
echo "Error: Invalid Compressor Option.\n";
Usage();
exit(1);
}
if ('skip' != $opts['c']) {
echo "Compress using [".$opts['c']."]\n";
}
} else {
echo "Compress using [yui]\n";
}
return $compressor;
}
function SetLinter($opts)
{
$linter = 'jshint';
if (isset($opts['l'])) {
$linter = $opts['l'];
}
return $linter;
}
function RunLinter($opts, $linter, $jsFiles)
{
if (!isSet($opts['s']) && "skip" != $linter) {
echo "Validating JS using [$linter]...\n";
if ($linter === "eslint" && !file_exists("/usr/bin/eslint")) {
echo "Error: Compressor [eslint] only avaliable since DSM7.0\n";
exit(1);
}
if (ValidateJSFiles($linter, $jsFiles) > 0) {
exit(1);
}
} else {
echo "Skip JavaScript Validation\n";
}
}
function RunTranspiler($transpiler, $jsFiles, $transpiledOutFile)
{
$retCode = TranspileJSFiles($transpiler, $transpiledOutFile, $jsFiles);
if (0 != retCode) {
throw new Exception("ERROR >> Failed to transpile [".$transpiledOutFile."]\n");
}
}
function RunCompressor($compressor, $outFile, $transpiledOutFile)
{
CompressJSFiles($compressor, $outFile, $transpiledOutFile);
}
/* main process */
$opts = GetOptions();
if (FALSE === $opts || count($argv) < 3) {
Usage();
exit(1);
}
$VerboseMsg = isSet($opts['v']);
$transpiler = SetTranspiler($opts);
$compressor = SetCompressor($opts);
$linter = SetLinter($opts);
/* read depend js file */
$outFile = $argv[1];
if(isSet($opts['d'])) {
$jsDepCfg = DecodeJsonFile($argv[2]);
if (!is_array($jsDepCfg)) {
exit(1);
}
if (!is_array($jsDepCfg[$outFile])) {
echo "Error > ".$outFile." is not in config.depends\n";
exit(1);
}
if(isSet($jsDepCfg[$outFile]['files'])) {
$jsFiles = $jsDepCfg[$outFile]['files'];
} else {
$jsFiles = $jsDepCfg[$outFile];
}
} else {
$jsFiles = array_slice($argv, 2);
}
$transpiledOutFileOrig = tempnam("/tmp/", basename($outFile)."_transpiled_");
$transpiledOutFile = $transpiledOutFileOrig.'.js';
try {
RunLinter($opts, $linter, $jsFiles);
RunTranspiler($transpiler, $jsFiles, $transpiledOutFile);
RunCompressor($compressor, $outFile, $transpiledOutFile);
} catch(Exception $e) {
echo $e->getMessage();
}
@unlink($transpiledOutFileOrig);
@unlink($transpiledOutFile);
?>

62
tool/snpm Executable file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
import subprocess
import sys
toolchain = {
"babel": "/usr/bin/babel",
"uglifyjs": "/usr/bin/uglifyjs",
"terser": "/usr/bin/terser",
"jslint": "/usr/bin/jslint",
"jshint": "/usr/bin/jshint",
"eslint": "/usr/bin/eslint"
}
CSI = '\033['
FAIL = '91m'
OSC = '\033]'
def print_fail(line):
if sys.stderr is None:
return
if sys.stderr.isatty():
print(CSI + FAIL + line.decode('utf-8') + OSC, end='', file=sys.stderr)
else:
print(line.decode('utf-8'), end='', file=sys.stderr)
def print_succ(line):
if sys.stdout is None:
return
print(line.decode('utf-8'), end='', file=sys.stdout)
def find_toolchain(tool):
if tool in toolchain:
return toolchain[tool]
return tool
def main():
try:
args = sys.argv[1:] if len(sys.argv) > 1 else []
if len(args) >= 1 and args[0] == 'chroot':
tool = []
if len(args) >= 2:
tool = [find_toolchain(args[1])]
args = args[2:]
cmd_list = ['/usr/bin/node'] + tool + args
else:
cmd_list = ['/usr/bin/pnpm'] + args
proc = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
for line in iter(proc.stdout.readline, b''):
print_succ(line)
for line in iter(proc.stderr.readline, b''):
print_fail(line)
proc.wait()
except subprocess.CalledProcessError as err:
print("""Failed with %s.""" % str(err), file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()