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,579 @@
|
|
|
1
|
+
const assert = require("node:assert/strict")
|
|
2
|
+
const fsSync = require("node:fs")
|
|
3
|
+
const fs = require("node:fs/promises")
|
|
4
|
+
const http = require("node:http")
|
|
5
|
+
const path = require("node:path")
|
|
6
|
+
const childProcess = require("node:child_process")
|
|
7
|
+
const test = require("node:test")
|
|
8
|
+
const ejs = require("ejs")
|
|
9
|
+
|
|
10
|
+
function loadPlaywright() {
|
|
11
|
+
try {
|
|
12
|
+
return require("playwright")
|
|
13
|
+
} catch (_) {}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const bin = childProcess.execFileSync("which", ["playwright"], { encoding: "utf8" }).trim()
|
|
17
|
+
if (!bin) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
return require(path.join(path.dirname(path.dirname(bin)), "playwright"))
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const playwright = loadPlaywright()
|
|
27
|
+
|
|
28
|
+
const root = path.resolve(__dirname, "..")
|
|
29
|
+
const appViewPath = path.resolve(root, "server/views/app.ejs")
|
|
30
|
+
const statusClientPath = path.resolve(root, "server/views/partials/launch_requirements_status_client.ejs")
|
|
31
|
+
const homeViewPath = path.resolve(root, "server/views/index.ejs")
|
|
32
|
+
|
|
33
|
+
const browserTest = test
|
|
34
|
+
|
|
35
|
+
function chromiumExecutablePath() {
|
|
36
|
+
const candidates = [
|
|
37
|
+
process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE,
|
|
38
|
+
playwright && playwright.chromium.executablePath(),
|
|
39
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
40
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
41
|
+
].filter(Boolean)
|
|
42
|
+
return candidates.find((candidate) => fsSync.existsSync(candidate)) || ""
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function renderAppAutolaunchScript(initialState) {
|
|
46
|
+
const view = await fs.readFile(appViewPath, "utf8")
|
|
47
|
+
const start = view.indexOf(';(function() {\n const root = document.querySelector("[data-app-autolaunch]")')
|
|
48
|
+
assert.notEqual(start, -1)
|
|
49
|
+
const end = view.indexOf('\n\n;(function() {\n const scroller = document.querySelector(".appcanvas > aside .menu-scroller")', start)
|
|
50
|
+
assert.notEqual(end, -1)
|
|
51
|
+
return ejs.render(view.slice(start, end), {
|
|
52
|
+
autolaunch_app: initialState
|
|
53
|
+
}, {
|
|
54
|
+
filename: appViewPath
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function renderLaunchRequirementsStatusClient(enabled) {
|
|
59
|
+
const template = await fs.readFile(statusClientPath, "utf8")
|
|
60
|
+
return ejs.render(template, {
|
|
61
|
+
name: "target",
|
|
62
|
+
config: { title: "Target" },
|
|
63
|
+
launch_requirements_status_enabled: !!enabled
|
|
64
|
+
}, {
|
|
65
|
+
filename: statusClientPath
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function renderHomeAutolaunchScript() {
|
|
70
|
+
const view = await fs.readFile(homeViewPath, "utf8")
|
|
71
|
+
const start = view.indexOf("const startHomeAutolaunchPolling = () => {")
|
|
72
|
+
assert.notEqual(start, -1)
|
|
73
|
+
const call = view.indexOf("startHomeAutolaunchPolling()", start)
|
|
74
|
+
assert.notEqual(call, -1)
|
|
75
|
+
return ejs.render(view.slice(start, call + "startHomeAutolaunchPolling()".length), {
|
|
76
|
+
launch_complete: false
|
|
77
|
+
}, {
|
|
78
|
+
filename: homeViewPath
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function html(body, script = "") {
|
|
83
|
+
return `<!doctype html>
|
|
84
|
+
<html>
|
|
85
|
+
<head>
|
|
86
|
+
<meta charset="utf-8">
|
|
87
|
+
<style>
|
|
88
|
+
.hidden, [hidden] { display: none !important; }
|
|
89
|
+
body { font-family: sans-serif; }
|
|
90
|
+
.app-autolaunch-modal { position: fixed; inset: 20px; background: white; border: 1px solid #ccc; padding: 16px; }
|
|
91
|
+
.app-autolaunch-script-option, .app-autolaunch-dependency-option, .app-autolaunch-dependency-row { display: block; margin: 6px 0; }
|
|
92
|
+
.launch-requirements-card { width: 560px; margin: 40px auto; }
|
|
93
|
+
.launch-requirements-row { display: grid; grid-template-columns: 32px 1fr auto; gap: 8px; align-items: center; }
|
|
94
|
+
</style>
|
|
95
|
+
</head>
|
|
96
|
+
<body>
|
|
97
|
+
${body}
|
|
98
|
+
<script>${script}</script>
|
|
99
|
+
</body>
|
|
100
|
+
</html>`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function appFixtureHtml(initialApp) {
|
|
104
|
+
const script = await renderAppAutolaunchScript(initialApp)
|
|
105
|
+
return html(`
|
|
106
|
+
<div class="app-autolaunch" data-app-autolaunch data-app-id="target">
|
|
107
|
+
<button type="button" class="app-autolaunch-row" data-app-autolaunch-button data-enabled="${initialApp.autolaunch_enabled ? "true" : "false"}" aria-haspopup="dialog" aria-expanded="false">
|
|
108
|
+
<span class="app-autolaunch-label">Autolaunch</span>
|
|
109
|
+
<span class="app-autolaunch-status" data-app-autolaunch-status>${initialApp.autolaunch_enabled ? "ON" : "OFF"}</span>
|
|
110
|
+
</button>
|
|
111
|
+
<div class="app-autolaunch-modal hidden" data-app-autolaunch-modal role="dialog" aria-modal="true" aria-label="Autolaunch">
|
|
112
|
+
<button type="button" class="app-autolaunch-switch" role="switch" aria-checked="${initialApp.autolaunch_enabled ? "true" : "false"}" data-app-autolaunch-switch aria-label="Start with Pinokio">
|
|
113
|
+
<span data-app-autolaunch-switch-label>${initialApp.autolaunch_enabled ? "ON" : "OFF"}</span>
|
|
114
|
+
</button>
|
|
115
|
+
<button type="button" data-app-autolaunch-close>Close</button>
|
|
116
|
+
<section>
|
|
117
|
+
<div data-app-autolaunch-scripts></div>
|
|
118
|
+
</section>
|
|
119
|
+
<section>
|
|
120
|
+
<div data-app-autolaunch-dependencies></div>
|
|
121
|
+
</section>
|
|
122
|
+
<div data-app-autolaunch-feedback></div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
`, script)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function openWithoutLaunchingHtml() {
|
|
129
|
+
const script = await renderLaunchRequirementsStatusClient(false)
|
|
130
|
+
return html(`
|
|
131
|
+
<main>
|
|
132
|
+
<div data-launch-requirements-status hidden></div>
|
|
133
|
+
<div data-open-without-launching-content>Opened without launching.</div>
|
|
134
|
+
</main>
|
|
135
|
+
`, `
|
|
136
|
+
window.Socket = function Socket() {
|
|
137
|
+
throw new Error("Socket must not start for open without launching")
|
|
138
|
+
};
|
|
139
|
+
${script}
|
|
140
|
+
`)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function statusFixtureHtml() {
|
|
144
|
+
const script = await renderLaunchRequirementsStatusClient(true)
|
|
145
|
+
return html(`
|
|
146
|
+
<main>
|
|
147
|
+
<div data-launch-requirements-status hidden></div>
|
|
148
|
+
</main>
|
|
149
|
+
`, `
|
|
150
|
+
window.Socket = function Socket() {
|
|
151
|
+
this.run = function() { return Promise.resolve() }
|
|
152
|
+
this.close = function() {}
|
|
153
|
+
};
|
|
154
|
+
${script}
|
|
155
|
+
`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function homeFixtureHtml() {
|
|
159
|
+
const script = await renderHomeAutolaunchScript()
|
|
160
|
+
return html(`
|
|
161
|
+
<script>
|
|
162
|
+
window.reorderHomeSectionsByPreference = function() {};
|
|
163
|
+
</script>
|
|
164
|
+
<div class="running-apps"></div>
|
|
165
|
+
<div class="not-running-apps">
|
|
166
|
+
<div class="line align-top home-app-line" data-autolaunch-app="target" data-autolaunch-starting="0" data-autolaunch-script="start.js">
|
|
167
|
+
<h3><span class="title"><i class="fa-solid fa-circle"></i></span><span>Target</span></h3>
|
|
168
|
+
<div class="menu-btns">
|
|
169
|
+
<button class="open-actions-modal" data-dialog-id="actions-target" type="button">menu</button>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div id="actions-target"><div class="home-actions-title-row"></div></div>
|
|
174
|
+
`, script)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function createJsonResponse(res, status, payload) {
|
|
178
|
+
res.writeHead(status, {
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
"Cache-Control": "no-store"
|
|
181
|
+
})
|
|
182
|
+
res.end(JSON.stringify(payload))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function readBody(req) {
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
let body = ""
|
|
188
|
+
req.setEncoding("utf8")
|
|
189
|
+
req.on("data", (chunk) => { body += chunk })
|
|
190
|
+
req.on("end", () => {
|
|
191
|
+
try {
|
|
192
|
+
resolve(body ? JSON.parse(body) : {})
|
|
193
|
+
} catch (error) {
|
|
194
|
+
reject(error)
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
req.on("error", reject)
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function startFixtureServer(state) {
|
|
202
|
+
const pages = {
|
|
203
|
+
app: await appFixtureHtml(state.targetApp),
|
|
204
|
+
openWithoutLaunching: await openWithoutLaunchingHtml(),
|
|
205
|
+
status: await statusFixtureHtml(),
|
|
206
|
+
home: await homeFixtureHtml()
|
|
207
|
+
}
|
|
208
|
+
const server = http.createServer(async (req, res) => {
|
|
209
|
+
try {
|
|
210
|
+
const url = new URL(req.url, "http://127.0.0.1")
|
|
211
|
+
if (req.method === "GET" && url.pathname === "/app-fixture") {
|
|
212
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
213
|
+
res.end(pages.app)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
if (req.method === "GET" && url.pathname === "/open-without-launching") {
|
|
217
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
218
|
+
res.end(pages.openWithoutLaunching)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
if (req.method === "GET" && url.pathname === "/status-fixture") {
|
|
222
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
223
|
+
res.end(pages.status)
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
if (req.method === "GET" && url.pathname === "/home-fixture") {
|
|
227
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
228
|
+
res.end(pages.home)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
if (req.method === "GET" && url.pathname === "/autolaunch/candidates") {
|
|
232
|
+
const appId = url.searchParams.get("app")
|
|
233
|
+
state.candidateRequests.push(appId)
|
|
234
|
+
const payload = state.candidates[appId]
|
|
235
|
+
createJsonResponse(res, payload ? 200 : 404, payload || { ok: false, error: "missing app" })
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
if (req.method === "POST" && url.pathname === "/autolaunch") {
|
|
239
|
+
const body = await readBody(req)
|
|
240
|
+
state.autolaunchPosts.push(body)
|
|
241
|
+
const app = {
|
|
242
|
+
id: body.app,
|
|
243
|
+
title: body.app === "helper" ? "Helper" : "Target",
|
|
244
|
+
autolaunch: body.clear_script ? "" : (body.script || ""),
|
|
245
|
+
autolaunch_enabled: !!body.enabled,
|
|
246
|
+
autolaunch_depends: body.app === "target" ? state.targetDependencies.slice() : []
|
|
247
|
+
}
|
|
248
|
+
if (state.candidates.target && Array.isArray(state.candidates.target.dependency_apps)) {
|
|
249
|
+
state.candidates.target.dependency_apps = state.candidates.target.dependency_apps.map((candidate) => {
|
|
250
|
+
return candidate.id === app.id ? Object.assign({}, candidate, app) : candidate
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
if (body.app === "target") {
|
|
254
|
+
state.targetApp = Object.assign({}, state.targetApp, app)
|
|
255
|
+
if (state.candidates.target) {
|
|
256
|
+
state.candidates.target.current = app.autolaunch
|
|
257
|
+
state.candidates.target.app = Object.assign({}, state.candidates.target.app, app)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
createJsonResponse(res, 200, { ok: true, app })
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
if (req.method === "POST" && url.pathname === "/autolaunch/dependencies") {
|
|
264
|
+
const body = await readBody(req)
|
|
265
|
+
state.dependencyPosts.push(body)
|
|
266
|
+
state.targetDependencies = Array.isArray(body.dependencies) ? body.dependencies.slice() : []
|
|
267
|
+
const app = Object.assign({}, state.targetApp, {
|
|
268
|
+
autolaunch_depends: state.targetDependencies.slice()
|
|
269
|
+
})
|
|
270
|
+
createJsonResponse(res, 200, { ok: true, app })
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
if (req.method === "GET" && url.pathname.startsWith("/pinokio/launch-requirements/")) {
|
|
274
|
+
state.launchRequirementsGets += 1
|
|
275
|
+
createJsonResponse(res, 200, {
|
|
276
|
+
ok: true,
|
|
277
|
+
status: state.launchRequirementsStatus || {
|
|
278
|
+
state: "waiting",
|
|
279
|
+
requirements: [{ id: "helper", title: "Helper", state: "starting", script: "start.js" }]
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if (req.method === "GET" && url.pathname === "/pinokio/home_status") {
|
|
285
|
+
state.homeStatusGets += 1
|
|
286
|
+
createJsonResponse(res, 200, state.homeStatus)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
res.writeHead(404, { "Content-Type": "text/plain" })
|
|
290
|
+
res.end("not found")
|
|
291
|
+
} catch (error) {
|
|
292
|
+
res.writeHead(500, { "Content-Type": "text/plain" })
|
|
293
|
+
res.end(error && error.stack ? error.stack : String(error))
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve))
|
|
297
|
+
return {
|
|
298
|
+
server,
|
|
299
|
+
baseUrl: `http://127.0.0.1:${server.address().port}`
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function baseState(options = {}) {
|
|
304
|
+
const targetScript = options.targetScript || ""
|
|
305
|
+
const targetApp = {
|
|
306
|
+
id: "target",
|
|
307
|
+
title: "Target",
|
|
308
|
+
autolaunch: targetScript,
|
|
309
|
+
autolaunch_enabled: false,
|
|
310
|
+
autolaunch_depends: []
|
|
311
|
+
}
|
|
312
|
+
const helperScript = Object.prototype.hasOwnProperty.call(options, "helperScript")
|
|
313
|
+
? options.helperScript
|
|
314
|
+
: "start.js"
|
|
315
|
+
return {
|
|
316
|
+
targetApp,
|
|
317
|
+
targetDependencies: [],
|
|
318
|
+
candidateRequests: [],
|
|
319
|
+
autolaunchPosts: [],
|
|
320
|
+
dependencyPosts: [],
|
|
321
|
+
launchRequirementsGets: 0,
|
|
322
|
+
homeStatusGets: 0,
|
|
323
|
+
homeStatus: {
|
|
324
|
+
launch_complete: true,
|
|
325
|
+
running_apps: [],
|
|
326
|
+
running_scripts: [],
|
|
327
|
+
autolaunch: { apps: {} }
|
|
328
|
+
},
|
|
329
|
+
candidates: {
|
|
330
|
+
target: {
|
|
331
|
+
ok: true,
|
|
332
|
+
current: targetScript,
|
|
333
|
+
app: Object.assign({}, targetApp),
|
|
334
|
+
menu: [{ script: "start.js", label: "Start", menu_default: true }],
|
|
335
|
+
other: [{ script: "target.custom.js", label: "Custom" }],
|
|
336
|
+
dependency_apps: [{
|
|
337
|
+
id: "helper",
|
|
338
|
+
title: "Helper",
|
|
339
|
+
workspace_path: "/Users/test/pinokio/api/helper",
|
|
340
|
+
icon: "/helper.png",
|
|
341
|
+
autolaunch: helperScript,
|
|
342
|
+
autolaunch_enabled: false
|
|
343
|
+
}]
|
|
344
|
+
},
|
|
345
|
+
helper: {
|
|
346
|
+
ok: true,
|
|
347
|
+
current: helperScript,
|
|
348
|
+
app: {
|
|
349
|
+
id: "helper",
|
|
350
|
+
title: "Helper",
|
|
351
|
+
autolaunch: helperScript,
|
|
352
|
+
autolaunch_enabled: false,
|
|
353
|
+
autolaunch_depends: []
|
|
354
|
+
},
|
|
355
|
+
menu: [{ script: "start.js", label: "Start", menu_default: true }],
|
|
356
|
+
other: [{ script: "helper.custom.js", label: "Custom" }],
|
|
357
|
+
dependency_apps: []
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function withBrowser(state, callback) {
|
|
364
|
+
assert.ok(playwright, "Playwright is required. Run: npx -y -p playwright node --test test/launch-requirements-browser.test.js")
|
|
365
|
+
const { server, baseUrl } = await startFixtureServer(state)
|
|
366
|
+
let browser = null
|
|
367
|
+
let page = null
|
|
368
|
+
const errors = []
|
|
369
|
+
try {
|
|
370
|
+
const executablePath = chromiumExecutablePath()
|
|
371
|
+
assert.ok(executablePath, "No Chromium executable is available for browser tests")
|
|
372
|
+
browser = await playwright.chromium.launch({
|
|
373
|
+
headless: true,
|
|
374
|
+
executablePath
|
|
375
|
+
})
|
|
376
|
+
page = await browser.newPage()
|
|
377
|
+
page.on("pageerror", (error) => errors.push(error.message || String(error)))
|
|
378
|
+
await callback({ page, baseUrl, state })
|
|
379
|
+
assert.deepEqual(errors, [])
|
|
380
|
+
} finally {
|
|
381
|
+
if (page) {
|
|
382
|
+
await page.close().catch(() => {})
|
|
383
|
+
}
|
|
384
|
+
if (browser) {
|
|
385
|
+
await browser.close().catch(() => {})
|
|
386
|
+
}
|
|
387
|
+
await new Promise((resolve) => server.close(resolve))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function textContent(page, selector) {
|
|
392
|
+
return page.locator(selector).first().textContent()
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
browserTest("browser: selecting a launch script from empty state persists and stays off", async () => {
|
|
396
|
+
const state = baseState({ targetScript: "" })
|
|
397
|
+
await withBrowser(state, async ({ page, baseUrl }) => {
|
|
398
|
+
await page.goto(`${baseUrl}/app-fixture`)
|
|
399
|
+
await page.click("[data-app-autolaunch-button]")
|
|
400
|
+
await page.waitForSelector('input[name="app-autolaunch-script"][value="start.js"]')
|
|
401
|
+
await page.click('input[name="app-autolaunch-script"][value="start.js"]')
|
|
402
|
+
await page.waitForFunction(() => {
|
|
403
|
+
const feedback = document.querySelector("[data-app-autolaunch-feedback]")
|
|
404
|
+
return feedback && feedback.textContent.includes("Launch script saved.")
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
assert.deepEqual(state.autolaunchPosts[0], {
|
|
408
|
+
app: "target",
|
|
409
|
+
script: "start.js",
|
|
410
|
+
enabled: false
|
|
411
|
+
})
|
|
412
|
+
assert.equal(await page.isChecked('input[name="app-autolaunch-script"][value="start.js"]'), true)
|
|
413
|
+
assert.equal((await textContent(page, "[data-app-autolaunch-status]")).trim(), "OFF")
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
browserTest("browser: adding a requirement with an existing script still requires script confirmation", async () => {
|
|
418
|
+
const state = baseState({ targetScript: "target.custom.js", helperScript: "start.js" })
|
|
419
|
+
await withBrowser(state, async ({ page, baseUrl }) => {
|
|
420
|
+
await page.goto(`${baseUrl}/app-fixture`)
|
|
421
|
+
await page.click("[data-app-autolaunch-button]")
|
|
422
|
+
await page.waitForSelector("[data-app-autolaunch-toggle-dependency-picker]")
|
|
423
|
+
await page.click("[data-app-autolaunch-toggle-dependency-picker]")
|
|
424
|
+
await page.click('[data-app-autolaunch-add-dependency="helper"]')
|
|
425
|
+
|
|
426
|
+
await page.waitForSelector(".app-autolaunch-dependency-script-modal")
|
|
427
|
+
assert.equal(state.dependencyPosts.length, 0)
|
|
428
|
+
assert.equal(await page.isChecked('[data-app-autolaunch-dependency-script-app="helper"][value="start.js"]'), true)
|
|
429
|
+
|
|
430
|
+
await page.click('[data-app-autolaunch-dependency-script-app="helper"][value="start.js"]')
|
|
431
|
+
assert.equal(state.dependencyPosts.length, 0)
|
|
432
|
+
assert.equal(state.autolaunchPosts.length, 0)
|
|
433
|
+
await page.click('[data-app-autolaunch-confirm-dependency-script]')
|
|
434
|
+
await page.waitForFunction(() => {
|
|
435
|
+
const feedback = document.querySelector("[data-app-autolaunch-feedback]")
|
|
436
|
+
return feedback && feedback.textContent.includes("Requirement saved.")
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
assert.deepEqual(state.autolaunchPosts.at(-1), {
|
|
440
|
+
app: "helper",
|
|
441
|
+
script: "start.js",
|
|
442
|
+
enabled: false
|
|
443
|
+
})
|
|
444
|
+
assert.deepEqual(state.dependencyPosts.at(-1), {
|
|
445
|
+
app: "target",
|
|
446
|
+
dependencies: ["helper"]
|
|
447
|
+
})
|
|
448
|
+
assert.equal(await page.locator(".app-autolaunch-dependency-script-modal").count(), 0)
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
browserTest("browser: blocked setup status exposes script selection and stop actions", async () => {
|
|
453
|
+
const state = baseState({ targetScript: "target.custom.js", helperScript: "" })
|
|
454
|
+
state.launchRequirementsStatus = {
|
|
455
|
+
state: "blocked",
|
|
456
|
+
blocked_reason: "Comfyui has no launch script selected",
|
|
457
|
+
requirements: [{
|
|
458
|
+
id: "helper",
|
|
459
|
+
title: "Comfyui",
|
|
460
|
+
state: "blocked",
|
|
461
|
+
blocked_reason: "Comfyui has no launch script selected",
|
|
462
|
+
icon: "/pinokio-black.png"
|
|
463
|
+
}]
|
|
464
|
+
}
|
|
465
|
+
await withBrowser(state, async ({ page, baseUrl }) => {
|
|
466
|
+
await page.goto(`${baseUrl}/status-fixture`)
|
|
467
|
+
await page.waitForSelector("text=Launch needs setup")
|
|
468
|
+
assert.equal(await page.locator('[data-launch-requirements-choose-script="helper"]').count(), 1)
|
|
469
|
+
assert.equal(await page.locator("[data-launch-requirements-stop]").count(), 1)
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
browserTest("browser: adding a requirement with no script requires selecting one before saving", async () => {
|
|
474
|
+
const state = baseState({ targetScript: "target.custom.js", helperScript: "" })
|
|
475
|
+
await withBrowser(state, async ({ page, baseUrl }) => {
|
|
476
|
+
await page.goto(`${baseUrl}/app-fixture`)
|
|
477
|
+
await page.click("[data-app-autolaunch-button]")
|
|
478
|
+
await page.waitForSelector("[data-app-autolaunch-toggle-dependency-picker]")
|
|
479
|
+
await page.click("[data-app-autolaunch-toggle-dependency-picker]")
|
|
480
|
+
await page.click('[data-app-autolaunch-add-dependency="helper"]')
|
|
481
|
+
|
|
482
|
+
await page.waitForSelector(".app-autolaunch-dependency-script-modal")
|
|
483
|
+
assert.equal(state.dependencyPosts.length, 0)
|
|
484
|
+
assert.equal(await page.isChecked('[data-app-autolaunch-dependency-script-app="helper"][value="start.js"]'), false)
|
|
485
|
+
|
|
486
|
+
await page.click('[data-app-autolaunch-dependency-script-app="helper"][value="start.js"]')
|
|
487
|
+
assert.equal(state.dependencyPosts.length, 0)
|
|
488
|
+
assert.equal(state.autolaunchPosts.length, 0)
|
|
489
|
+
await page.click('[data-app-autolaunch-confirm-dependency-script]')
|
|
490
|
+
await page.waitForFunction(() => {
|
|
491
|
+
const feedback = document.querySelector("[data-app-autolaunch-feedback]")
|
|
492
|
+
return feedback && feedback.textContent.includes("Requirement saved.")
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
assert.deepEqual(state.autolaunchPosts.at(-1), {
|
|
496
|
+
app: "helper",
|
|
497
|
+
script: "start.js",
|
|
498
|
+
enabled: false
|
|
499
|
+
})
|
|
500
|
+
assert.deepEqual(state.dependencyPosts.at(-1), {
|
|
501
|
+
app: "target",
|
|
502
|
+
dependencies: ["helper"]
|
|
503
|
+
})
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
browserTest("browser: open without launching does not fetch or show requirement status", async () => {
|
|
508
|
+
const state = baseState()
|
|
509
|
+
await withBrowser(state, async ({ page, baseUrl }) => {
|
|
510
|
+
await page.goto(`${baseUrl}/open-without-launching`)
|
|
511
|
+
await page.waitForTimeout(250)
|
|
512
|
+
|
|
513
|
+
assert.equal(state.launchRequirementsGets, 0)
|
|
514
|
+
assert.equal(await page.locator("[data-launch-requirements-status]").isHidden(), true)
|
|
515
|
+
assert.equal(await page.locator("text=Preparing required apps").count(), 0)
|
|
516
|
+
})
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
browserTest("browser: home autolaunch status appears and transitions without refresh", async () => {
|
|
520
|
+
const state = baseState()
|
|
521
|
+
state.homeStatus = {
|
|
522
|
+
launch_complete: false,
|
|
523
|
+
running_apps: [],
|
|
524
|
+
running_scripts: [],
|
|
525
|
+
autolaunch: {
|
|
526
|
+
apps: {
|
|
527
|
+
target: {
|
|
528
|
+
id: "target",
|
|
529
|
+
title: "Target",
|
|
530
|
+
script: "start.js",
|
|
531
|
+
state: "waiting",
|
|
532
|
+
waiting_for: ["helper"],
|
|
533
|
+
dependencies: ["helper"]
|
|
534
|
+
},
|
|
535
|
+
helper: {
|
|
536
|
+
id: "helper",
|
|
537
|
+
title: "Helper",
|
|
538
|
+
state: "starting"
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
await withBrowser(state, async ({ page, baseUrl }) => {
|
|
544
|
+
await page.goto(`${baseUrl}/home-fixture`)
|
|
545
|
+
await page.waitForFunction(() => {
|
|
546
|
+
const chip = document.querySelector(".home-autolaunch-status")
|
|
547
|
+
return chip && chip.textContent.includes("Waiting for Helper")
|
|
548
|
+
})
|
|
549
|
+
assert.ok(state.homeStatusGets > 0)
|
|
550
|
+
|
|
551
|
+
state.homeStatus = {
|
|
552
|
+
launch_complete: true,
|
|
553
|
+
running_apps: ["target"],
|
|
554
|
+
running_scripts: [{
|
|
555
|
+
app: "target",
|
|
556
|
+
home_path: "api/target/start.js",
|
|
557
|
+
path: "target/start.js",
|
|
558
|
+
script_path: "start.js"
|
|
559
|
+
}],
|
|
560
|
+
autolaunch: {
|
|
561
|
+
apps: {
|
|
562
|
+
target: {
|
|
563
|
+
id: "target",
|
|
564
|
+
title: "Target",
|
|
565
|
+
script: "start.js",
|
|
566
|
+
state: "ready"
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
await page.waitForFunction(() => {
|
|
572
|
+
const button = document.querySelector(".home-app-line .shutdown")
|
|
573
|
+
return button && button.textContent.includes("Stop start.js")
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
assert.equal(await page.locator(".home-autolaunch-status").count(), 0)
|
|
577
|
+
assert.match(await textContent(page, ".home-app-line .shutdown"), /Stop start\.js/)
|
|
578
|
+
})
|
|
579
|
+
})
|