@aifabrix/builder 2.49.2 → 2.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +49 -0
- package/.cursor/rules/plan-rules/application-lifecycle.mdc +30 -0
- package/.cursor/rules/plan-rules/plan-frontmatter.mdc +35 -0
- package/.cursor/rules/plan-rules/plan-lifecycle.mdc +31 -0
- package/.cursor/rules/plan-rules/plan-validation-gate.mdc +34 -0
- package/.cursor/skills/README.md +1 -0
- package/.dockerignore +52 -0
- package/.eslintignore +22 -0
- package/README.md +2 -0
- package/bin/builder-api-cleanup.js +26 -0
- package/bin/builder-api-nest.js +10 -0
- package/bin/builder-api-spike.js +19 -0
- package/enterprise-knowledge/README.md +50 -0
- package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +22 -0
- package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +15 -0
- package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +69 -0
- package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +52 -0
- package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +29 -0
- package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +2 -0
- package/integration/README.md +81 -0
- package/integration/github/README.md +53 -0
- package/integration/github/application.json +21 -0
- package/integration/github/deploy.js +72 -0
- package/integration/github/env.template +11 -0
- package/integration/github/github-datasource-issue-comments.json +239 -0
- package/integration/github/github-datasource-issues.json +399 -0
- package/integration/github/github-datasource-repository.json +354 -0
- package/integration/github/github-deploy.json +1202 -0
- package/integration/github/github-system.json +82 -0
- package/integration/hubspot-test/README.md +1 -1
- package/jest.isolated-projects.js +31 -1
- package/jest.projects.js +87 -1
- package/lib/api/dev-server-https.js +1 -1
- package/lib/api/enterprise-knowledge.api.js +63 -0
- package/lib/api/evidence.api.js +172 -0
- package/lib/api/external-systems.api.js +8 -2
- package/lib/api/resource-types.api.js +1 -0
- package/lib/api/types/enterprise-knowledge.types.js +46 -0
- package/lib/api/types/evidence.types.js +67 -0
- package/lib/api/types/resource-types.types.js +2 -0
- package/lib/app/controller-application-filter.js +36 -0
- package/lib/app/deploy.js +1 -1
- package/lib/app/list-external-systems.js +130 -0
- package/lib/app/list.js +20 -8
- package/lib/app/run.js +3 -1
- package/lib/app/show-display.js +8 -3
- package/lib/app/show-online.js +178 -0
- package/lib/app/show.js +37 -88
- package/lib/build/builder-api-docker-staging.js +47 -0
- package/lib/build/miso-controller-npm-sdk-guard.js +2 -2
- package/lib/build/standard-docker-build.js +2 -0
- package/lib/certification/post-unified-cert-sync.js +4 -31
- package/lib/certification/sync-after-external-command.js +0 -1
- package/lib/certification/sync-system-certification.js +0 -3
- package/lib/channels/add-channel.js +40 -34
- package/lib/channels/approval-guides/chatgpt.js +33 -0
- package/lib/channels/approval-guides/credential-portal.js +38 -0
- package/lib/channels/approval-guides/index.js +110 -0
- package/lib/channels/approval-guides/microsoft-copilot.js +34 -0
- package/lib/channels/approval-guides/slack.js +34 -0
- package/lib/channels/approval-guides/teams-bot.js +35 -0
- package/lib/channels/approval-guides/web-embed.js +32 -0
- package/lib/channels/channel-approval-guide.js +11 -0
- package/lib/channels/channel-artifact-generator.js +16 -4
- package/lib/channels/channel-audit.js +3 -0
- package/lib/channels/channel-auth-context.js +1 -1
- package/lib/channels/channel-description.js +59 -0
- package/lib/channels/channel-integration-generator.js +13 -230
- package/lib/channels/channel-integration-template.js +94 -0
- package/lib/channels/channel-runtime-resolver.js +43 -3
- package/lib/channels/channel-status-display.js +66 -26
- package/lib/channels/channel-targets.js +3 -3
- package/lib/cli/index.js +27 -3
- package/lib/cli/infra-guided.js +5 -1
- package/lib/cli/setup-app-external-create.js +111 -0
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.js +38 -125
- package/lib/cli/setup-app.test-commands.js +26 -52
- package/lib/cli/setup-certification-commands.js +4 -4
- package/lib/cli/setup-certification.help.js +9 -9
- package/lib/cli/setup-dev.js +7 -2
- package/lib/cli/setup-external-system-help.js +4 -6
- package/lib/cli/setup-external-system.js +58 -52
- package/lib/cli/setup-infra-up-builder-api-action.js +64 -0
- package/lib/cli/setup-infra-up-platform-action.js +3 -1
- package/lib/cli/setup-infra.js +15 -0
- package/lib/cli/setup-list.js +43 -0
- package/lib/cli/setup-utility-repair.js +26 -7
- package/lib/cli/setup-utility.js +5 -3
- package/lib/commands/app.js +15 -43
- package/lib/commands/channel-add-setup.js +110 -0
- package/lib/commands/channel.js +42 -8
- package/lib/commands/datasource-sync-display.js +96 -0
- package/lib/commands/datasource-sync-policy.js +210 -0
- package/lib/commands/datasource-test-trust-cli.js +6 -6
- package/lib/commands/datasource-unified-test-cli.js +2 -1
- package/lib/commands/datasource-unified-test-cli.options.js +33 -23
- package/lib/commands/deploy-gate-failure-details.js +163 -0
- package/lib/commands/deploy-gate-orchestrator.js +322 -0
- package/lib/commands/dev-cli-handlers.js +1 -59
- package/lib/commands/dev-host-ops-params.js +91 -0
- package/lib/commands/dev-host-ops-poll.js +111 -0
- package/lib/commands/dev-host-ops.js +107 -94
- package/lib/commands/dev-init.js +3 -1
- package/lib/commands/enterprise-knowledge.js +284 -0
- package/lib/commands/evidence-cli-leaves.js +250 -0
- package/lib/commands/evidence.js +51 -0
- package/lib/commands/governance-cli-shared.js +2 -2
- package/lib/commands/governance-command-action.js +2 -2
- package/lib/commands/governance-fixture-subjects.js +85 -0
- package/lib/commands/governance.js +1 -1
- package/lib/commands/lifecycle-command-action.js +2 -2
- package/lib/commands/lifecycle-external.js +3 -3
- package/lib/commands/manifest-cli-auth.js +29 -0
- package/lib/commands/manifest-cli-commands.js +170 -0
- package/lib/commands/manifest-cli-integration-path.js +63 -0
- package/lib/commands/manifest.js +21 -0
- package/lib/commands/protection-cli-leaves.js +3 -3
- package/lib/commands/repair-auth-apply.js +15 -1
- package/lib/commands/repair-authentications-454.js +169 -0
- package/lib/commands/repair-capability-openapi-warnings.js +126 -0
- package/lib/commands/repair-datasource-apply.js +135 -0
- package/lib/commands/repair-datasource-auto-rbac.js +62 -1
- package/lib/commands/repair-datasource-http-service-contract.js +239 -0
- package/lib/commands/repair-datasource-operation-security.js +180 -0
- package/lib/commands/repair-datasource-run.js +7 -2
- package/lib/commands/repair-datasource-sync-operations.js +177 -0
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +133 -0
- package/lib/commands/repair-datasource.js +12 -68
- package/lib/commands/repair-env-template.js +11 -1
- package/lib/commands/repair-external-run.js +121 -31
- package/lib/commands/repair-internal.js +1 -1
- package/lib/commands/repair-openapi-export.js +156 -0
- package/lib/commands/repair-openapi-sync.js +19 -2
- package/lib/commands/repair-orchestration-datasource-split.js +259 -0
- package/lib/commands/repair-persist.js +22 -1
- package/lib/commands/repair-rbac-platform-preset.js +204 -0
- package/lib/commands/repair-rbac-reader-roles.js +191 -0
- package/lib/commands/repair-rbac.js +163 -86
- package/lib/commands/repair-steps.js +83 -18
- package/lib/commands/repair-system-permissions.js +1 -0
- package/lib/commands/repair.js +1 -1
- package/lib/commands/resource-type.js +65 -26
- package/lib/commands/run-post-publish-data-sync.js +68 -0
- package/lib/commands/setup-modes.js +1 -1
- package/lib/commands/test-e2e-external.js +21 -8
- package/lib/commands/test-governance-cli-options.js +2 -2
- package/lib/commands/test-trust-cli-options.js +2 -2
- package/lib/commands/up-builder-api-bootstrap.js +71 -0
- package/lib/commands/up-builder-api-provision.js +241 -0
- package/lib/commands/up-builder-api.js +88 -0
- package/lib/commands/up-common.js +25 -1
- package/lib/commands/upload-credential-push-log.js +117 -0
- package/lib/commands/upload-data-sync-finalize.js +60 -0
- package/lib/commands/upload-probe.js +66 -0
- package/lib/commands/upload-publish-context.js +4 -1
- package/lib/commands/upload-sidecar-followups.js +55 -27
- package/lib/commands/upload.js +116 -157
- package/lib/commands/verify-governance-combined-external.js +1 -1
- package/lib/commands/verify-governance-db.js +2 -2
- package/lib/commands/verify-operations-command-action.js +2 -2
- package/lib/commands/verify-operations-step-plan.js +2 -2
- package/lib/commands/verify-operations-steps.js +2 -2
- package/lib/commands/verify-trust-command-action.js +1 -1
- package/lib/commands/wizard-core.js +1 -1
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/core/admin-secrets.js +2 -6
- package/lib/core/secrets-env-content.js +10 -4
- package/lib/core/secrets-env-write.js +2 -13
- package/lib/datasource/agent-trust-run.js +3 -3
- package/lib/datasource/deploy.js +3 -1
- package/lib/datasource/test-e2e.js +6 -1
- package/lib/datasource/unified-validation-run-body.js +4 -1
- package/lib/datasource/unified-validation-run.js +1 -1
- package/lib/deployment/deploy-poll-ui.js +73 -12
- package/lib/deployment/deployer-poll-run.js +113 -0
- package/lib/deployment/deployer.js +17 -87
- package/lib/evidence/dataplane-context.js +21 -0
- package/lib/evidence/evidence-display.js +78 -0
- package/lib/evidence/evidence-service.js +392 -0
- package/lib/external-system/deploy.js +290 -112
- package/lib/external-system/download.js +7 -3
- package/lib/external-system/test-execution.js +1 -1
- package/lib/external-system/test-system-level.js +8 -10
- package/lib/external-system/test.js +12 -6
- package/lib/generator/deploy-script.js +73 -0
- package/lib/generator/external.js +2 -1
- package/lib/generator/index.js +17 -2
- package/lib/generator/wizard.js +3 -36
- package/lib/internal/fs-real-sync.js +22 -1
- package/lib/internal/node-fs.js +2 -0
- package/lib/lifecycle/lifecycle-governance-options.js +1 -1
- package/lib/lifecycle/verify-step-progress.js +1 -1
- package/lib/programmatic/access-log.js +26 -0
- package/lib/programmatic/auth-config.js +44 -0
- package/lib/programmatic/bearer-auth.js +176 -0
- package/lib/programmatic/builder-api-request.js +175 -0
- package/lib/programmatic/builder-api-route-dispatch.js +82 -0
- package/lib/programmatic/builder-api-runtime-config.js +42 -0
- package/lib/programmatic/builder-api-server.js +230 -0
- package/lib/programmatic/builder-help-authentication-catalog.js +282 -0
- package/lib/programmatic/builder-help-connected-system-ui.js +266 -0
- package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +60 -0
- package/lib/programmatic/builder-help-envelope.js +36 -0
- package/lib/programmatic/builder-help-fixtures.js +200 -0
- package/lib/programmatic/builder-help-governance.js +172 -0
- package/lib/programmatic/builder-help-schema-catalog.js +92 -0
- package/lib/programmatic/builder-help.js +361 -0
- package/lib/programmatic/capability-registry.js +77 -0
- package/lib/programmatic/command-options.js +32 -0
- package/lib/programmatic/enterprise-mcp-client.js +98 -0
- package/lib/programmatic/http-problem-details.js +99 -0
- package/lib/programmatic/http-route-match.js +40 -0
- package/lib/programmatic/idempotency-cache.js +69 -0
- package/lib/programmatic/index.js +53 -0
- package/lib/programmatic/integration-path-scope.js +39 -0
- package/lib/programmatic/log-redaction.js +66 -0
- package/lib/programmatic/manifest-get-section.js +146 -0
- package/lib/programmatic/manifest-patch-section.js +252 -0
- package/lib/programmatic/manifest-repair.js +122 -0
- package/lib/programmatic/manifest-section-dependency-graph.js +160 -0
- package/lib/programmatic/manifest-section-io.js +225 -0
- package/lib/programmatic/manifest-section-quality.js +171 -0
- package/lib/programmatic/manifest-section-registry-data.js +269 -0
- package/lib/programmatic/manifest-section-registry.js +205 -0
- package/lib/programmatic/nest-http-bridge.js +34 -0
- package/lib/programmatic/openapi-component-schemas.js +68 -0
- package/lib/programmatic/openapi-description-enricher.js +117 -0
- package/lib/programmatic/openapi-descriptions.js +190 -0
- package/lib/programmatic/openapi-docs-serving.js +123 -0
- package/lib/programmatic/openapi-help-schemas.js +63 -0
- package/lib/programmatic/openapi-manifest-schemas.js +141 -0
- package/lib/programmatic/openapi-operation-bindings.js +274 -0
- package/lib/programmatic/openapi-request-schemas.js +155 -0
- package/lib/programmatic/openapi-result-schemas.js +476 -0
- package/lib/programmatic/openapi-schema-descriptions-certify.js +139 -0
- package/lib/programmatic/openapi-schema-descriptions-core.js +159 -0
- package/lib/programmatic/openapi-schema-descriptions-help.js +40 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +143 -0
- package/lib/programmatic/openapi-schema-descriptions-mcp.js +118 -0
- package/lib/programmatic/openapi-schema-descriptions-operations.js +281 -0
- package/lib/programmatic/openapi-schema-helpers.js +93 -0
- package/lib/programmatic/openapi-spec.js +203 -0
- package/lib/programmatic/orchestration-openapi-meta.js +159 -0
- package/lib/programmatic/platform-rbac-catalog.js +90 -0
- package/lib/programmatic/request-timeout.js +53 -0
- package/lib/programmatic/route-handler-map.js +143 -0
- package/lib/programmatic/route-handlers-context.js +29 -0
- package/lib/programmatic/route-handlers.js +33 -0
- package/lib/programmatic/run-certify.js +57 -0
- package/lib/programmatic/run-enterprise-mcp.js +32 -0
- package/lib/programmatic/run-lifecycle.js +29 -0
- package/lib/programmatic/run-repair.js +41 -0
- package/lib/programmatic/run-repository-emcp.js +54 -0
- package/lib/programmatic/run-template-workspace.js +61 -0
- package/lib/programmatic/run-test.js +49 -0
- package/lib/programmatic/run-upload.js +63 -0
- package/lib/programmatic/run-validate.js +53 -0
- package/lib/programmatic/run-wizard.js +40 -0
- package/lib/programmatic/system-key.js +48 -0
- package/lib/programmatic/wizard-config-normalize.js +118 -0
- package/lib/programmatic/workspace-cleanup.js +128 -0
- package/lib/programmatic/workspace-context.js +145 -0
- package/lib/programmatic/workspace-lock.js +75 -0
- package/lib/programmatic/workspace-meta.js +64 -0
- package/lib/programmatic/workspace-ops.js +149 -0
- package/lib/protection/run-commands-upload-sync.js +35 -0
- package/lib/protection/run-commands.js +2 -20
- package/lib/protection/upload-batch.js +11 -7
- package/lib/resolvers/resource-type-file.js +22 -0
- package/lib/schema/external-datasource.schema.json +161 -1
- package/lib/schema/external-system.schema.json +244 -118
- package/lib/schema/flag-map-validation-run.json +3 -0
- package/lib/schema/type/credential-field-registry.json +2 -2
- package/lib/schema/type/document-storage.json +9 -0
- package/lib/utils/agent-trust-run-display.js +1 -1
- package/lib/utils/app-path-resolve.js +168 -0
- package/lib/utils/authentication-env-template.js +222 -0
- package/lib/utils/cli-sync-options.js +7 -11
- package/lib/utils/cli-utils.js +4 -0
- package/lib/utils/cli-workspace-options.js +29 -0
- package/lib/utils/compose-generate-docker-compose.js +102 -0
- package/lib/utils/configuration-env-resolver.js +203 -27
- package/lib/utils/datasource-test-run-certificate-tty.js +28 -2
- package/lib/utils/datasource-test-run-certification-debug.js +130 -0
- package/lib/utils/datasource-test-run-debug-display.js +2 -0
- package/lib/utils/datasource-test-run-debug-slice.js +7 -0
- package/lib/utils/datasource-test-run-display.js +2 -1
- package/lib/utils/dev-init-ssh-merge.js +165 -23
- package/lib/utils/dev-pin-share-instructions.js +110 -0
- package/lib/utils/dev-ssh-config-helper.js +177 -33
- package/lib/utils/enterprise-knowledge-paths.js +142 -0
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-dotenv-format.js +103 -0
- package/lib/utils/external-env-template.js +4 -1
- package/lib/utils/external-system-display.js +5 -1
- package/lib/utils/external-system-readiness-compact-blocks.js +221 -0
- package/lib/utils/external-system-readiness-compact-display.js +324 -0
- package/lib/utils/external-system-readiness-core.js +16 -0
- package/lib/utils/external-system-readiness-deploy-display.js +29 -11
- package/lib/utils/external-system-readiness-display-internals.js +3 -1
- package/lib/utils/external-system-readiness-display.js +20 -30
- package/lib/utils/external-system-system-test-tty-certification.js +135 -0
- package/lib/utils/external-system-system-test-tty.js +5 -41
- package/lib/utils/help-builder.js +6 -1
- package/lib/utils/host-ops-job-labels.js +236 -0
- package/lib/utils/host-ops-job-result.js +61 -0
- package/lib/utils/host-ops-poll-ui.js +163 -0
- package/lib/utils/integration-path-resolve.js +112 -0
- package/lib/utils/paths-reload-source.js +148 -0
- package/lib/utils/paths-system-builder-keys.js +25 -0
- package/lib/utils/paths.js +151 -26
- package/lib/utils/runtime-configuration-values.js +64 -2
- package/lib/utils/secrets-helpers.js +5 -1
- package/lib/utils/ssh-known-hosts.js +130 -0
- package/lib/utils/test-target-cli-options.js +75 -0
- package/lib/utils/upload-sync-options.js +4 -0
- package/lib/utils/validation-run-request.js +11 -2
- package/lib/validation/external-manifest-validator.js +11 -1
- package/lib/validation/openapi-contract-surface-validator.js +426 -0
- package/lib/validation/openapi-contract-vendor-validation.js +238 -0
- package/lib/validation/validate-external-file.js +160 -0
- package/lib/validation/validate.js +6 -117
- package/package.json +46 -29
- package/packages/builder-api/README.md +120 -0
- package/packages/builder-api/package.json +30 -0
- package/packages/builder-api/src/app.module.ts +7 -0
- package/packages/builder-api/src/auth/auth.module.ts +5 -0
- package/packages/builder-api/src/external-system/external-system.module.ts +5 -0
- package/packages/builder-api/src/main.ts +16 -0
- package/packages/builder-api/tsconfig.json +15 -0
- package/scripts/check-builder-api-architecture-grep.js +79 -0
- package/scripts/docker_image_content_gate.js +47 -0
- package/scripts/docker_image_content_gate.sh +5 -0
- package/scripts/export-builder-api-evidence.js +262 -0
- package/scripts/lib/builder-api-docker-staging-copy.js +60 -0
- package/scripts/lib/builder-api-docker-staging-core.js +183 -0
- package/scripts/lib/docker-image-content-gate-core.js +166 -0
- package/scripts/lib/plan-actor.js +109 -0
- package/scripts/lib/plan-config.js +20 -0
- package/scripts/lib/plan-gates.js +154 -0
- package/scripts/lib/plan-meta.js +104 -0
- package/scripts/lib/plan-parse.js +162 -0
- package/scripts/lib/plan-prevalidate-gates.js +326 -0
- package/scripts/lib/plan-run-core.js +125 -0
- package/scripts/lib/plan-run-report.js +108 -0
- package/scripts/plan-approve.js +24 -0
- package/scripts/plan-archive.js +49 -0
- package/scripts/plan-run.js +13 -0
- package/scripts/plan-set-actor.js +21 -0
- package/scripts/plan-validate.js +80 -0
- package/scripts/pnpm/af.mjs +110 -0
- package/scripts/pnpm/check-quiet.mjs +32 -0
- package/scripts/pnpm/dev-install.mjs +29 -0
- package/scripts/pnpm/dev-reload.mjs +18 -0
- package/scripts/pnpm/help.mjs +55 -0
- package/scripts/pnpm/lib/dev-reload-core.mjs +189 -0
- package/scripts/pnpm/lib/dev-reload-log-capture.mjs +67 -0
- package/scripts/pnpm/lib/dev-reload-log-utils.mjs +32 -0
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +124 -0
- package/scripts/pnpm/lib/resolve-app-url.mjs +183 -0
- package/scripts/pnpm/lib/spinner.mjs +42 -0
- package/scripts/stage_builder_api_for_docker.js +65 -0
- package/scripts/sync-builder-api-template.js +44 -0
- package/templates/README.md +3 -0
- package/templates/applications/builder-api/Dockerfile +38 -0
- package/templates/applications/builder-api/application.yaml +75 -0
- package/templates/applications/builder-api/env.template +48 -0
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +10 -6
- package/templates/applications/dataplane/rbac.yaml +21 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +1 -3
- package/templates/channels/README.md +18 -0
- package/templates/channels/ai-fabrix-icon.jpg +0 -0
- package/templates/channels/chatgpt/install.template.md.hbs +21 -24
- package/templates/channels/chatgpt/integration/README.md +11 -0
- package/templates/channels/chatgpt/integration/application.json +15 -0
- package/templates/channels/chatgpt/integration/chatgpt-system.json +29 -0
- package/templates/channels/chatgpt/integration/env.template +4 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +20 -25
- package/templates/channels/microsoft-copilot/integration/README.md +11 -0
- package/templates/channels/microsoft-copilot/integration/application.json +15 -0
- package/templates/channels/microsoft-copilot/integration/env.template +6 -0
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +34 -0
- package/templates/channels/slack/install.template.md.hbs +48 -18
- package/templates/channels/slack/integration/README.md +38 -0
- package/templates/channels/slack/integration/application.json +18 -0
- package/templates/channels/slack/integration/env.template +29 -0
- package/templates/channels/slack/integration/slack-datasource-conversations.json +226 -0
- package/templates/channels/slack/integration/slack-datasource-messages.json +269 -0
- package/templates/channels/slack/integration/slack-system.json +86 -0
- package/templates/channels/slack/manifest.template.json.hbs +20 -10
- package/templates/channels/teams-bot/install.template.md.hbs +31 -17
- package/templates/channels/teams-bot/integration/README.md +18 -0
- package/templates/channels/teams-bot/integration/application.json +17 -0
- package/templates/channels/teams-bot/integration/env.template +5 -0
- package/templates/channels/teams-bot/integration/teams-bot-system.json +55 -0
- package/templates/channels/teams-bot/manifest.template.json.hbs +27 -0
- package/templates/channels/web-embed/install.template.md.hbs +16 -26
- package/templates/channels/web-embed/integration/README.md +11 -0
- package/templates/channels/web-embed/integration/application.json +15 -0
- package/templates/channels/web-embed/integration/env.template +4 -0
- package/templates/channels/web-embed/integration/web-embed-system.json +29 -0
- package/templates/openapi/redoc_page.html +86 -0
- package/templates/python/docker-compose.hbs +11 -0
- package/templates/python/requirements.txt +1 -1
- package/templates/typescript/docker-compose.hbs +11 -0
- package/lib/certification/cli-cert-sync-skip.js +0 -21
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
* @fileoverview After dev init: merge SSH config Host alias from sync settings / server URL.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const path = require('path');
|
|
5
6
|
const chalk = require('chalk');
|
|
6
7
|
const config = require('../core/config');
|
|
7
8
|
const logger = require('./logger');
|
|
8
|
-
const { ensureDevSshConfigBlock } = require('./dev-ssh-config-helper');
|
|
9
|
+
const { ensureDevSshConfigBlock, listHostAliasesForUserHost } = require('./dev-ssh-config-helper');
|
|
10
|
+
const { ensureSshDir } = require('./ssh-key-helper');
|
|
11
|
+
const { nodeFs } = require('../internal/node-fs');
|
|
12
|
+
const {
|
|
13
|
+
collectDevSshKnownHostCandidates,
|
|
14
|
+
refreshDevSshKnownHosts
|
|
15
|
+
} = require('./ssh-known-hosts');
|
|
9
16
|
const { successGlyph } = require('./cli-layout-chalk');
|
|
10
17
|
|
|
11
18
|
/**
|
|
@@ -24,6 +31,134 @@ function hostnameFromBaseUrl(baseUrl) {
|
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} sshDir
|
|
36
|
+
* @returns {Promise<string>}
|
|
37
|
+
*/
|
|
38
|
+
async function readSshConfigContent(sshDir) {
|
|
39
|
+
const configPath = path.join(ensureSshDir(sshDir), 'config');
|
|
40
|
+
try {
|
|
41
|
+
return await nodeFs().promises.readFile(configPath, 'utf8');
|
|
42
|
+
} catch (e) {
|
|
43
|
+
if (e.code === 'ENOENT') {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {Object} p
|
|
52
|
+
* @param {string} p.baseUrl
|
|
53
|
+
* @param {string} p.syncHost
|
|
54
|
+
* @param {string} p.syncUser
|
|
55
|
+
* @param {string} p.hostAlias
|
|
56
|
+
* @param {string} p.existingSshConfig
|
|
57
|
+
* @param {string|number} [p.developerId]
|
|
58
|
+
* @param {string} [p.developerRootDomain]
|
|
59
|
+
* @returns {Promise<void>}
|
|
60
|
+
*/
|
|
61
|
+
async function refreshKnownHostsForDevSsh(p) {
|
|
62
|
+
const legacyHostAliases = listHostAliasesForUserHost(
|
|
63
|
+
p.existingSshConfig,
|
|
64
|
+
p.syncHost,
|
|
65
|
+
p.syncUser
|
|
66
|
+
);
|
|
67
|
+
const candidates = collectDevSshKnownHostCandidates({
|
|
68
|
+
syncHost: p.syncHost,
|
|
69
|
+
syncUser: p.syncUser,
|
|
70
|
+
hostAlias: p.hostAlias,
|
|
71
|
+
baseUrl: p.baseUrl,
|
|
72
|
+
developerId: p.developerId,
|
|
73
|
+
developerRootDomain: p.developerRootDomain,
|
|
74
|
+
legacyHostAliases
|
|
75
|
+
});
|
|
76
|
+
const { cleared, failed } = await refreshDevSshKnownHosts(candidates);
|
|
77
|
+
if (cleared.length > 0) {
|
|
78
|
+
logger.log(
|
|
79
|
+
`${chalk.green(' ')}${successGlyph()}${chalk.green(' Cleared stale SSH host keys for ')}` +
|
|
80
|
+
chalk.cyan(cleared.join(', ')) +
|
|
81
|
+
chalk.gray(' (accept the new key on first connect if prompted).')
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (failed.some((row) => row.error === 'ssh-keygen not found')) {
|
|
85
|
+
logger.log(
|
|
86
|
+
chalk.yellow(
|
|
87
|
+
' ⚠ ssh-keygen not found; if SSH reports "REMOTE HOST IDENTIFICATION HAS CHANGED", ' +
|
|
88
|
+
`run: ssh-keygen -R ${p.syncHost}`
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string|undefined} developerRootDomain
|
|
96
|
+
* @returns {string|undefined}
|
|
97
|
+
*/
|
|
98
|
+
function trimDeveloperRootDomain(developerRootDomain) {
|
|
99
|
+
const trimmed = developerRootDomain && String(developerRootDomain).trim();
|
|
100
|
+
return trimmed || undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {Object} res
|
|
105
|
+
* @param {string} syncUser
|
|
106
|
+
* @param {string} syncHost
|
|
107
|
+
* @returns {void}
|
|
108
|
+
*/
|
|
109
|
+
function logSshConfigMergeResult(res, syncUser, syncHost) {
|
|
110
|
+
if (res.skippedDuplicate) {
|
|
111
|
+
logger.log(
|
|
112
|
+
chalk.gray(' SSH config already has ') +
|
|
113
|
+
chalk.cyan(`${syncUser}@${syncHost}`) +
|
|
114
|
+
chalk.gray(` (Host ${res.hostAlias}); left unchanged.`)
|
|
115
|
+
);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const prefix =
|
|
119
|
+
`${chalk.green(' ')}${successGlyph()}${chalk.green(' SSH config updated: ')}` +
|
|
120
|
+
chalk.cyan(`Host ${res.hostAlias}`);
|
|
121
|
+
const suffix = res.migratedFrom
|
|
122
|
+
? chalk.gray(` (was ${res.migratedFrom}) → ${res.configPath}`)
|
|
123
|
+
: chalk.gray(` → ${res.configPath}`);
|
|
124
|
+
logger.log(prefix + suffix);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {Object} p
|
|
129
|
+
* @param {string} p.baseUrl
|
|
130
|
+
* @param {string} p.devId
|
|
131
|
+
* @param {string} p.syncUser
|
|
132
|
+
* @param {string} p.syncHost
|
|
133
|
+
* @param {string|undefined} p.rootTrimmed
|
|
134
|
+
* @returns {Promise<{ hostAlias: string|null, syncUser: string, syncHost: string }>}
|
|
135
|
+
*/
|
|
136
|
+
async function mergeDevSshConfigBlock(p) {
|
|
137
|
+
const sshDir = ensureSshDir();
|
|
138
|
+
const existingSshConfig = await readSshConfigContent(sshDir);
|
|
139
|
+
const res = await ensureDevSshConfigBlock(p.syncUser, p.syncHost, sshDir, {
|
|
140
|
+
developerId: p.devId,
|
|
141
|
+
developerRootDomain: p.rootTrimmed
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok || !res.configPath || !res.hostAlias) {
|
|
144
|
+
if (!res.ok && res.error) {
|
|
145
|
+
logger.log(chalk.yellow(` ⚠ Could not update SSH config: ${res.error}`));
|
|
146
|
+
}
|
|
147
|
+
return { hostAlias: null, syncUser: p.syncUser, syncHost: p.syncHost };
|
|
148
|
+
}
|
|
149
|
+
await refreshKnownHostsForDevSsh({
|
|
150
|
+
baseUrl: p.baseUrl,
|
|
151
|
+
syncHost: p.syncHost,
|
|
152
|
+
syncUser: p.syncUser,
|
|
153
|
+
hostAlias: res.hostAlias,
|
|
154
|
+
existingSshConfig,
|
|
155
|
+
developerId: p.devId,
|
|
156
|
+
developerRootDomain: p.rootTrimmed
|
|
157
|
+
});
|
|
158
|
+
logSshConfigMergeResult(res, p.syncUser, p.syncHost);
|
|
159
|
+
return { hostAlias: res.hostAlias, syncUser: p.syncUser, syncHost: p.syncHost };
|
|
160
|
+
}
|
|
161
|
+
|
|
27
162
|
/**
|
|
28
163
|
* Update ~/.ssh/config with Host <user>.<host> for interactive SSH; log result.
|
|
29
164
|
* @param {string} baseUrl - Builder Server base URL
|
|
@@ -37,30 +172,37 @@ async function mergeDevSshConfigAfterInit(baseUrl, devId) {
|
|
|
37
172
|
return { hostAlias: null, syncUser, syncHost: null };
|
|
38
173
|
}
|
|
39
174
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} else {
|
|
49
|
-
logger.log(
|
|
50
|
-
`${chalk.green(' ')}${successGlyph()}${chalk.green(' SSH config updated: ')}` +
|
|
51
|
-
chalk.cyan(`Host ${res.hostAlias}`) +
|
|
52
|
-
chalk.gray(` → ${res.configPath}`)
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
return { hostAlias: res.hostAlias, syncUser, syncHost };
|
|
56
|
-
}
|
|
57
|
-
if (!res.ok && res.error) {
|
|
58
|
-
logger.log(chalk.yellow(` ⚠ Could not update SSH config: ${res.error}`));
|
|
59
|
-
}
|
|
175
|
+
const rootTrimmed = trimDeveloperRootDomain(await config.getDeveloperRootDomain());
|
|
176
|
+
return await mergeDevSshConfigBlock({
|
|
177
|
+
baseUrl,
|
|
178
|
+
devId,
|
|
179
|
+
syncUser,
|
|
180
|
+
syncHost,
|
|
181
|
+
rootTrimmed
|
|
182
|
+
});
|
|
60
183
|
} catch (e) {
|
|
61
184
|
logger.log(chalk.yellow(` ⚠ Could not update SSH config: ${e.message || e}`));
|
|
185
|
+
return { hostAlias: null, syncUser, syncHost };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {{ hostAlias: string|null }} sshInfo
|
|
191
|
+
* @returns {void}
|
|
192
|
+
*/
|
|
193
|
+
function logDevSshConnectHint(sshInfo) {
|
|
194
|
+
if (!sshInfo || !sshInfo.hostAlias) {
|
|
195
|
+
return;
|
|
62
196
|
}
|
|
63
|
-
|
|
197
|
+
logger.log(
|
|
198
|
+
chalk.gray(' SSH: ') +
|
|
199
|
+
chalk.cyan(`ssh ${sshInfo.hostAlias}`) +
|
|
200
|
+
chalk.gray(' (host keys refreshed if the server was reprovisioned).')
|
|
201
|
+
);
|
|
64
202
|
}
|
|
65
203
|
|
|
66
|
-
module.exports = {
|
|
204
|
+
module.exports = {
|
|
205
|
+
mergeDevSshConfigAfterInit,
|
|
206
|
+
logDevSshConnectHint,
|
|
207
|
+
hostnameFromBaseUrl
|
|
208
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy-paste onboarding block for `aifabrix dev pin` (admin → developer).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Dev PIN share instructions with platform prerequisites
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
14
|
+
|
|
15
|
+
const NODE_INSTALL_URL = 'https://nodejs.org/';
|
|
16
|
+
const BUILDER_INSTALL_CMD = 'npm install -g @aifabrix/builder';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
21
|
+
function logDevPinPrerequisites() {
|
|
22
|
+
logger.log(chalk.bold('Prerequisites (once per machine):'));
|
|
23
|
+
logger.log('');
|
|
24
|
+
logger.log(chalk.cyan('Windows'));
|
|
25
|
+
logger.log(chalk.gray(` 1. Install Node.js 18+ from ${NODE_INSTALL_URL}`));
|
|
26
|
+
logger.log(` 2. ${BUILDER_INSTALL_CMD}`);
|
|
27
|
+
logger.log('');
|
|
28
|
+
logger.log(chalk.cyan('Linux & macOS'));
|
|
29
|
+
logger.log(
|
|
30
|
+
chalk.gray(` 1. Install Node.js 18+ (${NODE_INSTALL_URL} or your package manager)`)
|
|
31
|
+
);
|
|
32
|
+
logger.log(` 2. ${BUILDER_INSTALL_CMD}`);
|
|
33
|
+
logger.log('');
|
|
34
|
+
logger.log(chalk.gray(' After install, `af` is a shortcut for `aifabrix`.'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} baseInit
|
|
39
|
+
* @param {string} id
|
|
40
|
+
* @param {string} [hostsIp]
|
|
41
|
+
* @returns {void}
|
|
42
|
+
*/
|
|
43
|
+
function logPinHostsVariantLines(baseInit, id, hostsIp) {
|
|
44
|
+
logger.log(
|
|
45
|
+
chalk.cyan('2) Hosts file / administrator')
|
|
46
|
+
+ chalk.gray(
|
|
47
|
+
' — use when the hostname does not resolve and your organisation does not publish DNS for it. '
|
|
48
|
+
+ 'Run in an elevated terminal so the hosts file can be updated, or add the entry manually:'
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
if (hostsIp) {
|
|
52
|
+
logger.log(` ${baseInit} --add-hosts --hosts-ip ${hostsIp}`);
|
|
53
|
+
} else {
|
|
54
|
+
logger.log(` ${baseInit} --add-hosts --hosts-ip <server-LAN-IPv4>`);
|
|
55
|
+
logger.log(
|
|
56
|
+
chalk.gray(
|
|
57
|
+
` Replace <server-LAN-IPv4> with the Builder Server IPv4, or re-run: aifabrix dev pin ${id} --hosts-ip <ip>`
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Log copy-paste dev init commands for the developer (standard vs hosts-file).
|
|
65
|
+
* @param {Object} p
|
|
66
|
+
* @param {string} p.id - Developer ID
|
|
67
|
+
* @param {string} p.serverUrl - Builder Server base URL
|
|
68
|
+
* @param {string} p.pin - One-time PIN
|
|
69
|
+
* @param {string} p.expiresAt - PIN expiry (API string)
|
|
70
|
+
* @param {string} [p.hostsIp] - Optional IPv4 for the hosts variant
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
function logDevPinShareInstructions(p) {
|
|
74
|
+
const { id, serverUrl, pin, expiresAt, hostsIp } = p;
|
|
75
|
+
const baseInit = `aifabrix dev init --developer-id ${id} --server ${serverUrl} --pin ${pin}`;
|
|
76
|
+
const pinOnly = `aifabrix dev init --pin ${pin}`;
|
|
77
|
+
logger.log(formatSuccessLine(`PIN created for ${id}, expires ${expiresAt}.`));
|
|
78
|
+
logger.log('');
|
|
79
|
+
logger.log(
|
|
80
|
+
chalk.bold('Share with the developer — prerequisites and onboarding (PIN is one-time):')
|
|
81
|
+
);
|
|
82
|
+
logger.log('');
|
|
83
|
+
logDevPinPrerequisites();
|
|
84
|
+
logger.log('');
|
|
85
|
+
logger.log(chalk.bold('Then run one command to connect (choose the variant that fits):'));
|
|
86
|
+
logger.log('');
|
|
87
|
+
logger.log(
|
|
88
|
+
chalk.cyan('1) Standard')
|
|
89
|
+
+ chalk.gray(' — use when this machine can resolve the server hostname (DNS or hosts already set):')
|
|
90
|
+
);
|
|
91
|
+
logger.log(` ${baseInit}`);
|
|
92
|
+
logger.log('');
|
|
93
|
+
logPinHostsVariantLines(baseInit, id, hostsIp);
|
|
94
|
+
logger.log('');
|
|
95
|
+
logger.log(
|
|
96
|
+
chalk.cyan('3) Config already has remote-server + developer-id')
|
|
97
|
+
+ chalk.gray(
|
|
98
|
+
' — e.g. second machine / SSH session after settings were merged; same onboarding, PIN only:'
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
logger.log(` ${pinOnly}`);
|
|
102
|
+
logger.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
BUILDER_INSTALL_CMD,
|
|
107
|
+
NODE_INSTALL_URL,
|
|
108
|
+
logDevPinPrerequisites,
|
|
109
|
+
logDevPinShareInstructions
|
|
110
|
+
};
|
|
@@ -6,18 +6,49 @@
|
|
|
6
6
|
|
|
7
7
|
const nodeFsLib = require('../internal/node-fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const { ensureSshDir } = require('./ssh-key-helper');
|
|
9
|
+
const { ensureSshDir, getDefaultEd25519PrivateKeyPath } = require('./ssh-key-helper');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @param {string} sshUser
|
|
14
|
-
* @param {string} sshHost
|
|
12
|
+
* Legacy SSH Host alias: sync user + builder API hostname (e.g. dev03.builder01.aifabrix.dev).
|
|
13
|
+
* @param {string} sshUser
|
|
14
|
+
* @param {string} sshHost
|
|
15
15
|
* @returns {string}
|
|
16
16
|
*/
|
|
17
17
|
function getDevSshHostAlias(sshUser, sshHost) {
|
|
18
18
|
return `${sshUser}.${sshHost}`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @param {string|number} developerId
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
function formatDeveloperNumericId(developerId) {
|
|
26
|
+
const trimmed = String(developerId ?? '').trim();
|
|
27
|
+
if (/^\d+$/.test(trimmed)) {
|
|
28
|
+
return String(parseInt(trimmed, 10)).padStart(2, '0');
|
|
29
|
+
}
|
|
30
|
+
return trimmed.replace(/^dev/i, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Preferred SSH config Host alias — matches per-developer app host when developer-root-domain is set.
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} p
|
|
37
|
+
* @param {string} p.syncUser
|
|
38
|
+
* @param {string} p.syncHost - sync-ssh-host (HostName for TCP connect)
|
|
39
|
+
* @param {string|number} [p.developerId]
|
|
40
|
+
* @param {string} [p.developerRootDomain] - e.g. aifabrix.dev → dev03.aifabrix.dev; dev.aifabrix → dev03.dev.aifabrix
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function resolveDevSshHostAlias(p) {
|
|
44
|
+
const root = String(p.developerRootDomain || '').trim().replace(/^\.+|\.+$/g, '');
|
|
45
|
+
const id = p.developerId !== undefined && p.developerId !== null ? String(p.developerId).trim() : '';
|
|
46
|
+
if (root && id) {
|
|
47
|
+
return `dev${formatDeveloperNumericId(id)}.${root}`;
|
|
48
|
+
}
|
|
49
|
+
return getDevSshHostAlias(p.syncUser, p.syncHost);
|
|
50
|
+
}
|
|
51
|
+
|
|
21
52
|
/**
|
|
22
53
|
* Parse ssh config into Host blocks (lines before the first Host are ignored).
|
|
23
54
|
* @param {string} content
|
|
@@ -91,6 +122,48 @@ function resolveConnectAlias(matchedBlock, canonicalAlias) {
|
|
|
91
122
|
return matchedBlock.aliases[0] || canonicalAlias;
|
|
92
123
|
}
|
|
93
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @param {string[]} body
|
|
127
|
+
* @param {string} hostname
|
|
128
|
+
* @param {string} user
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
function isSshHostBlockForUserHost(body, hostname, user) {
|
|
132
|
+
return (
|
|
133
|
+
getHostBlockDirective(body, 'HostName') === hostname &&
|
|
134
|
+
getHostBlockDirective(body, 'User') === user
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Drop Host blocks that target the same HostName + User (legacy alias migration).
|
|
140
|
+
* @param {string} content
|
|
141
|
+
* @param {string} hostname
|
|
142
|
+
* @param {string} user
|
|
143
|
+
* @returns {string}
|
|
144
|
+
*/
|
|
145
|
+
function removeSshHostBlocksForUserHost(content, hostname, user) {
|
|
146
|
+
const lines = (content || '').split(/\r?\n/);
|
|
147
|
+
const out = [];
|
|
148
|
+
for (let i = 0; i < lines.length; i++) {
|
|
149
|
+
const m = lines[i].match(/^Host\s+(.+?)\s*$/i);
|
|
150
|
+
if (!m) {
|
|
151
|
+
out.push(lines[i]);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const body = [];
|
|
155
|
+
i++;
|
|
156
|
+
while (i < lines.length && !/^Host\s/i.test(lines[i])) {
|
|
157
|
+
body.push(lines[i++]);
|
|
158
|
+
}
|
|
159
|
+
if (!isSshHostBlockForUserHost(body, hostname, user)) {
|
|
160
|
+
out.push(`Host ${m[1].trim()}`, ...body);
|
|
161
|
+
}
|
|
162
|
+
i--;
|
|
163
|
+
}
|
|
164
|
+
return out.join('\n').replace(/\s+$/, '');
|
|
165
|
+
}
|
|
166
|
+
|
|
94
167
|
/**
|
|
95
168
|
* Replace an existing Host block for hostAlias, or append a new block at EOF.
|
|
96
169
|
* @param {string} content - Existing ssh config (may be empty)
|
|
@@ -100,7 +173,8 @@ function resolveConnectAlias(matchedBlock, canonicalAlias) {
|
|
|
100
173
|
* @returns {string} Updated config
|
|
101
174
|
*/
|
|
102
175
|
function upsertSshHostBlock(content, hostAlias, hostname, user) {
|
|
103
|
-
const
|
|
176
|
+
const withoutDupes = removeSshHostBlocksForUserHost(content, hostname, user);
|
|
177
|
+
const lines = (withoutDupes || '').split(/\r?\n/);
|
|
104
178
|
const out = [];
|
|
105
179
|
let i = 0;
|
|
106
180
|
while (i < lines.length) {
|
|
@@ -123,6 +197,7 @@ function upsertSshHostBlock(content, hostAlias, hostname, user) {
|
|
|
123
197
|
`Host ${hostAlias}`,
|
|
124
198
|
` HostName ${hostname}`,
|
|
125
199
|
` User ${user}`,
|
|
200
|
+
` IdentityFile ${getDefaultEd25519PrivateKeyPath().replace(/\\/g, '/')}`,
|
|
126
201
|
' IdentitiesOnly yes'
|
|
127
202
|
].join('\n');
|
|
128
203
|
const trimmed = out.join('\n').replace(/\s+$/, '');
|
|
@@ -130,12 +205,27 @@ function upsertSshHostBlock(content, hostAlias, hostname, user) {
|
|
|
130
205
|
return `${prefix}${block}\n`;
|
|
131
206
|
}
|
|
132
207
|
|
|
208
|
+
function listHostAliasesForUserHost(content, hostname, user) {
|
|
209
|
+
const aliases = [];
|
|
210
|
+
for (const block of parseSshHostBlocks(content)) {
|
|
211
|
+
const hn = getHostBlockDirective(block.body, 'HostName');
|
|
212
|
+
const u = getHostBlockDirective(block.body, 'User');
|
|
213
|
+
if (hn === hostname && u === user) {
|
|
214
|
+
aliases.push(...block.aliases);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return aliases;
|
|
218
|
+
}
|
|
219
|
+
|
|
133
220
|
/**
|
|
134
221
|
* @param {string} sshUser
|
|
135
222
|
* @param {string} sshHost
|
|
136
|
-
* @
|
|
223
|
+
* @param {Object} [options]
|
|
224
|
+
* @param {string|number} [options.developerId]
|
|
225
|
+
* @param {string} [options.developerRootDomain]
|
|
226
|
+
* @returns {{ error: string }|{ user: string, host: string, hostAlias: string, legacyHostAlias: string }}
|
|
137
227
|
*/
|
|
138
|
-
function parseEnsureSshInputs(sshUser, sshHost) {
|
|
228
|
+
function parseEnsureSshInputs(sshUser, sshHost, options = {}) {
|
|
139
229
|
if (!sshUser || typeof sshUser !== 'string' || !sshUser.trim()) {
|
|
140
230
|
return { error: 'missing ssh user' };
|
|
141
231
|
}
|
|
@@ -144,7 +234,53 @@ function parseEnsureSshInputs(sshUser, sshHost) {
|
|
|
144
234
|
}
|
|
145
235
|
const user = sshUser.trim();
|
|
146
236
|
const host = sshHost.trim();
|
|
147
|
-
|
|
237
|
+
const hostAlias = resolveDevSshHostAlias({
|
|
238
|
+
syncUser: user,
|
|
239
|
+
syncHost: host,
|
|
240
|
+
developerId: options.developerId,
|
|
241
|
+
developerRootDomain: options.developerRootDomain
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
user,
|
|
245
|
+
host,
|
|
246
|
+
hostAlias,
|
|
247
|
+
legacyHostAlias: getDevSshHostAlias(user, host)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {string} configPath
|
|
253
|
+
* @returns {Promise<{ content: string }|{ error: string }>}
|
|
254
|
+
*/
|
|
255
|
+
async function readExistingSshConfig(configPath) {
|
|
256
|
+
try {
|
|
257
|
+
const content = await nodeFsLib.nodeFs().promises.readFile(configPath, 'utf8');
|
|
258
|
+
return { content };
|
|
259
|
+
} catch (e) {
|
|
260
|
+
if (e.code === 'ENOENT') {
|
|
261
|
+
return { content: '' };
|
|
262
|
+
}
|
|
263
|
+
return { error: e.message || String(e) };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {{ aliases: string[], body: string[] }|null} matched
|
|
269
|
+
* @param {string} hostAlias
|
|
270
|
+
* @param {string} rootDomain
|
|
271
|
+
* @returns {{ skip: true, hostAlias: string }|{ skip: false, matched: typeof matched }}
|
|
272
|
+
*/
|
|
273
|
+
function resolveExistingSshBlock(matched, hostAlias, rootDomain) {
|
|
274
|
+
if (!matched) {
|
|
275
|
+
return { skip: false, matched: null };
|
|
276
|
+
}
|
|
277
|
+
const matchedAlias = resolveConnectAlias(matched, hostAlias);
|
|
278
|
+
const keepExisting =
|
|
279
|
+
!rootDomain || (matched.aliases.includes(hostAlias) && matchedAlias === hostAlias);
|
|
280
|
+
if (keepExisting) {
|
|
281
|
+
return { skip: true, hostAlias: matchedAlias };
|
|
282
|
+
}
|
|
283
|
+
return { skip: false, matched };
|
|
148
284
|
}
|
|
149
285
|
|
|
150
286
|
/**
|
|
@@ -152,44 +288,52 @@ function parseEnsureSshInputs(sshUser, sshHost) {
|
|
|
152
288
|
* @param {string} sshUser - SSH user (sync-ssh-user)
|
|
153
289
|
* @param {string} sshHost - Real hostname (sync-ssh-host)
|
|
154
290
|
* @param {string} [sshDir] - SSH directory (default ~/.ssh)
|
|
155
|
-
* @
|
|
291
|
+
* @param {Object} [options]
|
|
292
|
+
* @param {string|number} [options.developerId]
|
|
293
|
+
* @param {string} [options.developerRootDomain]
|
|
294
|
+
* @returns {Promise<{ ok: boolean, configPath?: string, hostAlias?: string, error?: string, skippedDuplicate?: boolean, updated?: boolean }>}
|
|
156
295
|
*/
|
|
157
|
-
async function ensureDevSshConfigBlock(sshUser, sshHost, sshDir) {
|
|
158
|
-
const parsed = parseEnsureSshInputs(sshUser, sshHost);
|
|
296
|
+
async function ensureDevSshConfigBlock(sshUser, sshHost, sshDir, options = {}) {
|
|
297
|
+
const parsed = parseEnsureSshInputs(sshUser, sshHost, options);
|
|
159
298
|
if ('error' in parsed) {
|
|
160
299
|
return { ok: false, error: parsed.error };
|
|
161
300
|
}
|
|
162
|
-
const { user, host, hostAlias } = parsed;
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
301
|
+
const { user, host, hostAlias, legacyHostAlias } = parsed;
|
|
302
|
+
const configPath = path.join(ensureSshDir(sshDir), 'config');
|
|
303
|
+
const readResult = await readExistingSshConfig(configPath);
|
|
304
|
+
if ('error' in readResult) {
|
|
305
|
+
return { ok: false, error: readResult.error };
|
|
306
|
+
}
|
|
307
|
+
const rootDomain = String(options.developerRootDomain || '').trim();
|
|
308
|
+
const matched = findMatchingHostBlockForUserHost(readResult.content, host, user);
|
|
309
|
+
const existing = resolveExistingSshBlock(matched, hostAlias, rootDomain);
|
|
310
|
+
if (existing.skip) {
|
|
311
|
+
return { ok: true, configPath, hostAlias: existing.hostAlias, skippedDuplicate: true };
|
|
172
312
|
}
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
175
|
-
return {
|
|
176
|
-
ok: true,
|
|
177
|
-
configPath,
|
|
178
|
-
hostAlias: resolveConnectAlias(matched, hostAlias),
|
|
179
|
-
skippedDuplicate: true
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
const next = upsertSshHostBlock(existing, hostAlias, host, user);
|
|
183
|
-
if (next === existing) {
|
|
313
|
+
const next = upsertSshHostBlock(readResult.content, hostAlias, host, user);
|
|
314
|
+
if (next === readResult.content) {
|
|
184
315
|
return { ok: true, configPath, hostAlias };
|
|
185
316
|
}
|
|
186
317
|
await nodeFsLib.nodeFs().promises.writeFile(configPath, next, { mode: 0o600 });
|
|
187
|
-
|
|
318
|
+
const migratedFrom = existing.matched
|
|
319
|
+
? resolveConnectAlias(existing.matched, hostAlias)
|
|
320
|
+
: legacyHostAlias;
|
|
321
|
+
return {
|
|
322
|
+
ok: true,
|
|
323
|
+
configPath,
|
|
324
|
+
hostAlias,
|
|
325
|
+
updated: true,
|
|
326
|
+
migratedFrom: migratedFrom !== hostAlias ? migratedFrom : undefined
|
|
327
|
+
};
|
|
188
328
|
}
|
|
189
329
|
|
|
190
330
|
module.exports = {
|
|
331
|
+
formatDeveloperNumericId,
|
|
191
332
|
getDevSshHostAlias,
|
|
333
|
+
resolveDevSshHostAlias,
|
|
192
334
|
findMatchingHostBlockForUserHost,
|
|
335
|
+
listHostAliasesForUserHost,
|
|
336
|
+
removeSshHostBlocksForUserHost,
|
|
193
337
|
resolveConnectAlias,
|
|
194
338
|
upsertSshHostBlock,
|
|
195
339
|
ensureDevSshConfigBlock
|