pinokiod 7.2.7 → 7.2.9
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/kernel/api/index.js +28 -0
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/index.js +2 -0
- package/kernel/shell.js +21 -2
- package/kernel/watch/context.js +42 -0
- package/kernel/watch/drivers/fs.js +71 -0
- package/kernel/watch/drivers/poll.js +33 -0
- package/kernel/watch/index.js +158 -0
- package/package.json +1 -1
- package/server/features/drafts/index.js +41 -0
- package/server/features/drafts/parser.js +169 -0
- package/server/features/drafts/public/drafts.js +1546 -0
- package/server/features/drafts/registry_import.js +427 -0
- package/server/features/drafts/routes.js +68 -0
- package/server/features/drafts/service.js +261 -0
- package/server/features/drafts/watcher.js +76 -0
- package/server/features/index.js +13 -0
- package/server/index.js +56 -7
- package/server/lib/workspace_catalog.js +151 -0
- package/server/lib/workspace_runtime.js +390 -0
- package/server/public/common.js +8 -0
- package/server/routes/workspaces.js +44 -0
- package/server/socket.js +22 -11
- package/server/views/app.ejs +159 -1
- package/server/views/partials/main_sidebar.ejs +1 -0
- package/server/views/partials/workspace_row.ejs +61 -0
- package/server/views/terminal.ejs +8 -0
- package/server/views/terminals.ejs +1 -0
- package/server/views/workspaces.ejs +812 -0
package/kernel/api/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const fastq = require('fastq')
|
|
|
11
11
|
const Loader = require("../loader")
|
|
12
12
|
const Environment = require("../environment")
|
|
13
13
|
const Util = require('../util')
|
|
14
|
+
const ShellRunTemplate = require('./shell_run_template')
|
|
14
15
|
|
|
15
16
|
class Api {
|
|
16
17
|
constructor(kernel) {
|
|
@@ -416,6 +417,12 @@ class Api {
|
|
|
416
417
|
await this.process(script.on.stop)
|
|
417
418
|
}
|
|
418
419
|
}
|
|
420
|
+
if (this.kernel.watch && typeof this.kernel.watch.stop === "function") {
|
|
421
|
+
if (req.params.id) {
|
|
422
|
+
await this.kernel.watch.stop(req.params.id)
|
|
423
|
+
}
|
|
424
|
+
await this.kernel.watch.stop(requestPath)
|
|
425
|
+
}
|
|
419
426
|
// reset modules
|
|
420
427
|
let modpath = this.resolvePath(cwd, req.params.uri)
|
|
421
428
|
if (this.child_procs[modpath]) {
|
|
@@ -1010,6 +1017,7 @@ class Api {
|
|
|
1010
1017
|
}
|
|
1011
1018
|
// replace {{{ }}} with {{ }}
|
|
1012
1019
|
rpc = this.kernel.template.flatten(rpc)
|
|
1020
|
+
rpc = ShellRunTemplate.renderEnvArgs(this.kernel, rpc, memory)
|
|
1013
1021
|
|
|
1014
1022
|
// 6. rpc must have method names
|
|
1015
1023
|
if (rpc.method) {
|
|
@@ -1454,6 +1462,25 @@ class Api {
|
|
|
1454
1462
|
}
|
|
1455
1463
|
return false
|
|
1456
1464
|
}
|
|
1465
|
+
async startWatchersForRequest(request, script, scriptDir, input) {
|
|
1466
|
+
if (!this.kernel.watch || typeof this.kernel.watch.startForScript !== "function") {
|
|
1467
|
+
return
|
|
1468
|
+
}
|
|
1469
|
+
const id = request.id || request.path
|
|
1470
|
+
if (!id) {
|
|
1471
|
+
return
|
|
1472
|
+
}
|
|
1473
|
+
const cwd = request.cwd || scriptDir
|
|
1474
|
+
await this.kernel.watch.startForScript({
|
|
1475
|
+
id,
|
|
1476
|
+
request,
|
|
1477
|
+
script,
|
|
1478
|
+
cwd,
|
|
1479
|
+
dirname: scriptDir,
|
|
1480
|
+
input,
|
|
1481
|
+
args: input
|
|
1482
|
+
})
|
|
1483
|
+
}
|
|
1457
1484
|
createQueue(queue_id, concurrency) {
|
|
1458
1485
|
this.queues[queue_id] = fastq.promise(async ({ request, rawrpc, input, step, total, cwd, args }) => {
|
|
1459
1486
|
try {
|
|
@@ -1695,6 +1722,7 @@ class Api {
|
|
|
1695
1722
|
}
|
|
1696
1723
|
|
|
1697
1724
|
const initialPayload = typeof request.input === "undefined" ? {} : request.input
|
|
1725
|
+
await this.startWatchersForRequest(request, script, cwd, initialPayload)
|
|
1698
1726
|
this.queue(request, steps[0], initialPayload, 0, steps.length, cwd, initialPayload)
|
|
1699
1727
|
|
|
1700
1728
|
} else {
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
function isCmdShellName(shellName) {
|
|
2
|
+
const name = (shellName || '').toLowerCase()
|
|
3
|
+
return name.includes('cmd.exe') || name === 'cmd'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function isPowerShellName(shellName) {
|
|
7
|
+
const name = (shellName || '').toLowerCase()
|
|
8
|
+
return name.includes('powershell') || name.includes('pwsh')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ENV_ARG_MARKER_RE = /__PINOKIO_ENVARG_(\d+)__/g
|
|
12
|
+
|
|
13
|
+
function envArgMarker(index) {
|
|
14
|
+
return `__PINOKIO_ENVARG_${index}__`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isPinokioEnvArgKey(key) {
|
|
18
|
+
return /^PINOKIO_ARG_\d+$/.test(key || "")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hasEnvArgMarker(value) {
|
|
22
|
+
ENV_ARG_MARKER_RE.lastIndex = 0
|
|
23
|
+
return ENV_ARG_MARKER_RE.test(String(value))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function quotePosixLiteral(value) {
|
|
27
|
+
const input = value == null ? "" : String(value)
|
|
28
|
+
return `'${input.split("'").join("'\"'\"'")}'`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function quotePowerShellComposite(value) {
|
|
32
|
+
const input = value == null ? "" : String(value)
|
|
33
|
+
ENV_ARG_MARKER_RE.lastIndex = 0
|
|
34
|
+
let output = '"'
|
|
35
|
+
let lastIndex = 0
|
|
36
|
+
for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
|
|
37
|
+
const literal = input.slice(lastIndex, match.index)
|
|
38
|
+
output += literal.replace(/[`"$]/g, (char) => "`" + char)
|
|
39
|
+
output += "${env:PINOKIO_ARG_" + match[1] + "}"
|
|
40
|
+
lastIndex = match.index + match[0].length
|
|
41
|
+
}
|
|
42
|
+
output += input.slice(lastIndex).replace(/[`"$]/g, (char) => "`" + char)
|
|
43
|
+
output += '"'
|
|
44
|
+
return output
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function quoteCmdComposite(value) {
|
|
48
|
+
const input = value == null ? "" : String(value)
|
|
49
|
+
ENV_ARG_MARKER_RE.lastIndex = 0
|
|
50
|
+
let output = '"'
|
|
51
|
+
let lastIndex = 0
|
|
52
|
+
for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
|
|
53
|
+
const literal = input.slice(lastIndex, match.index)
|
|
54
|
+
output += literal.replace(/([()%!^"<>&|])/g, '^$1')
|
|
55
|
+
output += "!PINOKIO_ARG_" + match[1] + "!"
|
|
56
|
+
lastIndex = match.index + match[0].length
|
|
57
|
+
}
|
|
58
|
+
output += input.slice(lastIndex).replace(/([()%!^"<>&|])/g, '^$1')
|
|
59
|
+
output += '"'
|
|
60
|
+
return output
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function quoteEnvArgComposite(value, shellName) {
|
|
64
|
+
const input = value == null ? "" : String(value)
|
|
65
|
+
if (isCmdShellName(shellName)) {
|
|
66
|
+
return quoteCmdComposite(input)
|
|
67
|
+
}
|
|
68
|
+
if (isPowerShellName(shellName)) {
|
|
69
|
+
return quotePowerShellComposite(input)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ENV_ARG_MARKER_RE.lastIndex = 0
|
|
73
|
+
const parts = []
|
|
74
|
+
let lastIndex = 0
|
|
75
|
+
for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
|
|
76
|
+
const literal = input.slice(lastIndex, match.index)
|
|
77
|
+
if (literal) {
|
|
78
|
+
parts.push(quotePosixLiteral(literal))
|
|
79
|
+
}
|
|
80
|
+
parts.push('"$PINOKIO_ARG_' + match[1] + '"')
|
|
81
|
+
lastIndex = match.index + match[0].length
|
|
82
|
+
}
|
|
83
|
+
const tail = input.slice(lastIndex)
|
|
84
|
+
if (tail) {
|
|
85
|
+
parts.push(quotePosixLiteral(tail))
|
|
86
|
+
}
|
|
87
|
+
return parts.length > 0 ? parts.join("") : "''"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function shellNameFor(kernel, params) {
|
|
91
|
+
let shellName = kernel && kernel.platform === "win32" ? "cmd.exe" : "bash"
|
|
92
|
+
if (params && typeof params.shell === "string" && params.shell.trim()) {
|
|
93
|
+
shellName = params.shell
|
|
94
|
+
}
|
|
95
|
+
return shellName
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isPlainObject(value) {
|
|
99
|
+
return value && value.constructor === Object
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function hasMultiline(value) {
|
|
103
|
+
return typeof value === "string" && /[\r\n]/.test(value)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isStructuredArgvMessage(value) {
|
|
107
|
+
return isPlainObject(value) && Array.isArray(value._)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function hasStructuredArgvMessage(value) {
|
|
111
|
+
if (isStructuredArgvMessage(value)) {
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return value.some((item) => isStructuredArgvMessage(item))
|
|
116
|
+
}
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function protectStructuredString(value, state) {
|
|
121
|
+
if (!hasMultiline(value)) {
|
|
122
|
+
return value
|
|
123
|
+
}
|
|
124
|
+
const name = `PINOKIO_ARG_${state.args.length}`
|
|
125
|
+
state.args.push({
|
|
126
|
+
name,
|
|
127
|
+
value: value == null ? "" : String(value)
|
|
128
|
+
})
|
|
129
|
+
return envArgMarker(state.args.length - 1)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function protectStructuredValue(value, state) {
|
|
133
|
+
if (typeof value === "string") {
|
|
134
|
+
return protectStructuredString(value, state)
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(value)) {
|
|
137
|
+
return value.map((item) => protectStructuredValue(item, state))
|
|
138
|
+
}
|
|
139
|
+
if (isPlainObject(value)) {
|
|
140
|
+
const rendered = {}
|
|
141
|
+
for (const [key, item] of Object.entries(value)) {
|
|
142
|
+
rendered[key] = protectStructuredValue(item, state)
|
|
143
|
+
}
|
|
144
|
+
return rendered
|
|
145
|
+
}
|
|
146
|
+
return value
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function protectStructuredMessage(value, state) {
|
|
150
|
+
if (isStructuredArgvMessage(value)) {
|
|
151
|
+
return protectStructuredValue(value, state)
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
return value.map((item) => isStructuredArgvMessage(item) ? protectStructuredValue(item, state) : item)
|
|
155
|
+
}
|
|
156
|
+
return value
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function renderEnvArgs(kernel, rpc, memory) {
|
|
160
|
+
if (!rpc || rpc.method !== "shell.run" || !rpc.params || !hasStructuredArgvMessage(rpc.params.message)) {
|
|
161
|
+
return rpc
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const shellName = shellNameFor(kernel, rpc.params)
|
|
165
|
+
const state = { args: [] }
|
|
166
|
+
const message = protectStructuredMessage(rpc.params.message, state)
|
|
167
|
+
|
|
168
|
+
if (state.args.length === 0) {
|
|
169
|
+
return rpc
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const env = Object.assign({}, rpc.params.env || {})
|
|
173
|
+
for (const arg of state.args) {
|
|
174
|
+
env[arg.name] = arg.value
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
...rpc,
|
|
179
|
+
params: {
|
|
180
|
+
...rpc.params,
|
|
181
|
+
message,
|
|
182
|
+
env,
|
|
183
|
+
_pinokio_env_args: state.args,
|
|
184
|
+
_pinokio_cmd_delayed_expansion: isCmdShellName(shellName)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function envArgDetails(value) {
|
|
190
|
+
const normalized = String(value == null ? "" : value)
|
|
191
|
+
.replace(/\r\n/g, "\n")
|
|
192
|
+
.replace(/\r/g, "\n")
|
|
193
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "?")
|
|
194
|
+
const lines = normalized.split("\n")
|
|
195
|
+
const previewLines = []
|
|
196
|
+
const maxLines = 8
|
|
197
|
+
const maxChars = 800
|
|
198
|
+
let used = 0
|
|
199
|
+
|
|
200
|
+
for (const line of lines.slice(0, maxLines)) {
|
|
201
|
+
const remaining = maxChars - used
|
|
202
|
+
if (remaining <= 0) {
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
if (line.length > remaining) {
|
|
206
|
+
previewLines.push(line.slice(0, remaining) + "...")
|
|
207
|
+
used = maxChars
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
previewLines.push(line)
|
|
211
|
+
used += line.length + 1
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const preview = previewLines.join("\n")
|
|
215
|
+
const truncated = lines.length > previewLines.length || normalized.length > preview.length
|
|
216
|
+
if (truncated && previewLines[previewLines.length - 1] !== "...") {
|
|
217
|
+
previewLines.push("...")
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
lineCount: normalized.length === 0 ? 0 : lines.length,
|
|
222
|
+
previewLines,
|
|
223
|
+
truncated
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function formatEnvArgsPreview(args) {
|
|
228
|
+
if (!Array.isArray(args) || args.length === 0) {
|
|
229
|
+
return ""
|
|
230
|
+
}
|
|
231
|
+
const lines = ["\r\nPinokio shell args", ""]
|
|
232
|
+
for (const arg of args) {
|
|
233
|
+
const details = envArgDetails(arg.value)
|
|
234
|
+
lines.push(`${arg.name} ${details.lineCount} lines`)
|
|
235
|
+
for (const previewLine of details.previewLines) {
|
|
236
|
+
lines.push(` ${previewLine}`)
|
|
237
|
+
}
|
|
238
|
+
lines.push("")
|
|
239
|
+
}
|
|
240
|
+
return lines.join("\r\n") + "\r\n"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function envArgSummary(value) {
|
|
244
|
+
const details = envArgDetails(value)
|
|
245
|
+
return {
|
|
246
|
+
type: "pinokio env arg",
|
|
247
|
+
lines: details.lineCount,
|
|
248
|
+
preview: details.previewLines.join("\n"),
|
|
249
|
+
truncated: details.truncated
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function redactEnvArgs(env) {
|
|
254
|
+
if (!env || typeof env !== "object") {
|
|
255
|
+
return env
|
|
256
|
+
}
|
|
257
|
+
const redacted = { ...env }
|
|
258
|
+
for (const key of Object.keys(redacted)) {
|
|
259
|
+
if (isPinokioEnvArgKey(key)) {
|
|
260
|
+
redacted[key] = envArgSummary(redacted[key])
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return redacted
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
module.exports = {
|
|
267
|
+
renderEnvArgs,
|
|
268
|
+
quoteEnvArgComposite,
|
|
269
|
+
hasEnvArgMarker,
|
|
270
|
+
isPinokioEnvArgKey,
|
|
271
|
+
formatEnvArgsPreview,
|
|
272
|
+
redactEnvArgs
|
|
273
|
+
}
|
package/kernel/index.js
CHANGED
|
@@ -40,6 +40,7 @@ const Git = require('./git')
|
|
|
40
40
|
const Connect = require('./connect')
|
|
41
41
|
const Favicon = require('./favicon')
|
|
42
42
|
const AppLauncher = require('./app_launcher')
|
|
43
|
+
const WatchManager = require('./watch')
|
|
43
44
|
const { DownloaderHelper } = require('node-downloader-helper');
|
|
44
45
|
const { ProxyAgent } = require('proxy-agent');
|
|
45
46
|
const fakeUa = require('fake-useragent');
|
|
@@ -975,6 +976,7 @@ class Kernel {
|
|
|
975
976
|
this.loader = new Loader()
|
|
976
977
|
this.bin = new Bin(this)
|
|
977
978
|
this.api = new Api(this)
|
|
979
|
+
this.watch = new WatchManager(this)
|
|
978
980
|
this.python = new Python(this)
|
|
979
981
|
this.shell = new Shells(this)
|
|
980
982
|
this.appLauncher = new AppLauncher(this)
|
package/kernel/shell.js
CHANGED
|
@@ -20,6 +20,7 @@ const { applyWindowsNodePackageManagerEnv } = require('./windows_node_package_ma
|
|
|
20
20
|
const ShellParser = require('./shell_parser')
|
|
21
21
|
const AnsiStreamTracker = require('./ansi_stream_tracker')
|
|
22
22
|
const ShellStateSync = require('./shell_state_sync')
|
|
23
|
+
const ShellRunTemplate = require('./api/shell_run_template')
|
|
23
24
|
const home = os.homedir()
|
|
24
25
|
|
|
25
26
|
function normalizeComparablePath(filePath, platform) {
|
|
@@ -308,7 +309,7 @@ class Shell {
|
|
|
308
309
|
delete this.env[key]
|
|
309
310
|
}
|
|
310
311
|
let val = this.env[key]
|
|
311
|
-
if (/[\r\n]/.test(val)) {
|
|
312
|
+
if (!ShellRunTemplate.isPinokioEnvArgKey(key) && /[\r\n]/.test(val)) {
|
|
312
313
|
const replaced = val.replaceAll(/[\r\n]+/g, ' ');
|
|
313
314
|
this.env[key] = replaced
|
|
314
315
|
// delete this.env[key]
|
|
@@ -362,6 +363,9 @@ class Shell {
|
|
|
362
363
|
}
|
|
363
364
|
quoteArgForShell(value, shellName=this.shell) {
|
|
364
365
|
const input = value == null ? "" : String(value)
|
|
366
|
+
if (ShellRunTemplate.hasEnvArgMarker(input)) {
|
|
367
|
+
return ShellRunTemplate.quoteEnvArgComposite(input, shellName)
|
|
368
|
+
}
|
|
365
369
|
if (this.isCmdShell(shellName)) {
|
|
366
370
|
return `"${input.replace(/([()%!^"<>&|])/g, '^$1')}"`
|
|
367
371
|
}
|
|
@@ -409,6 +413,7 @@ class Shell {
|
|
|
409
413
|
this.userActive = false
|
|
410
414
|
this.decsyncBuffer = ''
|
|
411
415
|
this.stateSync.reset()
|
|
416
|
+
this.envArgsPreviewed = false
|
|
412
417
|
|
|
413
418
|
/*
|
|
414
419
|
params := {
|
|
@@ -540,6 +545,9 @@ class Shell {
|
|
|
540
545
|
//}
|
|
541
546
|
}
|
|
542
547
|
}
|
|
548
|
+
if (params._pinokio_cmd_delayed_expansion && this.isCmdShell(this.shell) && !this.args.includes("/V:ON")) {
|
|
549
|
+
this.args.push("/V:ON")
|
|
550
|
+
}
|
|
543
551
|
|
|
544
552
|
// 3. path => path can be http, relative, absolute
|
|
545
553
|
this.path = params.path
|
|
@@ -757,6 +765,16 @@ class Shell {
|
|
|
757
765
|
}
|
|
758
766
|
this.stateSync.invalidate({ clearTail: true })
|
|
759
767
|
}
|
|
768
|
+
emitEnvArgsPreview(params) {
|
|
769
|
+
if (!params || !Array.isArray(params._pinokio_env_args) || params._pinokio_env_args.length === 0 || this.envArgsPreviewed) {
|
|
770
|
+
return
|
|
771
|
+
}
|
|
772
|
+
this.envArgsPreviewed = true
|
|
773
|
+
const raw = ShellRunTemplate.formatEnvArgsPreview(params._pinokio_env_args)
|
|
774
|
+
if (raw && this.ondata) {
|
|
775
|
+
this.ondata({ raw })
|
|
776
|
+
}
|
|
777
|
+
}
|
|
760
778
|
async run(params, cb) {
|
|
761
779
|
let r = await this.request(params, cb)
|
|
762
780
|
return r
|
|
@@ -1362,6 +1380,7 @@ class Shell {
|
|
|
1362
1380
|
}
|
|
1363
1381
|
async exec(params) {
|
|
1364
1382
|
this.parser = new ShellParser()
|
|
1383
|
+
this.emitEnvArgsPreview(params)
|
|
1365
1384
|
params = await this.activate(params)
|
|
1366
1385
|
this.cmd = this.build(params)
|
|
1367
1386
|
let res = await new Promise((resolve, reject) => {
|
|
@@ -1729,7 +1748,7 @@ class Shell {
|
|
|
1729
1748
|
cmd: this.cmd,
|
|
1730
1749
|
index: this.index,
|
|
1731
1750
|
group: this.group,
|
|
1732
|
-
env: this.env,
|
|
1751
|
+
env: ShellRunTemplate.redactEnvArgs(this.env),
|
|
1733
1752
|
done: this.done,
|
|
1734
1753
|
ready: this.ready,
|
|
1735
1754
|
id: this.id,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const path = require("path")
|
|
2
|
+
const { watchFs } = require("./drivers/fs")
|
|
3
|
+
const { poll } = require("./drivers/poll")
|
|
4
|
+
|
|
5
|
+
class WatchContext {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.kernel = options.kernel
|
|
8
|
+
this.manager = options.manager
|
|
9
|
+
this.id = options.id
|
|
10
|
+
this.cwd = path.resolve(options.cwd)
|
|
11
|
+
this.dirname = path.resolve(options.dirname || options.cwd)
|
|
12
|
+
this.request = options.request
|
|
13
|
+
this.script = options.script
|
|
14
|
+
this.declaration = options.declaration
|
|
15
|
+
this.input = options.input || {}
|
|
16
|
+
this.args = options.args || this.input
|
|
17
|
+
this.watch = {
|
|
18
|
+
fs: (targetPath, callback, watchOptions = {}) => {
|
|
19
|
+
return watchFs(this.resolve(targetPath), callback, {
|
|
20
|
+
...watchOptions,
|
|
21
|
+
onError: watchOptions.onError || ((error) => {
|
|
22
|
+
console.warn("[watch.fs]", error && error.message ? error.message : error)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
resolve(targetPath) {
|
|
30
|
+
return this.kernel.api.resolvePath(this.cwd, String(targetPath || "."))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
resolveModule(targetPath) {
|
|
34
|
+
return this.kernel.api.resolvePath(this.dirname, String(targetPath || "."))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
poll(interval, callback, options = {}) {
|
|
38
|
+
return poll(interval, callback, options)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = WatchContext
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const fs = require("fs")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
const ParcelWatcher = require("@parcel/watcher")
|
|
4
|
+
|
|
5
|
+
const DEFAULT_IGNORE = [
|
|
6
|
+
"**/.git/**",
|
|
7
|
+
"**/node_modules/**",
|
|
8
|
+
"**/__pycache__/**",
|
|
9
|
+
"**/.venv/**",
|
|
10
|
+
"**/venv/**",
|
|
11
|
+
"**/env/**"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
function isInside(candidate, parent) {
|
|
15
|
+
const relative = path.relative(parent, candidate)
|
|
16
|
+
return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function nearestExistingDirectory(targetPath) {
|
|
20
|
+
let current = path.resolve(targetPath)
|
|
21
|
+
while (current && current !== path.dirname(current)) {
|
|
22
|
+
const stats = await fs.promises.stat(current).catch(() => null)
|
|
23
|
+
if (stats && stats.isDirectory()) {
|
|
24
|
+
return current
|
|
25
|
+
}
|
|
26
|
+
current = path.dirname(current)
|
|
27
|
+
}
|
|
28
|
+
return current || path.parse(path.resolve(targetPath)).root
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function watchFs(targetPath, callback, options = {}) {
|
|
32
|
+
const resolvedTarget = path.resolve(targetPath)
|
|
33
|
+
const targetStats = await fs.promises.stat(resolvedTarget).catch(() => null)
|
|
34
|
+
const watchRoot = targetStats && targetStats.isDirectory()
|
|
35
|
+
? resolvedTarget
|
|
36
|
+
: await nearestExistingDirectory(resolvedTarget)
|
|
37
|
+
const filterToTarget = watchRoot !== resolvedTarget
|
|
38
|
+
|
|
39
|
+
const subscription = await ParcelWatcher.subscribe(
|
|
40
|
+
watchRoot,
|
|
41
|
+
(error, events) => {
|
|
42
|
+
if (error) {
|
|
43
|
+
if (typeof options.onError === "function") {
|
|
44
|
+
options.onError(error)
|
|
45
|
+
}
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
const normalizedEvents = Array.isArray(events) ? events : []
|
|
49
|
+
const filteredEvents = filterToTarget
|
|
50
|
+
? normalizedEvents.filter((event) => event && event.path && isInside(path.resolve(event.path), resolvedTarget))
|
|
51
|
+
: normalizedEvents
|
|
52
|
+
if (filteredEvents.length === 0) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
callback(filteredEvents)
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
ignore: Array.isArray(options.ignore) ? options.ignore : DEFAULT_IGNORE
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return async () => {
|
|
63
|
+
if (subscription && typeof subscription.unsubscribe === "function") {
|
|
64
|
+
await subscription.unsubscribe().catch(() => {})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
watchFs
|
|
71
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function poll(interval, callback, options = {}) {
|
|
2
|
+
const delay = Math.max(100, Number(interval || options.interval || 1000))
|
|
3
|
+
let stopped = false
|
|
4
|
+
let running = false
|
|
5
|
+
|
|
6
|
+
const tick = async () => {
|
|
7
|
+
if (stopped || running) return
|
|
8
|
+
running = true
|
|
9
|
+
try {
|
|
10
|
+
await callback()
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (typeof options.onError === "function") {
|
|
13
|
+
options.onError(error)
|
|
14
|
+
}
|
|
15
|
+
} finally {
|
|
16
|
+
running = false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (options.immediate !== false) {
|
|
21
|
+
setTimeout(tick, 0)
|
|
22
|
+
}
|
|
23
|
+
const timer = setInterval(tick, delay)
|
|
24
|
+
|
|
25
|
+
return async () => {
|
|
26
|
+
stopped = true
|
|
27
|
+
clearInterval(timer)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
poll
|
|
33
|
+
}
|