@slim-lang/core 1.2.0
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/README.md +666 -0
- package/package.json +55 -0
- package/packages/slim/.spm +7 -0
- package/packages/slim/converters/main.slim +106 -0
- package/packages/slim/helpers/array.slim +25 -0
- package/packages/slim/helpers/path.slim +3 -0
- package/packages/slim/helpers/request.slim +102 -0
- package/packages/slim/helpers/string.slim +27 -0
- package/packages/slim/main.slim +42 -0
- package/packages/slim/parse/main.slim +25 -0
- package/packages/slim/server/main.slim +423 -0
- package/packages/slim/time/main.slim +66 -0
- package/packages/slim/types/common.slim +6 -0
- package/packages/slim/types/formats.slim +23 -0
- package/packages/slim/types/hash.slim +6 -0
- package/packages/slim/types/mails.slim +3 -0
- package/packages/slim/types/numerical.slim +9 -0
- package/packages/slim/types/time.slim +3 -0
- package/run-dev-slim.js +133 -0
- package/run-slim.js +20 -0
- package/src/bin/api/github_auth.js +89 -0
- package/src/bin/api/github_get.js +139 -0
- package/src/bin/api/github_req.js +455 -0
- package/src/bin/api/lock.js +37 -0
- package/src/bin/api/spm.js +103 -0
- package/src/bin/api/storage.js +30 -0
- package/src/bin/cli.js +404 -0
- package/src/bin/config.default.json +5 -0
- package/src/bin/helpers.js +147 -0
- package/src/bin/parsers/spm.js +174 -0
- package/src/bin/spm.js +519 -0
- package/src/checker.js +926 -0
- package/src/compile.js +230 -0
- package/src/external/classErrors.js +202 -0
- package/src/external/client.js +38 -0
- package/src/external/core.js +861 -0
- package/src/external/defaults.js +25 -0
- package/src/external/helpers.js +541 -0
- package/src/external/slim-globals.d.ts +65 -0
- package/src/external/types.js +38 -0
- package/src/format.js +81 -0
- package/src/handlers/errorHandler.js +43 -0
- package/src/handlers/parser/components.js +250 -0
- package/src/handlers/parserHandler.js +793 -0
- package/src/jsdoc.js +273 -0
- package/src/lexer.js +174 -0
- package/src/modulePaths.js +74 -0
- package/src/parser.js +818 -0
- package/src/repl.js +32 -0
- package/src/sourcemap.js +0 -0
- package/src/test-runner.js +62 -0
- package/src/transform.js +765 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
use http from "http"
|
|
2
|
+
use * as fs from "fs"
|
|
3
|
+
use * as nodePath from "path"
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
|
|
6
|
+
export type ServerInstance = typeof Server;
|
|
7
|
+
|
|
8
|
+
export struct RedirectRoute {
|
|
9
|
+
from: string
|
|
10
|
+
to: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export struct StaticRoute {
|
|
14
|
+
from: string
|
|
15
|
+
to: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export struct SlimServerConf {
|
|
19
|
+
port: int = 3000
|
|
20
|
+
dev: bool = false
|
|
21
|
+
redirects: object[] = [{}]
|
|
22
|
+
statics: object[] = [{}]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class Server {
|
|
26
|
+
constructor(port: int = 3000) {
|
|
27
|
+
this.port = port;
|
|
28
|
+
this.routes = [];
|
|
29
|
+
this.apiRoutes = {};
|
|
30
|
+
this.clients = [];
|
|
31
|
+
this.staticDirs = [];
|
|
32
|
+
this.dev = false;
|
|
33
|
+
|
|
34
|
+
this.#loadConfig();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#loadConfig() {
|
|
38
|
+
let configDir = process.cwd();
|
|
39
|
+
let raw: object = {};
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const slimConf = JSON.parse(fs.readFileSync(nodePath.join(process.cwd(), "slimconfig.json"), "utf8"));
|
|
43
|
+
if (slimConf.main) configDir = nodePath.dirname(nodePath.resolve(slimConf.main));
|
|
44
|
+
} catch (e) {}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
raw = JSON.parse(fs.readFileSync(nodePath.join(configDir, "slimserver.json"), "utf8"));
|
|
48
|
+
} catch (e) {}
|
|
49
|
+
|
|
50
|
+
const conf = SlimServerConf.new(raw);
|
|
51
|
+
|
|
52
|
+
this.port = conf.port;
|
|
53
|
+
this.dev = conf.dev || process.env.SLIM_DEV === "1";
|
|
54
|
+
|
|
55
|
+
this.redirect(conf.redirects);
|
|
56
|
+
|
|
57
|
+
const statics = conf.statics.map(entry => {
|
|
58
|
+
if (empty entry) return entry;
|
|
59
|
+
return { from: entry.from, to: nodePath.resolve(configDir, entry.to) };
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.static(statics);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
page(path: string, render: function, options?: object) {
|
|
66
|
+
let additionalScript = ""
|
|
67
|
+
let scriptId = 0;
|
|
68
|
+
|
|
69
|
+
func renderTag(content: string[]) {
|
|
70
|
+
const tags: object = {
|
|
71
|
+
"css": {
|
|
72
|
+
tag: "link",
|
|
73
|
+
attr: "href",
|
|
74
|
+
additionalAttrs: {
|
|
75
|
+
rel: "stylesheet"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"js": {
|
|
79
|
+
tag: "script",
|
|
80
|
+
attr: "src"
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
content.forEach(c => {
|
|
85
|
+
const ext = c.split(".").pop();
|
|
86
|
+
|
|
87
|
+
if (ext in tags) {
|
|
88
|
+
const tag = tags[ext].tag;
|
|
89
|
+
const attr = tags[ext].attr;
|
|
90
|
+
const varName = `${tag}_${scriptId++}`;
|
|
91
|
+
let additional = ""
|
|
92
|
+
|
|
93
|
+
if ("additionalAttrs" in tags[ext]) {
|
|
94
|
+
const additionalAttrs = tags[ext].additionalAttrs
|
|
95
|
+
|
|
96
|
+
Object.keys(additionalAttrs).forEach(e => {
|
|
97
|
+
additional += `${varName}.${e} = "${additionalAttrs[e]}"`
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
additionalScript += `
|
|
102
|
+
const ${varName} = document.createElement("${tag}");
|
|
103
|
+
${varName}.${attr} = ${JSON.stringify(c)};
|
|
104
|
+
${additional}
|
|
105
|
+
document.head.appendChild(${varName});
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if(options) {
|
|
112
|
+
if ("head" in options) {
|
|
113
|
+
const headLinks: string[] = options.head
|
|
114
|
+
|
|
115
|
+
renderTag(headLinks)
|
|
116
|
+
}
|
|
117
|
+
if ("title" in options) {
|
|
118
|
+
const title: string = options.title
|
|
119
|
+
|
|
120
|
+
additionalScript += `document.title = "${title}";`
|
|
121
|
+
}
|
|
122
|
+
if ("icon" in options) {
|
|
123
|
+
const icon: string = options.icon
|
|
124
|
+
|
|
125
|
+
additionalScript += `
|
|
126
|
+
link = document.createElement("link");
|
|
127
|
+
link.rel = "icon";
|
|
128
|
+
link.href = "${icon}";
|
|
129
|
+
document.head.appendChild(link);
|
|
130
|
+
`
|
|
131
|
+
}
|
|
132
|
+
if ("onload" in options) {
|
|
133
|
+
const onload: function = options.onload
|
|
134
|
+
|
|
135
|
+
additionalScript += `
|
|
136
|
+
const __onload = ${String(onload)};__onload({ port: ${this.port}});
|
|
137
|
+
`
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.get(path, async (req, res, params) => {
|
|
142
|
+
const isDev = this.dev
|
|
143
|
+
let html = render({ dev: isDev, params });
|
|
144
|
+
const eventsScript = __flush_events__();
|
|
145
|
+
|
|
146
|
+
if(kindof html == "promise") {
|
|
147
|
+
html = await html
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if(kindof html == "object") {
|
|
151
|
+
res.writeHead(200, {
|
|
152
|
+
"Content-Type": "text/json; charset=utf-8",
|
|
153
|
+
"Cache-Control": "no-store"
|
|
154
|
+
});
|
|
155
|
+
res.end(JSON.stringify(html))
|
|
156
|
+
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
html += `<script>`
|
|
161
|
+
|
|
162
|
+
html += additionalScript
|
|
163
|
+
|
|
164
|
+
if (isDev) {
|
|
165
|
+
html += `
|
|
166
|
+
(function () {
|
|
167
|
+
let connectedBefore = false;
|
|
168
|
+
|
|
169
|
+
const es = new EventSource("/__slim_reload__");
|
|
170
|
+
|
|
171
|
+
es.onopen = () => {
|
|
172
|
+
if (connectedBefore) {
|
|
173
|
+
location.reload();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
connectedBefore = true;
|
|
177
|
+
};
|
|
178
|
+
})();`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
html += `</script>`
|
|
182
|
+
|
|
183
|
+
if (eventsScript) {
|
|
184
|
+
html += `<script>${eventsScript}</script>`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
res.writeHead(200, {
|
|
188
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
189
|
+
"Cache-Control": "no-store"
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
res.end(html);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
get(path: string, callback: function) {
|
|
197
|
+
const compiled = this.compileRoute(path);
|
|
198
|
+
this.routes.push({ regex: compiled.regex, keys: compiled.keys, callback });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
escapeRegex(str: string) {
|
|
202
|
+
const specials = ".^$*+?()[]{}|\\";
|
|
203
|
+
let out = "";
|
|
204
|
+
|
|
205
|
+
for (const ch of str) {
|
|
206
|
+
if (specials.includes(ch)) out += "\\";
|
|
207
|
+
out += ch;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
compileRoute(route: string) {
|
|
214
|
+
const keys = [];
|
|
215
|
+
|
|
216
|
+
const pattern = route.split("/").map(segment => {
|
|
217
|
+
if (segment.length > 2 && segment.startsWith("{") && segment.endsWith("}")) {
|
|
218
|
+
keys.push(segment.slice(1, -1));
|
|
219
|
+
return "([^/]+)";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return this.escapeRegex(segment);
|
|
223
|
+
}).join("/");
|
|
224
|
+
|
|
225
|
+
return { regex: new RegExp("^" + pattern + "$"), keys };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
contentType(file: string) {
|
|
229
|
+
const types: object = {
|
|
230
|
+
html: "text/html",
|
|
231
|
+
css: "text/css",
|
|
232
|
+
js: "text/javascript",
|
|
233
|
+
mjs: "text/javascript",
|
|
234
|
+
json: "application/json",
|
|
235
|
+
svg: "image/svg+xml",
|
|
236
|
+
png: "image/png",
|
|
237
|
+
jpg: "image/jpeg",
|
|
238
|
+
jpeg: "image/jpeg",
|
|
239
|
+
gif: "image/gif",
|
|
240
|
+
webp: "image/webp",
|
|
241
|
+
ico: "image/x-icon",
|
|
242
|
+
woff: "font/woff",
|
|
243
|
+
woff2: "font/woff2",
|
|
244
|
+
ttf: "font/ttf",
|
|
245
|
+
txt: "text/plain"
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const ext = file.split(".").pop().toLowerCase();
|
|
249
|
+
const mime = types[ext] ?? "application/octet-stream";
|
|
250
|
+
const isText = mime.startsWith("text/") || mime == "application/json" || mime == "image/svg+xml";
|
|
251
|
+
|
|
252
|
+
return isText ? `${mime}; charset=utf-8` : mime;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
colorStatus(status: int) {
|
|
256
|
+
const text = String(status);
|
|
257
|
+
|
|
258
|
+
if (!process.stdout.isTTY) return text;
|
|
259
|
+
|
|
260
|
+
const esc = String.fromCharCode(27);
|
|
261
|
+
let color = "32";
|
|
262
|
+
|
|
263
|
+
if (status >= 500) color = "31";
|
|
264
|
+
else if (status >= 400) color = "33";
|
|
265
|
+
else if (status >= 300) color = "36";
|
|
266
|
+
|
|
267
|
+
return `${esc}[${color}m${text}${esc}[0m`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
api(path: string, handler: function) {
|
|
271
|
+
this.apiRoutes[path] = handler;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
redirect(paths: object[]) {
|
|
275
|
+
for(const path of paths) {
|
|
276
|
+
if(empty path) continue
|
|
277
|
+
|
|
278
|
+
RedirectRoute.verify(path)
|
|
279
|
+
|
|
280
|
+
const from = path.from
|
|
281
|
+
const to = path.to
|
|
282
|
+
|
|
283
|
+
this.get(from, async (req, res) => {
|
|
284
|
+
res.writeHead(200, {
|
|
285
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
286
|
+
"Cache-Control": "no-store"
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
res.end(`<script>window.location.href="${to}"</script>`);
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
static(paths: object[]) {
|
|
295
|
+
for(const path of paths) {
|
|
296
|
+
if(empty path) continue
|
|
297
|
+
|
|
298
|
+
StaticRoute.verify(path)
|
|
299
|
+
|
|
300
|
+
const from = path.from
|
|
301
|
+
const to = path.to
|
|
302
|
+
|
|
303
|
+
const root = nodePath.resolve(to);
|
|
304
|
+
|
|
305
|
+
if (!fs.existsSync(root)) {
|
|
306
|
+
log(`[SlimServer] Static setup ("${from}" -> "${to}"): directory not found: ${to}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
this.staticDirs.push({ prefix: from, dir: to });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
start() {
|
|
314
|
+
this.get("/__slim_reload__", (req, res) => {
|
|
315
|
+
res.writeHead(200, {
|
|
316
|
+
"Content-Type": "text/event-stream",
|
|
317
|
+
"Cache-Control": "no-cache",
|
|
318
|
+
"Connection": "keep-alive"
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
res.write("retry: 300\n\n");
|
|
322
|
+
|
|
323
|
+
this.clients.push(res);
|
|
324
|
+
|
|
325
|
+
req.on("close", () => {
|
|
326
|
+
this.clients = this.clients.filter(x => x !== res);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
http.createServer((req, res) => {
|
|
331
|
+
const url = req.url.split("?")[0];
|
|
332
|
+
|
|
333
|
+
if (this.dev && url != "/__slim_reload__") {
|
|
334
|
+
const started = Date.now();
|
|
335
|
+
|
|
336
|
+
res.on("finish", () => {
|
|
337
|
+
const ms = Date.now() - started;
|
|
338
|
+
log(`${req.method} ${req.url} ${this.colorStatus(res.statusCode)} in ${ms}ms`);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (const route of this.routes) {
|
|
343
|
+
const match = url.match(route.regex);
|
|
344
|
+
|
|
345
|
+
if (match) {
|
|
346
|
+
const params: object = {};
|
|
347
|
+
route.keys.forEach((key, i) => {
|
|
348
|
+
params[key] = decodeURIComponent(match[i + 1]);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
route.callback(req, res, params);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const apiRoute = this.apiRoutes[url];
|
|
357
|
+
|
|
358
|
+
if (apiRoute) {
|
|
359
|
+
let body = "";
|
|
360
|
+
req.on("data", chunk => { body += chunk });
|
|
361
|
+
req.on("end", async () => {
|
|
362
|
+
let data = null;
|
|
363
|
+
|
|
364
|
+
if (body) {
|
|
365
|
+
try {
|
|
366
|
+
data = JSON.parse(body);
|
|
367
|
+
} catch (e) {
|
|
368
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
369
|
+
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const result = await apiRoute(data, req);
|
|
376
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
377
|
+
res.end(JSON.stringify(result ?? null));
|
|
378
|
+
} catch (e) {
|
|
379
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
380
|
+
res.end(JSON.stringify({ error: e && e.message ? e.message : String(e) }));
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (const mount of this.staticDirs) {
|
|
387
|
+
if (url == mount.prefix || url.startsWith(mount.prefix + "/")) {
|
|
388
|
+
let relative = url.slice(mount.prefix.length);
|
|
389
|
+
while (relative.startsWith("/")) relative = relative.slice(1);
|
|
390
|
+
|
|
391
|
+
const root = nodePath.resolve(mount.dir);
|
|
392
|
+
const filePath = nodePath.join(root, relative);
|
|
393
|
+
|
|
394
|
+
if (filePath != root && !filePath.startsWith(root + nodePath.sep)) {
|
|
395
|
+
res.writeHead(403);
|
|
396
|
+
res.end("403");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
fs.promises.readFile(filePath).then(data => {
|
|
401
|
+
res.writeHead(200, {
|
|
402
|
+
"Content-Type": this.contentType(filePath),
|
|
403
|
+
"Cache-Control": "no-store"
|
|
404
|
+
});
|
|
405
|
+
res.end(data);
|
|
406
|
+
}).catch(() => {
|
|
407
|
+
res.writeHead(404);
|
|
408
|
+
res.end("404");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
res.writeHead(404);
|
|
416
|
+
res.end("404");
|
|
417
|
+
}).listen(this.port);
|
|
418
|
+
|
|
419
|
+
log(`http://localhost:${this.port}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export { Server };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export default class TimeHelper {
|
|
2
|
+
static #convertMsToUnix(ms: int) {
|
|
3
|
+
if(sizeof ms > 12) return Math.floor(ms / 1000)
|
|
4
|
+
return ms
|
|
5
|
+
}
|
|
6
|
+
static now(): int {
|
|
7
|
+
return Date.now()
|
|
8
|
+
}
|
|
9
|
+
static toUnix(time: int): int {
|
|
10
|
+
return this.#convertMsToUnix(time)
|
|
11
|
+
}
|
|
12
|
+
static toDate(time: int, {
|
|
13
|
+
format = "{{dd}}.{{MM}}.{{yyyy}} {{hh}}:{{mm}}:{{ss}}:{{ii}}",
|
|
14
|
+
ampm = false
|
|
15
|
+
} = {}): string {
|
|
16
|
+
const unix: int = this.#convertMsToUnix(time)
|
|
17
|
+
const dateNow: Date = new Date(unix * 1000)
|
|
18
|
+
|
|
19
|
+
const pad: function = (value, len = 2) => String(value).padStart(len, "0")
|
|
20
|
+
|
|
21
|
+
let hours: int = dateNow.getHours()
|
|
22
|
+
let ampmSuffix: string = ""
|
|
23
|
+
|
|
24
|
+
if (ampm) {
|
|
25
|
+
ampmSuffix = hours >= 12 ? "PM" : "AM"
|
|
26
|
+
hours %= 12
|
|
27
|
+
|
|
28
|
+
if (hours === 0) {
|
|
29
|
+
hours = 12
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const map: object = {
|
|
34
|
+
"{{d}}": dateNow.getDate(),
|
|
35
|
+
"{{dd}}": pad(dateNow.getDate()),
|
|
36
|
+
|
|
37
|
+
"{{M}}": dateNow.getMonth() + 1,
|
|
38
|
+
"{{MM}}": pad(dateNow.getMonth() + 1),
|
|
39
|
+
|
|
40
|
+
"{{yy}}": String(dateNow.getFullYear()).slice(-2),
|
|
41
|
+
"{{yyyy}}": dateNow.getFullYear(),
|
|
42
|
+
|
|
43
|
+
"{{h}}": hours,
|
|
44
|
+
"{{hh}}": pad(hours),
|
|
45
|
+
|
|
46
|
+
"{{m}}": dateNow.getMinutes(),
|
|
47
|
+
"{{mm}}": pad(dateNow.getMinutes()),
|
|
48
|
+
|
|
49
|
+
"{{s}}": dateNow.getSeconds(),
|
|
50
|
+
"{{ss}}": pad(dateNow.getSeconds()),
|
|
51
|
+
|
|
52
|
+
"{{i}}": dateNow.getMilliseconds(),
|
|
53
|
+
"{{ii}}": pad(dateNow.getMilliseconds(), 3),
|
|
54
|
+
|
|
55
|
+
"{{ampm}}": ampmSuffix
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let result: string = format
|
|
59
|
+
|
|
60
|
+
for (const [token, value] of Object.entries(map)) {
|
|
61
|
+
result = result.replaceAll(token, value)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export func __isJSON(value) {
|
|
2
|
+
try {
|
|
3
|
+
JSON.parse(value)
|
|
4
|
+
return true
|
|
5
|
+
}
|
|
6
|
+
catch (e) {
|
|
7
|
+
return false
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type HTMLObject(v) {
|
|
12
|
+
return v instanceof HTMLElement
|
|
13
|
+
}
|
|
14
|
+
export type HTMLString(v) {
|
|
15
|
+
return /<\/?[^>]+(>|$)/g.test(v)
|
|
16
|
+
}
|
|
17
|
+
export type JSONString(v) {
|
|
18
|
+
return __isJSON(v)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SemVer(v) {
|
|
22
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/.test(v);
|
|
23
|
+
}
|
package/run-dev-slim.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import chokidar from "chokidar";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
|
|
5
|
+
const config = JSON.parse(
|
|
6
|
+
fs.readFileSync("slimconfig.json", "utf8")
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
const entry = `dist/${config.main}.js`;
|
|
10
|
+
const hot = process.argv.includes("--hot");
|
|
11
|
+
|
|
12
|
+
let app = null;
|
|
13
|
+
let rebuilding = false;
|
|
14
|
+
let pending = false;
|
|
15
|
+
|
|
16
|
+
function run() {
|
|
17
|
+
app = spawn("node", ["--enable-source-maps", "--no-warnings", entry], {
|
|
18
|
+
stdio: "inherit",
|
|
19
|
+
detached: process.platform !== "win32",
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
SLIM_DEV: "1",
|
|
23
|
+
SLIM_HOT: hot ? "1" : "0"
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
app.on("exit", code => {
|
|
28
|
+
if (code && code !== 0)
|
|
29
|
+
console.log(`Application exited with code ${code}`);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stop() {
|
|
34
|
+
return new Promise(resolve => {
|
|
35
|
+
if (!app || app.killed) {
|
|
36
|
+
app = null;
|
|
37
|
+
return resolve();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const proc = app;
|
|
41
|
+
|
|
42
|
+
proc.once("exit", () => {
|
|
43
|
+
app = null;
|
|
44
|
+
resolve();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const killTree = signal => {
|
|
48
|
+
if (process.platform === "win32") {
|
|
49
|
+
spawn("taskkill", ["/pid", String(proc.pid), "/T", "/F"]);
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
process.kill(-proc.pid, signal);
|
|
53
|
+
} catch {
|
|
54
|
+
proc.kill(signal);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
killTree("SIGTERM");
|
|
60
|
+
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
if (app === proc && !proc.killed) {
|
|
63
|
+
killTree("SIGKILL");
|
|
64
|
+
}
|
|
65
|
+
}, 1000);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function compile() {
|
|
70
|
+
return new Promise(resolve => {
|
|
71
|
+
const compiler = spawn("node", ["src/compile.js"], {
|
|
72
|
+
stdio: "inherit"
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
compiler.on("exit", code => {
|
|
76
|
+
resolve(code === 0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function rebuild() {
|
|
82
|
+
if (rebuilding) {
|
|
83
|
+
pending = true;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
rebuilding = true;
|
|
88
|
+
|
|
89
|
+
console.clear();
|
|
90
|
+
console.log("Compiling...");
|
|
91
|
+
|
|
92
|
+
const ok = await compile();
|
|
93
|
+
|
|
94
|
+
if (ok) {
|
|
95
|
+
await stop();
|
|
96
|
+
run();
|
|
97
|
+
console.log("Ready");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
rebuilding = false;
|
|
101
|
+
|
|
102
|
+
if (pending) {
|
|
103
|
+
pending = false;
|
|
104
|
+
rebuild();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await rebuild();
|
|
109
|
+
|
|
110
|
+
const watchTarget = config.watch ?? ".";
|
|
111
|
+
|
|
112
|
+
const watcher = chokidar.watch(watchTarget, {
|
|
113
|
+
ignoreInitial: true,
|
|
114
|
+
awaitWriteFinish: {
|
|
115
|
+
stabilityThreshold: 150,
|
|
116
|
+
pollInterval: 50
|
|
117
|
+
},
|
|
118
|
+
ignored: p => /(^|[\\/])(node_modules|dist|\.git)([\\/]|$)/.test(p)
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
watcher.on("all", (_, file) => {
|
|
122
|
+
if (!file || !file.endsWith(".slim")) return;
|
|
123
|
+
console.log(`Changed: ${file}`);
|
|
124
|
+
rebuild();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
process.on("SIGINT", async () => {
|
|
128
|
+
watcher.close();
|
|
129
|
+
|
|
130
|
+
await stop();
|
|
131
|
+
|
|
132
|
+
process.exit(0);
|
|
133
|
+
});
|
package/run-slim.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const config = JSON.parse(readFileSync(path.join(__dirname, 'slimconfig.json'), 'utf8'));
|
|
11
|
+
const mainFile = config.main;
|
|
12
|
+
|
|
13
|
+
const dist = path.join(__dirname, 'dist', `${mainFile}.js`);
|
|
14
|
+
const args = ['--enable-source-maps', '--no-warnings', dist];
|
|
15
|
+
|
|
16
|
+
const proc = spawn('node', args, { stdio: 'inherit' });
|
|
17
|
+
|
|
18
|
+
proc.on('exit', (code) => {
|
|
19
|
+
process.exit(code);
|
|
20
|
+
});
|