nadesiko3 3.7.20 → 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.
Files changed (40) hide show
  1. package/batch/command.txt +115 -113
  2. package/core/package.json +2 -2
  3. package/core/src/README.md +98 -0
  4. package/core/src/nako_core_version.mjs +2 -2
  5. package/core/src/nako_core_version.mts +2 -2
  6. package/package.json +21 -27
  7. package/release/_hash.txt +36 -36
  8. package/release/_script-tags.txt +16 -16
  9. package/release/command.json +1 -1
  10. package/release/command.json.js +1 -1
  11. package/release/command_cnako3.json +1 -1
  12. package/release/command_list.json +1 -1
  13. package/release/edit_main.js +2 -2
  14. package/release/edit_main.js.map +3 -3
  15. package/release/editor.js +2 -2
  16. package/release/plugin_datetime.js +1 -1
  17. package/release/plugin_datetime.js.map +3 -3
  18. package/release/plugin_markup.js +28 -28
  19. package/release/plugin_markup.js.map +3 -3
  20. package/release/version.js +2 -2
  21. package/release/version_main.js +2 -2
  22. package/release/version_main.js.map +2 -2
  23. package/release/wnako3.js +13 -13
  24. package/release/wnako3.js.map +2 -2
  25. package/release/wnako3webworker.js +1 -1
  26. package/release/wnako3webworker.js.map +1 -1
  27. package/src/cnako3mod.mjs +1 -2
  28. package/src/cnako3mod.mts +1 -2
  29. package/src/nako_version.mjs +2 -2
  30. package/src/nako_version.mts +2 -2
  31. package/src/plugin_httpserver.mjs +184 -13
  32. package/src/plugin_httpserver.mts +176 -10
  33. package/src/plugin_node.mjs +0 -1
  34. package/src/plugin_node.mts +0 -1
  35. package/src/wnako3mod.mjs +3 -0
  36. package/src/wnako3mod.mts +3 -0
  37. package/test/common/wnako3mod_test.mjs +48 -0
  38. package/test/node/package_json_test.mjs +17 -0
  39. package/test/node/plugin_httpserver_test.mjs +239 -0
  40. package/tools/nako3server/index.nako3 +4 -2
package/src/cnako3mod.mjs CHANGED
@@ -8,7 +8,6 @@ import { exec } from 'node:child_process';
8
8
  import path from 'node:path';
9
9
  import url from 'node:url';
10
10
  import fse from 'fs-extra';
11
- import fetch from 'node-fetch';
12
11
  import { NakoCompiler, newCompilerOptions } from '../core/src/nako3.mjs';
13
12
  import { NakoImportError } from '../core/src/nako_errors.mjs';
14
13
  import { NakoGenOptions } from '../core/src/nako_gen.mjs';
