@gentbajko/slopify 2.5.0 → 3.0.1
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 +24 -41
- package/SUBTITLES.md +1 -1
- package/dist/adapter-registry.js +31 -0
- 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 +44 -5
- package/dist/adapters/host-cli/transport.js +7 -5
- package/dist/adapters/image/bytes.js +2 -2
- package/dist/adapters/image/codex-output.js +1 -1
- package/dist/adapters/image/codex.js +87 -22
- package/dist/adapters/image/fal.js +13 -5
- package/dist/adapters/image/google.js +11 -8
- package/dist/adapters/image/openai.js +14 -7
- package/dist/adapters/image/reference.js +34 -0
- package/dist/adapters/image/replicate.js +7 -2
- package/dist/adapters/image/video.js +2 -2
- package/dist/adapters/key-probes.js +85 -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.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 +12 -7
- package/dist/adapters/tts/cartesia.js +10 -0
- package/dist/adapters/tts/elevenlabs.js +31 -9
- package/dist/adapters/tts/gemini.js +206 -0
- 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/pcm-mp3.js +62 -0
- package/dist/adapters/tts/system.js +147 -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 +38 -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/assets/style-preview/narration.mp3 +0 -0
- package/dist/assets/style-preview/tall-1.jpg +0 -0
- package/dist/assets/style-preview/tall-2.jpg +0 -0
- package/dist/assets/style-preview/tall-3.jpg +0 -0
- package/dist/assets/style-preview/wide-1.jpg +0 -0
- package/dist/assets/style-preview/wide-2.jpg +0 -0
- package/dist/assets/style-preview/wide-3.jpg +0 -0
- package/dist/catalog/merge.js +132 -0
- package/dist/catalog/registry.js +17 -14
- package/dist/catalog/schema.js +2 -0
- package/dist/catalog/store.js +147 -8
- package/dist/catalog/validate.js +2 -2
- package/dist/compose.yaml +71 -0
- package/dist/edge/autostart/docker-record.js +32 -0
- package/dist/edge/autostart/docker.js +118 -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 +72 -0
- package/dist/edge/autostart/service.js +90 -0
- package/dist/edge/cli-args.js +71 -0
- package/dist/edge/cli.js +64 -63
- 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 +160 -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 +3 -1
- package/dist/edge/host-cli.js +1 -1
- package/dist/edge/http/actions.js +38 -11
- 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/byte-range.js +42 -0
- package/dist/edge/http/channel-memory.js +92 -0
- package/dist/edge/http/channels.js +128 -0
- package/dist/edge/http/diagnostics.js +1 -0
- package/dist/edge/http/draft-files.js +2 -4
- package/dist/edge/http/entries.js +22 -3
- package/dist/edge/http/files.js +11 -7
- package/dist/edge/http/folder-location.js +1 -1
- package/dist/edge/http/home.js +39 -0
- package/dist/edge/http/host-cli.js +29 -5
- package/dist/edge/http/narration-peaks.js +76 -0
- package/dist/edge/http/onboarding.js +212 -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/project-create.js +87 -0
- package/dist/edge/http/project-templates.js +19 -2
- package/dist/edge/http/projects.js +37 -100
- package/dist/edge/http/prompts.js +40 -3
- package/dist/edge/http/pronunciations.js +23 -4
- package/dist/edge/http/providers.js +119 -3
- package/dist/edge/http/reviews.js +131 -0
- package/dist/edge/http/revision-files.js +3 -5
- package/dist/edge/http/revisions.js +1 -1
- package/dist/edge/http/run-cost.js +25 -0
- package/dist/edge/http/schedules.js +115 -6
- package/dist/edge/http/settings.js +84 -6
- package/dist/edge/http/storage-files.js +50 -0
- package/dist/edge/http/storage.js +43 -2
- package/dist/edge/http/studio.js +287 -0
- package/dist/edge/http/style-preview.js +61 -0
- package/dist/edge/http/subtitles.js +1 -1
- package/dist/edge/http/trash.js +69 -0
- package/dist/edge/http/tutorials.js +59 -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 +92 -0
- package/dist/edge/native-update.js +62 -0
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/host-cli/gemini-login.js +83 -0
- package/dist/host-cli/open-folder.js +10 -10
- package/dist/host-cli/runtime.js +9 -1
- package/dist/host-cli/status.js +4 -2
- 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/db/migrations/0041-cast-hosts.sql +3 -0
- package/dist/kernel/paths.js +27 -6
- package/dist/kernel/pipeline.js +4 -1
- package/dist/kernel/ports/host-cli.js +122 -14
- package/dist/kernel/ports/key-probe.js +1 -0
- package/dist/kernel/ports/languages.js +157 -0
- package/dist/kernel/ports/model.js +10 -5
- package/dist/kernel/ports/narration-aliases.js +60 -0
- package/dist/kernel/ports/plan-limits.js +36 -0
- package/dist/kernel/ports/system-speech.js +203 -0
- package/dist/kernel/runner/attempt.js +17 -8
- package/dist/kernel/runner/audition.js +36 -0
- package/dist/kernel/runner/graph.js +40 -5
- package/dist/kernel/runner/index.js +39 -9
- package/dist/kernel/runner/meter.js +1 -0
- package/dist/kernel/runner/providers.js +142 -7
- 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 +277 -16
- package/dist/model-catalog.js +2 -0
- 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/3.0.1.md +105 -0
- package/dist/patch-notes/index.json +32 -0
- package/dist/slices/admission/model.js +27 -0
- package/dist/slices/admission/repo.js +24 -4
- package/dist/slices/admission/rules.js +80 -2
- package/dist/slices/admission/schema.js +58 -1
- package/dist/slices/admission/short-mode.js +47 -0
- package/dist/slices/admission/start.js +42 -6
- 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/folder.js +14 -6
- package/dist/slices/batch/index.js +7 -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 +221 -0
- package/dist/slices/channels/runs.js +129 -0
- package/dist/slices/channels/schema.js +152 -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 +5 -2
- package/dist/slices/control/providers.js +4 -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 +109 -28
- package/dist/slices/estimate/requests.js +68 -2
- package/dist/slices/eta/history.js +56 -0
- package/dist/slices/eta/model.js +63 -0
- package/dist/slices/eta/view.js +25 -0
- 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 +7 -1
- 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 +185 -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 +47 -3
- package/dist/slices/notifications/rules.js +48 -3
- package/dist/slices/onboarding/copy.js +143 -0
- package/dist/slices/onboarding/first-run.js +72 -0
- package/dist/slices/onboarding/full-video.js +81 -0
- package/dist/slices/onboarding/install.js +119 -0
- package/dist/slices/onboarding/model.js +85 -0
- package/dist/slices/onboarding/packs.js +491 -0
- package/dist/slices/onboarding/quick-short.js +144 -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 +127 -8
- package/dist/slices/play-drafts/readiness.js +4 -1
- package/dist/slices/play-drafts/repo.js +2 -0
- package/dist/slices/play-drafts/review-inputs.js +20 -3
- package/dist/slices/play-drafts/schema.js +89 -0
- package/dist/slices/play-drafts/service.js +4 -1
- package/dist/slices/play-drafts/start.js +43 -18
- package/dist/slices/project-templates/from-project.js +59 -6
- package/dist/slices/project-templates/next-chapter.js +94 -0
- package/dist/slices/project-templates/one-off.js +26 -0
- package/dist/slices/project-templates/repo.js +2 -1
- package/dist/slices/project-templates/schema.js +7 -1
- package/dist/slices/project-templates/service.js +15 -4
- package/dist/slices/project-templates/setup.js +18 -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 +46 -3
- 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 +8 -4
- 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 +67 -8
- package/dist/slices/rebuild/recipe-input-schema.js +35 -0
- package/dist/slices/rebuild/recipe-legacy.js +9 -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 +15 -1
- 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 +23 -3
- package/dist/slices/rebuild/recipe-text.js +114 -19
- package/dist/slices/rebuild/recipe-validation.js +14 -2
- package/dist/slices/rebuild/recipe-visual.js +96 -33
- 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 +125 -0
- package/dist/slices/rebuild/runtime-actions.js +5 -0
- 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 +21 -2
- package/dist/slices/rebuild/runtime-local.js +148 -24
- 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 +27 -0
- package/dist/slices/rebuild/runtime-provider.js +126 -18
- package/dist/slices/rebuild/runtime-publication.js +21 -5
- package/dist/slices/rebuild/runtime-review.js +267 -0
- package/dist/slices/rebuild/runtime-run.js +17 -11
- package/dist/slices/rebuild/runtime-shorts.js +44 -17
- package/dist/slices/rebuild/runtime-store.js +62 -11
- 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 +3 -3
- package/dist/slices/rebuild/soften.js +49 -0
- package/dist/slices/rebuild/transition-repo.js +9 -2
- 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 +118 -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 +5 -2
- package/dist/slices/revisions/adopt.js +3 -1
- package/dist/slices/revisions/downloads.js +7 -1
- package/dist/slices/revisions/image-plan.js +183 -0
- package/dist/slices/revisions/mutation-assets.js +10 -0
- package/dist/slices/revisions/mutation-cues.js +71 -0
- package/dist/slices/revisions/mutation-request.js +2 -1
- package/dist/slices/revisions/mutations.js +97 -3
- package/dist/slices/revisions/schema.js +11 -1
- 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 +256 -0
- package/dist/slices/run-cost/pricing.js +95 -0
- package/dist/slices/run-cost/week.js +108 -0
- package/dist/slices/schedules/agenda.js +222 -0
- package/dist/slices/schedules/repo.js +58 -18
- package/dist/slices/schedules/scheduler.js +68 -17
- package/dist/slices/schedules/schema.js +179 -1
- package/dist/slices/schedules/service.js +43 -4
- package/dist/slices/schedules/similar.js +128 -0
- package/dist/slices/schedules/topic-list.js +163 -0
- package/dist/slices/schedules/topics.js +523 -0
- package/dist/slices/settings/first-run.js +106 -0
- package/dist/slices/settings/health.js +247 -0
- package/dist/slices/settings/key-guides.js +138 -0
- package/dist/slices/settings/key-test.js +93 -0
- package/dist/slices/settings/keys.js +10 -5
- package/dist/slices/settings/model-reach.js +52 -0
- package/dist/slices/settings/model.js +55 -1
- package/dist/slices/settings/models.js +5 -1
- package/dist/slices/settings/playback.js +15 -0
- package/dist/slices/settings/readiness.js +14 -2
- package/dist/slices/settings/repo.js +17 -3
- package/dist/slices/settings/tutorial-schema.js +5 -0
- 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 +63 -13
- 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 +87 -0
- package/dist/slices/storage/downloads.js +33 -1
- package/dist/slices/storage/files-location.js +531 -0
- package/dist/slices/storage/layout.js +14 -4
- package/dist/slices/storage/model.js +11 -0
- package/dist/slices/storage/portable.js +94 -12
- package/dist/slices/storage/reconcile.js +14 -1
- 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 +30 -0
- package/dist/slices/studio/pack.js +211 -0
- package/dist/slices/studio/queue.js +69 -0
- package/dist/slices/studio/settings.js +170 -0
- package/dist/slices/style-preview/images.js +61 -0
- package/dist/slices/style-preview/narration.js +33 -0
- package/dist/slices/style-preview/render.js +148 -0
- package/dist/slices/style-preview/schema.js +59 -0
- package/dist/slices/style-preview/service.js +92 -0
- package/dist/slices/style-preview/settings.js +139 -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/tutorials/anchors.js +57 -0
- package/dist/slices/tutorials/library.js +185 -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 +87 -0
- package/dist/slices/voices/audition.js +25 -0
- package/dist/slices/voices/cast.js +74 -0
- package/dist/slices/voices/grouping.js +59 -0
- package/dist/slices/voices/join.js +35 -0
- package/dist/slices/voices/languages.js +25 -0
- package/dist/slices/voices/model.js +218 -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 +84 -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/tutorials/AI-CLIs.md +146 -0
- package/dist/tutorials/Backups.md +121 -0
- package/dist/tutorials/Calendar.md +103 -0
- package/dist/tutorials/Cast-Library.md +112 -0
- package/dist/tutorials/Channels.md +148 -0
- package/dist/tutorials/Costs-and-Run-Cost.md +136 -0
- package/dist/tutorials/Docker.md +171 -0
- package/dist/tutorials/Document-Themes.md +207 -0
- package/dist/tutorials/Editing-a-Project.md +204 -0
- package/dist/tutorials/FAQ.md +134 -0
- package/dist/tutorials/First-Launch-and-Welcome.md +153 -0
- package/dist/tutorials/Home-and-Projects.md +174 -0
- package/dist/tutorials/Home.md +119 -0
- package/dist/tutorials/Install.md +154 -0
- package/dist/tutorials/Intros-and-Outros.md +86 -0
- package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +215 -0
- package/dist/tutorials/Library-Overview.md +75 -0
- package/dist/tutorials/Models.md +133 -0
- package/dist/tutorials/Multiple-Voices.md +161 -0
- package/dist/tutorials/Narration-Aliases-and-Glossary.md +129 -0
- package/dist/tutorials/Notifications.md +86 -0
- package/dist/tutorials/Other-Languages.md +110 -0
- package/dist/tutorials/PDF-Documents.md +100 -0
- package/dist/tutorials/Play-Images.md +135 -0
- package/dist/tutorials/Play-Narration.md +167 -0
- package/dist/tutorials/Play-Outputs.md +109 -0
- package/dist/tutorials/Play-Overview.md +216 -0
- package/dist/tutorials/Play-Title-and-Article.md +135 -0
- package/dist/tutorials/Play-Video-and-Style.md +143 -0
- package/dist/tutorials/Project-Page.md +228 -0
- package/dist/tutorials/Prompts.md +136 -0
- package/dist/tutorials/Providers-and-Keys.md +290 -0
- package/dist/tutorials/Publishing-to-YouTube.md +135 -0
- package/dist/tutorials/Recovery-and-Retries.md +141 -0
- package/dist/tutorials/Reviews-and-Checkpoints.md +143 -0
- package/dist/tutorials/Schedules.md +154 -0
- package/dist/tutorials/Settings-Reference.md +258 -0
- package/dist/tutorials/Shorts.md +116 -0
- package/dist/tutorials/Start-at-Login.md +126 -0
- package/dist/tutorials/Studio-Extension.md +146 -0
- package/dist/tutorials/Templates.md +146 -0
- package/dist/tutorials/Trash.md +87 -0
- package/dist/tutorials/Troubleshooting.md +149 -0
- package/dist/tutorials/Uninstalling-and-Moving.md +113 -0
- package/dist/tutorials/Updating-and-Patch-Notes.md +120 -0
- package/dist/tutorials/Video-Editing.md +135 -0
- package/dist/tutorials/Where-Your-Files-Live.md +149 -0
- package/dist/tutorials/YouTube-Description.md +132 -0
- package/dist/tutorials/Your-First-Short.md +125 -0
- package/dist/tutorials/_Footer.md +1 -0
- package/dist/tutorials/_Sidebar.md +69 -0
- package/dist/updater/service.js +66 -21
- package/dist/web/assets/index-DBLSfqA-.css +1 -0
- package/dist/web/assets/index-DFdIhmm0.js +307 -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-BLF2i8c2.js → pdf-B9OY1ioX.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +5 -5
- 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-CktpxlJd.js +0 -171
- package/dist/web/assets/index-DB-vOnGU.css +0 -1
- package/scripts/docker-run.sh +0 -3
- /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { cp, lstat, mkdir, open, readdir, readFile, rename, rm, rmdir, unlink, } from "node:fs/promises";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
import { hasCode } from "../../host-cli/paths.js";
|
|
7
|
+
import { privateRead } from "../../host-cli/service.js";
|
|
8
|
+
import { recoveryId } from "./engine.js";
|
|
9
|
+
import { installSchema, legacyJournalSchema, legacyReceiptSchema, privateDirectory, readState, settledLegacyPhases, updateSchema, writeState, writeText, } from "./state.js";
|
|
10
|
+
import { contains, identity, isMissing, privateTree, safePath, treeDigest } from "./tree.js";
|
|
11
|
+
const upArgs = ["up", "--detach", "--force-recreate", "--no-build", "--pull", "never"];
|
|
12
|
+
/**
|
|
13
|
+
* Installs or updates the Docker installation `<name>`: one transaction that waits for running
|
|
14
|
+
* work, snapshots the data volume, starts the new container through compose, and either commits
|
|
15
|
+
* it once it answers as the new version or puts the previous container and data back.
|
|
16
|
+
*
|
|
17
|
+
* The data volume is only ever reused by name: it is created when missing and never removed or
|
|
18
|
+
* recreated. Older installations (a plain `docker run` container, or the container the 2.5.0
|
|
19
|
+
* launcher managed) are adopted: the old container is kept stopped under another name until the
|
|
20
|
+
* new one is committed.
|
|
21
|
+
*/
|
|
22
|
+
export async function applyDocker(o, e) {
|
|
23
|
+
const directory = join(o.root, o.name);
|
|
24
|
+
await privateDirectory(directory, o.uid);
|
|
25
|
+
const release = await lock(join(directory, "update.lock"));
|
|
26
|
+
try {
|
|
27
|
+
return await locked(o, e, directory);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
await release();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function locked(o, e, directory) {
|
|
34
|
+
const paths = {
|
|
35
|
+
install: join(directory, "install.json"),
|
|
36
|
+
update: join(directory, "update.json"),
|
|
37
|
+
env: join(directory, ".env"),
|
|
38
|
+
compose: join(directory, "compose.yaml"),
|
|
39
|
+
activation: join(directory, "activation"),
|
|
40
|
+
off: join(directory, "host-cli-off"),
|
|
41
|
+
};
|
|
42
|
+
const context = await e.context(o.uid, o.gid);
|
|
43
|
+
const interrupted = await readState(paths.update, updateSchema, o.uid);
|
|
44
|
+
if (interrupted !== null) {
|
|
45
|
+
o.report("The last Slopify install or update was cut off before it finished. Putting the previous version back first...");
|
|
46
|
+
await rollback(o, e, directory, interrupted);
|
|
47
|
+
await unlink(paths.update);
|
|
48
|
+
}
|
|
49
|
+
const install = await readState(paths.install, installSchema, o.uid);
|
|
50
|
+
if (install !== null && (install.name !== o.name || install.volume !== o.volume))
|
|
51
|
+
throw new Error(`${paths.install} belongs to another installation (${install.name}, volume ${install.volume}). Use SLOPIFY_DOCKER_NAME and SLOPIFY_DOCKER_VOLUME to pick it, and try again.`);
|
|
52
|
+
if (install !== null && install.daemon !== context.daemon)
|
|
53
|
+
throw new Error("This is not the Docker that Slopify was installed with (a different Docker context or DOCKER_HOST is active). Switch back to the original one (docker context use <name>, or unset DOCKER_HOST) and try again.");
|
|
54
|
+
const current = await e.inspect(o.name);
|
|
55
|
+
const legacy = install === null ? await adoptable(o, directory, current) : null;
|
|
56
|
+
if (o.mode === "update" && install === null && current === null)
|
|
57
|
+
throw new Error(`Nothing to update: there is no Slopify Docker installation named ${o.name}. Install it first: npx @gentbajko/slopify --docker`);
|
|
58
|
+
if (current !== null) {
|
|
59
|
+
const data = current.mounts.find((m) => m.destination === "/data");
|
|
60
|
+
if (data?.type !== "volume" || data.name !== o.volume)
|
|
61
|
+
throw new Error(`The existing container ${o.name} keeps its data in ${data?.type === "volume" ? `the volume ${data.name}` : "no named volume"}, not ${o.volume}. Run the command with SLOPIFY_DOCKER_VOLUME=${data?.type === "volume" ? data.name : "<its volume>"} so Slopify reuses that data, and try again.`);
|
|
62
|
+
}
|
|
63
|
+
const source = install?.projects ?? legacy?.projects ?? null;
|
|
64
|
+
const fallback = o.documents === undefined
|
|
65
|
+
? o.name === "slopify"
|
|
66
|
+
? join(o.home, "Slopify/Projects")
|
|
67
|
+
: join(o.home, "Slopify", o.name, "Projects")
|
|
68
|
+
: o.name === "slopify"
|
|
69
|
+
? join(o.documents, "Slopify", "Projects")
|
|
70
|
+
: join(o.documents, "Slopify", o.name, "Projects");
|
|
71
|
+
const projects = o.projects ?? source ?? fallback;
|
|
72
|
+
// Only a brand-new install that takes the Documents default gets a Backups folder of its own
|
|
73
|
+
// beside Projects; everything else keeps backups where they were (inside Projects).
|
|
74
|
+
const backups = install !== null
|
|
75
|
+
? (install.backups ?? null)
|
|
76
|
+
: source === null && o.projects === null && o.documents !== undefined
|
|
77
|
+
? join(dirname(projects), "Backups")
|
|
78
|
+
: null;
|
|
79
|
+
await safePath(projects, o.home, o.root, true);
|
|
80
|
+
if (backups !== null) {
|
|
81
|
+
await safePath(backups, o.home, o.root, true);
|
|
82
|
+
if (contains(projects, backups) || contains(backups, projects))
|
|
83
|
+
throw new Error(`The Backups folder ${backups} and the project folder ${projects} are inside each other. Choose a separate project folder with --projects-dir <folder>.`);
|
|
84
|
+
}
|
|
85
|
+
if (source !== null &&
|
|
86
|
+
source !== projects &&
|
|
87
|
+
(contains(source, projects) || contains(projects, source)))
|
|
88
|
+
throw new Error(`The new project folder ${projects} and the current one ${source} are inside each other. Choose a separate folder with --projects-dir <folder>.`);
|
|
89
|
+
if (source !== null && source === projects)
|
|
90
|
+
await assertFolder(projects);
|
|
91
|
+
if (source !== projects)
|
|
92
|
+
await assertEmpty(projects);
|
|
93
|
+
const port = o.port ?? install?.port ?? current?.port ?? 6969;
|
|
94
|
+
await e.image(o.image);
|
|
95
|
+
const imageVersion = await e.imageVersion(o.image);
|
|
96
|
+
if (imageVersion !== o.version)
|
|
97
|
+
throw new Error(`The Docker image ${o.image} is Slopify ${imageVersion} but this installer is ${o.version}. Run the latest of both (npx @gentbajko/slopify@latest --docker), or if you set SLOPIFY_DOCKER_IMAGE, point it at version ${o.version}. Nothing was changed.`);
|
|
98
|
+
if (install !== null &&
|
|
99
|
+
current !== null &&
|
|
100
|
+
current.project === o.name &&
|
|
101
|
+
install.image === o.image &&
|
|
102
|
+
install.appVersion === o.version &&
|
|
103
|
+
install.user === context.user &&
|
|
104
|
+
install.projects === projects &&
|
|
105
|
+
(install.backups ?? null) === backups &&
|
|
106
|
+
(o.port === null || install.port === o.port) &&
|
|
107
|
+
install.hostCli === o.hostCli.enabled) {
|
|
108
|
+
if (!current.running)
|
|
109
|
+
await e.start(current.id);
|
|
110
|
+
await e.ready(current.id, null);
|
|
111
|
+
if (o.hostCli.enabled)
|
|
112
|
+
await o.hostCli.ensure();
|
|
113
|
+
return {
|
|
114
|
+
url: `http://127.0.0.1:${current.port ?? install.port}`,
|
|
115
|
+
projects,
|
|
116
|
+
backups,
|
|
117
|
+
changed: false,
|
|
118
|
+
recovery: install.recovery,
|
|
119
|
+
removed: [],
|
|
120
|
+
problems: [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (current?.running)
|
|
124
|
+
await waitForIdle(o, e, current.id);
|
|
125
|
+
const share = o.hostCli.enabled ? await o.hostCli.ensure() : null;
|
|
126
|
+
const id = randomUUID();
|
|
127
|
+
const token = randomBytes(32).toString("hex");
|
|
128
|
+
let u = {
|
|
129
|
+
version: 2,
|
|
130
|
+
id,
|
|
131
|
+
phase: "stopping",
|
|
132
|
+
image: o.image,
|
|
133
|
+
previous: current === null
|
|
134
|
+
? { kind: "none" }
|
|
135
|
+
: install !== null && current.project === o.name
|
|
136
|
+
? {
|
|
137
|
+
kind: "compose",
|
|
138
|
+
env: (await privateRead(paths.env, o.uid)) ?? "",
|
|
139
|
+
activation: (await privateRead(join(paths.activation, "activation.json"), o.uid)) ?? "",
|
|
140
|
+
}
|
|
141
|
+
: {
|
|
142
|
+
kind: "legacy",
|
|
143
|
+
id: current.id,
|
|
144
|
+
name: o.name,
|
|
145
|
+
renamed: `${o.name}-previous-${id}`,
|
|
146
|
+
running: current.running,
|
|
147
|
+
restart: current.restart,
|
|
148
|
+
},
|
|
149
|
+
backup: null,
|
|
150
|
+
backupDigest: null,
|
|
151
|
+
published: null,
|
|
152
|
+
};
|
|
153
|
+
const save = async (change) => {
|
|
154
|
+
u = updateSchema.parse({ ...u, ...change });
|
|
155
|
+
await writeState(paths.update, u);
|
|
156
|
+
};
|
|
157
|
+
await save({});
|
|
158
|
+
try {
|
|
159
|
+
if (u.previous.kind === "legacy") {
|
|
160
|
+
await e.restartPolicy(u.previous.id, "no");
|
|
161
|
+
if (u.previous.running)
|
|
162
|
+
await e.stop(u.previous.id);
|
|
163
|
+
await e.rename(u.previous.id, u.previous.renamed);
|
|
164
|
+
}
|
|
165
|
+
else if (u.previous.kind === "compose")
|
|
166
|
+
await e.compose(directory, o.name, ["stop"]);
|
|
167
|
+
const fresh = !(await e.volumeExists(o.volume));
|
|
168
|
+
if (fresh)
|
|
169
|
+
await e.createVolume(o.volume);
|
|
170
|
+
else {
|
|
171
|
+
const backup = `${o.volume}-recovery-${id}`;
|
|
172
|
+
await save({ phase: "snapshot", backup });
|
|
173
|
+
o.report(`Saving a recovery copy of your data in the Docker volume ${backup}...`);
|
|
174
|
+
const backupDigest = await e.snapshot(o.image, o.volume, backup, {
|
|
175
|
+
"io.slopify.transaction": id,
|
|
176
|
+
"io.slopify.container": o.name,
|
|
177
|
+
});
|
|
178
|
+
await save({ backupDigest });
|
|
179
|
+
}
|
|
180
|
+
if (source !== projects) {
|
|
181
|
+
await publishProjects(o, e, source, projects, id, current);
|
|
182
|
+
await save({ published: { path: projects, identity: await identity(projects) } });
|
|
183
|
+
}
|
|
184
|
+
if (backups !== null)
|
|
185
|
+
await mkdir(backups, { recursive: true, mode: 0o700 });
|
|
186
|
+
const previousUser = install?.user ?? current?.user ?? null;
|
|
187
|
+
if (fresh || previousUser !== context.user)
|
|
188
|
+
await e.own(o.image, o.volume, context.user);
|
|
189
|
+
await writeText(paths.compose, await readFile(o.composeFile, "utf8"));
|
|
190
|
+
await mkdir(paths.off, { recursive: true, mode: 0o700 });
|
|
191
|
+
await mkdir(paths.activation, { recursive: true, mode: 0o700 });
|
|
192
|
+
await writeState(join(paths.activation, "activation.json"), {
|
|
193
|
+
version: 1,
|
|
194
|
+
token,
|
|
195
|
+
committed: false,
|
|
196
|
+
});
|
|
197
|
+
await writeText(paths.env, composeEnv({
|
|
198
|
+
SLOPIFY_NAME: o.name,
|
|
199
|
+
SLOPIFY_IMAGE: o.image,
|
|
200
|
+
SLOPIFY_USER: context.user,
|
|
201
|
+
SLOPIFY_PORT: port === 0 ? "" : String(port),
|
|
202
|
+
SLOPIFY_VOLUME: o.volume,
|
|
203
|
+
SLOPIFY_PROJECTS_DIR: projects,
|
|
204
|
+
...(backups === null ? {} : { SLOPIFY_BACKUPS_DIR: backups }),
|
|
205
|
+
SLOPIFY_HOST_CLI_SHARE: share ?? paths.off,
|
|
206
|
+
SLOPIFY_ACTIVATION_DIR: paths.activation,
|
|
207
|
+
SLOPIFY_UPDATE_TOKEN: token,
|
|
208
|
+
SLOPIFY_UPDATE_PENDING: "1",
|
|
209
|
+
}));
|
|
210
|
+
await save({ phase: "starting" });
|
|
211
|
+
await e.compose(directory, o.name, upArgs);
|
|
212
|
+
const candidate = await e.inspect(o.name);
|
|
213
|
+
if (candidate === null)
|
|
214
|
+
throw new Error("Docker Compose finished but the Slopify container isn't there.");
|
|
215
|
+
await e.ready(candidate.id, { token, version: o.version });
|
|
216
|
+
const next = {
|
|
217
|
+
version: 2,
|
|
218
|
+
name: o.name,
|
|
219
|
+
volume: o.volume,
|
|
220
|
+
daemon: context.daemon,
|
|
221
|
+
image: o.image,
|
|
222
|
+
appVersion: o.version,
|
|
223
|
+
user: context.user,
|
|
224
|
+
port,
|
|
225
|
+
projects,
|
|
226
|
+
projectsIdentity: await identity(projects),
|
|
227
|
+
...(backups === null ? {} : { backups }),
|
|
228
|
+
hostCli: share !== null,
|
|
229
|
+
token,
|
|
230
|
+
recovery: u.backup,
|
|
231
|
+
};
|
|
232
|
+
await writeState(paths.install, next);
|
|
233
|
+
await writeState(join(paths.activation, "activation.json"), {
|
|
234
|
+
version: 1,
|
|
235
|
+
token,
|
|
236
|
+
committed: true,
|
|
237
|
+
});
|
|
238
|
+
await unlink(paths.update);
|
|
239
|
+
const tidy = await tidyUp(o, e, u);
|
|
240
|
+
return {
|
|
241
|
+
url: `http://127.0.0.1:${candidate.port ?? port}`,
|
|
242
|
+
projects,
|
|
243
|
+
backups,
|
|
244
|
+
changed: true,
|
|
245
|
+
recovery: u.backup,
|
|
246
|
+
...tidy,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (cause) {
|
|
250
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
251
|
+
try {
|
|
252
|
+
await rollback(o, e, directory, u);
|
|
253
|
+
await unlink(paths.update);
|
|
254
|
+
}
|
|
255
|
+
catch (failure) {
|
|
256
|
+
throw new Error(`Slopify could not finish, and could not fully put the previous version back either. Don't delete anything: ${u.backup ? `your data's recovery copy is the Docker volume ${u.backup}, ` : ""}progress is recorded in ${paths.update}. Fix the problem below and run the same command again; it finishes the undo first. What failed: ${reason} Undo problem: ${failure instanceof Error ? failure.message : String(failure)}`, { cause });
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`${o.mode === "update" ? "The update" : "The install"} failed, so Slopify put the previous version back${u.previous.kind === "none" ? "" : " and nothing was lost"}. What failed: ${reason}${u.backup ? ` The recovery copy of your data is kept in the Docker volume ${u.backup}.` : ""}`, { cause });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Puts back what `u` changed. Safe to repeat: each step checks what is actually there. */
|
|
262
|
+
async function rollback(o, e, directory, u) {
|
|
263
|
+
const paths = { env: join(directory, ".env"), activation: join(directory, "activation") };
|
|
264
|
+
const occupant = await e.inspect(o.name);
|
|
265
|
+
const candidate = occupant !== null && !(u.previous.kind === "legacy" && occupant.id === u.previous.id)
|
|
266
|
+
? occupant
|
|
267
|
+
: null;
|
|
268
|
+
if (candidate !== null) {
|
|
269
|
+
if (u.previous.kind === "compose")
|
|
270
|
+
await e.compose(directory, o.name, ["stop"]);
|
|
271
|
+
else
|
|
272
|
+
await e.removeContainer(candidate.id);
|
|
273
|
+
}
|
|
274
|
+
// Only a started candidate can have changed the data (for example by migrating the database).
|
|
275
|
+
if (u.phase === "starting" && u.backup !== null && u.backupDigest !== null) {
|
|
276
|
+
o.report(`Copying your data back from the recovery volume ${u.backup}...`);
|
|
277
|
+
await e.restore(u.image, u.backup, o.volume, u.backupDigest);
|
|
278
|
+
}
|
|
279
|
+
if (u.published !== null) {
|
|
280
|
+
// Only the copy this run made, and only if it is still the same folder.
|
|
281
|
+
const now = await identity(u.published.path).catch(() => null);
|
|
282
|
+
if (now?.dev === u.published.identity.dev && now.ino === u.published.identity.ino)
|
|
283
|
+
await rm(u.published.path, { recursive: true, force: true });
|
|
284
|
+
}
|
|
285
|
+
if (u.previous.kind === "compose") {
|
|
286
|
+
await writeText(paths.env, u.previous.env);
|
|
287
|
+
await mkdir(paths.activation, { recursive: true, mode: 0o700 });
|
|
288
|
+
await writeText(join(paths.activation, "activation.json"), u.previous.activation);
|
|
289
|
+
await e.compose(directory, o.name, upArgs);
|
|
290
|
+
const back = await e.inspect(o.name);
|
|
291
|
+
if (back !== null)
|
|
292
|
+
await e.ready(back.id, null);
|
|
293
|
+
}
|
|
294
|
+
else if (u.previous.kind === "legacy") {
|
|
295
|
+
const renamed = await e.inspect(u.previous.renamed);
|
|
296
|
+
if (renamed !== null)
|
|
297
|
+
await e.rename(renamed.id, u.previous.name);
|
|
298
|
+
await e.restartPolicy(u.previous.id, u.previous.restart);
|
|
299
|
+
if (u.previous.running) {
|
|
300
|
+
await e.start(u.previous.id);
|
|
301
|
+
await e.ready(u.previous.id, null);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/** After a commit: remove what the new recovery copy makes redundant. Never fails the update. */
|
|
306
|
+
async function tidyUp(o, e, u) {
|
|
307
|
+
const removed = [];
|
|
308
|
+
const problems = [];
|
|
309
|
+
const attempt = async (what, fix, action) => {
|
|
310
|
+
try {
|
|
311
|
+
await action();
|
|
312
|
+
removed.push(what);
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
problems.push(`Couldn't remove ${what}, so it is kept. ${fix} Why: ${error instanceof Error ? error.message : String(error)}`);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
try {
|
|
319
|
+
// Stopped copies of the old container. Their data lives in the volume, which stays.
|
|
320
|
+
for (const old of await e.legacyPrevious(o.name))
|
|
321
|
+
if (!old.running &&
|
|
322
|
+
(old.launcher || (u.previous.kind === "legacy" && old.id === u.previous.id)))
|
|
323
|
+
await attempt(`the old container ${old.name}`, `Remove it with docker rm ${old.name}.`, () => e.removeContainer(old.id));
|
|
324
|
+
// Keep only the newest recovery volume: the one this update just made.
|
|
325
|
+
for (const volume of await e.recoveryVolumes(o.volume)) {
|
|
326
|
+
const id = recoveryId(o.volume, volume.name);
|
|
327
|
+
if (id === null || volume.name === u.backup || volume.transaction !== id)
|
|
328
|
+
continue;
|
|
329
|
+
if (volume.container !== null && volume.container !== o.name)
|
|
330
|
+
continue;
|
|
331
|
+
await attempt(`the older recovery volume ${volume.name}`, `Once nothing uses it, remove it with docker volume rm ${volume.name}.`, () => e.removeVolume(volume.name));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
problems.push(`Couldn't list older recovery volumes and containers to tidy up, so all of them are kept. See them with docker volume ls --filter label=io.slopify.transaction. Why: ${error instanceof Error ? error.message : String(error)}`);
|
|
336
|
+
}
|
|
337
|
+
return { removed, problems };
|
|
338
|
+
}
|
|
339
|
+
async function waitForIdle(o, e, id) {
|
|
340
|
+
let told = false;
|
|
341
|
+
for (;;) {
|
|
342
|
+
if ((await e.busy(id)) !== true)
|
|
343
|
+
return;
|
|
344
|
+
if (!told)
|
|
345
|
+
o.report("Slopify is generating right now. Waiting for running work to finish before updating (press Ctrl+C to cancel; nothing has been changed yet)...");
|
|
346
|
+
told = true;
|
|
347
|
+
await delay(o.pollMs ?? 5000);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** The installation 2.5.0 or older left behind, if it is safe to adopt. */
|
|
351
|
+
async function adoptable(o, directory, current) {
|
|
352
|
+
const journal = await readState(join(directory, "journal.json"), legacyJournalSchema, o.uid).catch(() => null);
|
|
353
|
+
if (journal !== null && !settledLegacyPhases.has(journal.phase))
|
|
354
|
+
throw new Error(`An update by an older Slopify launcher (2.5.0 or earlier) didn't finish (step "${journal.phase}", recovery volume ${journal.backup}). Let it finish or undo first by running npx @gentbajko/slopify@2.5.0 --docker once, then run this command again. Nothing was changed.`);
|
|
355
|
+
const receipt = await readState(join(directory, "receipt.json"), legacyReceiptSchema, o.uid).catch(() => null);
|
|
356
|
+
if (current === null)
|
|
357
|
+
return receipt === null ? null : { projects: receipt.projects };
|
|
358
|
+
if (current.project !== null)
|
|
359
|
+
throw new Error(`A container named ${o.name} is already managed by Docker Compose (project ${current.project}) from another folder, so Slopify won't take it over. Stop it in that folder (docker compose down, which keeps the data volume), or install under another name with SLOPIFY_DOCKER_NAME=<name>.`);
|
|
360
|
+
const bind = current.mounts.find((m) => m.destination === "/data/projects");
|
|
361
|
+
if (bind !== undefined && bind.type !== "bind")
|
|
362
|
+
throw new Error(`The existing container ${o.name} keeps project files in a ${bind.type} mount, which Slopify can't adopt. Recreate it with a host folder for /data/projects, or install under another name with SLOPIFY_DOCKER_NAME=<name>.`);
|
|
363
|
+
return { projects: bind?.source ?? null };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Makes `projects` hold the project files: a verified copy of the old host folder, or of the
|
|
367
|
+
* files a plain `docker run` kept inside the volume. The source is never changed.
|
|
368
|
+
*/
|
|
369
|
+
async function publishProjects(o, e, source, projects, id, current) {
|
|
370
|
+
const staging = join(dirname(projects), `.slopify-projects-${id}`);
|
|
371
|
+
await rm(staging, { recursive: true, force: true });
|
|
372
|
+
if (source !== null) {
|
|
373
|
+
o.report(`Copying your project files from ${source} to ${projects}...`);
|
|
374
|
+
const before = await treeDigest(source);
|
|
375
|
+
await cp(source, staging, {
|
|
376
|
+
recursive: true,
|
|
377
|
+
errorOnExist: true,
|
|
378
|
+
force: false,
|
|
379
|
+
preserveTimestamps: true,
|
|
380
|
+
});
|
|
381
|
+
await privateTree(staging, o.uid, o.gid);
|
|
382
|
+
if ((await treeDigest(staging)).hash !== before.hash)
|
|
383
|
+
throw new Error(`The copy of your project files in ${staging} doesn't match ${source}. The original folder is unchanged.`);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
const inVolume = current === null ? null : await e.volumeProjects(o.image, o.volume);
|
|
387
|
+
await mkdir(staging, { mode: 0o700 });
|
|
388
|
+
if (inVolume !== null && inVolume.files > 0) {
|
|
389
|
+
o.report(`Copying the project files kept inside the data volume to ${projects}...`);
|
|
390
|
+
await e.copyVolumeProjects(o.image, o.volume, staging, id);
|
|
391
|
+
await privateTree(staging, o.uid, o.gid);
|
|
392
|
+
if ((await treeDigest(staging)).hash !== inVolume.hash)
|
|
393
|
+
throw new Error(`The copy of the project files from the data volume doesn't match the original. They are unchanged inside the volume ${o.volume}.`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// assertEmpty checked it before anything changed; rmdir still refuses if files appeared since.
|
|
397
|
+
await rmdir(projects).catch((error) => {
|
|
398
|
+
if (!isMissing(error))
|
|
399
|
+
throw error;
|
|
400
|
+
});
|
|
401
|
+
await rename(staging, projects);
|
|
402
|
+
}
|
|
403
|
+
async function assertFolder(path) {
|
|
404
|
+
const s = await lstat(path).catch((error) => {
|
|
405
|
+
if (isMissing(error))
|
|
406
|
+
return null;
|
|
407
|
+
throw error;
|
|
408
|
+
});
|
|
409
|
+
if (s === null || !s.isDirectory())
|
|
410
|
+
throw new Error(`The project folder ${path} is missing. Put it back where it was (or restore it from a backup) and run the command again. Nothing was changed.`);
|
|
411
|
+
}
|
|
412
|
+
async function assertEmpty(path) {
|
|
413
|
+
const entries = await readdir(path).catch((error) => {
|
|
414
|
+
if (isMissing(error))
|
|
415
|
+
return [];
|
|
416
|
+
throw error;
|
|
417
|
+
});
|
|
418
|
+
if (entries.length > 0)
|
|
419
|
+
throw new Error(`The project folder ${path} already has files in it that Slopify didn't put there. Choose an empty or new folder with --projects-dir <folder>, or move those files away, then try again.`);
|
|
420
|
+
}
|
|
421
|
+
/** The .env compose reads. Single quotes keep every value literal. */
|
|
422
|
+
export function composeEnv(values) {
|
|
423
|
+
return Object.entries(values)
|
|
424
|
+
.map(([key, value]) => {
|
|
425
|
+
if (/['\n\r]/.test(value))
|
|
426
|
+
throw new Error(`${value} can't be used in the Docker settings because it contains a quote or a line break. Choose a folder whose name has neither.`);
|
|
427
|
+
return `${key}='${value}'\n`;
|
|
428
|
+
})
|
|
429
|
+
.join("");
|
|
430
|
+
}
|
|
431
|
+
async function lock(path) {
|
|
432
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
433
|
+
try {
|
|
434
|
+
const file = await open(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
|
|
435
|
+
await file.writeFile(String(process.pid));
|
|
436
|
+
await file.close();
|
|
437
|
+
return () => unlink(path);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
if (!hasCode(error, "EEXIST"))
|
|
441
|
+
throw error;
|
|
442
|
+
const pid = Number((await readFile(path, "utf8").catch(() => "")).trim());
|
|
443
|
+
if (Number.isInteger(pid) && pid > 0 && alive(pid))
|
|
444
|
+
throw new Error(`Another Slopify install or update is already running (process ${pid}). Wait for it to finish, then run the command again.`);
|
|
445
|
+
await unlink(path).catch(() => { });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
throw new Error(`Could not take the lock ${path}. Delete it if no Slopify install is running, and try again.`);
|
|
449
|
+
}
|
|
450
|
+
function alive(pid) {
|
|
451
|
+
try {
|
|
452
|
+
process.kill(pid, 0);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
return hasCode(error, "EPERM");
|
|
457
|
+
}
|
|
458
|
+
}
|