@gentbajko/slopify 2.4.0 → 3.0.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/LICENSE +201 -21
- package/NOTICE +12 -0
- package/README.md +21 -40
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +11 -1
- package/dist/adapters/alignment/cache.js +4 -1
- package/dist/adapters/alignment/ctc.js +25 -23
- package/dist/adapters/alignment/index.js +37 -6
- package/dist/adapters/alignment/multilingual.js +267 -0
- package/dist/adapters/alignment/protocol.js +11 -0
- package/dist/adapters/alignment/quality.js +4 -3
- package/dist/adapters/alignment/sentences.js +95 -0
- package/dist/adapters/alignment/spec.js +24 -0
- package/dist/adapters/alignment/spell.js +769 -0
- package/dist/adapters/alignment/text.js +56 -9
- package/dist/adapters/alignment/window.js +34 -30
- package/dist/adapters/alignment/worker.js +23 -21
- package/dist/adapters/explain.js +11 -11
- package/dist/adapters/host-cli/index.js +55 -5
- package/dist/adapters/host-cli/transport.js +13 -6
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +60 -18
- package/dist/adapters/image/codex.js +166 -25
- package/dist/adapters/image/fal.js +31 -3
- package/dist/adapters/image/google.js +17 -2
- package/dist/adapters/image/openai.js +45 -16
- package/dist/adapters/image/reference.js +41 -0
- package/dist/adapters/image/replicate.js +14 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +36 -0
- package/dist/adapters/llm/claude-code.js +168 -32
- package/dist/adapters/llm/cli-login-error.js +1 -1
- package/dist/adapters/llm/codex-limits.js +157 -0
- package/dist/adapters/llm/codex-models.js +2 -1
- package/dist/adapters/llm/codex-server-models.js +2 -1
- package/dist/adapters/llm/codex.js +99 -12
- package/dist/adapters/llm/document-workspace.js +2 -2
- package/dist/adapters/llm/gemini-workspace.js +1 -1
- package/dist/adapters/llm/gemini.js +16 -3
- package/dist/adapters/llm/image-workspace.js +58 -0
- package/dist/adapters/llm/openrouter.js +11 -3
- package/dist/adapters/llm/run-cli.js +26 -9
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/inworld-async.js +9 -5
- package/dist/adapters/tts/inworld.js +17 -3
- package/dist/adapters/tts/openai.js +3 -0
- package/dist/adapters/tts/voice-languages.js +80 -0
- package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
- package/dist/assets/fonts/noto/OFL.txt +93 -0
- package/dist/assets/fonts/noto/SOURCE.txt +18 -0
- package/dist/assets/models.yaml +7 -0
- package/dist/assets/sample/sample-audiobook.tar +0 -0
- package/dist/assets/sample/sample-podcast.tar +0 -0
- package/dist/assets/sample/sample-project.tar +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +33 -12
- package/dist/catalog/runtime-models.js +1 -1
- package/dist/catalog/schema.js +12 -1
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +17 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +50 -0
- package/dist/edge/autostart/index.js +78 -0
- package/dist/edge/autostart/launcher.js +176 -0
- package/dist/edge/autostart/model.js +3 -0
- package/dist/edge/autostart/native.js +210 -0
- package/dist/edge/autostart/prompt.js +66 -0
- package/dist/edge/autostart/service.js +81 -0
- package/dist/edge/cli.js +57 -51
- package/dist/edge/docker-install/activation.js +56 -0
- package/dist/edge/docker-install/apply.js +458 -0
- package/dist/edge/docker-install/engine.js +362 -0
- package/dist/edge/docker-install/run.js +154 -0
- package/dist/edge/docker-install/state.js +146 -0
- package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
- package/dist/edge/docker.js +14 -22
- package/dist/edge/events/hub.js +11 -0
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +32 -10
- package/dist/edge/http/app.js +60 -2
- package/dist/edge/http/auditions.js +70 -0
- package/dist/edge/http/autostart.js +37 -0
- package/dist/edge/http/backups.js +40 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +1 -1
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +45 -6
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +152 -0
- package/dist/edge/http/open-folder.js +1 -1
- package/dist/edge/http/patch-notes.js +45 -0
- package/dist/edge/http/planning.js +4 -2
- package/dist/edge/http/problem.js +1 -0
- package/dist/edge/http/project-create.js +87 -0
- package/dist/edge/http/projects.js +31 -99
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +86 -0
- package/dist/edge/http/reviews.js +121 -0
- package/dist/edge/http/revision-files.js +1 -1
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +111 -6
- package/dist/edge/http/settings.js +128 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +29 -2
- package/dist/edge/http/studio.js +207 -0
- package/dist/edge/http/style-preview.js +100 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/update.js +24 -3
- package/dist/edge/http/whats-new.js +15 -0
- package/dist/edge/http/youtube-edits.js +83 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +12 -3
- package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
- package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
- package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
- package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
- package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
- package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
- package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
- package/dist/kernel/db/migrations/0032-channels.sql +65 -0
- package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
- package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
- package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
- package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
- package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
- package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +139 -16
- package/dist/kernel/ports/image.js +3 -1
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/llm.js +3 -1
- package/dist/kernel/ports/model.js +5 -2
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/runner/attempt.js +18 -9
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +24 -5
- package/dist/kernel/runner/index.js +35 -5
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +164 -9
- package/dist/kernel/runner/retry-policy.js +43 -0
- package/dist/kernel/runner/standalone.js +134 -0
- package/dist/kernel/runner/work-authority.js +28 -2
- package/dist/main.js +321 -20
- package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
- package/dist/patch-notes/1.0-to-2.0.md +74 -0
- package/dist/patch-notes/2.0-to-3.0.md +257 -0
- package/dist/patch-notes/3.0.0.md +70 -0
- package/dist/patch-notes/index.json +26 -0
- package/dist/slices/admission/model.js +35 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +126 -1
- package/dist/slices/admission/schema.js +68 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +90 -16
- package/dist/slices/article/continuation.js +2 -2
- package/dist/slices/article/provided-entries.js +2 -2
- package/dist/slices/article/run.js +6 -2
- package/dist/slices/article/segments.js +3 -3
- package/dist/slices/backups/files.js +135 -0
- package/dist/slices/backups/folder.js +43 -0
- package/dist/slices/backups/model.js +65 -0
- package/dist/slices/backups/repo.js +28 -0
- package/dist/slices/backups/schedule.js +60 -0
- package/dist/slices/backups/service.js +311 -0
- package/dist/slices/batch/index.js +9 -2
- package/dist/slices/cancel/index.js +1 -1
- package/dist/slices/channels/cast-images.js +110 -0
- package/dist/slices/channels/cast-match.js +32 -0
- package/dist/slices/channels/images.js +19 -0
- package/dist/slices/channels/model.js +5 -0
- package/dist/slices/channels/rebrand.js +100 -0
- package/dist/slices/channels/repo.js +218 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +129 -0
- package/dist/slices/channels/service.js +180 -0
- package/dist/slices/channels/studio-csv.js +110 -0
- package/dist/slices/channels/videos.js +120 -0
- package/dist/slices/checkpoints/recovery.js +10 -0
- package/dist/slices/control/index.js +2 -1
- package/dist/slices/document/fonts.js +1 -1
- package/dist/slices/document/render.js +1 -1
- package/dist/slices/episodes/related.js +90 -0
- package/dist/slices/episodes/repo.js +60 -0
- package/dist/slices/episodes/service.js +58 -0
- package/dist/slices/episodes/summarize.js +170 -0
- package/dist/slices/estimate/index.js +113 -24
- package/dist/slices/estimate/requests.js +60 -2
- package/dist/slices/fixes/rules.js +73 -0
- package/dist/slices/fonts/coverage.js +82 -0
- package/dist/slices/fonts/sfnt.js +74 -0
- package/dist/slices/images/run.js +8 -5
- package/dist/slices/images/scale.js +84 -0
- package/dist/slices/library/diff.js +122 -0
- package/dist/slices/library/history.js +100 -0
- package/dist/slices/library/model.js +3 -0
- package/dist/slices/library/photorealistic.js +49 -0
- package/dist/slices/library/repo.js +19 -14
- package/dist/slices/library/save.js +62 -12
- package/dist/slices/library/slots.js +15 -3
- package/dist/slices/library/snapshot.js +8 -2
- package/dist/slices/library/used-by.js +153 -0
- package/dist/slices/loudness/level-pieces.js +41 -0
- package/dist/slices/loudness/loudnorm.js +164 -0
- package/dist/slices/loudness/model.js +153 -0
- package/dist/slices/model-upkeep/model.js +10 -0
- package/dist/slices/model-upkeep/switch.js +217 -0
- package/dist/slices/model-upkeep/usage.js +183 -0
- package/dist/slices/narration/aliases-library.js +31 -0
- package/dist/slices/narration/aliases-schema.js +53 -0
- package/dist/slices/narration/aliases.js +49 -0
- package/dist/slices/narration/blocks.js +352 -0
- package/dist/slices/narration/chunk.js +19 -7
- package/dist/slices/narration/concat.js +2 -2
- package/dist/slices/narration/describe.js +45 -0
- package/dist/slices/narration/pauses-model.js +63 -0
- package/dist/slices/narration/pauses.js +269 -0
- package/dist/slices/narration/peaks-model.js +1 -0
- package/dist/slices/narration/peaks.js +94 -0
- package/dist/slices/narration/preparation.js +21 -3
- package/dist/slices/narration/pronunciation-chunks.js +3 -3
- package/dist/slices/narration/pronunciation.js +72 -25
- package/dist/slices/narration/run.js +6 -6
- package/dist/slices/narration/shared-glossary.js +1 -1
- package/dist/slices/narration/steering.js +15 -4
- package/dist/slices/notifications/notifier.js +94 -0
- package/dist/slices/notifications/rules.js +102 -0
- package/dist/slices/notifications/send.js +40 -0
- package/dist/slices/notifications/settings.js +34 -0
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +46 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +64 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +129 -0
- package/dist/slices/onboarding/sample.js +132 -0
- package/dist/slices/onboarding/state.js +68 -0
- package/dist/slices/onboarding/template.js +73 -0
- package/dist/slices/patch-notes/library.js +73 -0
- package/dist/slices/patch-notes/seen.js +77 -0
- package/dist/slices/play-drafts/convert.js +160 -9
- package/dist/slices/play-drafts/readiness.js +3 -0
- package/dist/slices/play-drafts/repo.js +9 -1
- package/dist/slices/play-drafts/review-inputs.js +22 -3
- package/dist/slices/play-drafts/schema.js +109 -2
- package/dist/slices/play-drafts/service.js +10 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/play-drafts/uploads.js +2 -1
- package/dist/slices/project-templates/from-project.js +70 -4
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +29 -2
- package/dist/slices/rebuild/admission-repo.js +1 -1
- package/dist/slices/rebuild/model.js +6 -0
- package/dist/slices/rebuild/preview-details.js +67 -2
- package/dist/slices/rebuild/preview-plan.js +13 -6
- package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
- package/dist/slices/rebuild/recipe-audio.js +66 -12
- package/dist/slices/rebuild/recipe-build.js +15 -5
- package/dist/slices/rebuild/recipe-cast.js +18 -0
- package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
- package/dist/slices/rebuild/recipe-describe.js +93 -0
- package/dist/slices/rebuild/recipe-edit.js +13 -1
- package/dist/slices/rebuild/recipe-exports.js +62 -8
- package/dist/slices/rebuild/recipe-input-schema.js +43 -1
- package/dist/slices/rebuild/recipe-legacy.js +12 -0
- package/dist/slices/rebuild/recipe-loudness.js +45 -0
- package/dist/slices/rebuild/recipe-model.js +25 -6
- package/dist/slices/rebuild/recipe-pauses.js +7 -0
- package/dist/slices/rebuild/recipe-preparation.js +3 -3
- package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
- package/dist/slices/rebuild/recipe-reference.js +58 -0
- package/dist/slices/rebuild/recipe-reviews.js +117 -0
- package/dist/slices/rebuild/recipe-save.js +4 -1
- package/dist/slices/rebuild/recipe-shorts.js +32 -5
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +29 -3
- package/dist/slices/rebuild/recipe-visual.js +104 -29
- package/dist/slices/rebuild/recipe-voices.js +286 -0
- package/dist/slices/rebuild/recipe-work.js +77 -6
- package/dist/slices/rebuild/recipe-youtube.js +2 -0
- package/dist/slices/rebuild/recovery-conflict.js +7 -3
- package/dist/slices/rebuild/recovery-model.js +3 -0
- package/dist/slices/rebuild/recovery-selection.js +28 -2
- package/dist/slices/rebuild/recovery.js +35 -17
- package/dist/slices/rebuild/repo.js +3 -3
- package/dist/slices/rebuild/retry.js +18 -0
- package/dist/slices/rebuild/review-redo.js +110 -0
- package/dist/slices/rebuild/runtime-actions.js +14 -2
- package/dist/slices/rebuild/runtime-admission.js +4 -1
- package/dist/slices/rebuild/runtime-animate.js +4 -4
- package/dist/slices/rebuild/runtime-article.js +5 -5
- package/dist/slices/rebuild/runtime-document.js +4 -4
- package/dist/slices/rebuild/runtime-export-bed.js +43 -0
- package/dist/slices/rebuild/runtime-export-edit.js +70 -11
- package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
- package/dist/slices/rebuild/runtime-export-short.js +85 -0
- package/dist/slices/rebuild/runtime-export.js +84 -19
- package/dist/slices/rebuild/runtime-figure-card.js +87 -0
- package/dist/slices/rebuild/runtime-image.js +69 -0
- package/dist/slices/rebuild/runtime-local.js +154 -27
- package/dist/slices/rebuild/runtime-materialize.js +8 -0
- package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
- package/dist/slices/rebuild/runtime-narration-text.js +7 -7
- package/dist/slices/rebuild/runtime-pictures.js +28 -0
- package/dist/slices/rebuild/runtime-piece-label.js +29 -0
- package/dist/slices/rebuild/runtime-provider.js +134 -19
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +237 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +48 -23
- package/dist/slices/rebuild/runtime-store.js +39 -10
- package/dist/slices/rebuild/runtime-subtitles.js +72 -19
- package/dist/slices/rebuild/runtime-voices.js +103 -0
- package/dist/slices/rebuild/runtime-youtube.js +7 -6
- package/dist/slices/rebuild/service-readiness.js +15 -1
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +10 -3
- package/dist/slices/research/run.js +4 -4
- package/dist/slices/research/synthesis.js +2 -2
- package/dist/slices/reviews/model.js +105 -0
- package/dist/slices/reviews/outcome.js +22 -0
- package/dist/slices/reviews/repo.js +106 -0
- package/dist/slices/reviews/rules.js +63 -0
- package/dist/slices/reviews/verdict.js +71 -0
- package/dist/slices/reviews/view.js +30 -0
- package/dist/slices/revisions/adopt-content.js +12 -2
- package/dist/slices/revisions/adopt.js +9 -2
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +43 -9
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-prepare.js +8 -10
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +16 -2
- package/dist/slices/run-cost/limits.js +128 -0
- package/dist/slices/run-cost/meter.js +47 -0
- package/dist/slices/run-cost/panel.js +234 -0
- package/dist/slices/run-cost/pricing.js +92 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +158 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +67 -16
- package/dist/slices/schedules/schema.js +138 -1
- package/dist/slices/schedules/service.js +48 -4
- package/dist/slices/schedules/similar.js +90 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +422 -0
- package/dist/slices/settings/first-run.js +102 -0
- package/dist/slices/settings/health.js +190 -0
- package/dist/slices/settings/key-guides.js +128 -0
- package/dist/slices/settings/key-test.js +85 -0
- package/dist/slices/settings/model.js +11 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/voices.js +64 -4
- package/dist/slices/settings/whats-new.js +64 -0
- package/dist/slices/shorts/captions.js +9 -0
- package/dist/slices/shorts/pick.js +3 -3
- package/dist/slices/shorts/prompts.js +1 -1
- package/dist/slices/shorts/render.js +54 -5
- package/dist/slices/storage/asset-name.js +3 -0
- package/dist/slices/storage/backup-export.js +48 -9
- package/dist/slices/storage/backup-format.js +38 -1
- package/dist/slices/storage/backup-import.js +191 -19
- package/dist/slices/storage/delete-project.js +7 -4
- package/dist/slices/storage/documents.js +79 -0
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +20 -0
- package/dist/slices/storage/model.js +14 -0
- package/dist/slices/storage/portable.js +95 -12
- package/dist/slices/storage/reconcile.js +19 -2
- package/dist/slices/storage/repo.js +4 -1
- package/dist/slices/storage/schema.js +11 -0
- package/dist/slices/storage/trim.js +146 -0
- package/dist/slices/studio/disclosure.js +74 -0
- package/dist/slices/studio/model.js +28 -0
- package/dist/slices/studio/pack.js +209 -0
- package/dist/slices/studio/settings.js +133 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/render.js +161 -0
- package/dist/slices/style-preview/schema.js +45 -0
- package/dist/slices/style-preview/service.js +93 -0
- package/dist/slices/style-preview/settings.js +89 -0
- package/dist/slices/subtitles/captions.js +42 -9
- package/dist/slices/subtitles/model.js +22 -0
- package/dist/slices/subtitles/prepare.js +27 -16
- package/dist/slices/subtitles/transcript.js +1 -1
- package/dist/slices/thumbnail/run.js +7 -7
- package/dist/slices/trash/model.js +25 -0
- package/dist/slices/trash/service.js +273 -0
- package/dist/slices/uploads/repo.js +20 -0
- package/dist/slices/video/ambient-bed-schema.js +15 -0
- package/dist/slices/video/ambient-bed.js +95 -0
- package/dist/slices/video/ambient-mix.js +72 -0
- package/dist/slices/video/audio-inputs.js +2 -2
- package/dist/slices/video/cards.js +5 -2
- package/dist/slices/video/edit-list.js +23 -2
- package/dist/slices/video/edit-settings.js +13 -3
- package/dist/slices/video/ffmpeg.js +63 -9
- package/dist/slices/video/figure-card.js +492 -0
- package/dist/slices/video/figure-spans.js +51 -0
- package/dist/slices/video/plan.js +125 -13
- package/dist/slices/video/run.js +14 -12
- package/dist/slices/video/slideshow.js +39 -4
- package/dist/slices/voices/attribution.js +69 -0
- package/dist/slices/voices/audio-files.js +82 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +56 -0
- package/dist/slices/voices/grouping.js +49 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +179 -0
- package/dist/slices/voices/palette.js +26 -0
- package/dist/slices/voices/panel.js +91 -0
- package/dist/slices/voices/portraits.js +55 -0
- package/dist/slices/voices/script.js +136 -0
- package/dist/slices/voices/timing.js +58 -0
- package/dist/slices/youtube/answer.js +1 -1
- package/dist/slices/youtube/chapters.js +126 -0
- package/dist/slices/youtube/edits-repo.js +69 -0
- package/dist/slices/youtube/edits.js +89 -0
- package/dist/slices/youtube/placeholders.js +98 -0
- package/dist/slices/youtube/transcript.js +11 -4
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-CUA2sUxU.css +1 -0
- package/dist/web/assets/index-CX590kE-.js +303 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -4
- package/dist/edge/docker-launch.js +0 -81
- package/dist/edge/docker-projects/activation.js +0 -35
- package/dist/edge/docker-projects/claims.js +0 -85
- package/dist/edge/docker-projects/committed.js +0 -40
- package/dist/edge/docker-projects/engine.js +0 -461
- package/dist/edge/docker-projects/install.js +0 -254
- package/dist/edge/docker-projects/prune.js +0 -66
- package/dist/edge/docker-projects/recover.js +0 -100
- package/dist/edge/docker-projects/state.js +0 -225
- package/dist/web/assets/index-BGgYMm2u.css +0 -1
- package/dist/web/assets/index-BVVgtntL.js +0 -171
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
import { lstat, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { containerSchema, digestSchema, } from "./state.js";
|
|
6
|
-
import { contains } from "./tree.js";
|
|
7
|
-
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
8
|
-
// A recovery volume says which update, installation and container made it. Earlier launchers
|
|
9
|
-
// set only the transaction label; `prune.ts` asks those for the update's own record instead.
|
|
10
|
-
export function recoveryLabels(j) {
|
|
11
|
-
return {
|
|
12
|
-
"io.slopify.transaction": j.id,
|
|
13
|
-
"io.slopify.installation": j.installation,
|
|
14
|
-
"io.slopify.container": j.name,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
// The id a recovery volume of `volume` carries in its name, or null for any other volume.
|
|
18
|
-
export function recoveryId(volume, name) {
|
|
19
|
-
const prefix = `${volume}-recovery-`;
|
|
20
|
-
if (!name.startsWith(prefix))
|
|
21
|
-
return null;
|
|
22
|
-
const id = name.slice(prefix.length);
|
|
23
|
-
return uuid.test(id) ? id : null;
|
|
24
|
-
}
|
|
25
|
-
// Docker's exit code alone says nothing, so its last error line decides the advice: the daemon
|
|
26
|
-
// being stopped and the socket being off-limits are by far the most common first-run failures.
|
|
27
|
-
export function dockerFailure(args, result) {
|
|
28
|
-
const detail = (result.stderr ?? "").trim();
|
|
29
|
-
if (/permission denied[^\n]*docker\.sock|docker\.sock[^\n]*permission denied/i.test(detail))
|
|
30
|
-
return "Your user isn't allowed to use Docker. Give it access with sudo usermod -aG docker $USER, then log out and back in (or run newgrp docker) and try again.";
|
|
31
|
-
if (/cannot connect to the docker daemon|is the docker daemon running|error during connect/i.test(detail))
|
|
32
|
-
return "Docker is installed but not running. Start it (sudo systemctl start docker, or systemctl --user start docker for rootless Docker) and try again.";
|
|
33
|
-
if (/port is already allocated|address already in use/i.test(detail))
|
|
34
|
-
return "The port Slopify wants is already in use by another program. Stop that program, or start Slopify on another port: npx @gentbajko/slopify --docker --port 7070";
|
|
35
|
-
if (/no space left on device/i.test(detail))
|
|
36
|
-
return "The disk Docker uses is full. Free up space (for example docker system prune) and try again.";
|
|
37
|
-
const last = detail.split("\n").at(-1)?.trim().slice(0, 300);
|
|
38
|
-
return `Docker command "docker ${args[0] ?? ""}" failed (exit code ${result.code}${last ? `: ${last}` : ""}). Check that Docker works (docker info) and run the launcher again. Nothing was deleted, and any backup Slopify made is kept.`;
|
|
39
|
-
}
|
|
40
|
-
export function dockerEngine(runner, signal, env) {
|
|
41
|
-
const run = (args, localSignal = signal) => runner.exec("docker", args, localSignal);
|
|
42
|
-
async function command(args, localSignal = signal) {
|
|
43
|
-
const r = await run(args, localSignal);
|
|
44
|
-
if (r.code !== 0)
|
|
45
|
-
throw new Error(dockerFailure(args, r));
|
|
46
|
-
return r.stdout.trim();
|
|
47
|
-
}
|
|
48
|
-
const mount = (type, source, target, readonly = false) => [
|
|
49
|
-
"--mount",
|
|
50
|
-
`type=${type},source=${source},target=${target}${readonly ? ",readonly" : ""}${type === "volume" ? ",volume-nocopy" : ""}`,
|
|
51
|
-
];
|
|
52
|
-
const source = (volume, bind) => [
|
|
53
|
-
...mount("volume", volume, "/source", true),
|
|
54
|
-
...(bind === null ? [] : mount("bind", bind, "/source/projects", true)),
|
|
55
|
-
];
|
|
56
|
-
const helper = async (image, args, operation, user = "0:0") => JSON.parse(await command([
|
|
57
|
-
"run",
|
|
58
|
-
"--rm",
|
|
59
|
-
"--network",
|
|
60
|
-
"none",
|
|
61
|
-
"--read-only",
|
|
62
|
-
"--user",
|
|
63
|
-
operation === "probe" ? user : "0:0",
|
|
64
|
-
...(args.some((a) => a.includes("target=/data,")) ? [] : ["--tmpfs", "/data"]),
|
|
65
|
-
...args,
|
|
66
|
-
"--entrypoint",
|
|
67
|
-
"node",
|
|
68
|
-
image,
|
|
69
|
-
"/opt/slopify/packages/app/dist/edge/docker-projects/volume.js",
|
|
70
|
-
operation,
|
|
71
|
-
user,
|
|
72
|
-
]));
|
|
73
|
-
async function inspect(name) {
|
|
74
|
-
const present = await run([
|
|
75
|
-
"container",
|
|
76
|
-
"ls",
|
|
77
|
-
"-a",
|
|
78
|
-
"--filter",
|
|
79
|
-
`name=^/${name}$`,
|
|
80
|
-
"--format",
|
|
81
|
-
"{{.ID}}",
|
|
82
|
-
]);
|
|
83
|
-
if (present.code !== 0)
|
|
84
|
-
throw new Error(dockerFailure(["container", "ls"], present));
|
|
85
|
-
if (!present.stdout.trim() && !/^[a-f0-9]{12,64}$/.test(name))
|
|
86
|
-
return null;
|
|
87
|
-
const result = await run(["inspect", name]);
|
|
88
|
-
if (result.code !== 0) {
|
|
89
|
-
if (!present.stdout.trim())
|
|
90
|
-
return null;
|
|
91
|
-
throw new Error(dockerFailure(["inspect", name], result));
|
|
92
|
-
}
|
|
93
|
-
const schema = z.array(z.object({
|
|
94
|
-
Id: z.string(),
|
|
95
|
-
Name: z.string(),
|
|
96
|
-
Image: z.string(),
|
|
97
|
-
Config: z.object({ User: z.string(), Labels: z.record(z.string(), z.string()).nullable() }),
|
|
98
|
-
State: z.object({ Running: z.boolean() }),
|
|
99
|
-
HostConfig: z.object({
|
|
100
|
-
RestartPolicy: z.object({ Name: z.string(), MaximumRetryCount: z.number() }),
|
|
101
|
-
}),
|
|
102
|
-
Mounts: z.array(z.object({
|
|
103
|
-
Type: z.string(),
|
|
104
|
-
Name: z.string().optional(),
|
|
105
|
-
Source: z.string(),
|
|
106
|
-
Destination: z.string(),
|
|
107
|
-
RW: z.boolean(),
|
|
108
|
-
})),
|
|
109
|
-
NetworkSettings: z.object({
|
|
110
|
-
Ports: z.record(z.string(), z.array(z.object({ HostIp: z.string(), HostPort: z.string() })).nullable()),
|
|
111
|
-
}),
|
|
112
|
-
}));
|
|
113
|
-
const raw = schema.parse(JSON.parse(result.stdout))[0];
|
|
114
|
-
if (!raw)
|
|
115
|
-
throw new Error("Missing inspected container.");
|
|
116
|
-
const port = raw.NetworkSettings.Ports["6969/tcp"]?.[0];
|
|
117
|
-
return containerSchema.parse({
|
|
118
|
-
id: raw.Id,
|
|
119
|
-
name: raw.Name.replace(/^\//, ""),
|
|
120
|
-
image: raw.Image,
|
|
121
|
-
user: raw.Config.User,
|
|
122
|
-
running: raw.State.Running,
|
|
123
|
-
restart: raw.HostConfig.RestartPolicy,
|
|
124
|
-
signature: raw.Config.Labels?.["io.slopify.launcher"] ?? null,
|
|
125
|
-
installation: raw.Config.Labels?.["io.slopify.installation"] ?? null,
|
|
126
|
-
mounts: raw.Mounts.map((m) => ({
|
|
127
|
-
type: m.Type,
|
|
128
|
-
name: m.Name ?? "",
|
|
129
|
-
source: m.Source,
|
|
130
|
-
destination: m.Destination,
|
|
131
|
-
rw: m.RW,
|
|
132
|
-
})),
|
|
133
|
-
port: port ? `${port.HostIp}:${port.HostPort}` : null,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
async function claims(volume, permitted, spared = () => false) {
|
|
137
|
-
const ids = (await command(["container", "ls", "-a", "-q"])).split(/\s+/).filter(Boolean);
|
|
138
|
-
for (const id of ids) {
|
|
139
|
-
const c = await inspect(id);
|
|
140
|
-
if (!c)
|
|
141
|
-
throw new Error("Cannot verify volume claims: a Docker container disappeared while Slopify was checking. Run the launcher again.");
|
|
142
|
-
if (!permitted.includes(c.id) &&
|
|
143
|
-
!spared(c) &&
|
|
144
|
-
c.mounts.some((m) => m.type === "volume" && m.name === volume))
|
|
145
|
-
throw new Error(`The Docker volume ${volume} that holds Slopify's data is also used by the container ${c.name}. Remove that container if you no longer need it (docker rm ${c.name}), or give this Slopify its own volume with SLOPIFY_DOCKER_VOLUME=<name>, then try again.`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
async function writers(volume, paths, permitted) {
|
|
149
|
-
const ids = (await command(["container", "ls", "-q"])).split(/\s+/).filter(Boolean);
|
|
150
|
-
for (const id of ids) {
|
|
151
|
-
const c = await inspect(id);
|
|
152
|
-
if (!c || permitted.includes(c.id))
|
|
153
|
-
continue;
|
|
154
|
-
if (c.mounts.some((m) => m.rw &&
|
|
155
|
-
(m.name === volume ||
|
|
156
|
-
(m.type === "bind" &&
|
|
157
|
-
paths.some((p) => contains(p, m.source) || contains(m.source, p))))))
|
|
158
|
-
throw new Error(`Another running container (${c.name}) is writing to Slopify's data or project folder. Stop it (docker stop ${c.name}) and try again.`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
async function recoveryLabel(j) {
|
|
162
|
-
const label = await command([
|
|
163
|
-
"volume",
|
|
164
|
-
"inspect",
|
|
165
|
-
"--format",
|
|
166
|
-
'{{index .Labels "io.slopify.transaction"}}',
|
|
167
|
-
j.backup,
|
|
168
|
-
]);
|
|
169
|
-
if (label !== j.id)
|
|
170
|
-
throw new Error("Recovery volume identity conflict.");
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
command,
|
|
174
|
-
inspect,
|
|
175
|
-
claims,
|
|
176
|
-
writers,
|
|
177
|
-
context: async (uid, gid) => {
|
|
178
|
-
let endpoint = env.DOCKER_HOST;
|
|
179
|
-
if (env.DOCKER_CONTEXT || !endpoint) {
|
|
180
|
-
const raw = JSON.parse(await command([
|
|
181
|
-
"context",
|
|
182
|
-
"inspect",
|
|
183
|
-
...(env.DOCKER_CONTEXT ? [env.DOCKER_CONTEXT] : []),
|
|
184
|
-
]));
|
|
185
|
-
endpoint = z
|
|
186
|
-
.array(z.object({ Endpoints: z.object({ docker: z.object({ Host: z.string() }) }) }))
|
|
187
|
-
.parse(raw)[0]?.Endpoints.docker.Host;
|
|
188
|
-
}
|
|
189
|
-
if (!endpoint?.startsWith("unix://"))
|
|
190
|
-
throw new Error(`The --docker launcher only works with Docker running on this machine, not a remote Docker (${endpoint ?? "unknown endpoint"}). Switch to the local one (unset DOCKER_HOST, or docker context use default) and try again.`);
|
|
191
|
-
const rawInfo = JSON.parse(await command(["info", "--format", "{{json .}}"]));
|
|
192
|
-
// Some Docker versions print the daemon's absence inside the JSON instead of failing.
|
|
193
|
-
const serverErrors = z.object({ ServerErrors: z.array(z.string()) }).safeParse(rawInfo)
|
|
194
|
-
.data?.ServerErrors;
|
|
195
|
-
if (serverErrors !== undefined && serverErrors.length > 0)
|
|
196
|
-
throw new Error(dockerFailure(["info"], { code: 1, stderr: serverErrors.join("\n") }));
|
|
197
|
-
const info = z
|
|
198
|
-
.object({
|
|
199
|
-
ID: z.string(),
|
|
200
|
-
OSType: z.literal("linux"),
|
|
201
|
-
OperatingSystem: z.string(),
|
|
202
|
-
SecurityOptions: z.array(z.string()),
|
|
203
|
-
})
|
|
204
|
-
.parse(rawInfo);
|
|
205
|
-
if (/docker desktop/i.test(info.OperatingSystem))
|
|
206
|
-
throw new Error("Docker Desktop isn't supported by the --docker launcher because it can't give your project folder the right owner. Use Docker Engine on Linux (docker context use default), or run Slopify without Docker: npx @gentbajko/slopify");
|
|
207
|
-
const rootless = info.SecurityOptions.includes("name=rootless");
|
|
208
|
-
if (!rootless && info.SecurityOptions.some((s) => s.startsWith("name=userns")))
|
|
209
|
-
throw new Error("Your Docker uses userns-remap, which the --docker launcher doesn't support. Use rootless Docker or a standard Docker Engine setup, or run Slopify without Docker: npx @gentbajko/slopify. Don't turn off Docker's isolation to get around this.");
|
|
210
|
-
return { daemon: info.ID, user: rootless ? "0:0" : `${uid}:${gid}` };
|
|
211
|
-
},
|
|
212
|
-
image: async (ref) => {
|
|
213
|
-
if (ref === "ghcr.io/gentbajko/slopify:latest") {
|
|
214
|
-
await command(["pull", ref]);
|
|
215
|
-
return command(["image", "inspect", "--format", "{{.Id}}", ref]);
|
|
216
|
-
}
|
|
217
|
-
const found = await run(["image", "inspect", "--format", "{{.Id}}", ref]);
|
|
218
|
-
if (found.code !== 0)
|
|
219
|
-
await command(["pull", ref]);
|
|
220
|
-
return command(["image", "inspect", "--format", "{{.Id}}", ref]);
|
|
221
|
-
},
|
|
222
|
-
version: async (image) => z
|
|
223
|
-
.string()
|
|
224
|
-
.regex(/^\d+\.\d+\.\d+$/)
|
|
225
|
-
.parse(await command([
|
|
226
|
-
"run",
|
|
227
|
-
"--rm",
|
|
228
|
-
"--network",
|
|
229
|
-
"none",
|
|
230
|
-
"--read-only",
|
|
231
|
-
"--tmpfs",
|
|
232
|
-
"/data",
|
|
233
|
-
"--entrypoint",
|
|
234
|
-
"node",
|
|
235
|
-
image,
|
|
236
|
-
"-p",
|
|
237
|
-
"require('/opt/slopify/packages/app/package.json').version",
|
|
238
|
-
])),
|
|
239
|
-
ensureVolume: async (volume) => {
|
|
240
|
-
await command(["volume", "create", volume]);
|
|
241
|
-
},
|
|
242
|
-
volume: async (name) => {
|
|
243
|
-
const names = (await command(["volume", "ls", "--format", "{{.Name}}"])).split(/\s+/);
|
|
244
|
-
if (!names.includes(name))
|
|
245
|
-
return null;
|
|
246
|
-
const v = z
|
|
247
|
-
.object({
|
|
248
|
-
Name: z.string(),
|
|
249
|
-
CreatedAt: z.string(),
|
|
250
|
-
Driver: z.literal("local"),
|
|
251
|
-
Scope: z.literal("local"),
|
|
252
|
-
Options: z.record(z.string(), z.string()).nullable(),
|
|
253
|
-
})
|
|
254
|
-
.parse(JSON.parse(await command(["volume", "inspect", "--format", "{{json .}}", name])));
|
|
255
|
-
if (v.Name !== name || (v.Options && Object.keys(v.Options).length > 0))
|
|
256
|
-
throw new Error(`The Docker volume ${name} uses a custom driver or options, which Slopify doesn't support. Give Slopify a plain local volume with SLOPIFY_DOCKER_VOLUME=<new name> and try again.`);
|
|
257
|
-
return JSON.stringify([v.Name, v.CreatedAt, v.Driver]);
|
|
258
|
-
},
|
|
259
|
-
probe: async (c, image, user, destination) => {
|
|
260
|
-
const probe = await mkdtemp(join(dirname(destination), ".slopify-probe-"));
|
|
261
|
-
try {
|
|
262
|
-
await writeFile(join(probe, "host"), "host", { mode: 0o600 });
|
|
263
|
-
await helper(image, mount("bind", probe, "/probe"), "probe", user);
|
|
264
|
-
const s = await lstat(join(probe, "container"));
|
|
265
|
-
if (!s.isFile() ||
|
|
266
|
-
s.uid !== c.uid ||
|
|
267
|
-
s.gid !== c.gid ||
|
|
268
|
-
(s.mode & 0o077) !== 0 ||
|
|
269
|
-
(await readFile(join(probe, "container"), "utf8")) !== "slopify ownership probe")
|
|
270
|
-
throw new Error("Docker can't create files in your project folder as your user, so the files would end up with the wrong owner. Use a standard Docker Engine or rootless Docker setup, or run Slopify without Docker: npx @gentbajko/slopify");
|
|
271
|
-
await writeFile(join(probe, "container"), "host can write", { flag: "a" });
|
|
272
|
-
}
|
|
273
|
-
finally {
|
|
274
|
-
await rm(probe, { recursive: true, force: true });
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
projects: async (image, volume, bind) => {
|
|
278
|
-
const value = z
|
|
279
|
-
.discriminatedUnion("exists", [
|
|
280
|
-
z.object({ exists: z.literal(false) }),
|
|
281
|
-
z.object({ exists: z.literal(true), digest: digestSchema }),
|
|
282
|
-
])
|
|
283
|
-
.parse(await helper(image, source(volume, bind), "projects"));
|
|
284
|
-
return value.exists ? value.digest : null;
|
|
285
|
-
},
|
|
286
|
-
copy: async (image, volume, bind, destination, id) => {
|
|
287
|
-
const name = `slopify-reader-${id}`;
|
|
288
|
-
const reader = await command([
|
|
289
|
-
"create",
|
|
290
|
-
"--name",
|
|
291
|
-
name,
|
|
292
|
-
"--label",
|
|
293
|
-
`io.slopify.reader=${id}`,
|
|
294
|
-
"--network",
|
|
295
|
-
"none",
|
|
296
|
-
"--read-only",
|
|
297
|
-
"--tmpfs",
|
|
298
|
-
"/data",
|
|
299
|
-
...source(volume, bind),
|
|
300
|
-
"--entrypoint",
|
|
301
|
-
"true",
|
|
302
|
-
image,
|
|
303
|
-
]);
|
|
304
|
-
try {
|
|
305
|
-
await command(["cp", `${reader}:/source/projects/.`, destination]);
|
|
306
|
-
}
|
|
307
|
-
finally {
|
|
308
|
-
await command(["rm", reader], AbortSignal.timeout(10_000));
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
snapshot: async (j) => {
|
|
312
|
-
const all = (await command(["volume", "ls", "--format", "{{.Name}}"])).split(/\s+/);
|
|
313
|
-
if (all.includes(j.backup))
|
|
314
|
-
throw new Error("Recovery volume already exists; reconcile the journal before retrying.");
|
|
315
|
-
await command([
|
|
316
|
-
"volume",
|
|
317
|
-
"create",
|
|
318
|
-
...Object.entries(recoveryLabels(j)).flatMap(([key, value]) => [
|
|
319
|
-
"--label",
|
|
320
|
-
`${key}=${value}`,
|
|
321
|
-
]),
|
|
322
|
-
j.backup,
|
|
323
|
-
]);
|
|
324
|
-
return digestSchema.parse(await helper(j.image, [...source(j.volume, null), ...mount("volume", j.backup, "/backup")], "snapshot"));
|
|
325
|
-
},
|
|
326
|
-
recoveryVolumes: async (volume) => {
|
|
327
|
-
const names = (await command([
|
|
328
|
-
"volume",
|
|
329
|
-
"ls",
|
|
330
|
-
"--filter",
|
|
331
|
-
"label=io.slopify.transaction",
|
|
332
|
-
"--format",
|
|
333
|
-
"{{.Name}}",
|
|
334
|
-
]))
|
|
335
|
-
.split(/\s+/)
|
|
336
|
-
.filter((name) => recoveryId(volume, name) !== null);
|
|
337
|
-
const found = [];
|
|
338
|
-
for (const name of names) {
|
|
339
|
-
const labels = z
|
|
340
|
-
.record(z.string(), z.string())
|
|
341
|
-
.nullable()
|
|
342
|
-
.parse(JSON.parse(await command(["volume", "inspect", "--format", "{{json .Labels}}", name])));
|
|
343
|
-
found.push({
|
|
344
|
-
name,
|
|
345
|
-
transaction: labels?.["io.slopify.transaction"] ?? null,
|
|
346
|
-
installation: labels?.["io.slopify.installation"] ?? null,
|
|
347
|
-
container: labels?.["io.slopify.container"] ?? null,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
return found;
|
|
351
|
-
},
|
|
352
|
-
// Never forced: Docker refuses a volume a container still mounts, and that refusal stands.
|
|
353
|
-
removeVolume: async (name) => {
|
|
354
|
-
await command(["volume", "rm", name]);
|
|
355
|
-
},
|
|
356
|
-
restore: async (j) => {
|
|
357
|
-
if (!j.backupDigest)
|
|
358
|
-
throw new Error("No verified private snapshot; automatic restore is unsafe.");
|
|
359
|
-
await recoveryLabel(j);
|
|
360
|
-
const found = digestSchema.parse(await helper(j.image, source(j.backup, null), "private"));
|
|
361
|
-
if (found.hash !== j.backupDigest.hash)
|
|
362
|
-
throw new Error("Recovery snapshot changed; automatic restore refused.");
|
|
363
|
-
const result = digestSchema.parse(await helper(j.image, [...source(j.backup, null), ...mount("volume", j.volume, "/data")], "restore"));
|
|
364
|
-
if (result.hash !== j.backupDigest.hash)
|
|
365
|
-
throw new Error("Restored private data differs from its snapshot.");
|
|
366
|
-
},
|
|
367
|
-
own: async (j) => {
|
|
368
|
-
await helper(j.image, mount("volume", j.volume, "/data"), "own", j.user);
|
|
369
|
-
},
|
|
370
|
-
stop: async (c) => {
|
|
371
|
-
await command(["update", "--restart=no", c.id]);
|
|
372
|
-
await command(["stop", c.id]);
|
|
373
|
-
},
|
|
374
|
-
restart: async (c) => {
|
|
375
|
-
const current = await inspect(c.id);
|
|
376
|
-
if (!current)
|
|
377
|
-
throw new Error("Previous container is missing; use the retained recovery volume.");
|
|
378
|
-
if (current.name !== c.name)
|
|
379
|
-
await command(["rename", c.id, c.name]);
|
|
380
|
-
const restart = c.restart.Name === "on-failure" && c.restart.MaximumRetryCount > 0
|
|
381
|
-
? `on-failure:${c.restart.MaximumRetryCount}`
|
|
382
|
-
: c.restart.Name;
|
|
383
|
-
await command(["update", `--restart=${restart}`, c.id]);
|
|
384
|
-
if (c.running)
|
|
385
|
-
await command(["start", c.id]);
|
|
386
|
-
},
|
|
387
|
-
start: async (c, j, transactionDirectory) => {
|
|
388
|
-
const port = c.port === 0 ? "" : String(c.port);
|
|
389
|
-
return command([
|
|
390
|
-
"run",
|
|
391
|
-
"-d",
|
|
392
|
-
"--name",
|
|
393
|
-
c.name,
|
|
394
|
-
"--restart",
|
|
395
|
-
"no",
|
|
396
|
-
"--user",
|
|
397
|
-
j.user,
|
|
398
|
-
"--label",
|
|
399
|
-
`io.slopify.launcher=${j.signature}`,
|
|
400
|
-
"--label",
|
|
401
|
-
`io.slopify.installation=${j.installation}`,
|
|
402
|
-
"--label",
|
|
403
|
-
`io.slopify.transaction=${j.id}`,
|
|
404
|
-
"-p",
|
|
405
|
-
`127.0.0.1:${port}:6969`,
|
|
406
|
-
...mount("volume", c.volume, "/data"),
|
|
407
|
-
...mount("bind", j.destination, "/data/projects"),
|
|
408
|
-
...mount("bind", transactionDirectory, "/opt/slopify-install", true),
|
|
409
|
-
...(c.bridge
|
|
410
|
-
? [
|
|
411
|
-
...mount("bind", c.bridge, "/opt/slopify-host", true),
|
|
412
|
-
"--env",
|
|
413
|
-
"SLOPIFY_HOST_CLI_DIR=/opt/slopify-host",
|
|
414
|
-
]
|
|
415
|
-
: []),
|
|
416
|
-
"--env",
|
|
417
|
-
`SLOPIFY_DOCKER_PROJECTS_DIR=${j.destination}`,
|
|
418
|
-
"--env",
|
|
419
|
-
"SLOPIFY_DOCKER_INSTALL_STATE=/opt/slopify-install/activation.json",
|
|
420
|
-
"--env",
|
|
421
|
-
`SLOPIFY_UPDATE_TOKEN=${j.token}`,
|
|
422
|
-
"--env",
|
|
423
|
-
"SLOPIFY_UPDATE_PENDING=1",
|
|
424
|
-
j.image,
|
|
425
|
-
]);
|
|
426
|
-
},
|
|
427
|
-
health: async (id, token, expectedVersion) => {
|
|
428
|
-
const deadline = Date.now() + 120_000;
|
|
429
|
-
while (Date.now() < deadline) {
|
|
430
|
-
signal.throwIfAborted();
|
|
431
|
-
const remaining = deadline - Date.now();
|
|
432
|
-
const probe = AbortSignal.any([
|
|
433
|
-
signal,
|
|
434
|
-
AbortSignal.timeout(Math.max(1, Math.min(2000, remaining))),
|
|
435
|
-
]);
|
|
436
|
-
try {
|
|
437
|
-
const result = await run([
|
|
438
|
-
"exec",
|
|
439
|
-
id,
|
|
440
|
-
"node",
|
|
441
|
-
"-e",
|
|
442
|
-
"const [token,expected]=process.argv.slice(1); Promise.all([fetch('http://127.0.0.1:6969/api/health'),fetch('http://127.0.0.1:6969/api/update/ready',{headers:{'X-Slopify-Update-Token':token}})]).then(async rs=>{for(const r of rs){const b=await r.json();if(!r.ok||b.status!=='ok'||b.version!==expected)process.exit(1)}}).catch(()=>process.exit(1))",
|
|
443
|
-
token,
|
|
444
|
-
expectedVersion,
|
|
445
|
-
], probe);
|
|
446
|
-
if (result.code === 0) {
|
|
447
|
-
const running = await run(["inspect", "--format", "{{.State.Running}}", id], probe);
|
|
448
|
-
if (running.code === 0 && running.stdout.trim() === "true")
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
if (signal.aborted || !probe.aborted)
|
|
454
|
-
throw error;
|
|
455
|
-
}
|
|
456
|
-
await delay(Math.min(250, Math.max(0, deadline - Date.now())), undefined, { signal });
|
|
457
|
-
}
|
|
458
|
-
throw new Error(`The new Slopify container did not start up within 2 minutes. See why with: docker logs ${id}`);
|
|
459
|
-
},
|
|
460
|
-
};
|
|
461
|
-
}
|