@@ -238,7 +237,7 @@ export class CNako3 extends NakoCompiler {
238
237
  // or 以下のコピーだと依存ファイルがコピーされない package.jsonを見てコピーする必要がある
239
238
  const orgModule = path.join(__dirname, '..', 'node_modules');
240
239
  const dirNodeModules = path.join(path.dirname(opt.output), 'node_modules');
241
- const modlist = ['fs-extra', 'iconv-lite', 'opener', 'node-fetch', 'shell-quote'];
240
+ const modlist = ['fs-extra', 'iconv-lite', 'opener', 'shell-quote'];
242
241
  const copied = {};
243
242
  // 再帰的に必要なモジュールをコピーする
244
243
  const copyModule = (mod) => {
package/src/cnako3mod.mts CHANGED
@@ -11,7 +11,6 @@ import path from 'node:path'
11
11
  import url from 'node:url'
12
12
  import fse from 'fs-extra'
13
13
 
14
- import fetch from 'node-fetch'
15
14
  import { NakoCompiler, LoaderTool, newCompilerOptions } from '../core/src/nako3.mjs'
16
15
  import { NakoImportError } from '../core/src/nako_errors.mjs'
17
16
 
@@ -278,7 +277,7 @@ export class CNako3 extends NakoCompiler {
278
277
  // or 以下のコピーだと依存ファイルがコピーされない package.jsonを見てコピーする必要がある
279
278
  const orgModule = path.join(__dirname, '..', 'node_modules')
280
279
  const dirNodeModules = path.join(path.dirname(opt.output), 'node_modules')
281
- const modlist = ['fs-extra', 'iconv-lite', 'opener', 'node-fetch', 'shell-quote']
280
+ const modlist = ['fs-extra', 'iconv-lite', 'opener', 'shell-quote']
282
281
  const copied: { [key: string]: boolean } = {}
283
282
  // 再帰的に必要なモジュールをコピーする
284
283
  const copyModule = (mod: string) => {
@@ -1,8 +1,8 @@
1
1
  // 実際のバージョン定義 (自動生成されるので以下を編集しない)
2
2
  const nakoVersion = {
3
- version: '3.7.20',
3
+ version: '3.7.21',
4
4
  major: 3,
5
5
  minor: 7,
6
- patch: 20
6
+ patch: 21
7
7
  };
8
8
  export default nakoVersion;
@@ -11,9 +11,9 @@ export interface NakoVersion {
11
11
  }
12
12
  // 実際のバージョン定義 (自動生成されるので以下を編集しない)
13
13
  const nakoVersion: NakoVersion = {
14
- version: '3.7.20',
14
+ version: '3.7.21',
15
15
  major: 3,
16
16
  minor: 7,
17
- patch: 20
17
+ patch: 21
18
18
  }
19
19
  export default nakoVersion
@@ -2,9 +2,11 @@
2
2
  import fs from 'fs';
3
3
  import http from 'http';
4
4
  import path from 'path';
5
+ import os from 'os';
5
6
  // 定数
6
7
  const HTTPSERVER_LOGID = '[簡易HTTPサーバ]';
7
8
  const ERR_NOHTTPSERVER = '最初に『簡易HTTPサーバ起動時』を実行してサーバを起動する必要があります。';
9
+ const MAX_BODY_SIZE_POST = 10 * 1024 * 1024; // 10MB
8
10
  class EasyURLItem {
9
11
  constructor(action) {
10
12
  this.action = action;
@@ -30,19 +32,86 @@ class EasyURLDispather {
30
32
  const params = this.parseURL(req.url);
31
33
  const url = params['?URL'];
32
34
  this.sys.__setSysVar('GETデータ', params);
33
- // URLの一致を調べてアクションを実行
34
- const filtered = this.items.filter(v => url.startsWith(v.url)).sort((a, b) => { return b.url.length - a.url.length; });
35
- for (const it of filtered) {
36
- let isBreak = false;
37
- if (it.action === 'static') {
38
- isBreak = this.doRequestStatic(req, res, it);
39
- }
40
- else if (it.action === 'callback') {
41
- isBreak = this.doRequestCallback(req, res, it);
42
- }
43
- if (isBreak) {
44
- break;
35
+ const runDispatcher = (postData) => {
36
+ this.sys.__setSysVar('POSTデータ', postData);
37
+ // URLの一致を調べてアクションを実行
38
+ const filtered = this.items.filter(v => url.startsWith(v.url)).sort((a, b) => { return b.url.length - a.url.length; });
39
+ for (const it of filtered) {
40
+ let isBreak = false;
41
+ if (it.action === 'static') {
42
+ isBreak = this.doRequestStatic(req, res, it);
43
+ }
44
+ else if (it.action === 'callback') {
45
+ isBreak = this.doRequestCallback(req, res, it);
46
+ }
47
+ if (isBreak) {
48
+ break;
49
+ }
45
50
  }
51
+ };
52
+ if (req.method === 'POST') {
53
+ let bodySize = 0;
54
+ const chunks = [];
55
+ req.on('data', (chunk) => {
56
+ bodySize += chunk.length;
57
+ if (bodySize > MAX_BODY_SIZE_POST) {
58
+ res.statusCode = 413;
59
+ res.end('Request entity too large.');
60
+ req.destroy();
61
+ return;
62
+ }
63
+ chunks.push(chunk);
64
+ });
65
+ req.on('end', async () => {
66
+ const bodyBuffer = Buffer.concat(chunks);
67
+ let postData = {};
68
+ let filesData = [];
69
+ const contentType = req.headers['content-type'] || '';
70
+ try {
71
+ if (contentType.indexOf('multipart/form-data') >= 0) {
72
+ const boundaryMatch = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/);
73
+ if (boundaryMatch) {
74
+ const boundary = (boundaryMatch[1] || boundaryMatch[2] || '').trim();
75
+ const parsed = await parseMultipart(bodyBuffer, boundary);
76
+ postData = parsed.fields;
77
+ filesData = parsed.files;
78
+ }
79
+ }
80
+ else if (contentType.indexOf('application/json') >= 0) {
81
+ const bodyStr = bodyBuffer.toString('utf-8');
82
+ try {
83
+ postData = JSON.parse(bodyStr);
84
+ }
85
+ catch (e) {
86
+ postData = bodyStr;
87
+ }
88
+ }
89
+ else if (contentType.indexOf('application/x-www-form-urlencoded') >= 0) {
90
+ const bodyStr = bodyBuffer.toString('utf-8');
91
+ const searchParams = new URLSearchParams(bodyStr);
92
+ const obj = {};
93
+ for (const [key, val] of searchParams.entries()) {
94
+ obj[key] = val;
95
+ }
96
+ postData = obj;
97
+ }
98
+ else {
99
+ postData = bodyBuffer.toString('utf-8');
100
+ }
101
+ }
102
+ catch (err) {
103
+ console.error(`${HTTPSERVER_LOGID} アップロード保存エラー: ${err.message}`);
104
+ res.statusCode = 500;
105
+ res.end('Failed to save upload file.');
106
+ return;
107
+ }
108
+ this.sys.__setSysVar('FILESデータ', filesData);
109
+ runDispatcher(postData);
110
+ });
111
+ }
112
+ else {
113
+ this.sys.__setSysVar('FILESデータ', []);
114
+ runDispatcher({});
46
115
  }
47
116
  }
48
117
  return404(res) {
@@ -51,7 +120,9 @@ class EasyURLDispather {
51
120
  res.end('<html><meta charset="utf-8"><body><h1>404 見当たりません。</h1></body></html>');
52
121
  }
53
122
  doRequestStatic(req, res, it) {
54
- let url = ('' + req.url).replace(/\.\./g, ''); // URLの..を許可しない
123
+ const params = this.parseURL(req.url);
124
+ const rawUrl = params['?URL'] || '';
125
+ let url = ('' + rawUrl).replace(/\.\./g, ''); // URLの..を許可しない
55
126
  url = url.substring(it.url.length);
56
127
  let fpath = path.join(it.path, url);
57
128
  console.log('FILE=', fpath);
@@ -114,11 +185,106 @@ class EasyURLDispather {
114
185
  return params;
115
186
  }
116
187
  }
188
+ async function parseMultipart(body, boundary) {
189
+ const fields = {};
190
+ const files = [];
191
+ const boundaryBuffer = Buffer.from('--' + boundary);
192
+ let pos = 0;
193
+ const parts = [];
194
+ while (true) {
195
+ const nextIdx = body.indexOf(boundaryBuffer, pos);
196
+ if (nextIdx === -1) {
197
+ break;
198
+ }
199
+ if (pos > 0) {
200
+ let endPos = nextIdx;
201
+ if (body[nextIdx - 2] === 13 && body[nextIdx - 1] === 10) {
202
+ endPos -= 2;
203
+ }
204
+ else if (body[nextIdx - 1] === 10) {
205
+ endPos -= 1;
206
+ }
207
+ parts.push(body.subarray(pos, endPos));
208
+ }
209
+ pos = nextIdx + boundaryBuffer.length;
210
+ }
211
+ for (const part of parts) {
212
+ if (part.length === 0) {
213
+ continue;
214
+ }
215
+ let start = 0;
216
+ if (part[0] === 13 && part[1] === 10) {
217
+ start = 2;
218
+ }
219
+ else if (part[0] === 10) {
220
+ start = 1;
221
+ }
222
+ const headerEnd = part.indexOf(Buffer.from('\r\n\r\n'), start);
223
+ let bodyStart = 0;
224
+ let headerStr = '';
225
+ if (headerEnd !== -1) {
226
+ headerStr = part.toString('utf-8', start, headerEnd);
227
+ bodyStart = headerEnd + 4;
228
+ }
229
+ else {
230
+ const headerEndLf = part.indexOf(Buffer.from('\n\n'), start);
231
+ if (headerEndLf !== -1) {
232
+ headerStr = part.toString('utf-8', start, headerEndLf);
233
+ bodyStart = headerEndLf + 2;
234
+ }
235
+ }
236
+ if (bodyStart === 0) {
237
+ continue;
238
+ }
239
+ const partBody = part.subarray(bodyStart);
240
+ const headers = {};
241
+ const lines = headerStr.split(/\r?\n/);
242
+ for (const line of lines) {
243
+ const idx = line.indexOf(':');
244
+ if (idx !== -1) {
245
+ const key = line.substring(0, idx).trim().toLowerCase();
246
+ const val = line.substring(idx + 1).trim();
247
+ headers[key] = val;
248
+ }
249
+ }
250
+ const contentDisposition = headers['content-disposition'] || '';
251
+ const nameMatch = contentDisposition.match(/name="([^"]+)"/);
252
+ const filenameMatch = contentDisposition.match(/filename="([^"]+)"/);
253
+ if (nameMatch) {
254
+ const name = nameMatch[1];
255
+ if (filenameMatch) {
256
+ const filename = filenameMatch[1];
257
+ const safeFilename = path.basename(filename).replace(/[\\/]/g, '_');
258
+ const contentType = headers['content-type'] || 'application/octet-stream';
259
+ const uploadDir = path.join(os.tmpdir(), 'nako3-plugin_httpserver_upload');
260
+ if (!fs.existsSync(uploadDir)) {
261
+ await fs.promises.mkdir(uploadDir, { recursive: true });
262
+ }
263
+ const uniqueName = Date.now() + '_' + Math.random().toString(36).substring(2, 8) + '_' + safeFilename;
264
+ const filepath = path.join(uploadDir, uniqueName);
265
+ await fs.promises.writeFile(filepath, partBody);
266
+ files.push({
267
+ fieldName: name,
268
+ name: filename,
269
+ path: filepath,
270
+ size: partBody.length,
271
+ type: contentType
272
+ });
273
+ }
274
+ else {
275
+ fields[name] = partBody.toString('utf-8');
276
+ }
277
+ }
278
+ }
279
+ return { files, fields };
280
+ }
117
281
  // MIMEタイプ
118
282
  const MimeTypes = {
119
283
  '.html': 'text/html',
120
284
  '.css': 'text/css',
121
285
  '.js': 'text/javascript',
286
+ '.mjs': 'text/javascript',
287
+ '.nako3': 'text/nadesiko3',
122
288
  '.png': 'image/png',
123
289
  '.gif': 'image/gif',
124
290
  '.svg': 'svg+xml'
@@ -168,6 +334,8 @@ const PluginHttpServer = {
168
334
  },
169
335
  // @簡易HTTPサーバ
170
336
  'GETデータ': { type: 'const', value: '' }, // @GETでーた
337
+ 'POSTデータ': { type: 'const', value: '' }, // @POSTでーた
338
+ 'FILESデータ': { type: 'const', value: '' }, // @FILESでーた
171
339
  '簡易HTTPサーバ起動時': {
172
340
  type: 'func',
173
341
  josi: [['を'], ['の', 'で']],
@@ -179,6 +347,9 @@ const PluginHttpServer = {
179
347
  dp.server = http.createServer((req, res) => {
180
348
  dp.doRequest(req, res);
181
349
  });
350
+ dp.server.on('error', (err) => {
351
+ console.error(`${HTTPSERVER_LOGID} エラー: ${err.message}`);
352
+ });
182
353
  // サーバ起動
183
354
  dp.server.listen(port, () => {
184
355
  console.log(`${HTTPSERVER_LOGID} ポート番号(${port})で監視開始`);
@@ -2,10 +2,13 @@
2
2
  import fs from 'fs'
3
3
  import http from 'http'
4
4
  import path from 'path'
5
+ import os from 'os'
5
6
 
6
7
  // 定数
7
8
  const HTTPSERVER_LOGID = '[簡易HTTPサーバ]'
8
9
  const ERR_NOHTTPSERVER = '最初に『簡易HTTPサーバ起動時』を実行してサーバを起動する必要があります。'
10
+ const MAX_BODY_SIZE_POST = 10 * 1024 * 1024 // 10MB
11
+
9
12
  // オブジェクト
10
13
  type EasyURLActionType = 'static' | 'callback'
11
14
  type EasyURLCallback = (req: any, res: any) => void
@@ -49,16 +52,79 @@ class EasyURLDispather {
49
52
  const params = this.parseURL(req.url)
50
53
  const url = params['?URL']
51
54
  this.sys.__setSysVar('GETデータ', params)
52
- // URLの一致を調べてアクションを実行
53
- const filtered = this.items.filter(v => url.startsWith(v.url)).sort((a, b) => { return b.url.length - a.url.length })
54
- for (const it of filtered) {
55
- let isBreak = false
56
- if (it.action === 'static') {
57
- isBreak = this.doRequestStatic(req, res, it)
58
- } else if (it.action === 'callback') {
59
- isBreak = this.doRequestCallback(req, res, it)
55
+
56
+ const runDispatcher = (postData: any) => {
57
+ this.sys.__setSysVar('POSTデータ', postData)
58
+ // URLの一致を調べてアクションを実行
59
+ const filtered = this.items.filter(v => url.startsWith(v.url)).sort((a, b) => { return b.url.length - a.url.length })
60
+ for (const it of filtered) {
61
+ let isBreak = false
62
+ if (it.action === 'static') {
63
+ isBreak = this.doRequestStatic(req, res, it)
64
+ } else if (it.action === 'callback') {
65
+ isBreak = this.doRequestCallback(req, res, it)
66
+ }
67
+ if (isBreak) { break }
60
68
  }
61
- if (isBreak) { break }
69
+ }
70
+
71
+ if (req.method === 'POST') {
72
+ let bodySize = 0
73
+ const chunks: Buffer[] = []
74
+ req.on('data', (chunk: Buffer) => {
75
+ bodySize += chunk.length
76
+ if (bodySize > MAX_BODY_SIZE_POST) {
77
+ res.statusCode = 413
78
+ res.end('Request entity too large.')
79
+ req.destroy()
80
+ return
81
+ }
82
+ chunks.push(chunk)
83
+ })
84
+ req.on('end', async() => {
85
+ const bodyBuffer = Buffer.concat(chunks)
86
+ let postData: any = {}
87
+ let filesData: any[] = []
88
+ const contentType = req.headers['content-type'] || ''
89
+ try {
90
+ if (contentType.indexOf('multipart/form-data') >= 0) {
91
+ const boundaryMatch = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/)
92
+ if (boundaryMatch) {
93
+ const boundary = (boundaryMatch[1] || boundaryMatch[2] || '').trim()
94
+ const parsed = await parseMultipart(bodyBuffer, boundary)
95
+ postData = parsed.fields
96
+ filesData = parsed.files
97
+ }
98
+ } else if (contentType.indexOf('application/json') >= 0) {
99
+ const bodyStr = bodyBuffer.toString('utf-8')
100
+ try {
101
+ postData = JSON.parse(bodyStr)
102
+ } catch (e) {
103
+ postData = bodyStr
104
+ }
105
+ } else if (contentType.indexOf('application/x-www-form-urlencoded') >= 0) {
106
+ const bodyStr = bodyBuffer.toString('utf-8')
107
+ const searchParams = new URLSearchParams(bodyStr)
108
+ const obj: any = {}
109
+ for (const [key, val] of searchParams.entries()) {
110
+ obj[key] = val
111
+ }
112
+ postData = obj
113
+ } else {
114
+ postData = bodyBuffer.toString('utf-8')
115
+ }
116
+ } catch (err: any) {
117
+ console.error(`${HTTPSERVER_LOGID} アップロード保存エラー: ${err.message}`)
118
+ res.statusCode = 500
119
+ res.end('Failed to save upload file.')
120
+ return
121
+ }
122
+ this.sys.__setSysVar('FILESデータ', filesData)
123
+ runDispatcher(postData)
124
+ })
125
+ } else {
126
+ this.sys.__setSysVar('FILESデータ', [])
127
+ runDispatcher({})
62
128
  }
63
129
  }
64
130
 
@@ -69,7 +135,9 @@ class EasyURLDispather {
69
135
  }
70
136
 
71
137
  doRequestStatic(req: any, res: any, it: EasyURLItem): boolean {
72
- let url: string = ('' + req.url).replace(/\.\./g, '') // URLの..を許可しない
138
+ const params = this.parseURL(req.url)
139
+ const rawUrl = params['?URL'] || ''
140
+ let url: string = ('' + rawUrl).replace(/\.\./g, '') // URLの..を許可しない
73
141
  url = url.substring(it.url.length)
74
142
  let fpath = path.join(it.path, url)
75
143
  console.log('FILE=', fpath)
@@ -134,11 +202,104 @@ class EasyURLDispather {
134
202
  return params
135
203
  }
136
204
  }
205
+ async function parseMultipart(body: Buffer, boundary: string): Promise<{ files: any[], fields: any }> {
206
+ const fields: any = {}
207
+ const files: any[] = []
208
+
209
+ const boundaryBuffer = Buffer.from('--' + boundary)
210
+ let pos = 0
211
+ const parts: Buffer[] = []
212
+
213
+ while (true) {
214
+ const nextIdx = body.indexOf(boundaryBuffer, pos)
215
+ if (nextIdx === -1) { break }
216
+ if (pos > 0) {
217
+ let endPos = nextIdx
218
+ if (body[nextIdx - 2] === 13 && body[nextIdx - 1] === 10) {
219
+ endPos -= 2
220
+ } else if (body[nextIdx - 1] === 10) {
221
+ endPos -= 1
222
+ }
223
+ parts.push(body.subarray(pos, endPos))
224
+ }
225
+ pos = nextIdx + boundaryBuffer.length
226
+ }
227
+
228
+ for (const part of parts) {
229
+ if (part.length === 0) { continue }
230
+ let start = 0
231
+ if (part[0] === 13 && part[1] === 10) { start = 2 }
232
+ else if (part[0] === 10) { start = 1 }
233
+
234
+ const headerEnd = part.indexOf(Buffer.from('\r\n\r\n'), start)
235
+ let bodyStart = 0
236
+ let headerStr = ''
237
+ if (headerEnd !== -1) {
238
+ headerStr = part.toString('utf-8', start, headerEnd)
239
+ bodyStart = headerEnd + 4
240
+ } else {
241
+ const headerEndLf = part.indexOf(Buffer.from('\n\n'), start)
242
+ if (headerEndLf !== -1) {
243
+ headerStr = part.toString('utf-8', start, headerEndLf)
244
+ bodyStart = headerEndLf + 2
245
+ }
246
+ }
247
+
248
+ if (bodyStart === 0) { continue }
249
+
250
+ const partBody = part.subarray(bodyStart)
251
+ const headers: any = {}
252
+ const lines = headerStr.split(/\r?\n/)
253
+ for (const line of lines) {
254
+ const idx = line.indexOf(':')
255
+ if (idx !== -1) {
256
+ const key = line.substring(0, idx).trim().toLowerCase()
257
+ const val = line.substring(idx + 1).trim()
258
+ headers[key] = val
259
+ }
260
+ }
261
+
262
+ const contentDisposition = headers['content-disposition'] || ''
263
+ const nameMatch = contentDisposition.match(/name="([^"]+)"/)
264
+ const filenameMatch = contentDisposition.match(/filename="([^"]+)"/)
265
+
266
+ if (nameMatch) {
267
+ const name = nameMatch[1]
268
+ if (filenameMatch) {
269
+ const filename = filenameMatch[1]
270
+ const safeFilename = path.basename(filename).replace(/[\\/]/g, '_')
271
+ const contentType = headers['content-type'] || 'application/octet-stream'
272
+
273
+ const uploadDir = path.join(os.tmpdir(), 'nako3-plugin_httpserver_upload')
274
+ if (!fs.existsSync(uploadDir)) {
275
+ await fs.promises.mkdir(uploadDir, { recursive: true })
276
+ }
277
+ const uniqueName = Date.now() + '_' + Math.random().toString(36).substring(2, 8) + '_' + safeFilename
278
+ const filepath = path.join(uploadDir, uniqueName)
279
+ await fs.promises.writeFile(filepath, partBody)
280
+
281
+ files.push({
282
+ fieldName: name,
283
+ name: filename,
284
+ path: filepath,
285
+ size: partBody.length,
286
+ type: contentType
287
+ })
288
+ } else {
289
+ fields[name] = partBody.toString('utf-8')
290
+ }
291
+ }
292
+ }
293
+
294
+ return { files, fields }
295
+ }
137
296
  // MIMEタイプ
138
297
  const MimeTypes: any = {
139
298
  '.html': 'text/html',
140
299
  '.css': 'text/css',
141
300
  '.js': 'text/javascript',
301
+ '.mjs': 'text/javascript',
302
+ '.nako3': 'text/nadesiko3',
142
303
  '.png': 'image/png',
143
304
  '.gif': 'image/gif',
144
305
  '.svg': 'svg+xml'
@@ -184,6 +345,8 @@ const PluginHttpServer = {
184
345
  },
185
346
  // @簡易HTTPサーバ
186
347
  'GETデータ': { type: 'const', value: '' }, // @GETでーた
348
+ 'POSTデータ': { type: 'const', value: '' }, // @POSTでーた
349
+ 'FILESデータ': { type: 'const', value: '' }, // @FILESでーた
187
350
  '簡易HTTPサーバ起動時': { // @ポート番号PORTを指定して簡易HTTPサーバを起動して、CALLBACKを実行する。 // @かんいHTTPさーばきどうしたとき
188
351
  type: 'func',
189
352
  josi: [['を'], ['の', 'で']],
@@ -195,6 +358,9 @@ const PluginHttpServer = {
195
358
  dp.server = http.createServer((req: any, res: any) => {
196
359
  dp.doRequest(req, res)
197
360
  })
361
+ dp.server.on('error', (err: any) => {
362
+ console.error(`${HTTPSERVER_LOGID} エラー: ${err.message}`)
363
+ })
198
364
  // サーバ起動
199
365
  dp.server.listen(port, () => {
200
366
  console.log(`${HTTPSERVER_LOGID} ポート番号(${port})で監視開始`)
@@ -13,7 +13,6 @@ import url from 'node:url';
13
13
  import opener from 'opener';
14
14
  import iconv from 'iconv-lite';
15
15
  import shellQuote from 'shell-quote';
16
- import fetch, { FormData, Blob } from 'node-fetch';
17
16
  import fse from 'fs-extra';
18
17
  import { getEnv, isWindows, getCommandLineArgs } from './deno_wrapper.mjs';
19
18
  const __filename = url.fileURLToPath(import.meta.url);
@@ -14,7 +14,6 @@ import url from 'node:url'
14
14
  import opener from 'opener'
15
15
  import iconv from 'iconv-lite'
16
16
  import shellQuote from 'shell-quote'
17
- import fetch, { FormData, Blob } from 'node-fetch'
18
17
  import fse from 'fs-extra'
19
18
  import { NakoSystem } from '../core/src/plugin_api.mjs'
20
19
 
package/src/wnako3mod.mjs CHANGED
@@ -236,6 +236,9 @@ function dirname(s) {
236
236
  }
237
237
  function resolveURL(base, s) {
238
238
  const baseA = base.split('/');
239
+ if (baseA.length > 0 && baseA[baseA.length - 1] === '') {
240
+ baseA.pop();
241
+ }
239
242
  const sA = s.split('/');
240
243
  for (const p of sA) {
241
244
  if (p === '') {
package/src/wnako3mod.mts CHANGED
@@ -240,6 +240,9 @@ function dirname(s: string) {
240
240
 
241
241
  function resolveURL(base: string, s: string) {
242
242
  const baseA = base.split('/')
243
+ if (baseA.length > 0 && baseA[baseA.length - 1] === '') {
244
+ baseA.pop()
245
+ }
243
246
  const sA = s.split('/')
244
247
  for (const p of sA) {
245
248
  if (p === '') { continue }
@@ -0,0 +1,48 @@
1
+ import assert from 'assert'
2
+ import { WebNakoCompiler } from '../../src/wnako3mod.mjs'
3
+
4
+ describe('wnako3mod_test', () => {
5
+ let originalWindow
6
+
7
+ before(() => {
8
+ // window のモック
9
+ originalWindow = globalThis.window
10
+ globalThis.window = {
11
+ location: {
12
+ href: 'http://localhost:8888/index.html'
13
+ }
14
+ }
15
+ })
16
+
17
+ after(() => {
18
+ globalThis.window = originalWindow
19
+ })
20
+
21
+ it('resolvePath で baseDir が / のときの // スタートの不具合が修正されていること', () => {
22
+ const compiler = new WebNakoCompiler()
23
+ const token = { file: 'main', line: 1 }
24
+
25
+ // http://localhost:8888/index.html から hoge.nako3 を取り込む想定
26
+ const result = compiler.resolvePath('hoge.nako3', token, '')
27
+ // 結果が /hoge.nako3 になるべき(//hoge.nako3 ではなく)
28
+ assert.strictEqual(result.filePath, '/hoge.nako3')
29
+ })
30
+
31
+ it('resolvePath で baseDir が /dir/ のとき', () => {
32
+ const compiler = new WebNakoCompiler()
33
+ const token = { file: 'main', line: 1 }
34
+
35
+ globalThis.window.location.href = 'http://localhost:8888/dir/index.html'
36
+ const result = compiler.resolvePath('hoge.nako3', token, '')
37
+ assert.strictEqual(result.filePath, '/dir/hoge.nako3')
38
+ })
39
+
40
+ it('resolvePath で baseDir が /dir/subdir/ のとき', () => {
41
+ const compiler = new WebNakoCompiler()
42
+ const token = { file: 'main', line: 1 }
43
+
44
+ globalThis.window.location.href = 'http://localhost:8888/dir/subdir/index.html'
45
+ const result = compiler.resolvePath('hoge.nako3', token, '')
46
+ assert.strictEqual(result.filePath, '/dir/subdir/hoge.nako3')
47
+ })
48
+ })
@@ -0,0 +1,17 @@
1
+ import fs from 'node:fs'
2
+ import assert from 'node:assert'
3
+ import { test } from 'node:test'
4
+
5
+ test('package.json に node-fetch の依存がない', () => {
6
+ const pkgUrl = new URL('../../package.json', import.meta.url)
7
+ const pkg = JSON.parse(fs.readFileSync(pkgUrl, 'utf-8'))
8
+ const dependencySections = [
9
+ 'dependencies',
10
+ 'devDependencies',
11
+ 'optionalDependencies',
12
+ 'peerDependencies'
13
+ ]
14
+ for (const section of dependencySections) {
15
+ assert.equal(pkg[section]?.['node-fetch'], undefined, `${section} に node-fetch が含まれています`)
16
+ }
17
+ })