pinokiod 7.2.8 → 7.2.10
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 +26 -0
- package/kernel/environment.js +134 -2
- package/kernel/index.js +3 -0
- 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/{routes/draft_import.js → features/drafts/registry_import.js} +35 -77
- 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 +25 -36
- package/server/views/app.ejs +159 -14
- package/server/views/terminal.ejs +8 -6
- package/server/lib/drafts.js +0 -376
- package/server/public/drafts.js +0 -632
package/kernel/api/index.js
CHANGED
|
@@ -417,6 +417,12 @@ class Api {
|
|
|
417
417
|
await this.process(script.on.stop)
|
|
418
418
|
}
|
|
419
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
|
+
}
|
|
420
426
|
// reset modules
|
|
421
427
|
let modpath = this.resolvePath(cwd, req.params.uri)
|
|
422
428
|
if (this.child_procs[modpath]) {
|
|
@@ -1456,6 +1462,25 @@ class Api {
|
|
|
1456
1462
|
}
|
|
1457
1463
|
return false
|
|
1458
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
|
+
}
|
|
1459
1484
|
createQueue(queue_id, concurrency) {
|
|
1460
1485
|
this.queues[queue_id] = fastq.promise(async ({ request, rawrpc, input, step, total, cwd, args }) => {
|
|
1461
1486
|
try {
|
|
@@ -1697,6 +1722,7 @@ class Api {
|
|
|
1697
1722
|
}
|
|
1698
1723
|
|
|
1699
1724
|
const initialPayload = typeof request.input === "undefined" ? {} : request.input
|
|
1725
|
+
await this.startWatchersForRequest(request, script, cwd, initialPayload)
|
|
1700
1726
|
this.queue(request, steps[0], initialPayload, 0, steps.length, cwd, initialPayload)
|
|
1701
1727
|
|
|
1702
1728
|
} else {
|
package/kernel/environment.js
CHANGED
|
@@ -3,7 +3,139 @@ const portfinder = require('portfinder-cp')
|
|
|
3
3
|
const os = require('os')
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const Util = require('./util')
|
|
6
|
-
const
|
|
6
|
+
const TEMP_ENV_KEYS = ["TMP", "TEMP", "TMPDIR", "PIP_TMPDIR"]
|
|
7
|
+
const CACHE_ENV_KEYS = ["UV_CACHE_DIR", "PIP_CACHE_DIR"]
|
|
8
|
+
|
|
9
|
+
const envKey = (env, key) => {
|
|
10
|
+
return Object.keys(env).find((candidate) => candidate.toLowerCase() === key.toLowerCase())
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const setEnv = (env, key, value) => {
|
|
14
|
+
for (const candidate of Object.keys(env)) {
|
|
15
|
+
if (candidate.toLowerCase() === key.toLowerCase() && candidate !== key) {
|
|
16
|
+
delete env[candidate]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
env[key] = value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const normalizeEnvPath = (value, root) => {
|
|
23
|
+
if (typeof value !== "string") {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
const trimmed = value.trim()
|
|
27
|
+
if (!trimmed) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
if (path.isAbsolute(trimmed)) {
|
|
31
|
+
return path.resolve(trimmed)
|
|
32
|
+
}
|
|
33
|
+
if (trimmed.startsWith("./") || trimmed.startsWith(".\\")) {
|
|
34
|
+
return path.resolve(root, trimmed)
|
|
35
|
+
}
|
|
36
|
+
return path.resolve(root, trimmed)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const isInsidePath = (candidate, root) => {
|
|
40
|
+
const rel = path.relative(path.resolve(root), path.resolve(candidate))
|
|
41
|
+
return rel === "" || (!!rel && !rel.startsWith("..") && !path.isAbsolute(rel))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const canWriteDirectory = async (dirPath) => {
|
|
45
|
+
if (!dirPath) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
const testDir = path.resolve(
|
|
49
|
+
dirPath,
|
|
50
|
+
`.pinokio-write-test-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
51
|
+
)
|
|
52
|
+
const testFile = path.resolve(testDir, "probe.tmp")
|
|
53
|
+
const renamedFile = path.resolve(testDir, "probe-renamed.tmp")
|
|
54
|
+
try {
|
|
55
|
+
await fs.promises.mkdir(testDir, { recursive: true })
|
|
56
|
+
await fs.promises.writeFile(testFile, "pinokio")
|
|
57
|
+
await fs.promises.appendFile(testFile, "-probe")
|
|
58
|
+
await fs.promises.rename(testFile, renamedFile)
|
|
59
|
+
await fs.promises.unlink(renamedFile)
|
|
60
|
+
await fs.promises.rmdir(testDir)
|
|
61
|
+
return true
|
|
62
|
+
} catch (error) {
|
|
63
|
+
await fs.promises.rm(testDir, { recursive: true, force: true }).catch(() => {})
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ensureWritableDirectory = async (dirPath, options = {}) => {
|
|
69
|
+
if (!dirPath) {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
const repair = !!options.repair
|
|
73
|
+
try {
|
|
74
|
+
await fs.promises.mkdir(dirPath, { recursive: true })
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (!repair) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
await fs.promises.rm(dirPath, { recursive: true, force: true }).catch(() => {})
|
|
80
|
+
try {
|
|
81
|
+
await fs.promises.mkdir(dirPath, { recursive: true })
|
|
82
|
+
} catch (mkdirError) {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (await canWriteDirectory(dirPath)) {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
if (!repair) {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
await fs.promises.rm(dirPath, { recursive: true, force: true }).catch(() => {})
|
|
93
|
+
try {
|
|
94
|
+
await fs.promises.mkdir(dirPath, { recursive: true })
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
return canWriteDirectory(dirPath)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const canRepairManagedCachePath = (candidate, cacheRoot) => {
|
|
102
|
+
const rel = path.relative(path.resolve(cacheRoot), path.resolve(candidate))
|
|
103
|
+
return !!rel && !rel.startsWith("..") && !path.isAbsolute(rel)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const managedCacheEnvDefaults = () => {
|
|
107
|
+
const defaults = {}
|
|
108
|
+
for (const key of TEMP_ENV_KEYS.concat(CACHE_ENV_KEYS)) {
|
|
109
|
+
defaults[key] = `./cache/${key}`
|
|
110
|
+
}
|
|
111
|
+
return defaults
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const ensurePinokioCacheDirs = async (kernel) => {
|
|
115
|
+
if (!kernel || !kernel.homedir) {
|
|
116
|
+
return {}
|
|
117
|
+
}
|
|
118
|
+
const root = path.resolve(kernel.homedir)
|
|
119
|
+
const cacheRoot = path.resolve(root, "cache")
|
|
120
|
+
const envPath = path.resolve(root, "ENVIRONMENT")
|
|
121
|
+
const defaults = managedCacheEnvDefaults()
|
|
122
|
+
await Util.update_env(envPath, defaults)
|
|
123
|
+
const env = await get(root, kernel)
|
|
124
|
+
|
|
125
|
+
for (const key of TEMP_ENV_KEYS.concat(CACHE_ENV_KEYS)) {
|
|
126
|
+
const existingKey = envKey(env, key)
|
|
127
|
+
const dirPath = normalizeEnvPath(existingKey ? env[existingKey] : defaults[key], root)
|
|
128
|
+
const managedPath = path.resolve(cacheRoot, key)
|
|
129
|
+
const targetPath = dirPath && isInsidePath(dirPath, root) ? dirPath : managedPath
|
|
130
|
+
const repair = canRepairManagedCachePath(targetPath, cacheRoot)
|
|
131
|
+
if (!repair || !(await ensureWritableDirectory(targetPath, { repair: true }))) {
|
|
132
|
+
throw new Error(`Pinokio could not create a writable ${key} directory: ${targetPath}`)
|
|
133
|
+
}
|
|
134
|
+
setEnv(env, key, targetPath)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return env
|
|
138
|
+
}
|
|
7
139
|
const ENVS = async () => {
|
|
8
140
|
// const primary_port = 80
|
|
9
141
|
// const secondary_port = 42000
|
|
@@ -828,4 +960,4 @@ const init = async (options, kernel) => {
|
|
|
828
960
|
env_path: current
|
|
829
961
|
}
|
|
830
962
|
}
|
|
831
|
-
module.exports = { ENV, get, get2, init_folders, requirements, init, get_root }
|
|
963
|
+
module.exports = { ENV, get, get2, init_folders, ensurePinokioCacheDirs, requirements, init, get_root }
|
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)
|
|
@@ -1051,6 +1053,7 @@ class Kernel {
|
|
|
1051
1053
|
|
|
1052
1054
|
// 2. mkdir all the folders if not already created
|
|
1053
1055
|
await Environment.init_folders(this.homedir, this)
|
|
1056
|
+
await Environment.ensurePinokioCacheDirs(this)
|
|
1054
1057
|
|
|
1055
1058
|
// if key.json doesn't exist, create an empty json file
|
|
1056
1059
|
let ee = await this.exists(this.homedir, "key.json")
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const path = require("path")
|
|
2
|
+
const WatchContext = require("./context")
|
|
3
|
+
|
|
4
|
+
class WatchManager {
|
|
5
|
+
constructor(kernel) {
|
|
6
|
+
this.kernel = kernel
|
|
7
|
+
this.handlers = new Map()
|
|
8
|
+
this.sessions = new Map()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
registerHandler(name, handler) {
|
|
12
|
+
const normalized = typeof name === "string" ? name.trim() : ""
|
|
13
|
+
if (!normalized) {
|
|
14
|
+
throw new Error("watch handler name is required")
|
|
15
|
+
}
|
|
16
|
+
this.handlers.set(normalized, handler)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
hasHandler(script, handlerName) {
|
|
20
|
+
const watches = script && Array.isArray(script.watch) ? script.watch : []
|
|
21
|
+
return watches.some((watch) => watch && watch.handler === handlerName)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
renderDeclaration(raw, memory) {
|
|
25
|
+
let rendered = raw
|
|
26
|
+
let pass = 0
|
|
27
|
+
while (true) {
|
|
28
|
+
rendered = this.kernel.template.render(rendered, memory)
|
|
29
|
+
if (this.kernel.template.istemplate(rendered)) {
|
|
30
|
+
pass += 1
|
|
31
|
+
if (pass >= 4) break
|
|
32
|
+
} else {
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return this.kernel.template.flatten(rendered)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
buildMemory({ request, script, cwd, dirname, input, args }) {
|
|
40
|
+
return {
|
|
41
|
+
script: this.kernel.script,
|
|
42
|
+
input,
|
|
43
|
+
args,
|
|
44
|
+
cwd,
|
|
45
|
+
dirname,
|
|
46
|
+
uri: request.uri,
|
|
47
|
+
self: script,
|
|
48
|
+
kernel: this.kernel,
|
|
49
|
+
...this.kernel.vars
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async resolveExternalHandler(ctx, uri) {
|
|
54
|
+
const modpath = ctx.resolveModule(uri)
|
|
55
|
+
const loaded = await this.kernel.loader.load(modpath)
|
|
56
|
+
let handler = loaded && loaded.resolved
|
|
57
|
+
if (typeof handler === "function") {
|
|
58
|
+
handler = new handler()
|
|
59
|
+
}
|
|
60
|
+
return handler
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async startForScript(options = {}) {
|
|
64
|
+
const script = options.script
|
|
65
|
+
const declarations = script && Array.isArray(script.watch) ? script.watch : []
|
|
66
|
+
if (declarations.length === 0) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const id = options.id
|
|
71
|
+
if (!id) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
await this.stop(id)
|
|
75
|
+
|
|
76
|
+
const input = options.input || {}
|
|
77
|
+
const args = options.args || input
|
|
78
|
+
const cwd = path.resolve(options.cwd)
|
|
79
|
+
const dirname = path.resolve(options.dirname || options.cwd)
|
|
80
|
+
const memory = this.buildMemory({
|
|
81
|
+
request: options.request || {},
|
|
82
|
+
script,
|
|
83
|
+
cwd,
|
|
84
|
+
dirname,
|
|
85
|
+
input,
|
|
86
|
+
args
|
|
87
|
+
})
|
|
88
|
+
const disposers = []
|
|
89
|
+
|
|
90
|
+
for (const rawDeclaration of declarations) {
|
|
91
|
+
if (!rawDeclaration || typeof rawDeclaration !== "object") {
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const declaration = this.renderDeclaration(rawDeclaration, memory)
|
|
96
|
+
const ctx = new WatchContext({
|
|
97
|
+
kernel: this.kernel,
|
|
98
|
+
manager: this,
|
|
99
|
+
id,
|
|
100
|
+
cwd,
|
|
101
|
+
dirname,
|
|
102
|
+
request: options.request,
|
|
103
|
+
script,
|
|
104
|
+
declaration,
|
|
105
|
+
input,
|
|
106
|
+
args
|
|
107
|
+
})
|
|
108
|
+
const methodName = typeof declaration.method === "string" ? declaration.method.trim() : ""
|
|
109
|
+
let handler = null
|
|
110
|
+
if (declaration.handler) {
|
|
111
|
+
handler = this.handlers.get(String(declaration.handler).trim())
|
|
112
|
+
} else if (declaration.uri) {
|
|
113
|
+
handler = await this.resolveExternalHandler(ctx, declaration.uri)
|
|
114
|
+
}
|
|
115
|
+
if (!handler || !methodName || typeof handler[methodName] !== "function") {
|
|
116
|
+
console.warn("[watch] handler not found", declaration)
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
const cleanup = await handler[methodName](ctx, declaration.params || {})
|
|
120
|
+
if (cleanup) {
|
|
121
|
+
disposers.push(cleanup)
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.warn("[watch] failed to start", error && error.message ? error.message : error)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (disposers.length > 0) {
|
|
129
|
+
this.sessions.set(id, disposers)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async stop(id) {
|
|
134
|
+
const normalized = typeof id === "string" ? id : ""
|
|
135
|
+
if (!normalized || !this.sessions.has(normalized)) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
const disposers = this.sessions.get(normalized) || []
|
|
139
|
+
this.sessions.delete(normalized)
|
|
140
|
+
for (const disposer of disposers.reverse()) {
|
|
141
|
+
try {
|
|
142
|
+
if (typeof disposer === "function") {
|
|
143
|
+
await disposer()
|
|
144
|
+
} else if (disposer && typeof disposer.stop === "function") {
|
|
145
|
+
await disposer.stop()
|
|
146
|
+
} else if (disposer && typeof disposer.dispose === "function") {
|
|
147
|
+
await disposer.dispose()
|
|
148
|
+
} else if (disposer && typeof disposer.unsubscribe === "function") {
|
|
149
|
+
await disposer.unsubscribe()
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.warn("[watch] cleanup failed", error && error.message ? error.message : error)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = WatchManager
|
package/package.json
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { createDraftService } = require("./service")
|
|
2
|
+
const registerDraftRoutes = require("./routes")
|
|
3
|
+
const DraftWatcher = require("./watcher")
|
|
4
|
+
|
|
5
|
+
function createDraftFeature(options = {}) {
|
|
6
|
+
const { app, kernel } = options
|
|
7
|
+
if (!app) {
|
|
8
|
+
throw new Error("app is required")
|
|
9
|
+
}
|
|
10
|
+
if (!kernel) {
|
|
11
|
+
throw new Error("kernel is required")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const service = createDraftService({
|
|
15
|
+
kernel,
|
|
16
|
+
taskWorkspaceLinks: options.taskWorkspaceLinks
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
registerDraftRoutes(app, {
|
|
20
|
+
...options,
|
|
21
|
+
drafts: service
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (kernel.watch && typeof kernel.watch.registerHandler === "function") {
|
|
25
|
+
kernel.watch.registerHandler("draft", new DraftWatcher({ drafts: service }))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
service,
|
|
30
|
+
async start() {
|
|
31
|
+
await service.start()
|
|
32
|
+
},
|
|
33
|
+
async stop() {
|
|
34
|
+
await service.stop()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
createDraftFeature
|
|
41
|
+
}
|