nadesiko3 3.7.19 → 3.7.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/batch/command.txt +115 -113
- package/core/package.json +2 -2
- package/core/src/README.md +98 -0
- package/core/src/nako_ast.mts +1 -0
- package/core/src/nako_core_version.mjs +2 -2
- package/core/src/nako_core_version.mts +2 -2
- package/core/src/nako_gen.mjs +54 -0
- package/core/src/nako_gen.mts +61 -0
- package/core/src/nako_parser3.mjs +45 -7
- package/core/src/nako_parser3.mts +43 -7
- package/core/test/array_test.mjs +16 -0
- package/core/test/func_call.mjs +11 -0
- package/core/test/func_scope.mjs +112 -0
- package/package.json +21 -27
- package/release/_hash.txt +36 -36
- package/release/_script-tags.txt +16 -16
- package/release/command.json +1 -1
- package/release/command.json.js +1 -1
- package/release/command_cnako3.json +1 -1
- package/release/command_list.json +1 -1
- package/release/edit_main.js +2 -2
- package/release/edit_main.js.map +3 -3
- package/release/editor.js +2 -2
- package/release/plugin_datetime.js +1 -1
- package/release/plugin_datetime.js.map +3 -3
- package/release/plugin_markup.js +28 -28
- package/release/plugin_markup.js.map +3 -3
- package/release/version.js +2 -2
- package/release/version_main.js +2 -2
- package/release/version_main.js.map +2 -2
- package/release/wnako3.js +85 -77
- package/release/wnako3.js.map +3 -3
- package/release/wnako3webworker.js +106 -98
- package/release/wnako3webworker.js.map +3 -3
- package/src/cnako3mod.mjs +1 -2
- package/src/cnako3mod.mts +1 -2
- package/src/nako_version.mjs +2 -2
- package/src/nako_version.mts +2 -2
- package/src/plugin_httpserver.mjs +184 -13
- package/src/plugin_httpserver.mts +176 -10
- package/src/plugin_node.mjs +0 -1
- package/src/plugin_node.mts +2 -3
- package/src/wnako3mod.mjs +3 -0
- package/src/wnako3mod.mts +3 -0
- package/test/common/wnako3mod_test.mjs +48 -0
- package/test/node/package_json_test.mjs +17 -0
- package/test/node/plugin_httpserver_test.mjs +239 -0
- package/tools/nako3edit/html/edit.html +21 -0
- package/tools/nako3edit/html/nako3edit.css +10 -0
- package/tools/nako3edit/index.mjs +15 -4
- package/tools/nako3server/index.mjs +15 -4
- package/tools/nako3server/index.nako3 +4 -2
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import http from 'http'
|
|
6
|
+
import os from 'os'
|
|
7
|
+
import { NakoCompiler } from '../../core/src/nako3.mjs'
|
|
8
|
+
import PluginHttpServer from '../../src/plugin_httpserver.mjs'
|
|
9
|
+
|
|
10
|
+
// __dirname のために
|
|
11
|
+
import url from 'url'
|
|
12
|
+
const __filename = url.fileURLToPath(import.meta.url)
|
|
13
|
+
const __dirname = path.dirname(__filename)
|
|
14
|
+
|
|
15
|
+
describe('plugin_httpserver_test', () => {
|
|
16
|
+
let nako
|
|
17
|
+
let serverDp
|
|
18
|
+
|
|
19
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
serverDp = null
|
|
23
|
+
nako = new NakoCompiler()
|
|
24
|
+
// PluginHttpServerの登録。名前は 'plugin_httpserver.js' とする。
|
|
25
|
+
nako.addPluginFile('PluginHttpServer', 'plugin_httpserver.js', PluginHttpServer)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
if (serverDp && serverDp.server) {
|
|
30
|
+
await new Promise((resolve) => {
|
|
31
|
+
serverDp.server.close(() => {
|
|
32
|
+
resolve()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('クエリパラメータ付きのURLで静的ファイルが取得できること', async () => {
|
|
39
|
+
// テスト用のダミーファイルを作成
|
|
40
|
+
const tempDir = path.join(__dirname, 'fixtures_http')
|
|
41
|
+
if (!fs.existsSync(tempDir)) {
|
|
42
|
+
fs.mkdirSync(tempDir, { recursive: true })
|
|
43
|
+
}
|
|
44
|
+
const tempFile = path.join(tempDir, 'test.txt')
|
|
45
|
+
fs.writeFileSync(tempFile, 'hello world')
|
|
46
|
+
|
|
47
|
+
let port = 0
|
|
48
|
+
// `簡易HTTPサーバ起動時`を呼ぶ
|
|
49
|
+
const code = `
|
|
50
|
+
●ダミー起動
|
|
51
|
+
戻る。
|
|
52
|
+
ここまで。
|
|
53
|
+
「ダミー起動」を${port}で簡易HTTPサーバ起動時。
|
|
54
|
+
「/」を「${tempDir.replace(/\\/g, '/')}」に簡易HTTPサーバ静的パス指定。
|
|
55
|
+
`
|
|
56
|
+
const g = await nako.runAsync(code, 'main')
|
|
57
|
+
serverDp = g.__httpserver
|
|
58
|
+
await wait(100)
|
|
59
|
+
port = serverDp.server.address().port
|
|
60
|
+
|
|
61
|
+
// クエリパラメータ付きでリクエストを送る
|
|
62
|
+
const resText = await new Promise((resolve, reject) => {
|
|
63
|
+
http.get(`http://localhost:${port}/test.txt?foo=bar&baz=123`, (res) => {
|
|
64
|
+
let data = ''
|
|
65
|
+
res.on('data', (chunk) => { data += chunk })
|
|
66
|
+
res.on('end', () => { resolve(data) })
|
|
67
|
+
}).on('error', reject)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
assert.strictEqual(resText, 'hello world')
|
|
71
|
+
|
|
72
|
+
// 一時ファイルを削除
|
|
73
|
+
try {
|
|
74
|
+
fs.unlinkSync(tempFile)
|
|
75
|
+
fs.rmdirSync(tempDir)
|
|
76
|
+
} catch (e) {}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('POSTメソッドで送信されたJSONデータを取得できること', async () => {
|
|
80
|
+
let port = 0
|
|
81
|
+
const code = `
|
|
82
|
+
●ダミー起動
|
|
83
|
+
戻る。
|
|
84
|
+
ここまで。
|
|
85
|
+
●受信処理
|
|
86
|
+
POSTデータ["message"]を簡易HTTPサーバ出力。
|
|
87
|
+
ここまで。
|
|
88
|
+
「ダミー起動」を${port}で簡易HTTPサーバ起動時。
|
|
89
|
+
「受信処理」を「/post-json」に簡易HTTPサーバ受信時。
|
|
90
|
+
`
|
|
91
|
+
const g = await nako.runAsync(code, 'main')
|
|
92
|
+
serverDp = g.__httpserver
|
|
93
|
+
await wait(100)
|
|
94
|
+
port = serverDp.server.address().port
|
|
95
|
+
|
|
96
|
+
const postData = JSON.stringify({ message: 'hello post json' })
|
|
97
|
+
const resText = await new Promise((resolve, reject) => {
|
|
98
|
+
const req = http.request({
|
|
99
|
+
hostname: 'localhost',
|
|
100
|
+
port: port,
|
|
101
|
+
path: '/post-json',
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
106
|
+
}
|
|
107
|
+
}, (res) => {
|
|
108
|
+
let data = ''
|
|
109
|
+
res.on('data', (chunk) => { data += chunk })
|
|
110
|
+
res.on('end', () => { resolve(data) })
|
|
111
|
+
})
|
|
112
|
+
req.on('error', reject)
|
|
113
|
+
req.write(postData)
|
|
114
|
+
req.end()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
assert.strictEqual(resText, 'hello post json')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('POSTメソッドで送信されたurlencodedデータを取得できること', async () => {
|
|
121
|
+
let port = 0
|
|
122
|
+
const code = `
|
|
123
|
+
●ダミー起動
|
|
124
|
+
戻る。
|
|
125
|
+
ここまで。
|
|
126
|
+
●受信処理
|
|
127
|
+
POSTデータ["message"]を簡易HTTPサーバ出力。
|
|
128
|
+
ここまで。
|
|
129
|
+
「ダミー起動」を${port}で簡易HTTPサーバ起動時。
|
|
130
|
+
「受信処理」を「/post-urlencoded」に簡易HTTPサーバ受信時。
|
|
131
|
+
`
|
|
132
|
+
const g = await nako.runAsync(code, 'main')
|
|
133
|
+
serverDp = g.__httpserver
|
|
134
|
+
await wait(100)
|
|
135
|
+
port = serverDp.server.address().port
|
|
136
|
+
|
|
137
|
+
const postData = 'message=hello+post+urlencoded'
|
|
138
|
+
const resText = await new Promise((resolve, reject) => {
|
|
139
|
+
const req = http.request({
|
|
140
|
+
hostname: 'localhost',
|
|
141
|
+
port: port,
|
|
142
|
+
path: '/post-urlencoded',
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
146
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
147
|
+
}
|
|
148
|
+
}, (res) => {
|
|
149
|
+
let data = ''
|
|
150
|
+
res.on('data', (chunk) => { data += chunk })
|
|
151
|
+
res.on('end', () => { resolve(data) })
|
|
152
|
+
})
|
|
153
|
+
req.on('error', reject)
|
|
154
|
+
req.write(postData)
|
|
155
|
+
req.end()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
assert.strictEqual(resText, 'hello post urlencoded')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('POSTメソッドでファイルをアップロードしてFILESデータを取得できること', async () => {
|
|
162
|
+
const originalWriteFileSync = fs.writeFileSync
|
|
163
|
+
fs.writeFileSync = () => {
|
|
164
|
+
throw new Error('writeFileSync should not be used during upload handling')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let port = 0
|
|
168
|
+
const code = `
|
|
169
|
+
●ダミー起動
|
|
170
|
+
戻る。
|
|
171
|
+
ここまで。
|
|
172
|
+
●受信処理
|
|
173
|
+
ファイル情報=FILESデータ[0]
|
|
174
|
+
もし、ファイル情報["path"]が空でなければ
|
|
175
|
+
ファイル名=ファイル情報["name"]
|
|
176
|
+
ファイルサイズ=ファイル情報["size"]
|
|
177
|
+
「OK:{ファイル名}:{ファイルサイズ}」を簡易HTTPサーバ出力。
|
|
178
|
+
違えば
|
|
179
|
+
「NG」を簡易HTTPサーバ出力。
|
|
180
|
+
ここまで。
|
|
181
|
+
ここまで。
|
|
182
|
+
「ダミー起動」を${port}で簡易HTTPサーバ起動時。
|
|
183
|
+
「受信処理」を「/upload」に簡易HTTPサーバ受信時。
|
|
184
|
+
`
|
|
185
|
+
const g = await nako.runAsync(code, 'main')
|
|
186
|
+
serverDp = g.__httpserver
|
|
187
|
+
await wait(100)
|
|
188
|
+
port = serverDp.server.address().port
|
|
189
|
+
|
|
190
|
+
const boundary = '----TestBoundary'
|
|
191
|
+
const parts = [
|
|
192
|
+
`--${boundary}\r\n`,
|
|
193
|
+
`Content-Disposition: form-data; name="file1"; filename="hello.txt"\r\n`,
|
|
194
|
+
`Content-Type: text/plain\r\n\r\n`,
|
|
195
|
+
`hello world nako3 upload\r\n`,
|
|
196
|
+
`--${boundary}--\r\n`
|
|
197
|
+
]
|
|
198
|
+
const postData = Buffer.from(parts.join(''))
|
|
199
|
+
|
|
200
|
+
const resText = await new Promise((resolve, reject) => {
|
|
201
|
+
const req = http.request({
|
|
202
|
+
hostname: 'localhost',
|
|
203
|
+
port: port,
|
|
204
|
+
path: '/upload',
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: {
|
|
207
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
208
|
+
'Content-Length': postData.length
|
|
209
|
+
}
|
|
210
|
+
}, (res) => {
|
|
211
|
+
let data = ''
|
|
212
|
+
res.on('data', (chunk) => { data += chunk })
|
|
213
|
+
res.on('end', () => { resolve(data) })
|
|
214
|
+
})
|
|
215
|
+
req.on('error', reject)
|
|
216
|
+
req.write(postData)
|
|
217
|
+
req.end()
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
assert.strictEqual(resText, 'OK:hello.txt:24')
|
|
222
|
+
|
|
223
|
+
const uploadDir = path.join(os.tmpdir(), 'nako3-plugin_httpserver_upload')
|
|
224
|
+
if (fs.existsSync(uploadDir)) {
|
|
225
|
+
const files = fs.readdirSync(uploadDir)
|
|
226
|
+
for (const file of files) {
|
|
227
|
+
try {
|
|
228
|
+
fs.unlinkSync(path.join(uploadDir, file))
|
|
229
|
+
} catch (e) {}
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
fs.rmdirSync(uploadDir)
|
|
233
|
+
} catch (e) {}
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
fs.writeFileSync = originalWriteFileSync
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
})
|
|
@@ -64,6 +64,12 @@ FNAME="無題.nako3"
|
|
|
64
64
|
Hの「(\[\d+m)」を「」に正規表現置換してHに代入。
|
|
65
65
|
Hの「(\[.*?\])」を「<span style='color:red;font-weight:bold;'>$1</span>」に正規表現置換してHに代入。
|
|
66
66
|
Hの「(『.*?』)」を「<span style='color:blue;font-weight:bold;'>$1</span>」に正規表現置換してHに代入。
|
|
67
|
+
H = 「
|
|
68
|
+
<div class="error">{H}</div>
|
|
69
|
+
<div style="text-align: right; margin-top: 8px;">
|
|
70
|
+
<button class="btn btn-sm" onclick="copyError()">エラーをコピー</button>
|
|
71
|
+
</div>
|
|
72
|
+
」
|
|
67
73
|
ここまで。
|
|
68
74
|
「#result」にHをDOMテキスト設定。
|
|
69
75
|
ここまで。
|
|
@@ -168,4 +174,19 @@ FNAME="無題.nako3"
|
|
|
168
174
|
</div>
|
|
169
175
|
</div>
|
|
170
176
|
|
|
177
|
+
<!-- 補助スクリプト -->
|
|
178
|
+
<script>
|
|
179
|
+
function copyError() {
|
|
180
|
+
const errorText = document.querySelector('.error').innerText;
|
|
181
|
+
navigator.clipboard.writeText(errorText).then(() => {
|
|
182
|
+
alert('エラー内容をコピーしました');
|
|
183
|
+
}).catch(err => {
|
|
184
|
+
const errorMessage = (err && err.message) ? err.message : String(err);
|
|
185
|
+
console.error('エラー内容のコピーに失敗しました', err);
|
|
186
|
+
alert('エラー内容のコピーに失敗しました。ブラウザの権限設定や HTTPS 接続を確認してください。\n詳細: ' + errorMessage);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
</script>
|
|
191
|
+
|
|
171
192
|
</html>
|
|
@@ -12,8 +12,8 @@ const __filename = url.fileURLToPath(import.meta.url)
|
|
|
12
12
|
const __dirname = path.dirname(__filename)
|
|
13
13
|
|
|
14
14
|
// CONST
|
|
15
|
-
const SERVER_PORT =
|
|
16
|
-
const SERVER_HOST = process.env.NAKO3EDIT_HOST ||
|
|
15
|
+
const SERVER_PORT = resolveServerPort(8888)
|
|
16
|
+
const SERVER_HOST = process.env.NAKO3EDIT_HOST || 'localhost'
|
|
17
17
|
const rootDir = path.resolve(__dirname)
|
|
18
18
|
const releaseDir = path.resolve(path.join(__dirname, '../../release'))
|
|
19
19
|
const isWin = process.platform === 'win32'
|
|
@@ -132,9 +132,8 @@ const server = http.createServer(function (req, res) {
|
|
|
132
132
|
})
|
|
133
133
|
// サーバを起動
|
|
134
134
|
server.listen(SERVER_PORT, SERVER_HOST, function () {
|
|
135
|
-
const url = 'http://
|
|
135
|
+
const url = 'http://' + SERVER_HOST + ':' + SERVER_PORT
|
|
136
136
|
console.log('### 超簡易Webサーバが起動しました')
|
|
137
|
-
console.log('### script: /tools/nako3edit/index.mjs')
|
|
138
137
|
console.log('[URL]', url)
|
|
139
138
|
if (process.env.NAKO3EDIT_OPEN !== '0') {
|
|
140
139
|
opener(url)
|
|
@@ -355,3 +354,15 @@ function apiAddPlugins(res, params) {
|
|
|
355
354
|
res.end('"[ERROR] プラグインの取得に失敗しました。"')
|
|
356
355
|
}
|
|
357
356
|
}
|
|
357
|
+
|
|
358
|
+
// ポート番号の解決
|
|
359
|
+
function resolveServerPort (defaultPort) {
|
|
360
|
+
const rawPort = process.env.NAKO3EDIT_PORT || process.argv[2] || defaultPort
|
|
361
|
+
const port = Number.parseInt(rawPort, 10)
|
|
362
|
+
if (Number.isNaN(port) || port < 0 || port > 65535) {
|
|
363
|
+
console.error(`[ERROR] 無効なポート番号です: ${rawPort}`)
|
|
364
|
+
console.error('[ERROR] 環境変数 PORT またはコマンドライン引数には 0 から 65535 の整数を指定してください。')
|
|
365
|
+
process.exit(1)
|
|
366
|
+
}
|
|
367
|
+
return port
|
|
368
|
+
}
|
|
@@ -12,8 +12,8 @@ const __filename = url.fileURLToPath(import.meta.url)
|
|
|
12
12
|
const __dirname = path.dirname(__filename)
|
|
13
13
|
|
|
14
14
|
// CONST
|
|
15
|
-
const SERVER_PORT =
|
|
16
|
-
const SERVER_HOST = process.env.NAKO3SERVER_HOST ||
|
|
15
|
+
const SERVER_PORT = resolveServerPort(3000)
|
|
16
|
+
const SERVER_HOST = process.env.NAKO3SERVER_HOST || 'localhost'
|
|
17
17
|
const rootDir = path.resolve(__dirname, '../../')
|
|
18
18
|
|
|
19
19
|
// ライブラリがあるかチェック
|
|
@@ -86,9 +86,8 @@ const server = http.createServer(function (req, res) {
|
|
|
86
86
|
})
|
|
87
87
|
// サーバを起動
|
|
88
88
|
server.listen(SERVER_PORT, SERVER_HOST, function () {
|
|
89
|
-
const url = 'http://
|
|
89
|
+
const url = 'http://' + SERVER_HOST + ':' + SERVER_PORT
|
|
90
90
|
console.log('### 超簡易Webサーバが起動しました')
|
|
91
|
-
console.log('### script: /src/nako3server.mjs')
|
|
92
91
|
console.log('[URL]', url)
|
|
93
92
|
if (process.env.NAKO3SERVER_OPEN !== '0') {
|
|
94
93
|
opener(url)
|
|
@@ -124,3 +123,15 @@ function isDir (pathName) {
|
|
|
124
123
|
}
|
|
125
124
|
return false
|
|
126
125
|
}
|
|
126
|
+
|
|
127
|
+
// ポート番号を検証して、安全な値のみを利用する
|
|
128
|
+
function resolveServerPort (defaultPort) {
|
|
129
|
+
const rawPort = process.env.PORT || process.argv[2] || defaultPort
|
|
130
|
+
const port = Number.parseInt(rawPort, 10)
|
|
131
|
+
if (Number.isNaN(port) || port < 0 || port > 65535) {
|
|
132
|
+
console.error(`[ERROR] 無効なポート番号です: ${rawPort}`)
|
|
133
|
+
console.error('[ERROR] 環境変数 PORT またはコマンドライン引数には 0 から 65535 の整数を指定してください。')
|
|
134
|
+
process.exit(1)
|
|
135
|
+
}
|
|
136
|
+
return port
|
|
137
|
+
}
|