nadesiko3
Version:
Japanese Programming Language
718 lines (676 loc) • 27.5 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
// deno-lint-ignore-file no-explicit-any
/**
* コマンドライン版のなでしこ3をモジュールとして定義
* 実際には cnako3.mjs から読み込まれる
*/
import fs from 'node:fs'
import fse from 'fs-extra'
import { exec } from 'node:child_process'
import path from 'node:path'
import { NakoCompiler, LoaderTool, newCompilerOptions } from '../core/src/nako3.mjs'
import { NakoImportError } from '../core/src/nako_errors.mjs'
import { Ast, CompilerOptions } from '../core/src/nako_types.mjs'
import { NakoGlobal } from '../core/src/nako_global.mjs'
import nakoVersion from './nako_version.mjs'
import PluginNode from './plugin_node.mjs'
import app from './commander_ja.mjs'
import fetch from 'node-fetch'
import { NakoGenOptions } from '../core/src/nako_gen.mjs'
import { getEnv, isWindows, getCommandLineArgs, exit } from './deno_wrapper.mjs'
// __dirname のために
import url from 'node:url'
const __filename = url.fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
/** コマンドラインアクション */
interface CNako3ArgOptions {
warn: boolean
debug: boolean
compile: any | boolean
test: any | boolean
one_liner: any | boolean
trace: any | boolean
run: any | boolean
repl: any | boolean
source: any | string
mainfile: any | string
man: string
browsers: boolean
ast: boolean
lex: boolean
}
interface CNako3Options {
nostd: boolean
}
/** CNako3 */
export class CNako3 extends NakoCompiler {
debug: boolean
version: string
constructor (opts:CNako3Options = { nostd: false }) {
super({ useBasicPlugin: !opts.nostd })
this.debug = false
this.filename = 'main.nako3'
this.version = nakoVersion.version
if (!opts.nostd) {
this.addPluginFromFile('PluginNode', PluginNode, true)
}
// 必要な定数を設定
this.addListener('beforeRun', (g: NakoGlobal) => {
g.__varslist[0].set('ナデシコ種類', 'cnako3')
g.__varslist[0].set('ナデシコバージョン', this.version)
})
}
// CNAKO3で使えるコマンドを登録する
registerCommands () {
// コマンドライン引数を得る
const args: string[] = getCommandLineArgs()
// コマンド引数がないならば、ヘルプを表示(-hはcommandarにデフォルト用意されている)
if (args.length <= 2) { args.push('-h') }
const verInfo = `v${nakoVersion.version}`
// commanderを使って引数を解析する
app
.title('日本語プログラミング言語「なでしこ」' + verInfo)
.version(verInfo, '-v, --version')
.usage('[オプション] 入力ファイル.nako3')
.option('-h, --help', 'コマンドの使い方を表示')
.option('-w, --warn', '警告を表示する')
.option('-d, --debug', 'デバッグモードの指定')
.option('-D, --trace', '詳細デバッグモードの指定')
.option('-c, --compile', 'コンパイルモードの指定')
.option('-t, --test', 'コンパイルモードの指定 (テスト用コードを出力)')
.option('-r, --run', 'コンパイルモードでも実行する')
.option('-e, --eval [src]', '直接プログラムを実行するワンライナーモード')
.option('-o, --output', '出力ファイル名の指定')
.option('-s, --silent', 'サイレントモードの指定')
.option('-l, --repl', '対話シェル(REPL)の実行')
.option('-b, --browsers', '対応機器/Webブラウザを表示する')
.option('-m, --man [command]', 'マニュアルを表示する')
.option('-p, --speed', 'スピード優先モードの指定')
.option('-A, --ast', '構文解析した結果をASTで出力する')
.option('-X, --lex', '字句解析した結果をJSONで出力する')
// .option('-h, --help', '使い方を表示する')
// .option('-v, --version', 'バージョンを表示する')
.parse(args)
return app
}
/** コマンドライン引数を解析 */
checkArguments (): CNako3ArgOptions {
const app: any = this.registerCommands()
let logLevel = 'error'
if (app.trace) {
logLevel = 'trace'
} else if (app.debug) {
logLevel = 'debug'
} else if (app.warn) {
logLevel = 'warn'
}
this.getLogger().addListener(logLevel, ({ nodeConsole }) => {
console.log(nodeConsole)
})
const args: any = {
compile: app.compile || false,
run: app.run || false,
source: app.eval || '',
man: app.man || '',
one_liner: app.eval || false,
debug: this.debug || false,
trace: app.trace,
warn: app.warn,
repl: app.repl || false,
test: app.test || false,
browsers: app.browsers || false,
speed: app.speed || false,
ast: app.ast || false,
lex: app.lex || false
}
args.mainfile = app.args[0]
args.output = app.output
// todo: ESModule 対応の '.mjs' のコードを吐くように修正 #1217
const ext = '.mjs'
if (/\.(nako|nako3|txt|bak)$/.test(args.mainfile)) {
if (!args.output) {
if (args.test) {
args.output = args.mainfile.replace(/\.(nako|nako3)$/, '.spec' + ext)
} else {
args.output = args.mainfile.replace(/\.(nako|nako3)$/, ext)
}
}
} else {
if (!args.output) {
if (args.test) {
args.output = args.mainfile + '.spec' + ext
} else {
args.output = args.mainfile + ext
}
}
args.mainfile += '.nako3'
}
return args
}
// 実行する
async execCommand () {
// コマンドを解析
const opt: CNako3ArgOptions = this.checkArguments()
// 使い方の表示か?
if (opt.man) {
this.cnakoMan(opt.man)
return
}
// 対応ブラウザを表示する
if (opt.browsers) {
this.cnakoBrowsers()
return
}
// REPLを実行する
if (opt.repl) {
this.cnakoRepl()
return
}
// ワンライナーで実行する
if (opt.one_liner) {
this.cnakoOneLiner(opt)
return
}
// メインプログラムを読み込む
this.filename = opt.mainfile
const src = fs.readFileSync(opt.mainfile, 'utf-8')
if (opt.compile) {
await this.nakoCompile(opt, src, false)
return
}
// 字句解析の結果をJSONで出力
if (opt.lex) {
const lex = this.lex(src, opt.mainfile)
console.log(this.outputJSON(lex, 0))
return
}
// ASTを出力する
if (opt.ast) {
try {
await this.loadDependencies(src, opt.mainfile, '')
} catch (err: any) {
if (this.numFailures > 0) {
this.logger.error(err)
exit(1)
}
}
this.outputAST(opt, src)
return
}
// テストを実行する
if (opt.test) {
try {
await this.loadDependencies(src, opt.mainfile, '')
this.test(src, opt.mainfile)
return
} catch (e: any) {
if (this.numFailures > 0) {
this.logger.error(e)
exit(1)
}
}
}
// ファイルを読んで実行する
try {
// コンパイルと実行を行うメソッド
const g = await this.runAsync(src, opt.mainfile)
return g
} catch (e: any) {
// 文法エラーなどがあった場合
if (opt.debug || opt.trace) {
throw e
}
}
}
/**
* コンパイルモードの場合
*/
async nakoCompile (opt: any, src: string, isTest: boolean) {
// 依存ライブラリなどを読み込む
await this.loadDependencies(src, this.filename, '')
// JSにコンパイル
const genOpt = new NakoGenOptions(
isTest,
['plugin_node.mjs'],
'self.__setSysVar(\'ナデシコ種類\', \'cnako3\');'
)
const jscode = this.compileStandalone(src, this.filename, genOpt)
console.log(opt.output)
fs.writeFileSync(opt.output, jscode, 'utf-8')
// 実行に必要なファイルをコピー
const nakoRuntime = __dirname
const outRuntime = path.join(path.dirname(opt.output), 'nako3runtime')
if (!fs.existsSync(outRuntime)) { fs.mkdirSync(outRuntime) }
// from ./src
for (const mod of ['nako_version.mjs', 'plugin_node.mjs', 'deno_wrapper.mjs']) {
fs.copyFileSync(path.join(nakoRuntime, mod), path.join(outRuntime, mod))
}
// from nadesiko3core/src
const srcDir = path.join(__dirname, '..', 'core', 'src')
const baseFiles = ['nako_errors.mjs', 'nako_core_version.mjs',
'plugin_system.mjs', 'plugin_math.mjs', 'plugin_promise.mjs', 'plugin_test.mjs', 'plugin_csv.mjs', 'nako_csv.mjs']
for (const mod of baseFiles) {
fs.copyFileSync(path.join(srcDir, mod), path.join(outRuntime, mod))
}
// or 以下のコピーだと依存ファイルがコピーされない package.jsonを見てコピーする必要がある
const orgModule = path.join(__dirname, '..', 'node_modules')
const dirNodeModules = path.join(path.dirname(opt.output), 'node_modules')
const modlist = ['fs-extra', 'iconv-lite', 'opener', 'node-fetch', 'shell-quote']
const copied: { [key: string]: boolean } = {}
// 再帰的に必要なモジュールをコピーする
const copyModule = (mod: string) => {
if (copied[mod]) { return }
copied[mod] = true
// ライブラリ自身をコピー
fse.copySync(path.join(orgModule, mod), path.join(dirNodeModules, mod))
// 依存ライブラリをコピー
const packageFile = path.join(orgModule, mod, 'package.json')
const jsonStr = fs.readFileSync(packageFile, 'utf-8')
const jsonData = JSON.parse(jsonStr)
// サブモジュールをコピー
for (const smod in jsonData.dependencies) {
copyModule(smod)
}
}
for (const mod of modlist) {
copyModule(mod)
}
if (opt.run) {
exec(`node ${opt.output}`, function (err, stdout, stderr) {
if (err) { console.log('[ERROR]', stderr) }
console.log(stdout)
})
}
}
// ワンライナーの場合
async cnakoOneLiner (opt: any) {
const org = opt.source
try {
if (opt.source.indexOf('表示') < 0) {
opt.source = '' + opt.source + 'を表示。'
}
await this.runAsync(opt.source, 'main.nako3')
} catch (e) {
// エラーになったら元のワンライナーで再挑戦
try {
if (opt.source !== org) {
await this.runAsync(org, 'main.nako3')
} else {
throw e
}
} catch (e: any) {
if (this.debug) {
throw e
} else {
console.error(e.message)
}
}
}
}
/**
* JSONを出力
*/
outputJSON (ast: any, level: number): string {
const makeIndent = (level: number) => {
let s = ''
for (let i = 0; i < level; i++) { s += ' ' }
return s
}
const trim = (s: string) => { return s.replace(/(^\s+|\s+$)/g, '') }
if (typeof (ast) === 'string') {
return makeIndent(level) + '"' + ast + '"'
}
if (typeof (ast) === 'number') {
return makeIndent(level) + ast
}
if (ast instanceof Array) {
const s = makeIndent(level) + '[\n'
const sa: string[] = []
ast.forEach((a: Ast) => {
sa.push(this.outputJSON(a, level + 1))
})
return s + sa.join(',\n') + '\n' + makeIndent(level) + ']'
}
if (ast instanceof Object) {
const s = makeIndent(level) + '{\n'
const sa = []
for (const key in ast) {
const sv = trim(this.outputJSON((ast as any)[key], level + 1))
const so = makeIndent(level + 1) + '"' + key + '": ' + sv
sa.push(so)
}
return s + sa.join(',\n') + '\n' + makeIndent(level) + '}'
}
return makeIndent(level) + ast
}
/**
* ASTを出力
*/
outputAST (opt: any, src: string) {
const ast = this.parse(src, opt.mainfile)
console.log(this.outputJSON(ast, 0))
}
// REPL(対話実行環境)の場合
async cnakoRepl () {
const fname = path.join(__dirname, 'repl.nako3')
const src = fs.readFileSync(fname, 'utf-8')
await this.runAsync(src, 'main.nako3')
}
// マニュアルを表示する
cnakoMan (command: string) {
try {
const pathCommands = path.join(__dirname, '../release/command_cnako3.json')
const commands = JSON.parse(fs.readFileSync(pathCommands, 'utf-8'))
const data = commands[command]
for (const key in data) {
console.log(`${key}: ${data[key]}`)
}
} catch (e: any) {
if (e.code === 'MODULE_NOT_FOUND') {
console.log('コマンド一覧がないため、マニュアルを表示できません。以下のコマンドでコマンド一覧を生成してください。\n$ npm run build')
} else {
throw e
}
}
}
// 対応機器/Webブラウザを表示する
cnakoBrowsers () {
const fileMD = path.resolve(__dirname, '../doc', 'browsers.md')
console.log(fs.readFileSync(fileMD, 'utf-8'))
}
// (js|nako3) loader
getLoaderTools () {
/** @type {string[]} */
const log: string[] = []
const tools: LoaderTool = {
resolvePath: (name: string, token: any, fromFile: string): {filePath: string, type: string} => {
// 最初に拡張子があるかどうかをチェック
// JSプラグインか?
if (/\.(js|mjs)(\.txt)?$/.test(name)) {
const jspath = CNako3.findJSPluginFile(name, fromFile, __dirname, log)
if (jspath === '') {
throw new NakoImportError(`JSプラグイン『${name}』が見つかりません。コマンドラインで『npm install ${name}』を実行してみてください。以下のパスを検索しました。\n${log.join('\n')}`, token.file, token.line)
}
return { filePath: jspath, type: 'js' }
}
// なでしこプラグインか?
if (/\.(nako3|nako)(\.txt)?$/.test(name)) {
// ファイルかHTTPか
if (name.startsWith('http://') || name.startsWith('https://')) {
return { filePath: name, type: 'nako3' }
}
if (path.isAbsolute(name)) {
return { filePath: path.resolve(name), type: 'nako3' }
} else {
// filename が undefined のとき token.file が undefined になる。
if (token.file === undefined) { throw new Error('ファイル名を指定してください。') }
const dir = path.dirname(fromFile)
return { filePath: path.resolve(path.join(dir, name)), type: 'nako3' }
}
}
// 拡張子がない、あるいは、(.js|.mjs|.nako3|.nako)以外はJSモジュールと見なす
const jspath2 = CNako3.findJSPluginFile(name, fromFile, __dirname, log)
if (jspath2 === '') {
throw new NakoImportError(`JSプラグイン『${name}』が見つかりません。コマンドラインで『npm install ${name}』を実行してみてください。以下のパスを検索しました。\n${log.join('\n')}`, token.file, token.line)
}
return { filePath: jspath2, type: 'js' }
},
readNako3: (name, token) => {
const loader:any = { task: null }
// ファイルかHTTPか
if (name.startsWith('http://') || name.startsWith('https://')) {
// Webのファイルを非同期で読み込む
loader.task = (async () => {
const res = await fetch(name)
if (!res.ok) {
throw new NakoImportError(`『${name}』からのダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.file, token.line)
}
return await res.text()
})()
} else {
// ファイルを非同期で読み込む
// ファイルチェックだけ先に実行
if (!fs.existsSync(name)) {
throw new NakoImportError(`ファイル ${name} が存在しません。`, token.file, token.line)
}
loader.task = (new Promise((resolve, reject) => {
fs.readFile(name, { encoding: 'utf-8' }, (err, data) => {
if (err) { return reject(err) }
resolve(data)
})
}))
}
// 非同期で読み込む
return loader
},
readJs: (filePath, token) => {
const loader: any = { task: null }
if (isWindows()) {
if (filePath.substring(1, 3) === ':\\') {
filePath = 'file://' + filePath
}
}
// + プラグインの読み込みタスクを生成する
// | プラグインがWeb(https?://...)に配置されている場合
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
// 動的 import が http 未対応のため、一度、Webのファイルを非同期で読み込んで/tmpに保存してから動的importを行う
loader.task = (
new Promise((resolve, reject) => {
// 一時フォルダを得る
const tempDir = getEnv('TEMP')
const osTmpDir = isWindows() ? tempDir : '/tmp'
const osTmpDir2 = (osTmpDir) || path.join('./tmp')
const tmpDir = path.join(osTmpDir2, 'com.nadesi.v3.cnako')
const tmpFile = path.join(tmpDir, filePath.replace(/[^a-zA-Z0-9_.]/g, '_'))
if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir, { recursive: true }) }
// WEBからダウンロード
fetch(filePath)
.then((res: any) => {
return res.text()
})
.then((txt: string) => {
// ダウンロード
if (txt.indexOf('Failed to fetch') >= 0) {
const errFetch = new NakoImportError(`URL『${filePath}』のライブラリが存在しないか、指定のバージョンが間違っています。`, token.file, token.line)
reject(errFetch)
}
// 一時ファイルに保存
try {
fs.writeFileSync(tmpFile, txt, 'utf-8')
} catch (err) {
const err2 = new NakoImportError(`URL『${filePath}』からダウンロードしたJSファイルがキャッシュに書き込めません。${err}`, token.file, token.line)
reject(err2)
}
})
.then(() => {
// 一時ファイルから読み込む
import(tmpFile).then((mod) => {
// プラグインは export default で宣言
const obj = Object.assign({}, mod)
resolve(() => {
return obj.default
})
}).catch((err) => {
const errS = '' + err
if (errS.indexOf('SyntaxError:') >= 0) {
const err2 = new NakoImportError(`URL『${filePath}』からダウンロードしたJSファイルに文法エラーがあります。${err}`, token.file, token.line)
reject(err2)
} else {
const err3 = new NakoImportError(`URL『${filePath}』からダウンロードしたはずのJSファイル読み込めません。${err}`, token.file, token.line)
reject(err3)
}
})
})
.catch((err: any) => {
const err2 = new NakoImportError(`URL『${filePath}』からJSファイルが読み込めません。${err}`, token.file, token.line)
reject(err2)
})
})
)
return loader
}
// | プラグインがファイル上に配置されている場合
loader.task = (
new Promise((resolve, reject) => {
import(filePath).then((mod) => {
// プラグインは export default で宣言
const obj = Object.assign({}, mod)
resolve(() => { return obj.default })
}).catch((err) => {
const err2 = new NakoImportError(`ファイル『${filePath}』が読み込めません。${err}`, token.file, token.line)
reject(err2)
})
})
)
return loader
}
}
return tools
}
/** 『!「xxx」を取込』の処理 */
async loadDependencies (code: string, filename: string, preCode: string) {
const tools = this.getLoaderTools()
await super._loadDependencies(code, filename, preCode, tools)
}
/**
* 非同期でなでしこのコードを実行する
*/
async runAsync (code: string, fname: string, options: CompilerOptions|undefined = undefined): Promise<NakoGlobal> {
// オプション
const opt = newCompilerOptions(options)
// 取り込む文
await this.loadDependencies(code, fname, opt.preCode)
// 実行
return await super.runAsync(code, fname, options)
}
/**
* プラグインファイルの検索を行う
* @param pname プラグインの名前
* @param filename 取り込み元ファイル名
* @param srcDir このファイルが存在するディレクトリ
* @param log
* @return フルパス、失敗した時は、''を返す
*/
static findJSPluginFile (pname: string, filename: string, srcDir: string, log: string[] = []): string {
log.length = 0
const cachePath: {[key: string]: boolean} = {}
/** キャッシュ付きでファイルがあるか検索 */
const exists = (f: string): boolean => {
// 同じパスを何度も検索することがないように
if (cachePath[f]) { return cachePath[f] }
try {
// ファイルがないと例外が出る
const stat = fs.statSync(f)
const b = !!(stat && stat.isFile())
cachePath[f] = b
return b
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_: any) {
return false
}
}
/** 普通にファイルをチェック */
const fCheck = (pathTest: string, desc: string): boolean => {
// 素直に指定されたパスをチェック
const bExists = exists(pathTest)
log.push(`- (${desc}) ${pathTest}, ${bExists}`)
return bExists
}
/** 通常 + package.json のパスを調べる */
const fCheckEx = (pathTest: string, desc: string): string => {
// 直接JSファイルが指定された?
if (/\.(js|mjs)$/.test(pathTest)) {
if (fCheck(pathTest, desc)) { return pathTest }
}
// 指定パスのpackage.jsonを調べる
const json = path.join(pathTest, 'package.json')
if (fCheck(json, desc + '/package.json')) {
// package.jsonを見つけたので、メインファイルを調べて取り込む (CommonJSモジュール対策)
const jsonText = fs.readFileSync(json, 'utf-8')
const obj = JSON.parse(jsonText)
if (!obj.main) { return '' }
const mainFile = path.resolve(path.join(pathTest, obj.main))
return mainFile
}
return ''
}
// URL指定か?
if (pname.substring(0, 8) === 'https://') {
return pname
}
// 各パスを検索していく
const p1 = pname.substring(0, 1)
// フルパス指定か?
if (p1 === '/' || pname.substring(1, 3).toLowerCase() === ':\\' || pname.substring(0, 6) === 'file:/') {
const fileFullpath = fCheckEx(pname, 'フルパス')
if (fileFullpath) { return fileFullpath }
return '' // フルパスの場合別のフォルダは調べない
}
// 相対パスか?
if (p1 === '.' || pname.indexOf('/') >= 0) {
// 相対パス指定なので、なでしこのプログラムからの相対指定を調べる
const pathRelative = path.join(path.resolve(path.dirname(filename)), pname)
const fileRelative = fCheckEx(pathRelative, '相対パス')
if (fileRelative) { return fileRelative }
return '' // 相対パスの場合も別のフォルダは調べない
}
// plugin_xxx.mjs のようにファイル名のみが指定された場合のみ、いくつかのパスを調べる
// 母艦パス(元ファイルと同じフォルダ)か?
const testScriptPath = path.join(path.resolve(path.dirname(filename)), pname)
const fileScript = fCheckEx(testScriptPath, '母艦パス')
if (fileScript) { return fileScript }
// ランタイムパス/src/<plugin>
if (pname.match(/^plugin_[a-z0-9_]+\.mjs/)) {
// cnako3mod.mjs は ランタイム/src に配置されていることが前提
const pathRoot = path.resolve(__dirname, '..')
const pathRuntimeSrc = path.join(pathRoot, 'src', pname)
const fileRuntimeSrc = fCheckEx(pathRuntimeSrc, 'CNAKO3パス')
if (fileRuntimeSrc) { return fileRuntimeSrc }
// ランタイム/core/src/<plugin>
const pathCore = path.join(pathRoot, 'core', 'src', pname)
const fileCore = fCheckEx(pathCore, 'CNAKO3パス')
if (fileCore) { return fileCore }
}
// 環境変数をチェック
// 環境変数 NAKO_LIB か?
const nako_lib = getEnv('NAKO_LIB')
if (nako_lib) {
const nako_lib_full = path.join(path.resolve(nako_lib), pname)
const nako_lib_full2 = fCheckEx(nako_lib_full, 'NAKO_LIB')
if (nako_lib_full2) { return nako_lib_full2 }
}
// ランタイムパス/node_modules/<plugin>
const pathRuntime = path.join(path.dirname(path.resolve(__dirname)))
const pathRuntimePname = path.join(pathRuntime, 'node_modules', pname)
const fileRuntime = fCheckEx(pathRuntimePname, 'runtime')
if (fileRuntime) { return fileRuntime }
// ランタイムと同じ配置 | ランタイムパス/../<plugin>
const runtimeLib = path.join(pathRuntime, '..', pname)
const fileLib = fCheckEx(runtimeLib, 'runtimeLib')
if (fileLib) { return fileLib }
// nadesiko3core | ランタイムパス/node_modules/nadesiko3core/src/<plugin>
const pathRuntimeSrc2 = path.join(pathRuntime, 'node_modules', 'nadesiko3core', 'src', pname) // cnako3mod.mjs は ランタイム/src に配置されていることが前提
const fileRuntimeSrc2 = fCheckEx(pathRuntimeSrc2, 'nadesiko3core')
if (fileRuntimeSrc2) { return fileRuntimeSrc2 }
// 環境変数 NAKO_HOMEか?
const nako_home = getEnv('NAKO_HOME')
if (nako_home) {
const nako_home_full = path.join(path.resolve(nako_home), 'node_modules', pname)
const nako_home_full2 = fCheckEx(nako_home_full, 'NAKO_HOME')
if (nako_home_full2) { return nako_home_full2 }
// NAKO_HOME/src ?
const pathNakoHomeSrc = path.join(nako_home, 'src', pname)
const fileNakoHomeSrc = fCheckEx(pathNakoHomeSrc, 'NAKO_HOME/src')
if (fileNakoHomeSrc) { return fileNakoHomeSrc }
}
// 環境変数 NODE_PATH (global) 以下にあるか?
const node_path = getEnv('NODE_PATH')
if (node_path) {
const pathNode = path.join(path.resolve(node_path), pname)
const fileNode = fCheckEx(pathNode, 'NODE_PATH')
if (fileNode) { return fileNode }
}
// Nodeのパス検索には任せない(importで必ず失敗するので)
return ''
}
}