@playcraft/cli 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -38
- package/dist/agent/local-backend.js +1 -1
- package/dist/cli-root-help.js +0 -1
- package/dist/commands/build.js +4 -9
- package/dist/commands/create.js +33 -26
- package/dist/commands/image.js +6 -2
- package/dist/commands/login.js +42 -84
- package/dist/commands/skills.js +0 -2
- package/dist/commands/tools-generation.js +4 -2
- package/dist/commands/tools.js +0 -2
- package/dist/config.js +4 -59
- package/dist/index.js +1 -6
- package/dist/remix/clone.js +0 -2
- package/dist/remix/init-template.js +0 -2
- package/dist/remix/pull.js +0 -2
- package/dist/remix/push.js +1 -21
- package/dist/utils/agent-api-client.js +29 -148
- package/dist/utils/playcraft-http-client.js +4 -6
- package/package.json +4 -10
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +61 -23
- package/dist/auth/account-activation.js +0 -157
- package/dist/auth/messages.js +0 -30
- package/dist/commands/platform-skills.generated.js +0 -19
- package/dist/commands/tools-batch.js +0 -59
- package/dist/commands/workspace-runtime.js +0 -54
- package/dist/project-skills/commands.js +0 -72
- package/dist/project-skills/lifecycle.js +0 -28
- package/dist/project-skills/local-run.js +0 -112
- package/dist/project-skills/local-store.js +0 -149
- package/dist/project-skills/messages.js +0 -72
- package/dist/project-skills/reconcile.js +0 -62
- package/dist/project-skills/remote-cache.js +0 -51
- package/dist/project-skills/validation.js +0 -19
- package/dist/utils/environment-endpoints.js +0 -17
- package/dist/utils/tool-operation-journal.js +0 -124
- package/dist/workspace-runtime/codex/app-server.js +0 -571
- package/dist/workspace-runtime/codex/config-toml.js +0 -108
- package/dist/workspace-runtime/codex/jsonrpc-stdio.js +0 -123
- package/dist/workspace-runtime/codex/launcher-client.js +0 -163
- package/dist/workspace-runtime/codex/loopback.js +0 -143
- package/dist/workspace-runtime/codex/model-broker.js +0 -239
- package/dist/workspace-runtime/codex/native-adapter.js +0 -9
- package/dist/workspace-runtime/main.js +0 -10
- package/dist/workspace-runtime/persistence/journal.js +0 -355
- package/dist/workspace-runtime/persistence/sqlite-client.js +0 -61
- package/dist/workspace-runtime/persistence/sqlite-worker.js +0 -1015
- package/dist/workspace-runtime/previews/publish.js +0 -259
- package/dist/workspace-runtime/previews/single-file.js +0 -43
- package/dist/workspace-runtime/processes/managed-writes.js +0 -69
- package/dist/workspace-runtime/production/contracts.js +0 -17
- package/dist/workspace-runtime/production/controller.js +0 -207
- package/dist/workspace-runtime/production/dependencies.js +0 -60
- package/dist/workspace-runtime/production/native.js +0 -70
- package/dist/workspace-runtime/production/processes.js +0 -86
- package/dist/workspace-runtime/production/save-command.js +0 -20
- package/dist/workspace-runtime/production/stages.js +0 -175
- package/dist/workspace-runtime/production/storage.js +0 -32
- package/dist/workspace-runtime/serve.js +0 -199
- package/dist/workspace-runtime/server/auth.js +0 -21
- package/dist/workspace-runtime/server/connectivity.js +0 -201
- package/dist/workspace-runtime/server/desktop-host.js +0 -429
- package/dist/workspace-runtime/server/desktop-preview-config.js +0 -81
- package/dist/workspace-runtime/server/desktop-preview-service.js +0 -157
- package/dist/workspace-runtime/server/desktop-preview-static.js +0 -53
- package/dist/workspace-runtime/server/desktop-preview.js +0 -189
- package/dist/workspace-runtime/server/dispatch.js +0 -845
- package/dist/workspace-runtime/server/endpoints.js +0 -19
- package/dist/workspace-runtime/server/execution-group.js +0 -85
- package/dist/workspace-runtime/server/http.js +0 -919
- package/dist/workspace-runtime/server/lock.js +0 -53
- package/dist/workspace-runtime/server/rfb-watch-gate.js +0 -168
- package/dist/workspace-runtime/server/terminal-host.js +0 -294
- package/dist/workspace-runtime/server/types.js +0 -1
- package/dist/workspace-runtime/workspaces/attachments.js +0 -109
- package/dist/workspace-runtime/workspaces/batch-commit.js +0 -142
- package/dist/workspace-runtime/workspaces/cli-credentials.js +0 -111
- package/dist/workspace-runtime/workspaces/context-error.js +0 -2
- package/dist/workspace-runtime/workspaces/dependencies.js +0 -264
- package/dist/workspace-runtime/workspaces/developer-command.js +0 -64
- package/dist/workspace-runtime/workspaces/directories.js +0 -106
- package/dist/workspace-runtime/workspaces/directory-access.js +0 -20
- package/dist/workspace-runtime/workspaces/directory-maintenance.js +0 -111
- package/dist/workspace-runtime/workspaces/directory-stats.js +0 -49
- package/dist/workspace-runtime/workspaces/empty-template.js +0 -32
- package/dist/workspace-runtime/workspaces/files.js +0 -113
- package/dist/workspace-runtime/workspaces/git-credential-broker.js +0 -312
- package/dist/workspace-runtime/workspaces/git-credential-helper.js +0 -68
- package/dist/workspace-runtime/workspaces/git-direct-store.js +0 -155
- package/dist/workspace-runtime/workspaces/git.js +0 -471
- package/dist/workspace-runtime/workspaces/json5-edit.js +0 -170
- package/dist/workspace-runtime/workspaces/parameters.js +0 -272
- package/dist/workspace-runtime/workspaces/prepare.js +0 -263
- package/dist/workspace-runtime/workspaces/registry.js +0 -192
- package/dist/workspace-runtime/workspaces/revisions.js +0 -31
- package/dist/workspace-runtime/workspaces/skill-artifact-loader.js +0 -46
- package/dist/workspace-runtime/workspaces/skills.js +0 -179
- package/project-template-v2/.claude/agents/artist.md +0 -82
- package/project-template-v2/.claude/agents/developer.md +0 -153
- package/project-template-v2/.claude/agents/game-designer.md +0 -264
- package/project-template-v2/.claude/agents/refs/artist-art-style-catalog.md +0 -533
- package/project-template-v2/.claude/agents/refs/artist-color-audio-recipes.md +0 -153
- package/project-template-v2/.claude/agents/refs/artist-dimension-axis.md +0 -27
- package/project-template-v2/.claude/agents/refs/artist-master-composite-recipes.md +0 -208
- package/project-template-v2/.claude/agents/refs/atom-skill-library.md +0 -81
- package/project-template-v2/.claude/agents/refs/developer-impl-cookbook.md +0 -432
- package/project-template-v2/.claude/agents/refs/framework-5-component-filter.md +0 -252
- package/project-template-v2/.claude/agents/refs/framework-game-feel-juice.md +0 -266
- package/project-template-v2/.claude/agents/refs/framework-mda.md +0 -147
- package/project-template-v2/.claude/agents/refs/game-designer-gameplay-sufficiency.md +0 -123
- package/project-template-v2/.claude/agents/refs/ta-3d-flip-recipe.md +0 -88
- package/project-template-v2/.claude/agents/refs/ta-atlas-deliverable-standard.md +0 -67
- package/project-template-v2/.claude/agents/refs/ta-batch-pipeline-recipes.md +0 -120
- package/project-template-v2/.claude/agents/refs/ta-image-generation-detail.md +0 -300
- package/project-template-v2/.claude/agents/refs/ta-image-ops-reference.md +0 -495
- package/project-template-v2/.claude/agents/refs/ta-pipeline-cookbook.md +0 -1141
- package/project-template-v2/.claude/agents/refs/ta-tools-reference.md +0 -111
- package/project-template-v2/.claude/agents/refs/ta-vfx-preset-catalog.md +0 -365
- package/project-template-v2/.claude/agents/refs/threejs-cannon-pitfalls.md +0 -412
- package/project-template-v2/.claude/agents/reviewer.md +0 -75
- package/project-template-v2/.claude/agents/technical-artist.md +0 -86
- package/project-template-v2/.claude/hooks/snapshot-milestone.mjs +0 -243
- package/project-template-v2/.claude/hooks/user-prompt.mjs +0 -133
- package/project-template-v2/.claude/settings.json +0 -33
- package/project-template-v2/.claude/skills/brainstorming/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/brainstorming/scripts/frame-template.html +0 -270
- package/project-template-v2/.claude/skills/brainstorming/scripts/helper.js +0 -177
- package/project-template-v2/.claude/skills/brainstorming/scripts/server.cjs +0 -354
- package/project-template-v2/.claude/skills/brainstorming/scripts/start-server.sh +0 -148
- package/project-template-v2/.claude/skills/brainstorming/scripts/stop-server.sh +0 -56
- package/project-template-v2/.claude/skills/brainstorming/scripts/wait-for-selection.sh +0 -62
- package/project-template-v2/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/project-template-v2/.claude/skills/brainstorming/visual-companion.md +0 -309
- package/project-template-v2/.claude/skills/playcraft-ad-psychology/SKILL.md +0 -182
- package/project-template-v2/.claude/skills/playcraft-art-style-guide/SKILL.md +0 -123
- package/project-template-v2/.claude/skills/playcraft-asset-state-sheet/SKILL.md +0 -205
- package/project-template-v2/.claude/skills/playcraft-audio-generation/SKILL.md +0 -280
- package/project-template-v2/.claude/skills/playcraft-batch-pipeline/SKILL.md +0 -184
- package/project-template-v2/.claude/skills/playcraft-build-optimizer/SKILL.md +0 -306
- package/project-template-v2/.claude/skills/playcraft-image-generation/SKILL.md +0 -298
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +0 -123
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +0 -254
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +0 -324
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +0 -97
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +0 -118
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +0 -137
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +0 -397
- package/project-template-v2/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +0 -296
- package/project-template-v2/.claude/skills/playcraft-image-ops/SKILL.md +0 -122
- package/project-template-v2/.claude/skills/playcraft-image-processing/SKILL.md +0 -219
- package/project-template-v2/.claude/skills/playcraft-masking/SKILL.md +0 -373
- package/project-template-v2/.claude/skills/playcraft-playable-optimization/SKILL.md +0 -161
- package/project-template-v2/.claude/skills/playcraft-research/SKILL.md +0 -215
- package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +0 -382
- package/project-template-v2/.claude/skills/playcraft-sprite-generation/SKILL.md +0 -423
- package/project-template-v2/.claude/skills/playcraft-sprite-remix/SKILL.md +0 -158
- package/project-template-v2/.claude/skills/playcraft-sprite-sheet/SKILL.md +0 -100
- package/project-template-v2/.claude/skills/playcraft-storyboard/SKILL.md +0 -167
- package/project-template-v2/.claude/skills/playcraft-style-qa/SKILL.md +0 -270
- package/project-template-v2/.claude/skills/playcraft-text-rendering/SKILL.md +0 -236
- package/project-template-v2/.claude/skills/playcraft-vfx-animation/SKILL.md +0 -130
- package/project-template-v2/.claude/skills/playwright-cli/SKILL.md +0 -390
- package/project-template-v2/.claude/skills/playwright-cli/references/element-attributes.md +0 -23
- package/project-template-v2/.claude/skills/playwright-cli/references/playwright-tests.md +0 -39
- package/project-template-v2/.claude/skills/playwright-cli/references/request-mocking.md +0 -87
- package/project-template-v2/.claude/skills/playwright-cli/references/running-code.md +0 -240
- package/project-template-v2/.claude/skills/playwright-cli/references/session-management.md +0 -226
- package/project-template-v2/.claude/skills/playwright-cli/references/spec-driven-testing.md +0 -308
- package/project-template-v2/.claude/skills/playwright-cli/references/storage-state.md +0 -275
- package/project-template-v2/.claude/skills/playwright-cli/references/test-generation.md +0 -134
- package/project-template-v2/.claude/skills/playwright-cli/references/tracing.md +0 -142
- package/project-template-v2/.claude/skills/playwright-cli/references/video-recording.md +0 -153
- package/project-template-v2/.claude/skills/session-analyzer/SKILL.md +0 -386
- package/project-template-v2/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +0 -182
- package/project-template-v2/.claude/skills/session-analyzer/scripts/find-turns.mjs +0 -72
- package/project-template-v2/.claude/skills/session-analyzer/scripts/heavy-output.mjs +0 -121
- package/project-template-v2/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -102
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +0 -127
- package/project-template-v2/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +0 -106
- package/project-template-v2/.claude/skills/session-analyzer/scripts/time-gaps.mjs +0 -128
- package/project-template-v2/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +0 -67
- package/project-template-v2/.claude/snapshot.mjs +0 -263
- package/project-template-v2/.playcraft/skills.lock.json +0 -152
- package/project-template-v2/CLAUDE.md +0 -146
- package/project-template-v2/assets/audio/bgm/.gitkeep +0 -0
- package/project-template-v2/assets/audio/sfx/.gitkeep +0 -0
- package/project-template-v2/assets/bundles/.gitkeep +0 -0
- package/project-template-v2/assets/images/bg/.gitkeep +0 -0
- package/project-template-v2/assets/images/reference/.gitkeep +0 -0
- package/project-template-v2/assets/images/storyboard/.gitkeep +0 -0
- package/project-template-v2/assets/images/tiles/.gitkeep +0 -0
- package/project-template-v2/assets/images/ui/.gitkeep +0 -0
- package/project-template-v2/assets/images/vfx/.gitkeep +0 -0
- package/project-template-v2/assets/models/.gitkeep +0 -0
- package/project-template-v2/docs/harness/iteration-1/context-flow.md +0 -254
- package/project-template-v2/docs/harness/iteration-1/generate-flow.md +0 -91
- package/project-template-v2/docs/harness/iteration-1/ideate-flow.md +0 -214
- package/project-template-v2/docs/harness/iteration-1/optimize-flow.md +0 -75
- package/project-template-v2/docs/harness/iteration-1/wrapup-flow.md +0 -63
- package/project-template-v2/docs/harness/iteration-2/context-flow.md +0 -223
- package/project-template-v2/docs/harness/iteration-2/generate-flow.md +0 -129
- package/project-template-v2/docs/harness/iteration-2/ideate-flow.md +0 -267
- package/project-template-v2/docs/harness/iteration-2/optimize-flow.md +0 -164
- package/project-template-v2/docs/harness/iteration-2/wrapup-flow.md +0 -115
- package/project-template-v2/docs/harness/orchestrator-flow.md +0 -364
- package/project-template-v2/docs/project-state.json +0 -60
- package/project-template-v2/docs/project-state.md +0 -72
- package/project-template-v2/docs/standards/README.md +0 -225
- package/project-template-v2/docs/standards/agent-behavior-standards.md +0 -174
- package/project-template-v2/docs/standards/artifacts/design-brief.md +0 -19
- package/project-template-v2/docs/standards/artifacts/design.md +0 -22
- package/project-template-v2/docs/standards/artifacts/game-code.md +0 -41
- package/project-template-v2/docs/standards/artifacts/todo-list.md +0 -41
- package/project-template-v2/docs/standards/iter1-agent-behavior-standards.md +0 -343
- package/project-template-v2/game/index.ts +0 -18
- package/project-template-v2/globals.d.ts +0 -51
- package/project-template-v2/index.css +0 -34
- package/project-template-v2/index.html +0 -18
- package/project-template-v2/main.ts +0 -9
- package/project-template-v2/package.json +0 -46
- package/project-template-v2/skills/_shared/scripts/dispatch-clear.mjs +0 -31
- package/project-template-v2/skills/_shared/scripts/dispatch-set.mjs +0 -86
- package/project-template-v2/skills/_shared/scripts/dod-check.mjs +0 -153
- package/project-template-v2/skills/_shared/scripts/handoff-append.mjs +0 -70
- package/project-template-v2/skills/_shared/scripts/lib/validator-artifacts.mjs +0 -91
- package/project-template-v2/skills/_shared/scripts/pipeline/dod-config.mjs +0 -131
- package/project-template-v2/skills/_shared/scripts/pipeline/index.mjs +0 -80
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-1-core.mjs +0 -90
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-2-wrap.mjs +0 -93
- package/project-template-v2/skills/_shared/scripts/pipeline/iteration-3-visual.mjs +0 -23
- package/project-template-v2/skills/_shared/scripts/render-project-state.mjs +0 -230
- package/project-template-v2/skills/_shared/scripts/state-advance-stage.mjs +0 -58
- package/project-template-v2/skills/_shared/scripts/state-get.mjs +0 -339
- package/project-template-v2/skills/_shared/scripts/state-handoff.mjs +0 -39
- package/project-template-v2/skills/_shared/scripts/state-set.mjs +0 -94
- package/project-template-v2/skills/_shared/scripts/state-store.mjs +0 -783
- package/project-template-v2/skills/_shared/scripts/todo-add.mjs +0 -88
- package/project-template-v2/skills/_shared/scripts/todo-get.mjs +0 -130
- package/project-template-v2/skills/_shared/scripts/todo-remove.mjs +0 -33
- package/project-template-v2/skills/_shared/scripts/todo-set.mjs +0 -47
- package/project-template-v2/skills/_shared/scripts/verify-asset-code-sync.mjs +0 -268
- package/project-template-v2/skills/_shared/scripts/verify-env.mjs +0 -285
- package/project-template-v2/skills/_shared/scripts/verify-placeholders.mjs +0 -161
- package/project-template-v2/skills/playable-autoplay/SKILL.md +0 -176
- package/project-template-v2/skills/playable-autoplay/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/SKILL.md +0 -116
- package/project-template-v2/skills/playable-debug/agents/openai.yaml +0 -4
- package/project-template-v2/skills/playable-debug/references/debug-config.md +0 -40
- package/project-template-v2/skills/playable-record/SKILL.md +0 -140
- package/project-template-v2/skills/playable-record/scripts/lib/dev-server.mjs +0 -104
- package/project-template-v2/skills/playable-record/scripts/lib/record-audio-bridge.js +0 -141
- package/project-template-v2/skills/playable-record/scripts/record-playable.mjs +0 -425
- package/project-template-v2/skills/playable-record/scripts/verify-contract.mjs +0 -261
- package/project-template-v2/skills/playable-record/scripts/verify-firstwin.mjs +0 -398
- package/project-template-v2/skills/playable-record/scripts/verify-fusion.mjs +0 -322
- package/project-template-v2/skills/playable-record/scripts/verify-lifecycle.mjs +0 -105
- package/project-template-v2/skills/playable-record/scripts/verify-vlm-video.mjs +0 -230
- package/project-template-v2/skills/playable-validate/SKILL.md +0 -61
- package/project-template-v2/skills/playable-validate/validation-rules.md +0 -14
- package/project-template-v2/skills/playable-verify-ui/SKILL.md +0 -63
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/__init__.py +0 -1
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/config_expr.js +0 -218
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/node_utils.js +0 -51
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/profile_loader.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/report.py +0 -106
- package/project-template-v2/skills/playable-verify-ui/scripts/lib/runtime_contract.js +0 -116
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +0 -59
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +0 -64
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_runtime_contract.js +0 -58
- package/project-template-v2/skills/playable-verify-ui/scripts/verify_text_overlap.py +0 -333
- package/project-template-v2/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template-v2/tsconfig.json +0 -20
- package/project-template-v2/vite.config.ts +0 -27
- package/project-templates/batch-locks/phaser.yaml +0 -922
- package/project-templates/batch-locks/threejs.yaml +0 -978
- package/project-templates/empty/AGENTS.md +0 -23
- package/project-templates/empty/gitignore +0 -31
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { readFile, realpath, stat } from 'node:fs/promises';
|
|
2
|
-
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
3
|
-
import { DesktopPreviewError } from './desktop-preview.js';
|
|
4
|
-
import JSON5 from 'json5';
|
|
5
|
-
export async function previewPlan(root) {
|
|
6
|
-
const config = await optionalJson(root, '.playcraft/workspace.json');
|
|
7
|
-
const preview = object(config?.preview);
|
|
8
|
-
if (config?.preview !== undefined) {
|
|
9
|
-
if (!preview || typeof preview !== 'object' || typeof preview.command !== 'string' || !preview.command.trim()
|
|
10
|
-
|| (preview.cwd !== undefined && typeof preview.cwd !== 'string') || (preview.path !== undefined && typeof preview.path !== 'string')) {
|
|
11
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
12
|
-
}
|
|
13
|
-
const cwd = typeof preview.cwd === 'string' ? preview.cwd : '.';
|
|
14
|
-
if (isAbsolute(cwd))
|
|
15
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
16
|
-
let realRoot;
|
|
17
|
-
try {
|
|
18
|
-
realRoot = await realpath(join(root, cwd));
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
22
|
-
}
|
|
23
|
-
const resolved = relative(await realpath(root), realRoot);
|
|
24
|
-
if (resolved === '..' || resolved.startsWith(`..${sep}`) || !(await stat(realRoot)).isDirectory()) {
|
|
25
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
26
|
-
}
|
|
27
|
-
return { root: realRoot, command: preview.command, path: entryPath(preview.path), dependencies: await exists(join(realRoot, 'package.json')) };
|
|
28
|
-
}
|
|
29
|
-
const manifest = await optionalJson(root, 'package.json');
|
|
30
|
-
if (manifest) {
|
|
31
|
-
const dev = object(manifest.scripts)?.dev;
|
|
32
|
-
const vite = object(manifest.dependencies)?.vite ?? object(manifest.devDependencies)?.vite;
|
|
33
|
-
if (typeof dev === 'string' && /^\s*vite(?:\s|$)/.test(dev) && typeof vite === 'string') {
|
|
34
|
-
return { root, command: 'pnpm run dev --host 127.0.0.1 --port {port} --strictPort', path: '/', dependencies: true };
|
|
35
|
-
}
|
|
36
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
37
|
-
}
|
|
38
|
-
return { root, path: '/', dependencies: false, welcome: !await exists(join(root, 'index.html')) };
|
|
39
|
-
}
|
|
40
|
-
function entryPath(path) {
|
|
41
|
-
if (path === undefined)
|
|
42
|
-
return '/';
|
|
43
|
-
if (typeof path !== 'string' || !path.startsWith('/') || path.startsWith('//') || path.includes('\\')) {
|
|
44
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
45
|
-
}
|
|
46
|
-
const url = new URL(path, 'http://127.0.0.1');
|
|
47
|
-
if (url.origin !== 'http://127.0.0.1')
|
|
48
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
49
|
-
return `${url.pathname}${url.search}${url.hash}`;
|
|
50
|
-
}
|
|
51
|
-
async function exists(path) { try {
|
|
52
|
-
return (await stat(path)).isFile();
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return false;
|
|
56
|
-
} }
|
|
57
|
-
export async function requiresNodeModules(root) {
|
|
58
|
-
const manifest = await optionalJson(root, 'package.json');
|
|
59
|
-
return ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']
|
|
60
|
-
.some((key) => Object.keys(object(manifest?.[key]) ?? {}).length > 0);
|
|
61
|
-
}
|
|
62
|
-
async function optionalJson(root, path) {
|
|
63
|
-
try {
|
|
64
|
-
const resolved = await realpath(join(root, path));
|
|
65
|
-
const location = relative(await realpath(root), resolved);
|
|
66
|
-
if (location === '..' || location.startsWith(`..${sep}`))
|
|
67
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
68
|
-
const result = object(JSON5.parse(await readFile(resolved, 'utf8')));
|
|
69
|
-
if (!result)
|
|
70
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
71
|
-
return result;
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
if (error.code === 'ENOENT')
|
|
75
|
-
return undefined;
|
|
76
|
-
throw new DesktopPreviewError('desktop_preview_unconfigured');
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function object(value) {
|
|
80
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
81
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { createServer } from 'node:net';
|
|
3
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
-
import { startLauncherProcess } from '../codex/launcher-client.js';
|
|
5
|
-
import { DesktopPreviewError, quote } from './desktop-preview.js';
|
|
6
|
-
// The supervisor runs under the same developer identity as its service. It proves that the
|
|
7
|
-
// loopback listener belongs to its process group before Runtime probes the configured page.
|
|
8
|
-
const supervisor = String.raw `
|
|
9
|
-
const fs = require('node:fs');
|
|
10
|
-
const {spawn,execFileSync} = require('node:child_process');
|
|
11
|
-
const [command,portText,marker] = process.argv.slice(1);
|
|
12
|
-
const port = Number(portText);
|
|
13
|
-
const child = spawn(process.platform==='linux'?'/usr/bin/bash':'/bin/bash',['-lc',command],{stdio:['ignore','inherit','inherit']});
|
|
14
|
-
child.on('error',()=>process.exit(97));
|
|
15
|
-
child.on('exit',code=>process.exit(code === 0 ? 97 : code || 97));
|
|
16
|
-
function listeners() {
|
|
17
|
-
if (process.platform === 'linux') {
|
|
18
|
-
const sockets = ['tcp','tcp6'].flatMap(kind => fs.readFileSync('/proc/net/'+kind,'utf8').trim().split('\n').slice(1).flatMap(line=>{
|
|
19
|
-
const columns=line.trim().split(/\s+/); const [address,p]=columns[1].split(':');
|
|
20
|
-
return parseInt(p,16)===port && columns[3]==='0A' ? [{id:columns[9],local:address==='0100007F'||address==='00000000000000000000000001000000'}] : [];
|
|
21
|
-
}));
|
|
22
|
-
const owned=new Set();
|
|
23
|
-
for (const pid of fs.readdirSync('/proc').filter(name=>/^\d+$/.test(name))) {
|
|
24
|
-
try {
|
|
25
|
-
const stat=fs.readFileSync('/proc/'+pid+'/stat','utf8');
|
|
26
|
-
if (Number(stat.slice(stat.lastIndexOf(')')+2).split(' ')[2])!==process.pid) continue;
|
|
27
|
-
for (const fd of fs.readdirSync('/proc/'+pid+'/fd')) {
|
|
28
|
-
try { const match=fs.readlinkSync('/proc/'+pid+'/fd/'+fd).match(/^socket:\[(\d+)\]$/); if(match)owned.add(match[1]); } catch {}
|
|
29
|
-
}
|
|
30
|
-
} catch {}
|
|
31
|
-
}
|
|
32
|
-
return sockets.map(socket=>({local:socket.local,owned:owned.has(socket.id)}));
|
|
33
|
-
}
|
|
34
|
-
const pids=new Set(execFileSync('/bin/ps',['-axo','pid=,pgid='],{encoding:'utf8'}).trim().split('\n').flatMap(line=>{
|
|
35
|
-
const [pid,group]=line.trim().split(/\s+/); return Number(group)===process.pid?[Number(pid)]:[];
|
|
36
|
-
}));
|
|
37
|
-
let listing='';try{listing=execFileSync('/usr/sbin/lsof',['-nP','-iTCP:'+port,'-sTCP:LISTEN','-Fpn'],{encoding:'utf8',stdio:['ignore','pipe','ignore']});}catch{}
|
|
38
|
-
let pid=0;return listing.split('\n').flatMap(line=>{
|
|
39
|
-
if(line.startsWith('p'))pid=Number(line.slice(1));
|
|
40
|
-
return line.startsWith('n')?[{local:line.slice(1).startsWith('127.0.0.1:')||line.slice(1).startsWith('[::1]:'),owned:pids.has(pid)}]:[];
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
let ready=false;
|
|
44
|
-
const timer=setInterval(()=>{
|
|
45
|
-
try {
|
|
46
|
-
const found=listeners();
|
|
47
|
-
if(found.some(socket=>!socket.local||!socket.owned)){console.log(marker+':port');process.exit(98);}
|
|
48
|
-
if(!ready&&found.some(socket=>socket.owned)){ready=true;clearInterval(timer);console.log(marker+':ready');}
|
|
49
|
-
} catch {console.log(marker+':failed');process.exit(97);}
|
|
50
|
-
},50);
|
|
51
|
-
`;
|
|
52
|
-
export async function startManagedPreview(input) {
|
|
53
|
-
const { state, options } = input;
|
|
54
|
-
const socketPath = options.launcherSocket ?? process.env.PLAYCRAFT_LAUNCHER_SOCKET;
|
|
55
|
-
if (!socketPath)
|
|
56
|
-
throw new DesktopPreviewError('desktop_preview_start_failed');
|
|
57
|
-
const port = await reservePort();
|
|
58
|
-
const url = new URL(input.path, `http://127.0.0.1:${port}/`).href;
|
|
59
|
-
const marker = `PLAYCRAFT_PREVIEW_${randomUUID()}`;
|
|
60
|
-
const command = `export HOST=127.0.0.1 PORT=${port} PLAYCRAFT_PREVIEW_PORT=${port}; exec ${quote(process.execPath)} -e ${quote(supervisor)} -- ${quote(input.command.replaceAll('{port}', String(port)))} ${port} ${quote(marker)}`;
|
|
61
|
-
const processHandle = await launchPreviewProcess({ socketPath,
|
|
62
|
-
processId: `desktop-preview-service-${randomUUID()}`, runtimeEpoch: state.runtimeEpoch,
|
|
63
|
-
argv: ['/usr/bin/bash', '-lc', command], cwd: input.root, env: {} }, options, input.deadline, input.cancelled, 'desktop_preview_start_failed');
|
|
64
|
-
if (!processHandle)
|
|
65
|
-
return undefined;
|
|
66
|
-
let alive = true;
|
|
67
|
-
let stopped = false;
|
|
68
|
-
let stopping;
|
|
69
|
-
let owned = false;
|
|
70
|
-
let failure;
|
|
71
|
-
let tail = '';
|
|
72
|
-
processHandle.onExit((code) => { alive = false; failure ??= code === 98 ? 'desktop_preview_port_unavailable' : 'desktop_preview_start_failed'; });
|
|
73
|
-
processHandle.stdout?.on('data', (chunk) => {
|
|
74
|
-
tail = (tail + chunk.toString('utf8')).slice(-8192);
|
|
75
|
-
if (tail.includes(`${marker}:ready`))
|
|
76
|
-
owned = true;
|
|
77
|
-
if (tail.includes(`${marker}:port`))
|
|
78
|
-
failure = 'desktop_preview_port_unavailable';
|
|
79
|
-
if (tail.includes(`${marker}:failed`))
|
|
80
|
-
failure = 'desktop_preview_start_failed';
|
|
81
|
-
});
|
|
82
|
-
processHandle.stderr?.resume();
|
|
83
|
-
const stop = () => stopping ??= processHandle.stop().then(() => { stopped = true; alive = false; });
|
|
84
|
-
try {
|
|
85
|
-
while (Date.now() < input.deadline) {
|
|
86
|
-
if (input.cancelled()) {
|
|
87
|
-
await stop();
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
if (!alive || failure)
|
|
91
|
-
throw new DesktopPreviewError(failure ?? 'desktop_preview_start_failed');
|
|
92
|
-
if (owned) {
|
|
93
|
-
try {
|
|
94
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(Math.min(500, Math.max(1, input.deadline - Date.now()))) });
|
|
95
|
-
await response.body?.cancel();
|
|
96
|
-
if (response.ok && alive)
|
|
97
|
-
return { url, alive: () => alive && !stopped && !stopping, stop };
|
|
98
|
-
}
|
|
99
|
-
catch { }
|
|
100
|
-
}
|
|
101
|
-
await delay(options.pollIntervalMs ?? 100);
|
|
102
|
-
}
|
|
103
|
-
throw new DesktopPreviewError('desktop_preview_timeout');
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
await stop();
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/** Starting the launcher is bounded too; late replies are stopped after cancellation. */
|
|
111
|
-
export async function launchPreviewProcess(input, options, deadline, cancelled, failure) {
|
|
112
|
-
let finished = false;
|
|
113
|
-
let abandoned = false;
|
|
114
|
-
let failed = false;
|
|
115
|
-
let child;
|
|
116
|
-
void (options.startProcess ?? startLauncherProcess)(input).then((value) => {
|
|
117
|
-
child = value;
|
|
118
|
-
finished = true;
|
|
119
|
-
if (abandoned)
|
|
120
|
-
void value.stop().catch(() => undefined);
|
|
121
|
-
}, () => { finished = true; failed = true; });
|
|
122
|
-
while (!finished) {
|
|
123
|
-
if (cancelled()) {
|
|
124
|
-
abandoned = true;
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
if (Date.now() >= deadline) {
|
|
128
|
-
abandoned = true;
|
|
129
|
-
throw new DesktopPreviewError('desktop_preview_timeout');
|
|
130
|
-
}
|
|
131
|
-
await delay(Math.min(options.pollIntervalMs ?? 50, Math.max(1, deadline - Date.now())));
|
|
132
|
-
}
|
|
133
|
-
if (failed || !child)
|
|
134
|
-
throw new DesktopPreviewError(failure);
|
|
135
|
-
if (cancelled()) {
|
|
136
|
-
await child.stop();
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
-
return child;
|
|
140
|
-
}
|
|
141
|
-
async function reservePort() {
|
|
142
|
-
const server = createServer();
|
|
143
|
-
try {
|
|
144
|
-
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
|
|
145
|
-
const address = server.address();
|
|
146
|
-
if (!address || typeof address === 'string')
|
|
147
|
-
throw new Error('port_unavailable');
|
|
148
|
-
return address.port;
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
throw new DesktopPreviewError('desktop_preview_port_unavailable');
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
if (server.listening)
|
|
155
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import { readFile, realpath, stat } from 'node:fs/promises';
|
|
3
|
-
import { extname, join, relative, sep } from 'node:path';
|
|
4
|
-
const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8',
|
|
5
|
-
'.css': 'text/css; charset=utf-8', '.json': 'application/json', '.svg': 'image/svg+xml',
|
|
6
|
-
'.png': 'image/png', '.jpg': 'image/jpeg', '.webp': 'image/webp', '.wasm': 'application/wasm' };
|
|
7
|
-
export async function startStaticPreview(root, welcome = false) {
|
|
8
|
-
const realRoot = await realpath(root);
|
|
9
|
-
const server = createServer((request, response) => {
|
|
10
|
-
void (async () => {
|
|
11
|
-
try {
|
|
12
|
-
const pathname = decodeURIComponent(new URL(request.url ?? '/', 'http://localhost').pathname);
|
|
13
|
-
if (welcome && pathname === '/') {
|
|
14
|
-
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
|
|
15
|
-
response.end(welcomePage());
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const segments = pathname.split('/').filter(Boolean);
|
|
19
|
-
if (segments.some((segment) => segment.startsWith('.')))
|
|
20
|
-
throw new Error('hidden_path');
|
|
21
|
-
const target = await realpath(join(realRoot, ...segments, ...(pathname.endsWith('/') ? ['index.html'] : [])));
|
|
22
|
-
const path = relative(realRoot, target);
|
|
23
|
-
if (path === '..' || path.startsWith(`..${sep}`) || !(await stat(target)).isFile())
|
|
24
|
-
throw new Error('outside_root');
|
|
25
|
-
const body = await readFile(target);
|
|
26
|
-
response.writeHead(200, { 'content-type': types[extname(target)] ?? 'application/octet-stream', 'cache-control': 'no-store' });
|
|
27
|
-
response.end(request.method === 'HEAD' ? undefined : body);
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
response.writeHead(404);
|
|
31
|
-
response.end();
|
|
32
|
-
}
|
|
33
|
-
})();
|
|
34
|
-
});
|
|
35
|
-
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
|
|
36
|
-
const address = server.address();
|
|
37
|
-
if (!address || typeof address === 'string')
|
|
38
|
-
throw new Error('preview_port_unavailable');
|
|
39
|
-
let stopped = false;
|
|
40
|
-
return { url: `http://127.0.0.1:${address.port}/`, alive: () => server.listening && !stopped,
|
|
41
|
-
async stop() {
|
|
42
|
-
if (stopped)
|
|
43
|
-
return;
|
|
44
|
-
stopped = true;
|
|
45
|
-
server.closeAllConnections();
|
|
46
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
47
|
-
} };
|
|
48
|
-
}
|
|
49
|
-
function welcomePage() {
|
|
50
|
-
const messages = { en: { title: 'Your workspace desktop is ready', body: 'Add a project to see its preview here.' },
|
|
51
|
-
zh: { title: '工作区桌面已就绪', body: '添加项目后,这里会显示项目预览。' } };
|
|
52
|
-
return `<!doctype html><html lang="en"><meta charset="utf-8"><title>${messages.en.title}</title><style>body{margin:0;background:#f1f0eb;color:#252724;font:22px system-ui;display:grid;min-height:100vh;place-items:center}main{max-width:680px;padding:48px}h1{font-size:42px}p{line-height:1.7;color:#596159}</style><main><h1>${messages.en.title}</h1><p>${messages.en.body}</p><h2 lang="zh">${messages.zh.title}</h2><p lang="zh">${messages.zh.body}</p></main></html>`;
|
|
53
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { lstat } from 'node:fs/promises';
|
|
5
|
-
import { workspaceRoot, WorkspaceContextError } from '../workspaces/registry.js';
|
|
6
|
-
import { startStaticPreview } from './desktop-preview-static.js';
|
|
7
|
-
import { previewPlan, requiresNodeModules } from './desktop-preview-config.js';
|
|
8
|
-
import { launchPreviewProcess, startManagedPreview } from './desktop-preview-service.js';
|
|
9
|
-
import { planInstall, scheduleDependencyInstall, readDependencies } from '../workspaces/dependencies.js';
|
|
10
|
-
export class DesktopPreviewError extends Error {
|
|
11
|
-
code;
|
|
12
|
-
constructor(code) {
|
|
13
|
-
super(code);
|
|
14
|
-
this.code = code;
|
|
15
|
-
this.name = 'DesktopPreviewError';
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
/** One visible browser; project services and browser profiles survive workspace switches. */
|
|
19
|
-
export class DesktopPreviewHost {
|
|
20
|
-
state;
|
|
21
|
-
options;
|
|
22
|
-
pending = new Map();
|
|
23
|
-
servers = new Map();
|
|
24
|
-
emptyDependencyReady = new Map();
|
|
25
|
-
activeBrowser;
|
|
26
|
-
activation = Promise.resolve();
|
|
27
|
-
disposed = false;
|
|
28
|
-
disposal;
|
|
29
|
-
requestedKey;
|
|
30
|
-
constructor(state, options = {}) {
|
|
31
|
-
this.state = state;
|
|
32
|
-
this.options = options;
|
|
33
|
-
}
|
|
34
|
-
open(workspaceId, directoryName = 'basic') {
|
|
35
|
-
if (!this.canNavigate())
|
|
36
|
-
return Promise.resolve();
|
|
37
|
-
const key = workspaceId ? `${workspaceId}:${directoryName}` : 'single';
|
|
38
|
-
this.requestedKey = key;
|
|
39
|
-
const existing = this.pending.get(key);
|
|
40
|
-
if (existing)
|
|
41
|
-
return existing;
|
|
42
|
-
const task = this.openWorkspace(key, workspaceId, directoryName).catch((error) => {
|
|
43
|
-
if (error instanceof DesktopPreviewError || error instanceof WorkspaceContextError)
|
|
44
|
-
throw error;
|
|
45
|
-
throw new DesktopPreviewError('desktop_preview_start_failed');
|
|
46
|
-
}).finally(() => this.pending.delete(key));
|
|
47
|
-
this.pending.set(key, task);
|
|
48
|
-
return task;
|
|
49
|
-
}
|
|
50
|
-
dispose() {
|
|
51
|
-
if (this.disposal)
|
|
52
|
-
return this.disposal;
|
|
53
|
-
this.disposed = true;
|
|
54
|
-
this.disposal = (async () => {
|
|
55
|
-
const stopped = await Promise.allSettled([
|
|
56
|
-
...(this.activeBrowser ? [this.activeBrowser.process.stop()] : []),
|
|
57
|
-
...[...this.servers.values()].map(({ server }) => server.stop()),
|
|
58
|
-
]);
|
|
59
|
-
await Promise.allSettled([...this.pending.values()]);
|
|
60
|
-
this.servers.clear();
|
|
61
|
-
this.activeBrowser = undefined;
|
|
62
|
-
if (stopped.some((result) => result.status === 'rejected'))
|
|
63
|
-
throw new DesktopPreviewError('desktop_preview_start_failed');
|
|
64
|
-
})();
|
|
65
|
-
return this.disposal;
|
|
66
|
-
}
|
|
67
|
-
canNavigate() {
|
|
68
|
-
return !this.disposed && !this.state.journal.destroyed() && (this.options.canNavigate?.() ?? true);
|
|
69
|
-
}
|
|
70
|
-
async openWorkspace(key, workspaceId, directoryName = 'basic') {
|
|
71
|
-
const root = await workspaceRoot(this.state, workspaceId, directoryName);
|
|
72
|
-
const deadline = Date.now() + (this.options.timeoutMs ?? 90_000);
|
|
73
|
-
if (!this.canNavigate())
|
|
74
|
-
return;
|
|
75
|
-
const plan = await previewPlan(root);
|
|
76
|
-
if (!this.canNavigate())
|
|
77
|
-
return;
|
|
78
|
-
if (plan.dependencies) {
|
|
79
|
-
const dependencyDeadline = Math.min(deadline, Date.now() + (this.options.dependencyTimeoutMs ?? 60_000));
|
|
80
|
-
if (!await this.ensureDependencies(key, plan.root, dependencyDeadline))
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (!this.canNavigate())
|
|
84
|
-
return;
|
|
85
|
-
const signature = JSON.stringify(plan);
|
|
86
|
-
const entry = this.servers.get(key);
|
|
87
|
-
let server = entry?.server;
|
|
88
|
-
if (!server?.alive() || entry?.signature !== signature) {
|
|
89
|
-
await server?.stop();
|
|
90
|
-
if (!this.canNavigate())
|
|
91
|
-
return;
|
|
92
|
-
server = plan.command ? await startManagedPreview({ state: this.state, options: this.options,
|
|
93
|
-
root: plan.root, command: plan.command, path: plan.path, deadline, cancelled: () => !this.canNavigate() }) : await startStaticPreview(root, plan.welcome);
|
|
94
|
-
if (!server)
|
|
95
|
-
return;
|
|
96
|
-
if (!this.canNavigate()) {
|
|
97
|
-
await server.stop();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
this.servers.set(key, { server, signature });
|
|
101
|
-
}
|
|
102
|
-
const current = this.activation.then(() => this.activate(key, root, server, deadline));
|
|
103
|
-
this.activation = current.catch(() => undefined);
|
|
104
|
-
await current;
|
|
105
|
-
}
|
|
106
|
-
async ensureDependencies(key, root, deadline) {
|
|
107
|
-
const initialPlan = await planInstall(root);
|
|
108
|
-
if (initialPlan.kind === 'install' && !await requiresNodeModules(root)
|
|
109
|
-
&& this.emptyDependencyReady.get(root) === initialPlan.lockHash)
|
|
110
|
-
return this.canNavigate();
|
|
111
|
-
let finished = false;
|
|
112
|
-
const install = () => {
|
|
113
|
-
finished = false;
|
|
114
|
-
void scheduleDependencyInstall(this.state, key, root, () => this.canNavigate()).finally(() => { finished = true; });
|
|
115
|
-
};
|
|
116
|
-
if (!this.canNavigate())
|
|
117
|
-
return false;
|
|
118
|
-
install();
|
|
119
|
-
while (Date.now() < deadline) {
|
|
120
|
-
if (!this.canNavigate())
|
|
121
|
-
return false;
|
|
122
|
-
if (finished) {
|
|
123
|
-
const current = await planInstall(root);
|
|
124
|
-
if (!this.canNavigate())
|
|
125
|
-
return false;
|
|
126
|
-
const record = readDependencies(this.state, key);
|
|
127
|
-
if (record.status === 'failed')
|
|
128
|
-
throw new DesktopPreviewError('dependencies_not_ready');
|
|
129
|
-
if (current.kind === 'skip' && record.status === 'none')
|
|
130
|
-
return true;
|
|
131
|
-
if (current.kind === 'install' && record.status === 'ready' && !await requiresNodeModules(root)) {
|
|
132
|
-
this.emptyDependencyReady.set(root, current.lockHash);
|
|
133
|
-
return this.canNavigate();
|
|
134
|
-
}
|
|
135
|
-
if (current.kind === 'install' && record.lockHash === current.lockHash) {
|
|
136
|
-
const modules = await lstat(join(root, 'node_modules')).catch(() => undefined);
|
|
137
|
-
if (record.status === 'ready' && modules?.isDirectory() && !modules.isSymbolicLink())
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
// A changed manifest or lockfile needs a current install before this project can start.
|
|
141
|
-
if (!this.canNavigate())
|
|
142
|
-
return false;
|
|
143
|
-
install();
|
|
144
|
-
}
|
|
145
|
-
await delay(this.options.pollIntervalMs ?? 100);
|
|
146
|
-
}
|
|
147
|
-
if (!this.canNavigate())
|
|
148
|
-
return false;
|
|
149
|
-
throw new DesktopPreviewError('dependencies_not_ready');
|
|
150
|
-
}
|
|
151
|
-
async activate(key, root, server, deadline) {
|
|
152
|
-
const canActivate = () => this.canNavigate() && this.requestedKey === key;
|
|
153
|
-
if (!canActivate())
|
|
154
|
-
return;
|
|
155
|
-
if (this.activeBrowser?.alive && this.activeBrowser.key === key && this.activeBrowser.server === server)
|
|
156
|
-
return;
|
|
157
|
-
if (Date.now() >= deadline)
|
|
158
|
-
throw new DesktopPreviewError('desktop_preview_timeout');
|
|
159
|
-
const socketPath = this.options.launcherSocket ?? process.env.PLAYCRAFT_LAUNCHER_SOCKET;
|
|
160
|
-
if (!socketPath)
|
|
161
|
-
throw new DesktopPreviewError('desktop_browser_unavailable');
|
|
162
|
-
if (this.activeBrowser)
|
|
163
|
-
await this.activeBrowser.process.stop();
|
|
164
|
-
this.activeBrowser = undefined;
|
|
165
|
-
if (!canActivate())
|
|
166
|
-
return;
|
|
167
|
-
const profile = join(this.options.profileRoot ?? '/home/playcraft-dev/.cache/playcraft-desktop', key);
|
|
168
|
-
const executable = this.options.browserExecutable ?? '/usr/bin/chromium';
|
|
169
|
-
const command = `test -x ${quote(executable)} || exit 127; mkdir -p -- ${quote(profile)} || exit 1; exec ${quote(executable)} --no-first-run --no-default-browser-check --disable-dev-shm-usage --start-maximized --user-data-dir=${quote(profile)} --app=${quote(server.url)}`;
|
|
170
|
-
const child = await launchPreviewProcess({ socketPath,
|
|
171
|
-
processId: `desktop-preview-browser-${randomUUID()}`, runtimeEpoch: this.state.runtimeEpoch,
|
|
172
|
-
argv: ['/usr/bin/bash', '-lc', command], cwd: root, env: { DISPLAY: process.env.DISPLAY ?? ':0' } }, this.options, deadline, () => !canActivate(), 'desktop_browser_start_failed');
|
|
173
|
-
if (!child)
|
|
174
|
-
return;
|
|
175
|
-
const browser = { key, server, process: child, alive: true };
|
|
176
|
-
let exitCode;
|
|
177
|
-
child.onExit((code) => { browser.alive = false; exitCode = code; });
|
|
178
|
-
child.stdout?.resume();
|
|
179
|
-
child.stderr?.resume();
|
|
180
|
-
this.activeBrowser = browser;
|
|
181
|
-
await delay(150);
|
|
182
|
-
if (!browser.alive) {
|
|
183
|
-
await child.stop();
|
|
184
|
-
this.activeBrowser = undefined;
|
|
185
|
-
throw new DesktopPreviewError(exitCode === 127 ? 'desktop_browser_unavailable' : 'desktop_browser_start_failed');
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
export function quote(value) { return `'${value.replaceAll("'", "'\\''")}'`; }
|