@flisk-sh/flisk 0.0.0-stage → 1.3.189
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/CHANGELOG.md +492 -0
- package/LICENSE +21 -0
- package/README.md +479 -2
- package/Tiltfile +151 -0
- package/cli/bin/flisk.js +42 -0
- package/cli/dist/agent-runtimes/custom.js +335 -0
- package/cli/dist/agent-runtimes/registry.js +34 -0
- package/cli/dist/backend-frameworks/elysia.js +38 -0
- package/cli/dist/backend-frameworks/express.js +49 -0
- package/cli/dist/backend-frameworks/fastify.js +43 -0
- package/cli/dist/backend-frameworks/h3.js +37 -0
- package/cli/dist/backend-frameworks/koa.js +47 -0
- package/cli/dist/backend-frameworks/mcp.js +86 -0
- package/cli/dist/backend-frameworks/nestjs.js +55 -0
- package/cli/dist/backend-frameworks/registry.js +134 -0
- package/cli/dist/backend-frameworks/types.js +1 -0
- package/cli/dist/backend-languages/bun.js +76 -0
- package/cli/dist/backend-languages/go.js +125 -0
- package/cli/dist/backend-languages/node.js +122 -0
- package/cli/dist/backend-languages/python.js +94 -0
- package/cli/dist/backend-languages/registry.js +37 -0
- package/cli/dist/backend-languages/rust.js +82 -0
- package/cli/dist/backend-languages/types.js +1 -0
- package/cli/dist/cli.js +29 -0
- package/cli/dist/commands/__tests__/project-e2e-helpers.js +49 -0
- package/cli/dist/commands/__tests__/ui-loading-screen.test.js +16 -0
- package/cli/dist/commands/agent.js +182 -0
- package/cli/dist/commands/completion-candidates.js +116 -0
- package/cli/dist/commands/completion.js +843 -0
- package/cli/dist/commands/config.js +362 -0
- package/cli/dist/commands/doctor.js +1504 -0
- package/cli/dist/commands/down.js +114 -0
- package/cli/dist/commands/eject.js +108 -0
- package/cli/dist/commands/help.js +152 -0
- package/cli/dist/commands/import.js +164 -0
- package/cli/dist/commands/logs.js +119 -0
- package/cli/dist/commands/maintainers.js +102 -0
- package/cli/dist/commands/mcp.js +28 -0
- package/cli/dist/commands/networks.js +306 -0
- package/cli/dist/commands/project.js +447 -0
- package/cli/dist/commands/projects.js +103 -0
- package/cli/dist/commands/registry.js +53 -0
- package/cli/dist/commands/resource.js +992 -0
- package/cli/dist/commands/resources.js +127 -0
- package/cli/dist/commands/restart.js +89 -0
- package/cli/dist/commands/runtime.js +75 -0
- package/cli/dist/commands/stack.js +171 -0
- package/cli/dist/commands/stacks.js +71 -0
- package/cli/dist/commands/status.js +211 -0
- package/cli/dist/commands/ui.js +725 -0
- package/cli/dist/commands/up.js +609 -0
- package/cli/dist/commands/upgrade.js +840 -0
- package/cli/dist/commands/version.js +10 -0
- package/cli/dist/components/Accessible.js +11 -0
- package/cli/dist/components/BaseTooltip.js +30 -0
- package/cli/dist/components/DetailPanel.js +33 -0
- package/cli/dist/components/FileTree.js +51 -0
- package/cli/dist/components/ResourceSelectInput.js +42 -0
- package/cli/dist/components/ResourceTable.js +35 -0
- package/cli/dist/components/ServiceIssues.js +13 -0
- package/cli/dist/components/TUIHeader.js +27 -0
- package/cli/dist/components/TabBar.js +27 -0
- package/cli/dist/components/Tooltip.js +13 -0
- package/cli/dist/components/index.js +11 -0
- package/cli/dist/components/ui-theme.js +120 -0
- package/cli/dist/config/platform-standards.js +182 -0
- package/cli/dist/frontend-frameworks/lit.js +64 -0
- package/cli/dist/frontend-frameworks/preact.js +62 -0
- package/cli/dist/frontend-frameworks/qwik.js +61 -0
- package/cli/dist/frontend-frameworks/react.js +69 -0
- package/cli/dist/frontend-frameworks/registry.js +83 -0
- package/cli/dist/frontend-frameworks/solid.js +62 -0
- package/cli/dist/frontend-frameworks/svelte.js +67 -0
- package/cli/dist/frontend-frameworks/tanstack-router.js +101 -0
- package/cli/dist/frontend-frameworks/types.js +1 -0
- package/cli/dist/frontend-frameworks/vanilla.js +59 -0
- package/cli/dist/frontend-frameworks/vue.js +72 -0
- package/cli/dist/generator/__tests__/golden-helpers.js +43 -0
- package/cli/dist/generator/extension-fetch.js +291 -0
- package/cli/dist/generator/resource-templates.js +133 -0
- package/cli/dist/generator/template-engine.js +801 -0
- package/cli/dist/index.js +10 -0
- package/cli/dist/mcp/server.js +101 -0
- package/cli/dist/mcp/tools.js +351 -0
- package/cli/dist/program.js +37 -0
- package/cli/dist/types/index.js +38 -0
- package/cli/dist/utils/agent-host.js +36 -0
- package/cli/dist/utils/atomic-write.js +36 -0
- package/cli/dist/utils/bun-floor.js +9 -0
- package/cli/dist/utils/bun-requirement.js +10 -0
- package/cli/dist/utils/cache.js +63 -0
- package/cli/dist/utils/cold-preflight.js +257 -0
- package/cli/dist/utils/command-helpers.js +57 -0
- package/cli/dist/utils/constants.js +160 -0
- package/cli/dist/utils/discovery-context.js +71 -0
- package/cli/dist/utils/discovery-paths.js +93 -0
- package/cli/dist/utils/docker.js +34 -0
- package/cli/dist/utils/doctor-crashloop.js +161 -0
- package/cli/dist/utils/doctor-fix.js +391 -0
- package/cli/dist/utils/doctor-host.js +66 -0
- package/cli/dist/utils/doctor-report.js +78 -0
- package/cli/dist/utils/doctor-runtime.js +880 -0
- package/cli/dist/utils/doctor-starlark.js +132 -0
- package/cli/dist/utils/doctor-wiring.js +698 -0
- package/cli/dist/utils/down-cleanup.js +125 -0
- package/cli/dist/utils/env-validator.js +266 -0
- package/cli/dist/utils/errors.js +175 -0
- package/cli/dist/utils/exec-async.js +31 -0
- package/cli/dist/utils/file-helpers.js +104 -0
- package/cli/dist/utils/formatting.js +165 -0
- package/cli/dist/utils/generated-paths.js +51 -0
- package/cli/dist/utils/help-command.js +11 -0
- package/cli/dist/utils/host-port-config.js +140 -0
- package/cli/dist/utils/host-port-plan.js +100 -0
- package/cli/dist/utils/json-output.js +29 -0
- package/cli/dist/utils/machine-output.js +33 -0
- package/cli/dist/utils/paths.js +43 -0
- package/cli/dist/utils/platform.js +58 -0
- package/cli/dist/utils/port-assignment.js +75 -0
- package/cli/dist/utils/port-conflict-fix.js +25 -0
- package/cli/dist/utils/port-fallback-notice.js +17 -0
- package/cli/dist/utils/port-listener-probe.js +20 -0
- package/cli/dist/utils/project-config-defaults.js +17 -0
- package/cli/dist/utils/project-features.js +91 -0
- package/cli/dist/utils/project-templates.js +32 -0
- package/cli/dist/utils/prompt.js +97 -0
- package/cli/dist/utils/resource-features.js +184 -0
- package/cli/dist/utils/resource-kind.js +9 -0
- package/cli/dist/utils/schema-version-gate.js +45 -0
- package/cli/dist/utils/search-status.js +12 -0
- package/cli/dist/utils/secret-redaction.js +73 -0
- package/cli/dist/utils/service-config-checks.js +189 -0
- package/cli/dist/utils/service-manifest.js +254 -0
- package/cli/dist/utils/service-runtime-state.js +66 -0
- package/cli/dist/utils/service-urls.js +112 -0
- package/cli/dist/utils/services.js +360 -0
- package/cli/dist/utils/shared-infra.js +45 -0
- package/cli/dist/utils/shared-platform-postgres.js +232 -0
- package/cli/dist/utils/single-flight.js +21 -0
- package/cli/dist/utils/smoke.js +401 -0
- package/cli/dist/utils/spinner.js +40 -0
- package/cli/dist/utils/stack-features.js +15 -0
- package/cli/dist/utils/stack-status.js +14 -0
- package/cli/dist/utils/startup-report.js +78 -0
- package/cli/dist/utils/status-message.js +21 -0
- package/cli/dist/utils/status-ports.js +33 -0
- package/cli/dist/utils/suggestions.js +63 -0
- package/cli/dist/utils/tar.js +46 -0
- package/cli/dist/utils/tdk-version.js +51 -0
- package/cli/dist/utils/terminal-layout.js +19 -0
- package/cli/dist/utils/tilt-events.js +194 -0
- package/cli/dist/utils/tilt-logs.js +61 -0
- package/cli/dist/utils/tilt-port-owner.js +63 -0
- package/cli/dist/utils/tilt-process.js +54 -0
- package/cli/dist/utils/tilt-resource-state.js +32 -0
- package/cli/dist/utils/tilt-startup.js +49 -0
- package/cli/dist/utils/tilt-unreachable-message.js +9 -0
- package/cli/dist/utils/tilt.js +104 -0
- package/cli/dist/utils/ui-keymap.js +129 -0
- package/cli/dist/utils/ui-service-state.js +34 -0
- package/cli/dist/utils/up-only.js +28 -0
- package/cli/dist/utils/up-readiness.js +118 -0
- package/cli/dist/utils/url-actions.js +103 -0
- package/cli/dist/utils/validation.js +62 -0
- package/cli/dist/utils/which.js +56 -0
- package/cli/dist/utils/windows-doctor.js +176 -0
- package/cli/dist-bundle/chunk-6EA4DGCY.js +2 -0
- package/cli/dist-bundle/chunk-BZ4H7MM4.js +3 -0
- package/cli/dist-bundle/chunk-QWYXYAX6.js +510 -0
- package/cli/dist-bundle/cli.js +2017 -0
- package/cli/dist-bundle/cold-preflight-MM46GFQA.js +2 -0
- package/cli/dist-bundle/devtools-ZUHXW33W.js +5 -0
- package/cli/dist-bundle/extension-fetch-J4FFH7TJ.js +2 -0
- package/cli/package.json +83 -0
- package/cli/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +25 -0
- package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +466 -0
- package/cli/shared-platform-engineering/docker-templates/install-deps.sh +154 -0
- package/cli/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +14 -0
- package/cli/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +58 -0
- package/cli/templates/.tiltignore.hbs +72 -0
- package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +220 -0
- package/cli/templates/TILT_TECH_STACK.star.hbs +149 -0
- package/cli/templates/Tiltfile.hbs +381 -0
- package/cli/templates/spec.master.hbs +192 -0
- package/discovery/AGENTS.md +362 -0
- package/discovery/TILT_DISCOVERY.star +429 -0
- package/discovery/Tiltfile +58 -0
- package/discovery/config.star +265 -0
- package/discovery/constants.star +167 -0
- package/discovery/deterministic.star +213 -0
- package/discovery/discovery_daemon.star +193 -0
- package/discovery/discovery_orchestrator.star +492 -0
- package/discovery/filtering.star +7 -0
- package/discovery/identity-management-backend/service.yaml +26 -0
- package/discovery/identity-management-frontend/service.yaml +29 -0
- package/discovery/identity-sdk/service.yaml +9 -0
- package/discovery/incremental_discovery.star +215 -0
- package/discovery/indexing.star +9 -0
- package/discovery/json_manifest_scanner.star +57 -0
- package/discovery/libraries.star +48 -0
- package/discovery/loading.star +25 -0
- package/discovery/manifest/constants.star +106 -0
- package/discovery/manifest/loading.star +418 -0
- package/discovery/manifest/normalize.star +289 -0
- package/discovery/manifest/validation.star +110 -0
- package/discovery/normalization.star +13 -0
- package/discovery/registry.star +562 -0
- package/discovery/resource_snapshot.py +384 -0
- package/discovery/resource_snapshot.star +145 -0
- package/discovery/schemas.star +3 -0
- package/discovery/services/platform/cdc/debezium/nats-connector/service.yaml +9 -0
- package/discovery/services/platform/cdc/nats-http-bridge/service.yaml +9 -0
- package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +26 -0
- package/discovery/services/platform/synthetic-monitor/service.yaml +9 -0
- package/discovery/test_resource_snapshot.py +253 -0
- package/discovery/validation.star +80 -0
- package/discovery/versioning.star +3 -0
- package/engine/AGENTS.md +706 -0
- package/engine/README.md +31 -0
- package/engine/REFACTORING_MANIFEST_SYSTEM.md +974 -0
- package/engine/Tiltfile +86 -0
- package/engine/assets/docker/golden-layers.Dockerfile +204 -0
- package/engine/assets/docker/wake-gateway/Dockerfile +28 -0
- package/engine/assets/docker/wake-gateway/decide.test.ts +177 -0
- package/engine/assets/docker/wake-gateway/decide.ts +135 -0
- package/engine/assets/docker/wake-gateway/server.ts +363 -0
- package/engine/assets/scripts/cleanup-completed-migrator-images.sh +39 -0
- package/engine/assets/scripts/cleanup-tilt-images.sh +122 -0
- package/engine/assets/scripts/mac-m1-resource-monitor.sh +123 -0
- package/engine/config.star +5 -0
- package/engine/entrypoint.star +101 -0
- package/engine/lifecycle/orchestrator.star +263 -0
- package/engine/paths.star +719 -0
- package/engine/schemas/project-schema.json +147 -0
- package/engine/schemas/service-schema.json +537 -0
- package/engine/scripts/README.md +89 -0
- package/engine/scripts/build-and-publish-lib.sh +207 -0
- package/engine/scripts/check-lib-publish.sh +87 -0
- package/engine/scripts/debug-container.sh +265 -0
- package/engine/scripts/generate-manifest.sh +200 -0
- package/engine/spec.master +702 -0
- package/engine/spec.master.example +341 -0
- package/engine/topologies/README.md +33 -0
- package/engine/topologies/platform/docker/AGENTS.md +59 -0
- package/engine/topologies/platform/docker/auth/auth.star +207 -0
- package/engine/topologies/platform/docker/build/frontend_build_plan.star +65 -0
- package/engine/topologies/platform/docker/build/golden_image.star +37 -0
- package/engine/topologies/platform/docker/build/golden_image_build.star +144 -0
- package/engine/topologies/platform/docker/build/golden_image_constants.star +100 -0
- package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +263 -0
- package/engine/topologies/platform/docker/compose/compose.star +637 -0
- package/engine/topologies/platform/docker/compose/compose_helpers.star +54 -0
- package/engine/topologies/platform/docker/compose/traefik_standalone.star +171 -0
- package/engine/topologies/platform/docker/config/bunignore.star +144 -0
- package/engine/topologies/platform/docker/config/dockerignore.star +152 -0
- package/engine/topologies/platform/docker/config/healthcheck.star +58 -0
- package/engine/topologies/platform/docker/constants.star +211 -0
- package/engine/topologies/platform/docker/dockerfile/dockerfile.star +56 -0
- package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +264 -0
- package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +76 -0
- package/engine/topologies/platform/docker/index.star +40 -0
- package/engine/topologies/platform/docker/layers/bun_helpers.star +25 -0
- package/engine/topologies/platform/docker/layers/infisical/index.star +42 -0
- package/engine/topologies/platform/docker/layers/infisical/infisical_docker.star +264 -0
- package/engine/topologies/platform/docker/layers/l1_base_layers.star +89 -0
- package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +147 -0
- package/engine/topologies/platform/docker/layers/l3_builder_layers.star +229 -0
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +282 -0
- package/engine/topologies/platform/docker/layers/prisma/constants.star +138 -0
- package/engine/topologies/platform/docker/layers/prisma/db_readiness.star +98 -0
- package/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +131 -0
- package/engine/topologies/platform/docker/layers/prisma/migrate_script.star +100 -0
- package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +128 -0
- package/engine/topologies/platform/docker/layers/prisma/prisma_runtime.star +205 -0
- package/engine/topologies/platform/docker/layers/prisma/secret_resolution.star +121 -0
- package/engine/topologies/platform/docker/networking/api_path_constants.star +206 -0
- package/engine/topologies/platform/docker/networking/sablier_container_cycle.star +31 -0
- package/engine/topologies/platform/docker/networking/traefik.star +266 -0
- package/engine/topologies/platform/docker/networking/traefik_constants.star +148 -0
- package/engine/topologies/platform/docker/networking/traefik_helpers.star +162 -0
- package/engine/topologies/platform/docker/networking/traefik_static_routes.star +165 -0
- package/engine/topologies/platform/docker/secrets/env_secrets.star +74 -0
- package/engine/topologies/platform/docker/secrets/infisical.star +109 -0
- package/engine/topologies/platform/net/AGENTS.md +64 -0
- package/engine/topologies/platform/net/proxy.star +6 -0
- package/engine/topologies/platform/registries/local.star +6 -0
- package/engine/topologies/platform/registries/verdaccio_loader.star +20 -0
- package/engine/topologies/platform/security/AGENTS.md +49 -0
- package/engine/topologies/platform/security/secrets.star +241 -0
- package/engine/topologies/platform/state/AGENTS.md +64 -0
- package/engine/topologies/platform/state/relational.star +6 -0
- package/engine/topologies/tilt/common/AGENTS.md +59 -0
- package/engine/topologies/tilt/common/utils.star +409 -0
- package/engine/topologies/tilt/common/utils_debug.star +71 -0
- package/engine/topologies/tilt/common/utils_deterministic.star +170 -0
- package/engine/topologies/tilt/common/utils_docker_networks.star +54 -0
- package/engine/topologies/tilt/common/utils_env.star +92 -0
- package/engine/topologies/tilt/common/utils_output.star +99 -0
- package/engine/topologies/tilt/config/AGENTS.md +68 -0
- package/engine/topologies/tilt/config/defaults.star +5 -0
- package/engine/topologies/tilt/config/feature_flags.star +12 -0
- package/engine/topologies/tilt/config/global.star +57 -0
- package/engine/topologies/tilt/config/profiles.star +398 -0
- package/engine/topologies/tilt/generators/agents_md.star +64 -0
- package/engine/topologies/tilt/generators/api_gateway.star +147 -0
- package/engine/topologies/tilt/generators/index.star +126 -0
- package/engine/topologies/tilt/generators/infisical/README.md +424 -0
- package/engine/topologies/tilt/generators/infisical/examples.star +314 -0
- package/engine/topologies/tilt/generators/infisical/index.star +59 -0
- package/engine/topologies/tilt/generators/infisical/machine_identity.star +422 -0
- package/engine/topologies/tilt/generators/infisical/organization.star +536 -0
- package/engine/topologies/tilt/generators/infisical/path_manager.star +478 -0
- package/engine/topologies/tilt/generators/infisical/secrets_generator.star +422 -0
- package/engine/topologies/tilt/generators/nginx_conf.star +145 -0
- package/engine/topologies/tilt/generators/npmrc.star +132 -0
- package/engine/topologies/tilt/generators/tsconfig.star +25 -0
- package/engine/topologies/tilt/generators/typescript/backend_library_tsconfig.star +73 -0
- package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +137 -0
- package/engine/topologies/tilt/generators/typescript/common.star +9 -0
- package/engine/topologies/tilt/generators/typescript/frontend_library_tsconfig.star +78 -0
- package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +140 -0
- package/engine/topologies/tilt/generators/typescript/include.star +25 -0
- package/engine/topologies/tilt/generators/typescript/playwright_config.star +42 -0
- package/engine/topologies/tilt/generators/typescript/prisma_config.star +51 -0
- package/engine/topologies/tilt/generators/validators.star +868 -0
- package/engine/topologies/tilt/generators/vite/backend.star +80 -0
- package/engine/topologies/tilt/generators/vite/frameworks/lit.star +92 -0
- package/engine/topologies/tilt/generators/vite/frameworks/preact.star +94 -0
- package/engine/topologies/tilt/generators/vite/frameworks/qwik.star +94 -0
- package/engine/topologies/tilt/generators/vite/frameworks/react.star +128 -0
- package/engine/topologies/tilt/generators/vite/frameworks/solid.star +94 -0
- package/engine/topologies/tilt/generators/vite/frameworks/svelte.star +94 -0
- package/engine/topologies/tilt/generators/vite/frameworks/vanilla.star +92 -0
- package/engine/topologies/tilt/generators/vite/frameworks/vue.star +94 -0
- package/engine/topologies/tilt/generators/vite/frontend.star +153 -0
- package/engine/topologies/tilt/generators/vite/generators.star +45 -0
- package/engine/topologies/tilt/generators/vite/helpers.star +256 -0
- package/engine/topologies/tilt/generators/vite/library.star +67 -0
- package/engine/topologies/tilt/generators/vite/sdk.star +75 -0
- package/engine/topologies/tilt/generators/vite/templates.star +460 -0
- package/engine/topologies/tilt/generators/vite_config.star +24 -0
- package/engine/topologies/tilt/manifest/AGENTS.md +61 -0
- package/engine/topologies/tilt/manifest/README.md +679 -0
- package/engine/topologies/tilt/manifest/__init__.star +220 -0
- package/engine/topologies/tilt/manifest/constants.star +262 -0
- package/engine/topologies/tilt/manifest/errors.star +268 -0
- package/engine/topologies/tilt/manifest/integration.star +348 -0
- package/engine/topologies/tilt/manifest/json_to_yaml.py +61 -0
- package/engine/topologies/tilt/manifest/loader.star +239 -0
- package/engine/topologies/tilt/manifest/mapper.star +229 -0
- package/engine/topologies/tilt/manifest/parser.star +490 -0
- package/engine/topologies/tilt/manifest/schema.star +502 -0
- package/engine/topologies/tilt/manifest/tests/test_integration.star +278 -0
- package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +475 -0
- package/engine/topologies/tilt/manifest/tests/test_manifest.star +319 -0
- package/engine/topologies/tilt/manifest/tests/test_validator.star +472 -0
- package/engine/topologies/tilt/manifest/validator.star +744 -0
- package/engine/topologies/tilt/manifest/yaml_to_json.py +20 -0
- package/engine/topologies/tilt/resources/AGENTS.md +71 -0
- package/engine/topologies/tilt/resources/build_concurrency.star +56 -0
- package/engine/topologies/tilt/resources/conditions.star +5 -0
- package/engine/topologies/tilt/resources/databases.star +246 -0
- package/engine/topologies/tilt/resources/declaration.star +5 -0
- package/engine/topologies/tilt/resources/deps.star +5 -0
- package/engine/topologies/tilt/resources/infra-loader.star +504 -0
- package/engine/topologies/tilt/resources/libs.star +32 -0
- package/engine/topologies/tilt/resources/libs_discovery.star +104 -0
- package/engine/topologies/tilt/resources/libs_paths.star +81 -0
- package/engine/topologies/tilt/resources/libs_resource_definition.star +153 -0
- package/engine/topologies/tilt/resources/libs_setup.star +166 -0
- package/engine/topologies/tilt/resources/libs_tsconfig.star +39 -0
- package/engine/topologies/tilt/resources/orchestrator/apply.star +79 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +864 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_manifest_orchestration.star +90 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_migrator_orchestration.star +150 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_resource_validation.star +38 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_runtime_flags.star +46 -0
- package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +193 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/AGENTS.md +75 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/index.star +23 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +136 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/env.star +219 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +172 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +577 -0
- package/engine/topologies/tilt/resources/orchestrator/helpers.star +35 -0
- package/engine/topologies/tilt/resources/orchestrator.star +56 -0
- package/engine/topologies/tilt/resources/ordering.star +5 -0
- package/engine/topologies/tilt/resources/shared-platform-postgres.star +30 -0
- package/engine/topologies/tilt/resources/triggers.star +5 -0
- package/engine/topologies/tilt/resources/watchers.star +304 -0
- package/engine/where-things-live.star +719 -0
- package/examples/tdk-example/LICENSE +21 -0
- package/examples/tdk-example/README.md +23 -0
- package/examples/tdk-example/TILT_SERVICE_DEFAULTS.star +42 -0
- package/examples/tdk-example/TILT_TECH_STACK.star +67 -0
- package/examples/tdk-example/Tiltfile +25 -0
- package/examples/tdk-example/services/platform/messaging/docker-compose.yml +26 -0
- package/examples/tdk-example/services/shop/orders-api/package.json +20 -0
- package/examples/tdk-example/services/shop/orders-api/service.json +14 -0
- package/examples/tdk-example/services/shop/orders-api/src/index.ts +81 -0
- package/examples/tdk-example/services/shop/orders-app/index.html +12 -0
- package/examples/tdk-example/services/shop/orders-app/package.json +23 -0
- package/examples/tdk-example/services/shop/orders-app/public/.gitkeep +0 -0
- package/examples/tdk-example/services/shop/orders-app/service.json +9 -0
- package/examples/tdk-example/services/shop/orders-app/src/main.tsx +9 -0
- package/examples/tdk-example/services/shop/orders-app/src/orders.tsx +67 -0
- package/examples/tdk-example/services/shop/orders-app/src/style.css +15 -0
- package/examples/tdk-example/services/shop/orders-worker/package.json +18 -0
- package/examples/tdk-example/services/shop/orders-worker/service.json +13 -0
- package/examples/tdk-example/services/shop/orders-worker/src/index.ts +33 -0
- package/ext/Tiltfile +66 -0
- package/ext/ide-components/README.md +221 -0
- package/ext/ide-components/code_viewer/server.py +208 -0
- package/ext/ide-components/config_inspector/server.py +1052 -0
- package/ext/ide-components/file_browser/server.py +287 -0
- package/ext/ide-components/shared/config_service.py +471 -0
- package/ext/ide-components/shared/file_utils.py +300 -0
- package/ext/ide-components/shared/http_utils.py +234 -0
- package/ext/ide-components/shared/static/styles.css +1334 -0
- package/ext/ide-components/shared/syntax_highlighter.py +142 -0
- package/ext/ide-components/shared/template_engine.py +125 -0
- package/ext/ide-components/shared/templates/base.html +121 -0
- package/ext/ide-components/shared/test_config_service.py +328 -0
- package/ext/ide-components/shared/tilt_integration.py +160 -0
- package/ext/ui-enhancements/cron-status-server.sh +51 -0
- package/ext/ui-enhancements/cron-status-tracker.sh +148 -0
- package/ext/ui-enhancements/extension.yaml +36 -0
- package/ext/ui-enhancements/tiltfile.py +101 -0
- package/ext/ui-enhancements/ui/cron-jobs-tab.css +396 -0
- package/ext/ui-enhancements/ui/help-panel.css +279 -0
- package/ext/ui-enhancements/ui/icons.css +125 -0
- package/ext/ui-enhancements/ui/tooltips.css +114 -0
- package/package.json +94 -4
- package/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +25 -0
- package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +466 -0
- package/shared-platform-engineering/docker-templates/install-deps.sh +154 -0
- package/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +14 -0
- package/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +58 -0
- package/specs/specs/TILT_SERVICE_DEFAULTS.star +207 -0
- package/specs/specs/TILT_TECH_STACK.star +161 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { DATABASE_MANAGEMENT_FEATURE, PROJECT_JSON } from "./constants.js";
|
|
6
|
+
import { DEFAULT_ALWAYS_ENABLED_INFRA } from "./project-config-defaults.js";
|
|
7
|
+
import { discoverResourcesFromRoot } from "./services.js";
|
|
8
|
+
/**
|
|
9
|
+
* Shared platform Postgres start predicate.
|
|
10
|
+
*
|
|
11
|
+
* Both names mean the one shared platform Postgres Tilt resource `postgres`.
|
|
12
|
+
* The engine start path is selection-bounded (flisk up --only); verify/doctor
|
|
13
|
+
* evaluate the project resource set they already inspect (no flisk up filter).
|
|
14
|
+
* Both surfaces use this same predicate shape over their own resource set.
|
|
15
|
+
*
|
|
16
|
+
* Feature state is read from project.json **and** the generated Tiltfile /
|
|
17
|
+
* spec.master when present, because a stock `flisk up` focus pass expands
|
|
18
|
+
* CORE_INFRA → database-management even when project.json omits it.
|
|
19
|
+
*
|
|
20
|
+
* Spec: openspec/changes/dependson-starts-platform-postgres
|
|
21
|
+
*/
|
|
22
|
+
export const POSTGRES_DEPENDENCY_NAMES = ["postgres", DATABASE_MANAGEMENT_FEATURE];
|
|
23
|
+
const PHASE_KEYS = ["pre_alpha", "alpha", "beta", "out_of_scope"];
|
|
24
|
+
export function isSharedPlatformPostgresDependency(name) {
|
|
25
|
+
return POSTGRES_DEPENDENCY_NAMES.includes(name);
|
|
26
|
+
}
|
|
27
|
+
function readProjectJson(projectRoot) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(readFileSync(join(projectRoot, ".tdk", PROJECT_JSON), "utf-8"));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function readTextIfExists(path) {
|
|
36
|
+
try {
|
|
37
|
+
return existsSync(path) ? readFileSync(path, "utf-8") : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Finds `name = [ ... ]` and returns its quoted strings. Plain string scanning, not a RegExp built from the name.
|
|
44
|
+
function parseStarlarkStringList(source, name) {
|
|
45
|
+
const skipSpaces = (at) => {
|
|
46
|
+
let i = at;
|
|
47
|
+
while (i < source.length && /\s/.test(source[i]))
|
|
48
|
+
i += 1;
|
|
49
|
+
return i;
|
|
50
|
+
};
|
|
51
|
+
let from = 0;
|
|
52
|
+
for (;;) {
|
|
53
|
+
const at = source.indexOf(name, from);
|
|
54
|
+
if (at === -1)
|
|
55
|
+
return undefined;
|
|
56
|
+
let i = skipSpaces(at + name.length);
|
|
57
|
+
if (source[i] === "=") {
|
|
58
|
+
i = skipSpaces(i + 1);
|
|
59
|
+
if (source[i] === "[") {
|
|
60
|
+
const end = source.indexOf("]", i + 1);
|
|
61
|
+
if (end === -1)
|
|
62
|
+
return undefined;
|
|
63
|
+
return [...source.slice(i + 1, end).matchAll(/"([^"]+)"|'([^']+)'/g)].map((x) => x[1] ?? x[2] ?? "");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
from = at + name.length;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function tiltfileAlwaysEnabledInfra(projectRoot) {
|
|
70
|
+
const path = join(projectRoot, ".tdk", ".tdk-out", "Tiltfile");
|
|
71
|
+
const text = readTextIfExists(path);
|
|
72
|
+
if (!text)
|
|
73
|
+
return undefined;
|
|
74
|
+
return parseStarlarkStringList(text, "ALWAYS_ENABLED_INFRA");
|
|
75
|
+
}
|
|
76
|
+
function specMasterHasDatabaseManagement(projectRoot) {
|
|
77
|
+
const path = join(projectRoot, ".tdk", ".tdk-out", "spec.master");
|
|
78
|
+
const text = readTextIfExists(path);
|
|
79
|
+
if (text === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
// PRE_ALPHA/DEFAULTS style: "database-management": True
|
|
82
|
+
return /"database-management"\s*:\s*True/.test(text);
|
|
83
|
+
}
|
|
84
|
+
function generatedDefaultFocusEnablesDatabaseManagement(projectRoot) {
|
|
85
|
+
const output = join(projectRoot, ".tdk", ".tdk-out");
|
|
86
|
+
const tiltfile = readTextIfExists(join(output, "Tiltfile"));
|
|
87
|
+
const spec = readTextIfExists(join(output, "spec.master"));
|
|
88
|
+
const config = readTextIfExists(join(output, "tdk-cli-ext", "discovery", "config.star"));
|
|
89
|
+
const profiles = readTextIfExists(join(output, "tdk-cli-ext", "engine", "topologies", "tilt", "config", "profiles.star"));
|
|
90
|
+
if (!tiltfile || !spec || !config || !profiles)
|
|
91
|
+
return false;
|
|
92
|
+
const preAlpha = spec.match(/PRE_ALPHA_RESOURCES\s*=\s*\{([^}]*)\}/)?.[1];
|
|
93
|
+
const coreInfra = parseStarlarkStringList(config, "CORE_INFRA");
|
|
94
|
+
return (/FOCUS_MODE\s*,\s*FOCUS_ENABLED_ALL\s*,\s*FOCUS_ENABLED_RESOURCES\s*=\s*Config\.apply_focus\(cfg\)/.test(tiltfile) &&
|
|
95
|
+
/if FOCUS_MODE and FOCUS_ENABLED_ALL\s*:/.test(tiltfile) &&
|
|
96
|
+
/"[^"]+"\s*:\s*True|'[^']+'\s*:\s*True/.test(preAlpha ?? "") &&
|
|
97
|
+
(coreInfra?.includes("postgres") ?? false) &&
|
|
98
|
+
/["']postgres["']\s*:\s*["']database-management["']/.test(config) &&
|
|
99
|
+
/for infra in CORE_INFRA_EXPORT\s*:/.test(profiles) &&
|
|
100
|
+
/needed\[INFRA_STACK_MAP_EXPORT\[infra\]\]\s*=\s*True/.test(profiles));
|
|
101
|
+
}
|
|
102
|
+
function projectJsonFeatureOn(projectRoot) {
|
|
103
|
+
const parsed = readProjectJson(projectRoot);
|
|
104
|
+
if (!parsed)
|
|
105
|
+
return false;
|
|
106
|
+
const phases = (parsed.phases ?? {});
|
|
107
|
+
for (const phase of PHASE_KEYS) {
|
|
108
|
+
const stacks = phases[phase]?.enabledStacks;
|
|
109
|
+
if (Array.isArray(stacks) && stacks.includes(DATABASE_MANAGEMENT_FEATURE)) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const alwaysEnabled = parsed.always_enabled_infra ?? DEFAULT_ALWAYS_ENABLED_INFRA;
|
|
114
|
+
return Array.isArray(alwaysEnabled) && alwaysEnabled.includes(DATABASE_MANAGEMENT_FEATURE);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* True when database-management is on for this project from project.json
|
|
118
|
+
* **or** the generated Tiltfile/spec.master when those files exist.
|
|
119
|
+
*/
|
|
120
|
+
export function databaseManagementEnabled(projectRoot) {
|
|
121
|
+
if (projectJsonFeatureOn(projectRoot))
|
|
122
|
+
return true;
|
|
123
|
+
const fromTilt = tiltfileAlwaysEnabledInfra(projectRoot);
|
|
124
|
+
if (Array.isArray(fromTilt) && fromTilt.includes(DATABASE_MANAGEMENT_FEATURE))
|
|
125
|
+
return true;
|
|
126
|
+
if (specMasterHasDatabaseManagement(projectRoot) === true)
|
|
127
|
+
return true;
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
/** Known names for unknown-dependsOn detection: discovered services/stacks + project stacks/infra. */
|
|
131
|
+
function knownDependsonNames(projectRoot, resources) {
|
|
132
|
+
const known = new Set();
|
|
133
|
+
for (const name of POSTGRES_DEPENDENCY_NAMES)
|
|
134
|
+
known.add(name);
|
|
135
|
+
for (const resource of resources) {
|
|
136
|
+
known.add(resource.name);
|
|
137
|
+
if (resource.stack)
|
|
138
|
+
known.add(resource.stack);
|
|
139
|
+
}
|
|
140
|
+
const parsed = readProjectJson(projectRoot);
|
|
141
|
+
if (parsed) {
|
|
142
|
+
const phases = (parsed.phases ?? {});
|
|
143
|
+
for (const phase of PHASE_KEYS) {
|
|
144
|
+
const stacks = phases[phase]?.enabledStacks;
|
|
145
|
+
if (Array.isArray(stacks)) {
|
|
146
|
+
for (const stack of stacks)
|
|
147
|
+
known.add(stack);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const alwaysEnabled = parsed.always_enabled_infra ?? DEFAULT_ALWAYS_ENABLED_INFRA;
|
|
151
|
+
if (Array.isArray(alwaysEnabled)) {
|
|
152
|
+
for (const entry of alwaysEnabled)
|
|
153
|
+
known.add(entry);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const fromTilt = tiltfileAlwaysEnabledInfra(projectRoot);
|
|
157
|
+
if (Array.isArray(fromTilt)) {
|
|
158
|
+
for (const entry of fromTilt)
|
|
159
|
+
known.add(entry);
|
|
160
|
+
}
|
|
161
|
+
return known;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Evaluate whether shared platform Postgres will start for this project.
|
|
165
|
+
*
|
|
166
|
+
* willStart = featureOn OR any inspected project resource depends on either name.
|
|
167
|
+
* Resource set is project scope (discoverResourcesFromRoot), NOT flisk up --only.
|
|
168
|
+
*
|
|
169
|
+
* Also reports focusWouldEnableDatabaseManagement so doctor/verify can warn that
|
|
170
|
+
* default focus expands CORE_INFRA → database-management even when project.json
|
|
171
|
+
* says the feature is off (the 7.1 gap).
|
|
172
|
+
*/
|
|
173
|
+
export function evaluateSharedPlatformPostgres(projectRoot, resources = discoverResourcesFromRoot(projectRoot)) {
|
|
174
|
+
const featureOnFromProjectJson = projectJsonFeatureOn(projectRoot);
|
|
175
|
+
const fromTilt = tiltfileAlwaysEnabledInfra(projectRoot);
|
|
176
|
+
const featureOnFromTiltfile = Array.isArray(fromTilt) && fromTilt.includes(DATABASE_MANAGEMENT_FEATURE);
|
|
177
|
+
const featureOnFromSpecMaster = specMasterHasDatabaseManagement(projectRoot) === true;
|
|
178
|
+
const featureOn = featureOnFromProjectJson || featureOnFromTiltfile || featureOnFromSpecMaster;
|
|
179
|
+
const focusWouldEnableDatabaseManagement = !featureOnFromProjectJson && generatedDefaultFocusEnablesDatabaseManagement(projectRoot);
|
|
180
|
+
const known = knownDependsonNames(projectRoot, resources);
|
|
181
|
+
const dependsOnUsers = [];
|
|
182
|
+
const unknownDependsOnNames = [];
|
|
183
|
+
for (const resource of resources) {
|
|
184
|
+
const deps = resource.config?.dependsOn ?? [];
|
|
185
|
+
let usesSharedPostgres = false;
|
|
186
|
+
for (const dep of deps) {
|
|
187
|
+
if (isSharedPlatformPostgresDependency(dep)) {
|
|
188
|
+
usesSharedPostgres = true;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!known.has(dep)) {
|
|
192
|
+
unknownDependsOnNames.push({ resource: resource.name, name: dep });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (usesSharedPostgres) {
|
|
196
|
+
dependsOnUsers.push(resource.name);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const willStart = featureOn || dependsOnUsers.length > 0;
|
|
200
|
+
const reason = featureOn
|
|
201
|
+
? "feature"
|
|
202
|
+
: dependsOnUsers.length > 0
|
|
203
|
+
? "dependsOn"
|
|
204
|
+
: "none";
|
|
205
|
+
return {
|
|
206
|
+
featureOn,
|
|
207
|
+
featureOnFromProjectJson,
|
|
208
|
+
featureOnFromTiltfile,
|
|
209
|
+
featureOnFromSpecMaster,
|
|
210
|
+
focusWouldEnableDatabaseManagement,
|
|
211
|
+
dependsOnUsers,
|
|
212
|
+
willStart,
|
|
213
|
+
reason,
|
|
214
|
+
unknownDependsOnNames,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/** Human-readable will-start / will-not-start sentence shared by doctor + verify. */
|
|
218
|
+
export function sharedPlatformPostgresMessage(evaluation) {
|
|
219
|
+
const projectScopeNote = " (project resource set; flisk up may select a subset and not start Postgres)";
|
|
220
|
+
if (!evaluation.willStart) {
|
|
221
|
+
const focusNote = evaluation.focusWouldEnableDatabaseManagement
|
|
222
|
+
? " Note: default focus/CORE_INFRA expansion would enable database-management on a typical flisk up even when project.json lists it off."
|
|
223
|
+
: "";
|
|
224
|
+
return `Shared platform Postgres will not start for this project.json/generated Tiltfile state (no database-management feature, no selected dependsOn).${focusNote}`;
|
|
225
|
+
}
|
|
226
|
+
const why = evaluation.reason === "feature"
|
|
227
|
+
? `the database-management feature is enabled${evaluation.featureOnFromTiltfile || evaluation.featureOnFromSpecMaster
|
|
228
|
+
? " (generated Tiltfile/spec.master)"
|
|
229
|
+
: ""}`
|
|
230
|
+
: `resource(s) ${evaluation.dependsOnUsers.join(", ")} depend on postgres/database-management`;
|
|
231
|
+
return `Shared platform Postgres will start because ${why}${projectScopeNote}`;
|
|
232
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Runs a load and applies its result, but starts nothing while a previous load is still running. A poll on a timer
|
|
5
|
+
* therefore never overlaps itself: results cannot arrive out of order, and a slow Tilt cannot make every poll get
|
|
6
|
+
* superseded before it finishes.
|
|
7
|
+
*/
|
|
8
|
+
export function singleFlight(apply) {
|
|
9
|
+
let running = false;
|
|
10
|
+
return async (load) => {
|
|
11
|
+
if (running)
|
|
12
|
+
return;
|
|
13
|
+
running = true;
|
|
14
|
+
try {
|
|
15
|
+
apply(await load());
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
running = false;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { copyFileSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { BRING_YOUR_OWN_TYPE } from "./constants.js";
|
|
6
|
+
import { isApiServiceType } from "./resource-kind.js";
|
|
7
|
+
import { createEnvRedactor } from "./secret-redaction.js";
|
|
8
|
+
import { resolveServicePath, resolveSubdomainBases } from "./service-urls.js";
|
|
9
|
+
export const DEFAULT_SMOKE_TIMEOUT_SECONDS = 60;
|
|
10
|
+
const RETRY_DELAY_MS = 1000;
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
12
|
+
const BODY_SNIPPET_CHARS = 200;
|
|
13
|
+
/** Largest response body kept in a smoke record. */
|
|
14
|
+
export const SMOKE_RECORD_BODY_MAX_BYTES = 64 * 1024;
|
|
15
|
+
/** Statuses a proxy answers while a route or its upstream is not up yet. Anything else is the service's real answer. */
|
|
16
|
+
const NOT_READY_STATUSES = new Set([404, 502, 503, 504]);
|
|
17
|
+
/** Error codes that prove the request never left this machine, so repeating even a write is safe. */
|
|
18
|
+
const PRE_SEND_ERROR_CODES = new Set([
|
|
19
|
+
"ECONNREFUSED",
|
|
20
|
+
"ENOTFOUND",
|
|
21
|
+
"EAI_AGAIN",
|
|
22
|
+
"EHOSTUNREACH",
|
|
23
|
+
"ENETUNREACH",
|
|
24
|
+
]);
|
|
25
|
+
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
/** A thrown request error is retried for safe methods, and for others only when its code shows the request was never sent. */
|
|
30
|
+
function shouldRetryRequestError(err, method) {
|
|
31
|
+
if (SAFE_METHODS.has(method))
|
|
32
|
+
return true;
|
|
33
|
+
if (!isRecord(err))
|
|
34
|
+
return false;
|
|
35
|
+
const cause = isRecord(err.cause) ? err.cause : undefined;
|
|
36
|
+
const code = err.code ?? cause?.code;
|
|
37
|
+
return typeof code === "string" && PRE_SEND_ERROR_CODES.has(code);
|
|
38
|
+
}
|
|
39
|
+
export function validateSmoke(smoke) {
|
|
40
|
+
if (!isRecord(smoke))
|
|
41
|
+
return ['smoke: must be an object with "via" and "steps"'];
|
|
42
|
+
const errors = [];
|
|
43
|
+
if (smoke.via !== "proxy") {
|
|
44
|
+
errors.push('smoke.via: only "proxy" is supported (the check must go through the public URL)');
|
|
45
|
+
}
|
|
46
|
+
if (smoke.timeoutSeconds !== undefined &&
|
|
47
|
+
!(typeof smoke.timeoutSeconds === "number" && smoke.timeoutSeconds > 0)) {
|
|
48
|
+
errors.push("smoke.timeoutSeconds: must be a number greater than 0");
|
|
49
|
+
}
|
|
50
|
+
if (!Array.isArray(smoke.steps) || smoke.steps.length === 0) {
|
|
51
|
+
errors.push("smoke.steps: needs at least one step");
|
|
52
|
+
return errors;
|
|
53
|
+
}
|
|
54
|
+
smoke.steps.forEach((step, index) => {
|
|
55
|
+
const at = `smoke.steps[${index}]`;
|
|
56
|
+
if (!isRecord(step)) {
|
|
57
|
+
errors.push(`${at}: must be an object`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const path = step.path;
|
|
61
|
+
if (typeof path !== "string" || !path.startsWith("/") || path.startsWith("//")) {
|
|
62
|
+
errors.push(`${at}.path: must start with "/" and is appended to the public URL; a full URL or relative path would skip the proxy`);
|
|
63
|
+
}
|
|
64
|
+
if (step.expect !== undefined &&
|
|
65
|
+
!(typeof step.expect === "number" &&
|
|
66
|
+
Number.isInteger(step.expect) &&
|
|
67
|
+
step.expect >= 100 &&
|
|
68
|
+
step.expect <= 599)) {
|
|
69
|
+
errors.push(`${at}.expect: must be an HTTP status between 100 and 599`);
|
|
70
|
+
}
|
|
71
|
+
if (step.bodyContains !== undefined && typeof step.bodyContains !== "string") {
|
|
72
|
+
errors.push(`${at}.bodyContains: must be a string`);
|
|
73
|
+
}
|
|
74
|
+
if (step.save !== undefined) {
|
|
75
|
+
const ok = isRecord(step.save) &&
|
|
76
|
+
Object.values(step.save).every((v) => typeof v === "string" && v.startsWith("$."));
|
|
77
|
+
if (!ok)
|
|
78
|
+
errors.push(`${at}.save: must map names to paths such as "$.id"`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return errors;
|
|
82
|
+
}
|
|
83
|
+
/** One plan per routable service that declares `smoke`. Workers and BYO with exposeViaProxy false have no public route. */
|
|
84
|
+
export function buildSmokePlans(resources, ingressPort) {
|
|
85
|
+
const { appBase, apiBase } = resolveSubdomainBases(ingressPort);
|
|
86
|
+
const plans = [];
|
|
87
|
+
for (const resource of resources) {
|
|
88
|
+
const config = resource.config;
|
|
89
|
+
if (!config?.smoke)
|
|
90
|
+
continue;
|
|
91
|
+
const appType = config.appType;
|
|
92
|
+
const isApi = isApiServiceType(appType) || appType === BRING_YOUR_OWN_TYPE;
|
|
93
|
+
if (!isApi && appType !== "frontend")
|
|
94
|
+
continue;
|
|
95
|
+
if (config.exposeViaProxy === false)
|
|
96
|
+
continue;
|
|
97
|
+
const base = isApi ? apiBase : appBase;
|
|
98
|
+
// The container healthcheck path (default /health for APIs) is the one request that is safe to repeat while the route comes up.
|
|
99
|
+
const readyPath = config.healthCheckPath || (isApi && appType !== BRING_YOUR_OWN_TYPE ? "/health" : undefined);
|
|
100
|
+
plans.push({
|
|
101
|
+
name: resource.name,
|
|
102
|
+
baseUrl: `${base}${resolveServicePath(resource).replace(/\/+$/, "")}`,
|
|
103
|
+
...(readyPath?.startsWith("/") ? { readyPath } : {}),
|
|
104
|
+
smoke: config.smoke,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return plans;
|
|
108
|
+
}
|
|
109
|
+
/** Caps each read-only smoke check so a doctor run cannot wait the full `flisk up` budget per service. */
|
|
110
|
+
const DOCTOR_SMOKE_TIMEOUT_SECONDS = 10;
|
|
111
|
+
/**
|
|
112
|
+
* The GET-only part of each smoke block, for `flisk doctor`. Doctor must never write to a service, and a step whose path reads
|
|
113
|
+
* a value saved by an earlier write (`{{name}}`) cannot run on its own, so both are dropped. A service with no such steps is left out.
|
|
114
|
+
* `bodyContains` is also cleared: it asserts on data that earlier writes created, so doctor would fail a healthy route whose data was removed.
|
|
115
|
+
* The status check stays, which is what shows a route exists.
|
|
116
|
+
*/
|
|
117
|
+
export function readOnlySmokePlans(plans, options = {}) {
|
|
118
|
+
const cap = options.timeoutSeconds ?? DOCTOR_SMOKE_TIMEOUT_SECONDS;
|
|
119
|
+
return plans.flatMap((plan) => {
|
|
120
|
+
const steps = plan.smoke.steps
|
|
121
|
+
.filter((step) => (step.method ?? "GET").toUpperCase() === "GET" &&
|
|
122
|
+
step.body === undefined &&
|
|
123
|
+
!step.path.includes("{{"))
|
|
124
|
+
.map((step) => ({ ...step, bodyContains: undefined }));
|
|
125
|
+
if (steps.length === 0)
|
|
126
|
+
return [];
|
|
127
|
+
const budget = Math.min(plan.smoke.timeoutSeconds ?? DEFAULT_SMOKE_TIMEOUT_SECONDS, cap);
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
name: plan.name,
|
|
131
|
+
baseUrl: plan.baseUrl,
|
|
132
|
+
smoke: { via: plan.smoke.via, timeoutSeconds: budget, steps },
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function readPath(value, path) {
|
|
138
|
+
const parts = path.replace(/^\$\.?/, "").match(/[^.[\]]+/g) ?? [];
|
|
139
|
+
let current = value;
|
|
140
|
+
for (const part of parts) {
|
|
141
|
+
if (current === null || current === undefined)
|
|
142
|
+
return undefined;
|
|
143
|
+
// Own properties only: a path like "__proto__" must not reach inherited values.
|
|
144
|
+
if (part === "__proto__" || part === "constructor" || part === "prototype")
|
|
145
|
+
return undefined;
|
|
146
|
+
if (!Object.hasOwn(current, part))
|
|
147
|
+
return undefined;
|
|
148
|
+
// Resolve only own data properties. Besides avoiding inherited properties, this
|
|
149
|
+
// avoids invoking accessors on values supplied by smoke-test responses.
|
|
150
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, part);
|
|
151
|
+
if (!descriptor || !("value" in descriptor))
|
|
152
|
+
return undefined;
|
|
153
|
+
current = descriptor.value;
|
|
154
|
+
}
|
|
155
|
+
return current;
|
|
156
|
+
}
|
|
157
|
+
function fill(template, saved) {
|
|
158
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => saved[key] ?? match);
|
|
159
|
+
}
|
|
160
|
+
function fillBody(body, saved) {
|
|
161
|
+
if (typeof body === "string")
|
|
162
|
+
return fill(body, saved);
|
|
163
|
+
if (Array.isArray(body))
|
|
164
|
+
return body.map((item) => fillBody(item, saved));
|
|
165
|
+
if (isRecord(body)) {
|
|
166
|
+
return Object.fromEntries(Object.entries(body).map(([k, v]) => [k, fillBody(v, saved)]));
|
|
167
|
+
}
|
|
168
|
+
return body;
|
|
169
|
+
}
|
|
170
|
+
function snippet(text) {
|
|
171
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
172
|
+
return flat.length > BODY_SNIPPET_CHARS ? `${flat.slice(0, BODY_SNIPPET_CHARS)}...` : flat;
|
|
173
|
+
}
|
|
174
|
+
function slug(text) {
|
|
175
|
+
return (text
|
|
176
|
+
.toLowerCase()
|
|
177
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
178
|
+
.replace(/^-+|-+$/g, "") || "step");
|
|
179
|
+
}
|
|
180
|
+
/** Two steps whose names slug the same (`read back`, `read-back`) must not share a record directory. */
|
|
181
|
+
function stepKey(seen, label) {
|
|
182
|
+
const base = slug(label);
|
|
183
|
+
const n = (seen.get(base) ?? 0) + 1;
|
|
184
|
+
seen.set(base, n);
|
|
185
|
+
return n === 1 ? base : `${base}-${n}`;
|
|
186
|
+
}
|
|
187
|
+
/** Writes latest.json (+ body.txt) for a step; a passing step also replaces the last-success pair. Returns the latest.json path. */
|
|
188
|
+
function writeStepRecord(recordDir, stepKey, record, body, passed) {
|
|
189
|
+
try {
|
|
190
|
+
const dir = join(recordDir, slug(record.service), stepKey);
|
|
191
|
+
mkdirSync(dir, { recursive: true });
|
|
192
|
+
const latest = join(dir, "latest.json");
|
|
193
|
+
const bodyPath = join(dir, "body.txt");
|
|
194
|
+
if (body === undefined) {
|
|
195
|
+
rmSync(bodyPath, { force: true });
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
writeFileSync(bodyPath, body);
|
|
199
|
+
}
|
|
200
|
+
writeFileSync(latest, `${JSON.stringify({ ...record, bodyPath: body === undefined ? null : "body.txt" }, null, 2)}\n`);
|
|
201
|
+
if (passed) {
|
|
202
|
+
copyFileSync(latest, join(dir, "last-success.json"));
|
|
203
|
+
if (body === undefined)
|
|
204
|
+
rmSync(join(dir, "last-success-body.txt"), { force: true });
|
|
205
|
+
else
|
|
206
|
+
copyFileSync(bodyPath, join(dir, "last-success-body.txt"));
|
|
207
|
+
}
|
|
208
|
+
return latest;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// The record is a diagnostic aid; failing to write it must not change the smoke result.
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Caps a body at SMOKE_RECORD_BODY_MAX_BYTES of UTF-8 without splitting a character. */
|
|
216
|
+
function capBody(text) {
|
|
217
|
+
const buf = Buffer.from(text, "utf8");
|
|
218
|
+
if (buf.length <= SMOKE_RECORD_BODY_MAX_BYTES)
|
|
219
|
+
return { body: text, truncated: false, bytes: buf.length };
|
|
220
|
+
const body = buf
|
|
221
|
+
.subarray(0, SMOKE_RECORD_BODY_MAX_BYTES)
|
|
222
|
+
.toString("utf8")
|
|
223
|
+
.replace(/\uFFFD$/, "");
|
|
224
|
+
return { body, truncated: true, bytes: Buffer.byteLength(body, "utf8") };
|
|
225
|
+
}
|
|
226
|
+
/** Runs the steps in order. A not-ready answer (connection error, 404, 502-504) is retried until the timeout; a real wrong answer fails at once. */
|
|
227
|
+
export async function runSmokePlan(plan, deps = {}) {
|
|
228
|
+
const doFetch = deps.fetch ?? ((url, init) => fetch(url, init));
|
|
229
|
+
const now = deps.now ?? Date.now;
|
|
230
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
231
|
+
const budgetMs = (plan.smoke.timeoutSeconds ?? DEFAULT_SMOKE_TIMEOUT_SECONDS) * 1000;
|
|
232
|
+
let deadline = now() + budgetMs;
|
|
233
|
+
const saved = {};
|
|
234
|
+
// A manifest with errors is still loaded, so a malformed block must fail the check, not throw out of `flisk up`.
|
|
235
|
+
const invalid = validateSmoke(plan.smoke);
|
|
236
|
+
if (invalid.length > 0) {
|
|
237
|
+
return {
|
|
238
|
+
name: plan.name,
|
|
239
|
+
ok: false,
|
|
240
|
+
failure: `${plan.name}: invalid smoke block: ${invalid.join("; ")}`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// `flisk up` starts the check once Tilt's UI is up, while images may still be building. Traefik then resets or 404s early requests.
|
|
244
|
+
// A write must not be repeated after a reset (it may have reached the service), so wait for readiness with GETs instead.
|
|
245
|
+
// If the service never answers, fall through: the first step then fails with its own record.
|
|
246
|
+
if (plan.readyPath) {
|
|
247
|
+
const readyUrl = `${plan.baseUrl}${plan.readyPath}`;
|
|
248
|
+
for (;;) {
|
|
249
|
+
try {
|
|
250
|
+
const response = await doFetch(readyUrl, {
|
|
251
|
+
method: "GET",
|
|
252
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
253
|
+
});
|
|
254
|
+
await response.text();
|
|
255
|
+
// A 5xx from the health path (migrations running, database not up) is still "not ready"; 401 or 3xx means something answered.
|
|
256
|
+
if (!NOT_READY_STATUSES.has(response.status) && response.status < 500)
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// not accepting connections yet
|
|
261
|
+
}
|
|
262
|
+
if (now() >= deadline)
|
|
263
|
+
break;
|
|
264
|
+
await sleep(RETRY_DELAY_MS);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// The steps get their own `timeoutSeconds`; a slow build must not eat the first step's budget.
|
|
268
|
+
deadline = now() + budgetMs;
|
|
269
|
+
const stepKeys = new Map();
|
|
270
|
+
for (const [index, step] of plan.smoke.steps.entries()) {
|
|
271
|
+
const label = step.name ?? `step ${index + 1}`;
|
|
272
|
+
const method = (step.method ?? "GET").toUpperCase();
|
|
273
|
+
const url = `${plan.baseUrl}${fill(step.path, saved)}`;
|
|
274
|
+
const expected = step.expect ?? 200;
|
|
275
|
+
const hasBody = step.body !== undefined;
|
|
276
|
+
let status;
|
|
277
|
+
let text = "";
|
|
278
|
+
let error;
|
|
279
|
+
let errorCode;
|
|
280
|
+
for (;;) {
|
|
281
|
+
error = undefined;
|
|
282
|
+
errorCode = undefined;
|
|
283
|
+
status = undefined;
|
|
284
|
+
let retryableError = false;
|
|
285
|
+
try {
|
|
286
|
+
const response = await doFetch(url, {
|
|
287
|
+
method,
|
|
288
|
+
headers: hasBody ? { "content-type": "application/json" } : undefined,
|
|
289
|
+
body: hasBody ? JSON.stringify(fillBody(step.body, saved)) : undefined,
|
|
290
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
291
|
+
});
|
|
292
|
+
status = response.status;
|
|
293
|
+
text = await response.text();
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
retryableError = shouldRetryRequestError(err, method);
|
|
297
|
+
error = err instanceof Error ? err.message : String(err);
|
|
298
|
+
const code = isRecord(err)
|
|
299
|
+
? (err.code ?? (isRecord(err.cause) ? err.cause.code : undefined))
|
|
300
|
+
: undefined;
|
|
301
|
+
errorCode = typeof code === "string" ? code : undefined;
|
|
302
|
+
}
|
|
303
|
+
const notReady = (error !== undefined && retryableError) ||
|
|
304
|
+
(status !== undefined && status !== expected && NOT_READY_STATUSES.has(status));
|
|
305
|
+
if (!notReady || now() >= deadline)
|
|
306
|
+
break;
|
|
307
|
+
await sleep(RETRY_DELAY_MS);
|
|
308
|
+
}
|
|
309
|
+
// The URL, the response body and the error can all hold a secret the project knows about. Mask them once, here, so the
|
|
310
|
+
// printed snippet, the stored body and the record cannot drift apart. The raw text still decides the assertions and the
|
|
311
|
+
// saved values: masking is for what is shown and stored, not for what is checked or sent.
|
|
312
|
+
let maskFailed = false;
|
|
313
|
+
const mask = (text) => {
|
|
314
|
+
if (maskFailed)
|
|
315
|
+
return undefined;
|
|
316
|
+
if (!deps.redact)
|
|
317
|
+
return text;
|
|
318
|
+
try {
|
|
319
|
+
return deps.redact(text);
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
maskFailed = true;
|
|
323
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
324
|
+
deps.warn?.(`${plan.name}: ${label}: the response text was neither shown nor stored: ${reason}`);
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const safeUrl = mask(url);
|
|
329
|
+
const safeText = status === undefined ? undefined : mask(text);
|
|
330
|
+
const safeError = error === undefined ? undefined : mask(error);
|
|
331
|
+
const recordError = error === undefined ? undefined : (errorCode ?? safeError);
|
|
332
|
+
const where = `${plan.name}: ${label}: ${method} ${safeUrl ?? "(url withheld)"}`;
|
|
333
|
+
// Every step that ran leaves a record. A step is "passed" for last-success only if status and bodyContains both match.
|
|
334
|
+
let recordPath;
|
|
335
|
+
if (deps.recordDir) {
|
|
336
|
+
const capped = safeText === undefined ? undefined : capBody(safeText);
|
|
337
|
+
const passed = error === undefined &&
|
|
338
|
+
status === expected &&
|
|
339
|
+
(step.bodyContains === undefined || text.includes(step.bodyContains));
|
|
340
|
+
recordPath = writeStepRecord(deps.recordDir, stepKey(stepKeys, label), {
|
|
341
|
+
service: plan.name,
|
|
342
|
+
step: label,
|
|
343
|
+
method,
|
|
344
|
+
...(safeUrl === undefined ? {} : { url: safeUrl }),
|
|
345
|
+
expectedStatus: expected,
|
|
346
|
+
status: status ?? null,
|
|
347
|
+
...(recordError === undefined ? {} : { error: recordError }),
|
|
348
|
+
truncated: capped?.truncated ?? false,
|
|
349
|
+
bodyBytes: capped?.bytes ?? 0,
|
|
350
|
+
}, capped?.body, passed);
|
|
351
|
+
}
|
|
352
|
+
const fail = (failure) => ({
|
|
353
|
+
name: plan.name,
|
|
354
|
+
ok: false,
|
|
355
|
+
failure,
|
|
356
|
+
recordPath,
|
|
357
|
+
});
|
|
358
|
+
if (error !== undefined)
|
|
359
|
+
return fail(`${where} never answered (${safeError ?? "the error was withheld"})`);
|
|
360
|
+
if (status !== expected) {
|
|
361
|
+
return fail(`${where} returned ${status}, expected ${expected}${safeText ? `: ${snippet(safeText)}` : ""}`);
|
|
362
|
+
}
|
|
363
|
+
if (step.bodyContains !== undefined && !text.includes(step.bodyContains)) {
|
|
364
|
+
return fail(`${where} returned ${status} but the body does not contain "${step.bodyContains}"${safeText ? `: ${snippet(safeText)}` : ""}`);
|
|
365
|
+
}
|
|
366
|
+
if (step.save) {
|
|
367
|
+
let parsed;
|
|
368
|
+
try {
|
|
369
|
+
parsed = JSON.parse(text);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return fail(`${where} returned a body that is not JSON, cannot save${safeText ? `: ${snippet(safeText)}` : ""}`);
|
|
373
|
+
}
|
|
374
|
+
for (const [key, path] of Object.entries(step.save)) {
|
|
375
|
+
const value = readPath(parsed, path);
|
|
376
|
+
if (value === undefined || value === null || typeof value === "object") {
|
|
377
|
+
return fail(`${where} response has no ${path} to save as {{${key}}}${safeText ? `: ${snippet(safeText)}` : ""}`);
|
|
378
|
+
}
|
|
379
|
+
saved[key] = String(value);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return { name: plan.name, ok: true };
|
|
384
|
+
}
|
|
385
|
+
export function formatSmokeFailure(result) {
|
|
386
|
+
return `Smoke check failed: ${result.failure}${result.recordPath ? ` (record: ${result.recordPath})` : ""}`;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* The dependencies that keep smoke text safe: the project's own secret values are masked before anything is shown or stored.
|
|
390
|
+
* Both `flisk up` and `flisk doctor` use them, so neither prints a body that holds a secret.
|
|
391
|
+
*/
|
|
392
|
+
export function safeSmokeDeps(projectRoot, warn) {
|
|
393
|
+
return {
|
|
394
|
+
redact: createEnvRedactor(projectRoot),
|
|
395
|
+
...(warn ? { warn } : {}),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/** Runs every plan side by side; each plan keeps its own timeout. */
|
|
399
|
+
export async function runSmokePlans(plans, deps = {}) {
|
|
400
|
+
return Promise.all(plans.map((plan) => runSmokePlan(plan, deps)));
|
|
401
|
+
}
|