pinokiod 7.3.14 → 7.4.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/kernel/api/index.js +111 -15
- package/kernel/api/script/index.js +10 -0
- package/kernel/autolaunch.js +111 -0
- package/kernel/environment.js +89 -1
- package/kernel/git.js +9 -19
- package/kernel/index.js +142 -43
- package/kernel/launch_requirements.js +1115 -0
- package/kernel/ready.js +231 -0
- package/kernel/script.js +16 -0
- package/kernel/shells.js +9 -1
- package/kernel/workspace_status.js +111 -45
- package/package.json +1 -1
- package/server/autolaunch.js +725 -0
- package/server/index.js +244 -411
- package/server/public/logs.js +15 -1
- package/server/public/style.css +99 -31
- package/server/views/app.ejs +263 -159
- package/server/views/autolaunch.ejs +363 -75
- package/server/views/index.ejs +550 -26
- package/server/views/logs.ejs +14 -12
- package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
- package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
- package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
- package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
- package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
- package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
- package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
- package/server/views/partials/home_action_modal.ejs +4 -1
- package/server/views/partials/launch_requirements_status_client.ejs +271 -0
- package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
- package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
- package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
- package/server/views/terminal.ejs +196 -2
- package/test/home-autolaunch-live-ui.test.js +455 -0
- package/test/launch-requirements-browser.test.js +579 -0
- package/test/launch-requirements-contract-coverage.test.js +627 -0
- package/test/launch-requirements-real-browser.js +625 -0
- package/test/launch-requirements-status-client.test.js +132 -0
- package/test/launch-requirements.test.js +1806 -0
- package/test/launch-settings-ui.test.js +370 -0
- package/test/ready-state.test.js +49 -0
- package/test/server-autolaunch.test.js +1052 -0
- package/test/startup-git-index-benchmark.js +409 -0
- package/test/startup-git-index-browser.js +320 -0
- package/test/startup-git-index-performance.test.js +380 -0
- package/test/startup-git-index-refactor.test.js +450 -0
- package/test/startup-git-index-route.test.js +588 -0
- package/test/universal-launcher.smoke.spec.js +10 -9
- package/test/workspace-gitignore-benchmark.js +815 -0
- package/test/workspace-gitignore-path-scoped.test.js +256 -0
- package/spec/INSTRUCTION_SYNC.md +0 -432
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
const assert = require("node:assert/strict")
|
|
2
|
+
const fs = require("node:fs/promises")
|
|
3
|
+
const os = require("node:os")
|
|
4
|
+
const path = require("node:path")
|
|
5
|
+
const test = require("node:test")
|
|
6
|
+
|
|
7
|
+
const Environment = require("../kernel/environment")
|
|
8
|
+
const ServerAutolaunch = require("../server/autolaunch")
|
|
9
|
+
const Util = require("../kernel/util")
|
|
10
|
+
|
|
11
|
+
const pathExists = async (target) => {
|
|
12
|
+
try {
|
|
13
|
+
await fs.stat(target)
|
|
14
|
+
return true
|
|
15
|
+
} catch (_) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const isSubpath = (parent, child) => {
|
|
21
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child))
|
|
22
|
+
return !relative || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test("autolaunch route preserves configured script when startup is disabled", async () => {
|
|
26
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
27
|
+
try {
|
|
28
|
+
const apiRoot = path.resolve(root, "api")
|
|
29
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
30
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
31
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
32
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
33
|
+
await fs.writeFile(envPath, "PINOKIO_SCRIPT_AUTOLAUNCH=start.js\n", "utf8")
|
|
34
|
+
|
|
35
|
+
const routes = {}
|
|
36
|
+
const kernel = {
|
|
37
|
+
homedir: root,
|
|
38
|
+
exists: pathExists,
|
|
39
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
40
|
+
api: {
|
|
41
|
+
userdir: apiRoot,
|
|
42
|
+
listApps: async () => [{ id: "target", title: "Target" }]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const server = {
|
|
46
|
+
kernel,
|
|
47
|
+
exists: pathExists,
|
|
48
|
+
is_subpath: isSubpath,
|
|
49
|
+
app: {
|
|
50
|
+
get: () => {},
|
|
51
|
+
post: (route, handler) => {
|
|
52
|
+
routes[route] = handler
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
57
|
+
|
|
58
|
+
let statusCode = 200
|
|
59
|
+
let payload = null
|
|
60
|
+
await new Promise((resolve, reject) => {
|
|
61
|
+
routes["/autolaunch"](
|
|
62
|
+
{ body: { app: "target", script: "start.js", enabled: false } },
|
|
63
|
+
{
|
|
64
|
+
status(code) {
|
|
65
|
+
statusCode = code
|
|
66
|
+
return this
|
|
67
|
+
},
|
|
68
|
+
json(body) {
|
|
69
|
+
payload = body
|
|
70
|
+
resolve()
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
(error) => {
|
|
74
|
+
if (error) reject(error)
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const env = await Util.parse_env(envPath)
|
|
80
|
+
assert.equal(statusCode, 200)
|
|
81
|
+
assert.equal(payload.ok, true)
|
|
82
|
+
assert.equal(payload.app.autolaunch, "start.js")
|
|
83
|
+
assert.equal(payload.app.autolaunch_startup, "")
|
|
84
|
+
assert.equal(payload.app.autolaunch_enabled, false)
|
|
85
|
+
assert.equal(env.PINOKIO_SCRIPT_AUTOLAUNCH, "start.js")
|
|
86
|
+
assert.equal(env[Environment.SCRIPT_AUTOLAUNCH_ENABLED_KEY], "false")
|
|
87
|
+
} finally {
|
|
88
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("autolaunch route rejects script save without explicit startup enabled state", async () => {
|
|
93
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
94
|
+
try {
|
|
95
|
+
const apiRoot = path.resolve(root, "api")
|
|
96
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
97
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
98
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
99
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
100
|
+
await fs.writeFile(envPath, [
|
|
101
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
102
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=false",
|
|
103
|
+
""
|
|
104
|
+
].join("\n"), "utf8")
|
|
105
|
+
|
|
106
|
+
const routes = {}
|
|
107
|
+
const kernel = {
|
|
108
|
+
homedir: root,
|
|
109
|
+
exists: pathExists,
|
|
110
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
111
|
+
api: {
|
|
112
|
+
userdir: apiRoot,
|
|
113
|
+
listApps: async () => [{ id: "target", title: "Target" }]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const server = {
|
|
117
|
+
kernel,
|
|
118
|
+
exists: pathExists,
|
|
119
|
+
is_subpath: isSubpath,
|
|
120
|
+
app: {
|
|
121
|
+
get: () => {},
|
|
122
|
+
post: (route, handler) => {
|
|
123
|
+
routes[route] = handler
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
128
|
+
|
|
129
|
+
let statusCode = 200
|
|
130
|
+
let payload = null
|
|
131
|
+
await new Promise((resolve, reject) => {
|
|
132
|
+
routes["/autolaunch"](
|
|
133
|
+
{ body: { app: "target", script: "start.js" } },
|
|
134
|
+
{
|
|
135
|
+
status(code) {
|
|
136
|
+
statusCode = code
|
|
137
|
+
return this
|
|
138
|
+
},
|
|
139
|
+
json(body) {
|
|
140
|
+
payload = body
|
|
141
|
+
resolve()
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
(error) => {
|
|
145
|
+
if (error) reject(error)
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const env = await Util.parse_env(envPath)
|
|
151
|
+
assert.equal(statusCode, 400)
|
|
152
|
+
assert.equal(payload.ok, false)
|
|
153
|
+
assert.match(payload.error, /enabled/i)
|
|
154
|
+
assert.equal(env.PINOKIO_SCRIPT_AUTOLAUNCH, "start.js")
|
|
155
|
+
assert.equal(env[Environment.SCRIPT_AUTOLAUNCH_ENABLED_KEY], "false")
|
|
156
|
+
} finally {
|
|
157
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test("autolaunch route rejects clearing launch script while requirements exist", async () => {
|
|
162
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
163
|
+
try {
|
|
164
|
+
const apiRoot = path.resolve(root, "api")
|
|
165
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
166
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
167
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
168
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
169
|
+
await fs.writeFile(envPath, [
|
|
170
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
171
|
+
"PINOKIO_SCRIPT_REQUIRES=helper",
|
|
172
|
+
""
|
|
173
|
+
].join("\n"), "utf8")
|
|
174
|
+
|
|
175
|
+
const routes = {}
|
|
176
|
+
const kernel = {
|
|
177
|
+
homedir: root,
|
|
178
|
+
exists: pathExists,
|
|
179
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
180
|
+
api: {
|
|
181
|
+
userdir: apiRoot,
|
|
182
|
+
listApps: async () => [{ id: "target", title: "Target" }, { id: "helper", title: "Helper" }]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const server = {
|
|
186
|
+
kernel,
|
|
187
|
+
exists: pathExists,
|
|
188
|
+
is_subpath: isSubpath,
|
|
189
|
+
app: {
|
|
190
|
+
get: () => {},
|
|
191
|
+
post: (route, handler) => {
|
|
192
|
+
routes[route] = handler
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
197
|
+
|
|
198
|
+
let statusCode = 200
|
|
199
|
+
let payload = null
|
|
200
|
+
await new Promise((resolve, reject) => {
|
|
201
|
+
routes["/autolaunch"](
|
|
202
|
+
{ body: { app: "target", clear_script: true } },
|
|
203
|
+
{
|
|
204
|
+
status(code) {
|
|
205
|
+
statusCode = code
|
|
206
|
+
return this
|
|
207
|
+
},
|
|
208
|
+
json(body) {
|
|
209
|
+
payload = body
|
|
210
|
+
resolve()
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
(error) => {
|
|
214
|
+
if (error) reject(error)
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
const env = await Util.parse_env(envPath)
|
|
220
|
+
assert.equal(statusCode, 400)
|
|
221
|
+
assert.equal(payload.ok, false)
|
|
222
|
+
assert.match(payload.error, /Remove requirements before clearing/)
|
|
223
|
+
assert.equal(env.PINOKIO_SCRIPT_AUTOLAUNCH, "start.js")
|
|
224
|
+
assert.equal(env[Environment.SCRIPT_REQUIREMENTS_KEY], "helper")
|
|
225
|
+
} finally {
|
|
226
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test("autolaunch route rejects empty script without explicit clear action", async () => {
|
|
231
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
232
|
+
try {
|
|
233
|
+
const apiRoot = path.resolve(root, "api")
|
|
234
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
235
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
236
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
237
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
238
|
+
await fs.writeFile(envPath, [
|
|
239
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
240
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=false",
|
|
241
|
+
""
|
|
242
|
+
].join("\n"), "utf8")
|
|
243
|
+
|
|
244
|
+
const routes = {}
|
|
245
|
+
const kernel = {
|
|
246
|
+
homedir: root,
|
|
247
|
+
exists: pathExists,
|
|
248
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
249
|
+
api: {
|
|
250
|
+
userdir: apiRoot,
|
|
251
|
+
listApps: async () => [{ id: "target", title: "Target" }]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const server = {
|
|
255
|
+
kernel,
|
|
256
|
+
exists: pathExists,
|
|
257
|
+
is_subpath: isSubpath,
|
|
258
|
+
app: {
|
|
259
|
+
get: () => {},
|
|
260
|
+
post: (route, handler) => {
|
|
261
|
+
routes[route] = handler
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
266
|
+
|
|
267
|
+
let statusCode = 200
|
|
268
|
+
let payload = null
|
|
269
|
+
await new Promise((resolve, reject) => {
|
|
270
|
+
routes["/autolaunch"](
|
|
271
|
+
{ body: { app: "target", script: "", enabled: false } },
|
|
272
|
+
{
|
|
273
|
+
status(code) {
|
|
274
|
+
statusCode = code
|
|
275
|
+
return this
|
|
276
|
+
},
|
|
277
|
+
json(body) {
|
|
278
|
+
payload = body
|
|
279
|
+
resolve()
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
(error) => {
|
|
283
|
+
if (error) reject(error)
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const env = await Util.parse_env(envPath)
|
|
289
|
+
assert.equal(statusCode, 400)
|
|
290
|
+
assert.equal(payload.ok, false)
|
|
291
|
+
assert.match(payload.error, /clear_script/i)
|
|
292
|
+
assert.equal(env.PINOKIO_SCRIPT_AUTOLAUNCH, "start.js")
|
|
293
|
+
assert.equal(env[Environment.SCRIPT_AUTOLAUNCH_ENABLED_KEY], "false")
|
|
294
|
+
} finally {
|
|
295
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test("home startup display graph skips cancelled startup roots", async () => {
|
|
300
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
301
|
+
try {
|
|
302
|
+
const apiRoot = path.resolve(root, "api")
|
|
303
|
+
const targetRoot = path.resolve(apiRoot, "target")
|
|
304
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
305
|
+
await fs.mkdir(targetRoot, { recursive: true })
|
|
306
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
307
|
+
await fs.writeFile(path.resolve(targetRoot, "ENVIRONMENT"), [
|
|
308
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
309
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=true",
|
|
310
|
+
"PINOKIO_SCRIPT_REQUIRES=helper",
|
|
311
|
+
""
|
|
312
|
+
].join("\n"), "utf8")
|
|
313
|
+
await fs.writeFile(path.resolve(helperRoot, "ENVIRONMENT"), [
|
|
314
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
315
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=false",
|
|
316
|
+
""
|
|
317
|
+
].join("\n"), "utf8")
|
|
318
|
+
|
|
319
|
+
const kernel = {
|
|
320
|
+
homedir: root,
|
|
321
|
+
launch_complete: false,
|
|
322
|
+
exists: pathExists,
|
|
323
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
324
|
+
launchRequirements: {
|
|
325
|
+
isCancelled: (appId) => appId === "target"
|
|
326
|
+
},
|
|
327
|
+
api: {
|
|
328
|
+
userdir: apiRoot,
|
|
329
|
+
listApps: async () => [
|
|
330
|
+
{ id: "target", title: "Target" },
|
|
331
|
+
{ id: "helper", title: "Helper" }
|
|
332
|
+
]
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const server = {
|
|
336
|
+
kernel,
|
|
337
|
+
exists: pathExists,
|
|
338
|
+
is_subpath: isSubpath,
|
|
339
|
+
app: { get: () => {}, post: () => {} }
|
|
340
|
+
}
|
|
341
|
+
const graph = await new ServerAutolaunch(server).buildHomeStartupDisplayGraph()
|
|
342
|
+
|
|
343
|
+
assert.equal(graph.has("target"), false)
|
|
344
|
+
assert.equal(graph.has("helper"), false)
|
|
345
|
+
} finally {
|
|
346
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test("autolaunch dependencies route rejects invalid dependency ids atomically", async () => {
|
|
351
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
352
|
+
try {
|
|
353
|
+
const apiRoot = path.resolve(root, "api")
|
|
354
|
+
const targetRoot = path.resolve(apiRoot, "target")
|
|
355
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
356
|
+
const existingRoot = path.resolve(apiRoot, "existing")
|
|
357
|
+
const targetEnv = path.resolve(targetRoot, "ENVIRONMENT")
|
|
358
|
+
await fs.mkdir(targetRoot, { recursive: true })
|
|
359
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
360
|
+
await fs.mkdir(existingRoot, { recursive: true })
|
|
361
|
+
await fs.writeFile(path.resolve(targetRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
362
|
+
await fs.writeFile(path.resolve(helperRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
363
|
+
await fs.writeFile(path.resolve(existingRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
364
|
+
await fs.writeFile(targetEnv, [
|
|
365
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
366
|
+
"PINOKIO_SCRIPT_REQUIRES=existing",
|
|
367
|
+
""
|
|
368
|
+
].join("\n"), "utf8")
|
|
369
|
+
await fs.writeFile(path.resolve(helperRoot, "ENVIRONMENT"), "PINOKIO_SCRIPT_AUTOLAUNCH=start.js\n", "utf8")
|
|
370
|
+
await fs.writeFile(path.resolve(existingRoot, "ENVIRONMENT"), "PINOKIO_SCRIPT_AUTOLAUNCH=start.js\n", "utf8")
|
|
371
|
+
|
|
372
|
+
const routes = {}
|
|
373
|
+
const kernel = {
|
|
374
|
+
homedir: root,
|
|
375
|
+
exists: pathExists,
|
|
376
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
377
|
+
api: {
|
|
378
|
+
userdir: apiRoot,
|
|
379
|
+
listApps: async () => [
|
|
380
|
+
{ id: "target", title: "Target" },
|
|
381
|
+
{ id: "helper", title: "Helper" },
|
|
382
|
+
{ id: "existing", title: "Existing" }
|
|
383
|
+
]
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const server = {
|
|
387
|
+
kernel,
|
|
388
|
+
exists: pathExists,
|
|
389
|
+
is_subpath: isSubpath,
|
|
390
|
+
app: {
|
|
391
|
+
get: () => {},
|
|
392
|
+
post: (route, handler) => {
|
|
393
|
+
routes[route] = handler
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
398
|
+
|
|
399
|
+
let statusCode = 200
|
|
400
|
+
let payload = null
|
|
401
|
+
await new Promise((resolve, reject) => {
|
|
402
|
+
routes["/autolaunch/dependencies"](
|
|
403
|
+
{ body: { app: "target", dependencies: ["helper", "missing"] } },
|
|
404
|
+
{
|
|
405
|
+
status(code) {
|
|
406
|
+
statusCode = code
|
|
407
|
+
return this
|
|
408
|
+
},
|
|
409
|
+
json(body) {
|
|
410
|
+
payload = body
|
|
411
|
+
resolve()
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
(error) => {
|
|
415
|
+
if (error) reject(error)
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
const env = await Util.parse_env(targetEnv)
|
|
421
|
+
assert.equal(statusCode, 400)
|
|
422
|
+
assert.equal(payload.ok, false)
|
|
423
|
+
assert.match(payload.error, /missing|invalid|installed/i)
|
|
424
|
+
assert.equal(env[Environment.SCRIPT_REQUIREMENTS_KEY], "existing")
|
|
425
|
+
} finally {
|
|
426
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
427
|
+
}
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
test("autolaunch route clears launch script when requirements are empty", async () => {
|
|
431
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
432
|
+
try {
|
|
433
|
+
const apiRoot = path.resolve(root, "api")
|
|
434
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
435
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
436
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
437
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
438
|
+
await fs.writeFile(envPath, [
|
|
439
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
440
|
+
"PINOKIO_SCRIPT_REQUIRES=",
|
|
441
|
+
""
|
|
442
|
+
].join("\n"), "utf8")
|
|
443
|
+
|
|
444
|
+
const routes = {}
|
|
445
|
+
const kernel = {
|
|
446
|
+
homedir: root,
|
|
447
|
+
exists: pathExists,
|
|
448
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
449
|
+
api: {
|
|
450
|
+
userdir: apiRoot,
|
|
451
|
+
listApps: async () => [{ id: "target", title: "Target" }]
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const server = {
|
|
455
|
+
kernel,
|
|
456
|
+
exists: pathExists,
|
|
457
|
+
is_subpath: isSubpath,
|
|
458
|
+
app: {
|
|
459
|
+
get: () => {},
|
|
460
|
+
post: (route, handler) => {
|
|
461
|
+
routes[route] = handler
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
466
|
+
|
|
467
|
+
let statusCode = 200
|
|
468
|
+
let payload = null
|
|
469
|
+
await new Promise((resolve, reject) => {
|
|
470
|
+
routes["/autolaunch"](
|
|
471
|
+
{ body: { app: "target", clear_script: true } },
|
|
472
|
+
{
|
|
473
|
+
status(code) {
|
|
474
|
+
statusCode = code
|
|
475
|
+
return this
|
|
476
|
+
},
|
|
477
|
+
json(body) {
|
|
478
|
+
payload = body
|
|
479
|
+
resolve()
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
(error) => {
|
|
483
|
+
if (error) reject(error)
|
|
484
|
+
}
|
|
485
|
+
)
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
const env = await Util.parse_env(envPath)
|
|
489
|
+
assert.equal(statusCode, 200)
|
|
490
|
+
assert.equal(payload.ok, true)
|
|
491
|
+
assert.equal(payload.app.autolaunch, "")
|
|
492
|
+
assert.equal(payload.app.autolaunch_enabled, false)
|
|
493
|
+
assert.equal(env.PINOKIO_SCRIPT_AUTOLAUNCH, "")
|
|
494
|
+
assert.equal(env[Environment.SCRIPT_AUTOLAUNCH_ENABLED_KEY], "")
|
|
495
|
+
assert.equal(env[Environment.SCRIPT_REQUIREMENTS_KEY], "")
|
|
496
|
+
} finally {
|
|
497
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
test("disable all startup launch preserves configured scripts", async () => {
|
|
502
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
503
|
+
try {
|
|
504
|
+
const apiRoot = path.resolve(root, "api")
|
|
505
|
+
const targetRoot = path.resolve(apiRoot, "target")
|
|
506
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
507
|
+
const targetEnv = path.resolve(targetRoot, "ENVIRONMENT")
|
|
508
|
+
const helperEnv = path.resolve(helperRoot, "ENVIRONMENT")
|
|
509
|
+
await fs.mkdir(targetRoot, { recursive: true })
|
|
510
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
511
|
+
await fs.writeFile(path.resolve(targetRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
512
|
+
await fs.writeFile(path.resolve(helperRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
513
|
+
await fs.writeFile(targetEnv, "PINOKIO_SCRIPT_AUTOLAUNCH=start.js\n", "utf8")
|
|
514
|
+
await fs.writeFile(helperEnv, [
|
|
515
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
516
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=false",
|
|
517
|
+
""
|
|
518
|
+
].join("\n"), "utf8")
|
|
519
|
+
|
|
520
|
+
const routes = {}
|
|
521
|
+
const kernel = {
|
|
522
|
+
homedir: root,
|
|
523
|
+
exists: pathExists,
|
|
524
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
525
|
+
api: {
|
|
526
|
+
userdir: apiRoot,
|
|
527
|
+
listApps: async () => [{ id: "target", title: "Target" }, { id: "helper", title: "Helper" }]
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const server = {
|
|
531
|
+
kernel,
|
|
532
|
+
exists: pathExists,
|
|
533
|
+
is_subpath: isSubpath,
|
|
534
|
+
app: {
|
|
535
|
+
get: () => {},
|
|
536
|
+
post: (route, handler) => {
|
|
537
|
+
routes[route] = handler
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
542
|
+
|
|
543
|
+
let payload = null
|
|
544
|
+
await new Promise((resolve, reject) => {
|
|
545
|
+
routes["/autolaunch/disable-all"](
|
|
546
|
+
{},
|
|
547
|
+
{
|
|
548
|
+
json(body) {
|
|
549
|
+
payload = body
|
|
550
|
+
resolve()
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
(error) => {
|
|
554
|
+
if (error) reject(error)
|
|
555
|
+
}
|
|
556
|
+
)
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
const target = await Util.parse_env(targetEnv)
|
|
560
|
+
const helper = await Util.parse_env(helperEnv)
|
|
561
|
+
assert.equal(payload.ok, true)
|
|
562
|
+
assert.equal(payload.disabled, 1)
|
|
563
|
+
assert.equal(target.PINOKIO_SCRIPT_AUTOLAUNCH, "start.js")
|
|
564
|
+
assert.equal(target[Environment.SCRIPT_AUTOLAUNCH_ENABLED_KEY], "false")
|
|
565
|
+
assert.equal(helper.PINOKIO_SCRIPT_AUTOLAUNCH, "start.js")
|
|
566
|
+
assert.equal(helper[Environment.SCRIPT_AUTOLAUNCH_ENABLED_KEY], "false")
|
|
567
|
+
const targetState = payload.apps.find((app) => app.id === "target")
|
|
568
|
+
assert.equal(targetState.autolaunch, "start.js")
|
|
569
|
+
assert.equal(targetState.autolaunch_enabled, false)
|
|
570
|
+
} finally {
|
|
571
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
572
|
+
}
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
test("autolaunch dependencies route rejects requirements without owning launch script", async () => {
|
|
576
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
577
|
+
try {
|
|
578
|
+
const apiRoot = path.resolve(root, "api")
|
|
579
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
580
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
581
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
582
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
583
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
584
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
585
|
+
await fs.writeFile(path.resolve(helperRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
586
|
+
await fs.writeFile(envPath, "PINOKIO_SCRIPT_AUTOLAUNCH=\n", "utf8")
|
|
587
|
+
|
|
588
|
+
const routes = {}
|
|
589
|
+
const kernel = {
|
|
590
|
+
homedir: root,
|
|
591
|
+
exists: pathExists,
|
|
592
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
593
|
+
api: {
|
|
594
|
+
userdir: apiRoot,
|
|
595
|
+
listApps: async () => [{ id: "target", title: "Target" }, { id: "helper", title: "Helper" }]
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const server = {
|
|
599
|
+
kernel,
|
|
600
|
+
exists: pathExists,
|
|
601
|
+
is_subpath: isSubpath,
|
|
602
|
+
app: {
|
|
603
|
+
get: () => {},
|
|
604
|
+
post: (route, handler) => {
|
|
605
|
+
routes[route] = handler
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
610
|
+
|
|
611
|
+
let statusCode = 200
|
|
612
|
+
let payload = null
|
|
613
|
+
await new Promise((resolve, reject) => {
|
|
614
|
+
routes["/autolaunch/dependencies"](
|
|
615
|
+
{ body: { app: "target", dependencies: ["helper"] } },
|
|
616
|
+
{
|
|
617
|
+
status(code) {
|
|
618
|
+
statusCode = code
|
|
619
|
+
return this
|
|
620
|
+
},
|
|
621
|
+
json(body) {
|
|
622
|
+
payload = body
|
|
623
|
+
resolve()
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
(error) => {
|
|
627
|
+
if (error) reject(error)
|
|
628
|
+
}
|
|
629
|
+
)
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
const env = await Util.parse_env(envPath)
|
|
633
|
+
assert.equal(statusCode, 400)
|
|
634
|
+
assert.equal(payload.ok, false)
|
|
635
|
+
assert.match(payload.error, /Choose this app's launch script/)
|
|
636
|
+
assert.equal(env[Environment.SCRIPT_REQUIREMENTS_KEY], undefined)
|
|
637
|
+
} finally {
|
|
638
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
test("autolaunch dependencies route rejects requirements whose app has no launch script", async () => {
|
|
643
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
644
|
+
try {
|
|
645
|
+
const apiRoot = path.resolve(root, "api")
|
|
646
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
647
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
648
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
649
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
650
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
651
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
652
|
+
await fs.writeFile(path.resolve(helperRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
653
|
+
await fs.writeFile(envPath, "PINOKIO_SCRIPT_AUTOLAUNCH=start.js\n", "utf8")
|
|
654
|
+
await fs.writeFile(path.resolve(helperRoot, "ENVIRONMENT"), "PINOKIO_SCRIPT_AUTOLAUNCH=\n", "utf8")
|
|
655
|
+
|
|
656
|
+
const routes = {}
|
|
657
|
+
const kernel = {
|
|
658
|
+
homedir: root,
|
|
659
|
+
exists: pathExists,
|
|
660
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
661
|
+
api: {
|
|
662
|
+
userdir: apiRoot,
|
|
663
|
+
listApps: async () => [{ id: "target", title: "Target" }, { id: "helper", title: "Helper" }]
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const server = {
|
|
667
|
+
kernel,
|
|
668
|
+
exists: pathExists,
|
|
669
|
+
is_subpath: isSubpath,
|
|
670
|
+
app: {
|
|
671
|
+
get: () => {},
|
|
672
|
+
post: (route, handler) => {
|
|
673
|
+
routes[route] = handler
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
new ServerAutolaunch(server).registerRoutes()
|
|
678
|
+
|
|
679
|
+
let statusCode = 200
|
|
680
|
+
let payload = null
|
|
681
|
+
await new Promise((resolve, reject) => {
|
|
682
|
+
routes["/autolaunch/dependencies"](
|
|
683
|
+
{ body: { app: "target", dependencies: ["helper"] } },
|
|
684
|
+
{
|
|
685
|
+
status(code) {
|
|
686
|
+
statusCode = code
|
|
687
|
+
return this
|
|
688
|
+
},
|
|
689
|
+
json(body) {
|
|
690
|
+
payload = body
|
|
691
|
+
resolve()
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
(error) => {
|
|
695
|
+
if (error) reject(error)
|
|
696
|
+
}
|
|
697
|
+
)
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
const env = await Util.parse_env(envPath)
|
|
701
|
+
assert.equal(statusCode, 400)
|
|
702
|
+
assert.equal(payload.ok, false)
|
|
703
|
+
assert.match(payload.error, /Choose Helper's launch script/)
|
|
704
|
+
assert.equal(env[Environment.SCRIPT_REQUIREMENTS_KEY], undefined)
|
|
705
|
+
} finally {
|
|
706
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
707
|
+
}
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
test("autolaunch candidate state does not migrate startup script to a second launch key", async () => {
|
|
711
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
712
|
+
try {
|
|
713
|
+
const apiRoot = path.resolve(root, "api")
|
|
714
|
+
const appRoot = path.resolve(apiRoot, "target")
|
|
715
|
+
const envPath = path.resolve(appRoot, "ENVIRONMENT")
|
|
716
|
+
await fs.mkdir(appRoot, { recursive: true })
|
|
717
|
+
await fs.writeFile(path.resolve(appRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
718
|
+
await fs.writeFile(envPath, "PINOKIO_SCRIPT_AUTOLAUNCH=start.js\n", "utf8")
|
|
719
|
+
|
|
720
|
+
const kernel = {
|
|
721
|
+
homedir: root,
|
|
722
|
+
exists: pathExists,
|
|
723
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
724
|
+
api: {
|
|
725
|
+
userdir: apiRoot,
|
|
726
|
+
listApps: async () => [{ id: "target", title: "Target" }]
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const server = {
|
|
730
|
+
kernel,
|
|
731
|
+
exists: pathExists,
|
|
732
|
+
is_subpath: isSubpath,
|
|
733
|
+
app: {
|
|
734
|
+
get: () => {},
|
|
735
|
+
post: () => {}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const autolaunch = new ServerAutolaunch(server)
|
|
739
|
+
const state = await autolaunch.buildAppState({ id: "target", title: "Target" })
|
|
740
|
+
const env = await Util.parse_env(envPath)
|
|
741
|
+
|
|
742
|
+
assert.equal(state.autolaunch, "start.js")
|
|
743
|
+
assert.equal(state.autolaunch_startup, "start.js")
|
|
744
|
+
assert.equal(state.autolaunch_enabled, true)
|
|
745
|
+
const forbiddenKey = "PINOKIO" + "_SCRIPT" + "_LAUNCH"
|
|
746
|
+
assert.equal(Object.prototype.hasOwnProperty.call(env, forbiddenKey), false)
|
|
747
|
+
} finally {
|
|
748
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
749
|
+
}
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
test("home startup state renders configured requirement waiting before runtime status", async () => {
|
|
753
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
754
|
+
try {
|
|
755
|
+
const apiRoot = path.resolve(root, "api")
|
|
756
|
+
const targetRoot = path.resolve(apiRoot, "target")
|
|
757
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
758
|
+
await fs.mkdir(targetRoot, { recursive: true })
|
|
759
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
760
|
+
await fs.writeFile(path.resolve(targetRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
761
|
+
await fs.writeFile(path.resolve(helperRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
762
|
+
await fs.writeFile(path.resolve(targetRoot, "ENVIRONMENT"), [
|
|
763
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
764
|
+
"PINOKIO_SCRIPT_REQUIRES=helper",
|
|
765
|
+
""
|
|
766
|
+
].join("\n"), "utf8")
|
|
767
|
+
await fs.writeFile(path.resolve(helperRoot, "ENVIRONMENT"), [
|
|
768
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
769
|
+
""
|
|
770
|
+
].join("\n"), "utf8")
|
|
771
|
+
|
|
772
|
+
const kernel = {
|
|
773
|
+
homedir: root,
|
|
774
|
+
launch_complete: false,
|
|
775
|
+
autolaunch_status: { running: true, apps: {} },
|
|
776
|
+
exists: pathExists,
|
|
777
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
778
|
+
api: {
|
|
779
|
+
userdir: apiRoot,
|
|
780
|
+
listApps: async () => [{ id: "target", title: "Target" }, { id: "helper", title: "Helper" }],
|
|
781
|
+
launcher: async (id) => ({ script: { title: id === "helper" ? "Helper" : "Target" } })
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const server = {
|
|
785
|
+
kernel,
|
|
786
|
+
exists: pathExists,
|
|
787
|
+
is_subpath: isSubpath,
|
|
788
|
+
app: {
|
|
789
|
+
get: () => {},
|
|
790
|
+
post: () => {}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const autolaunch = new ServerAutolaunch(server)
|
|
794
|
+
const item = {
|
|
795
|
+
name: "Target",
|
|
796
|
+
uri: "target",
|
|
797
|
+
filepath: targetRoot,
|
|
798
|
+
path: "target"
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const applied = await autolaunch.applyHomeStartingState(item, 0)
|
|
802
|
+
|
|
803
|
+
assert.equal(applied, true)
|
|
804
|
+
assert.equal(item.running, true)
|
|
805
|
+
assert.equal(item.autolaunch_starting, true)
|
|
806
|
+
assert.equal(item.autolaunch_waiting, true)
|
|
807
|
+
assert.equal(item.autolaunch_script, "start.js")
|
|
808
|
+
assert.equal(item.autolaunch_status_label, "Waiting for Helper")
|
|
809
|
+
} finally {
|
|
810
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
811
|
+
}
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
test("home startup state renders recursive requirement-only rows before runtime status", async () => {
|
|
815
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
816
|
+
try {
|
|
817
|
+
const apiRoot = path.resolve(root, "api")
|
|
818
|
+
const rootAppRoot = path.resolve(apiRoot, "root-app")
|
|
819
|
+
const middleAppRoot = path.resolve(apiRoot, "middle-app")
|
|
820
|
+
const leafAppRoot = path.resolve(apiRoot, "leaf-app")
|
|
821
|
+
await fs.mkdir(rootAppRoot, { recursive: true })
|
|
822
|
+
await fs.mkdir(middleAppRoot, { recursive: true })
|
|
823
|
+
await fs.mkdir(leafAppRoot, { recursive: true })
|
|
824
|
+
await fs.writeFile(path.resolve(rootAppRoot, "root.custom.js"), "module.exports = { run: [] }\n", "utf8")
|
|
825
|
+
await fs.writeFile(path.resolve(middleAppRoot, "middle.custom.js"), "module.exports = { run: [] }\n", "utf8")
|
|
826
|
+
await fs.writeFile(path.resolve(leafAppRoot, "leaf.custom.js"), "module.exports = { run: [] }\n", "utf8")
|
|
827
|
+
await fs.writeFile(path.resolve(rootAppRoot, "ENVIRONMENT"), [
|
|
828
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=root.custom.js",
|
|
829
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=true",
|
|
830
|
+
"PINOKIO_SCRIPT_REQUIRES=middle-app",
|
|
831
|
+
""
|
|
832
|
+
].join("\n"), "utf8")
|
|
833
|
+
await fs.writeFile(path.resolve(middleAppRoot, "ENVIRONMENT"), [
|
|
834
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=middle.custom.js",
|
|
835
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=false",
|
|
836
|
+
"PINOKIO_SCRIPT_REQUIRES=leaf-app",
|
|
837
|
+
""
|
|
838
|
+
].join("\n"), "utf8")
|
|
839
|
+
await fs.writeFile(path.resolve(leafAppRoot, "ENVIRONMENT"), [
|
|
840
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=leaf.custom.js",
|
|
841
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH_ENABLED=false",
|
|
842
|
+
""
|
|
843
|
+
].join("\n"), "utf8")
|
|
844
|
+
|
|
845
|
+
const titles = {
|
|
846
|
+
"root-app": "Root App",
|
|
847
|
+
"middle-app": "Middle App",
|
|
848
|
+
"leaf-app": "Leaf App"
|
|
849
|
+
}
|
|
850
|
+
const kernel = {
|
|
851
|
+
homedir: root,
|
|
852
|
+
launch_complete: false,
|
|
853
|
+
autolaunch_status: { running: true, apps: {} },
|
|
854
|
+
exists: pathExists,
|
|
855
|
+
getScriptProgress: () => null,
|
|
856
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
857
|
+
api: {
|
|
858
|
+
userdir: apiRoot,
|
|
859
|
+
listApps: async () => [
|
|
860
|
+
{ id: "root-app", title: titles["root-app"] },
|
|
861
|
+
{ id: "middle-app", title: titles["middle-app"] },
|
|
862
|
+
{ id: "leaf-app", title: titles["leaf-app"] }
|
|
863
|
+
],
|
|
864
|
+
launcher: async (id) => ({ script: { title: titles[id] || id } })
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const server = {
|
|
868
|
+
kernel,
|
|
869
|
+
exists: pathExists,
|
|
870
|
+
is_subpath: isSubpath,
|
|
871
|
+
app: {
|
|
872
|
+
get: () => {},
|
|
873
|
+
post: () => {}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
const autolaunch = new ServerAutolaunch(server)
|
|
877
|
+
const items = [
|
|
878
|
+
{ name: "Root App", uri: "root-app", filepath: rootAppRoot, path: "root-app" },
|
|
879
|
+
{ name: "Middle App", uri: "middle-app", filepath: middleAppRoot, path: "middle-app" },
|
|
880
|
+
{ name: "Leaf App", uri: "leaf-app", filepath: leafAppRoot, path: "leaf-app" }
|
|
881
|
+
]
|
|
882
|
+
|
|
883
|
+
const applied = []
|
|
884
|
+
for (const [index, item] of items.entries()) {
|
|
885
|
+
applied.push(await autolaunch.applyHomeStartingState(item, index))
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
assert.deepEqual(applied, [true, true, true])
|
|
889
|
+
assert.equal(items[0].autolaunch_status_label, "Waiting for Middle App")
|
|
890
|
+
assert.equal(items[1].autolaunch_status_label, "Waiting for Leaf App")
|
|
891
|
+
assert.equal(items[2].autolaunch_status_label, "Starting leaf.custom.js")
|
|
892
|
+
assert.equal(items[0].autolaunch_script, "root.custom.js")
|
|
893
|
+
assert.equal(items[1].autolaunch_script, "middle.custom.js")
|
|
894
|
+
assert.equal(items[2].autolaunch_script, "leaf.custom.js")
|
|
895
|
+
} finally {
|
|
896
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
897
|
+
}
|
|
898
|
+
})
|
|
899
|
+
|
|
900
|
+
test("home startup state renders requirement-only startup rows from runtime status", async () => {
|
|
901
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
902
|
+
try {
|
|
903
|
+
const apiRoot = path.resolve(root, "api")
|
|
904
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
905
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
906
|
+
await fs.writeFile(path.resolve(helperRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
907
|
+
await fs.writeFile(path.resolve(helperRoot, "ENVIRONMENT"), [
|
|
908
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
909
|
+
""
|
|
910
|
+
].join("\n"), "utf8")
|
|
911
|
+
|
|
912
|
+
const launchPath = path.resolve(helperRoot, "start.js")
|
|
913
|
+
const kernel = {
|
|
914
|
+
homedir: root,
|
|
915
|
+
launch_complete: false,
|
|
916
|
+
autolaunch_status: {
|
|
917
|
+
running: true,
|
|
918
|
+
apps: {
|
|
919
|
+
helper: {
|
|
920
|
+
id: "helper",
|
|
921
|
+
title: "Helper",
|
|
922
|
+
script: "start.js",
|
|
923
|
+
launch_path: launchPath,
|
|
924
|
+
state: "pending",
|
|
925
|
+
dependencies: [],
|
|
926
|
+
waiting_for: [],
|
|
927
|
+
startup_root: false
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
exists: pathExists,
|
|
932
|
+
getScriptProgress: () => null,
|
|
933
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
934
|
+
api: {
|
|
935
|
+
userdir: apiRoot,
|
|
936
|
+
listApps: async () => [{ id: "helper", title: "Helper" }],
|
|
937
|
+
launcher: async () => ({ script: { title: "Helper" } })
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const server = {
|
|
941
|
+
kernel,
|
|
942
|
+
exists: pathExists,
|
|
943
|
+
is_subpath: isSubpath,
|
|
944
|
+
app: {
|
|
945
|
+
get: () => {},
|
|
946
|
+
post: () => {}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const autolaunch = new ServerAutolaunch(server)
|
|
950
|
+
const item = {
|
|
951
|
+
name: "helper",
|
|
952
|
+
uri: "helper",
|
|
953
|
+
filepath: helperRoot,
|
|
954
|
+
path: "helper"
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const applied = await autolaunch.applyHomeStartingState(item, 0)
|
|
958
|
+
|
|
959
|
+
assert.equal(applied, true)
|
|
960
|
+
assert.equal(item.running, true)
|
|
961
|
+
assert.equal(item.autolaunch_starting, true)
|
|
962
|
+
assert.equal(item.autolaunch_script, "start.js")
|
|
963
|
+
assert.equal(item.autolaunch_status_label, "Starting start.js")
|
|
964
|
+
} finally {
|
|
965
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
966
|
+
}
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
test("home startup state renders active runtime status after launch_complete flips", async () => {
|
|
970
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "pinokio-server-autolaunch-"))
|
|
971
|
+
try {
|
|
972
|
+
const apiRoot = path.resolve(root, "api")
|
|
973
|
+
const targetRoot = path.resolve(apiRoot, "target")
|
|
974
|
+
const helperRoot = path.resolve(apiRoot, "helper")
|
|
975
|
+
await fs.mkdir(targetRoot, { recursive: true })
|
|
976
|
+
await fs.mkdir(helperRoot, { recursive: true })
|
|
977
|
+
await fs.writeFile(path.resolve(targetRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
978
|
+
await fs.writeFile(path.resolve(helperRoot, "start.js"), "module.exports = { run: [] }\n", "utf8")
|
|
979
|
+
await fs.writeFile(path.resolve(targetRoot, "ENVIRONMENT"), [
|
|
980
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
981
|
+
"PINOKIO_SCRIPT_REQUIRES=helper",
|
|
982
|
+
""
|
|
983
|
+
].join("\n"), "utf8")
|
|
984
|
+
await fs.writeFile(path.resolve(helperRoot, "ENVIRONMENT"), [
|
|
985
|
+
"PINOKIO_SCRIPT_AUTOLAUNCH=start.js",
|
|
986
|
+
""
|
|
987
|
+
].join("\n"), "utf8")
|
|
988
|
+
|
|
989
|
+
const kernel = {
|
|
990
|
+
homedir: root,
|
|
991
|
+
launch_complete: true,
|
|
992
|
+
autolaunch_status: {
|
|
993
|
+
running: false,
|
|
994
|
+
apps: {
|
|
995
|
+
target: {
|
|
996
|
+
id: "target",
|
|
997
|
+
title: "Target",
|
|
998
|
+
script: "start.js",
|
|
999
|
+
launch_path: path.resolve(targetRoot, "start.js"),
|
|
1000
|
+
state: "waiting",
|
|
1001
|
+
dependencies: ["helper"],
|
|
1002
|
+
waiting_for: ["helper"],
|
|
1003
|
+
startup_root: true
|
|
1004
|
+
},
|
|
1005
|
+
helper: {
|
|
1006
|
+
id: "helper",
|
|
1007
|
+
title: "Helper",
|
|
1008
|
+
script: "start.js",
|
|
1009
|
+
launch_path: path.resolve(helperRoot, "start.js"),
|
|
1010
|
+
state: "starting",
|
|
1011
|
+
dependencies: [],
|
|
1012
|
+
waiting_for: [],
|
|
1013
|
+
startup_root: false
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
exists: pathExists,
|
|
1018
|
+
getScriptProgress: () => null,
|
|
1019
|
+
path: (name, ...chunks) => path.resolve(root, name, ...chunks),
|
|
1020
|
+
api: {
|
|
1021
|
+
userdir: apiRoot,
|
|
1022
|
+
listApps: async () => [{ id: "target", title: "Target" }, { id: "helper", title: "Helper" }],
|
|
1023
|
+
launcher: async (id) => ({ script: { title: id === "helper" ? "Helper" : "Target" } })
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const server = {
|
|
1027
|
+
kernel,
|
|
1028
|
+
exists: pathExists,
|
|
1029
|
+
is_subpath: isSubpath,
|
|
1030
|
+
app: {
|
|
1031
|
+
get: () => {},
|
|
1032
|
+
post: () => {}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const autolaunch = new ServerAutolaunch(server)
|
|
1036
|
+
const item = {
|
|
1037
|
+
name: "target",
|
|
1038
|
+
uri: "target",
|
|
1039
|
+
filepath: targetRoot,
|
|
1040
|
+
path: "target"
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const applied = await autolaunch.applyHomeStartingState(item, 0)
|
|
1044
|
+
|
|
1045
|
+
assert.equal(applied, true)
|
|
1046
|
+
assert.equal(item.running, true)
|
|
1047
|
+
assert.equal(item.autolaunch_waiting, true)
|
|
1048
|
+
assert.equal(item.autolaunch_status_label, "Waiting for Helper")
|
|
1049
|
+
} finally {
|
|
1050
|
+
await fs.rm(root, { recursive: true, force: true })
|
|
1051
|
+
}
|
|
1052
|
+
})
|