@useatlas/create 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -18
- package/index.ts +191 -31
- package/package.json +1 -1
- package/templates/docker/.env.example +3 -3
- package/templates/docker/Dockerfile.sidecar +28 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +11 -11
- package/templates/docker/bin/atlas.ts +120 -56
- package/templates/docker/data/demo-semantic/catalog.yml +51 -27
- package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
- package/templates/docker/data/demo-semantic/glossary.yml +104 -8
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/docker/docker-compose.yml +1 -1
- package/templates/docker/docs/deploy.md +4 -41
- package/templates/docker/package.json +17 -1
- package/templates/docker/semantic/catalog.yml +62 -3
- package/templates/docker/semantic/entities/accounts.yml +162 -0
- package/templates/docker/semantic/entities/companies.yml +143 -0
- package/templates/docker/semantic/entities/people.yml +132 -0
- package/templates/docker/semantic/glossary.yml +116 -4
- package/templates/docker/semantic/metrics/accounts.yml +77 -0
- package/templates/docker/semantic/metrics/companies.yml +63 -0
- package/templates/docker/sidecar/Dockerfile +5 -6
- package/templates/docker/sidecar/railway.json +1 -2
- package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/docker/src/api/__tests__/health.test.ts +30 -8
- package/templates/docker/src/api/routes/admin.ts +549 -8
- package/templates/docker/src/api/routes/chat.ts +5 -20
- package/templates/docker/src/api/routes/health.ts +39 -27
- package/templates/docker/src/api/routes/openapi.ts +1329 -74
- package/templates/docker/src/api/routes/query.ts +2 -1
- package/templates/docker/src/api/server.ts +27 -0
- package/templates/docker/src/app/api/[...route]/route.ts +2 -2
- package/templates/docker/src/app/globals.css +13 -12
- package/templates/docker/src/app/layout.tsx +9 -2
- package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/docker/src/components/ui/badge.tsx +48 -0
- package/templates/docker/src/components/ui/button.tsx +64 -0
- package/templates/docker/src/components/ui/card.tsx +92 -0
- package/templates/docker/src/components/ui/collapsible.tsx +33 -0
- package/templates/docker/src/components/ui/command.tsx +184 -0
- package/templates/docker/src/components/ui/dialog.tsx +158 -0
- package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/docker/src/components/ui/input.tsx +21 -0
- package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
- package/templates/docker/src/components/ui/select.tsx +190 -0
- package/templates/docker/src/components/ui/separator.tsx +28 -0
- package/templates/docker/src/components/ui/sheet.tsx +143 -0
- package/templates/docker/src/components/ui/sidebar.tsx +726 -0
- package/templates/docker/src/components/ui/skeleton.tsx +13 -0
- package/templates/docker/src/components/ui/table.tsx +116 -0
- package/templates/docker/src/components/ui/tabs.tsx +91 -0
- package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
- package/templates/docker/src/components/ui/toggle.tsx +47 -0
- package/templates/docker/src/components/ui/tooltip.tsx +57 -0
- package/templates/docker/src/hooks/use-mobile.ts +19 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
- package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/docker/src/lib/agent-query.ts +5 -23
- package/templates/docker/src/lib/agent.ts +32 -112
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/docker/src/lib/auth/middleware.ts +30 -4
- package/templates/docker/src/lib/auth/migrate.ts +97 -0
- package/templates/docker/src/lib/auth/server.ts +12 -1
- package/templates/docker/src/lib/config.ts +38 -40
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/docker/src/lib/db/connection.ts +87 -265
- package/templates/docker/src/lib/db/internal.ts +6 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/docker/src/lib/plugins/index.ts +4 -1
- package/templates/docker/src/lib/plugins/migrate.ts +104 -1
- package/templates/docker/src/lib/plugins/registry.ts +14 -8
- package/templates/docker/src/lib/plugins/wiring.ts +113 -4
- package/templates/docker/src/lib/providers.ts +6 -1
- package/templates/docker/src/lib/security.ts +24 -0
- package/templates/docker/src/lib/semantic.ts +2 -0
- package/templates/docker/src/lib/sidecar-types.ts +12 -1
- package/templates/docker/src/lib/startup.ts +71 -101
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/docker/src/lib/tools/explore.ts +28 -32
- package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/docker/src/lib/tools/python.ts +367 -0
- package/templates/docker/src/lib/tools/registry.ts +49 -22
- package/templates/docker/src/lib/tools/sql.ts +88 -88
- package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/docker/src/ui/context.tsx +1 -1
- package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/docker/tsconfig.json +2 -2
- package/templates/nextjs-standalone/.env.example +1 -1
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +11 -11
- package/templates/nextjs-standalone/bin/atlas.ts +120 -56
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/nextjs-standalone/docs/deploy.md +4 -41
- package/templates/nextjs-standalone/package.json +11 -2
- package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
- package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
- package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
- package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
- package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
- package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
- package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
- package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
- package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
- package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
- package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
- package/templates/nextjs-standalone/src/api/server.ts +27 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
- package/templates/nextjs-standalone/src/app/globals.css +13 -12
- package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
- package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
- package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
- package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
- package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
- package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
- package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
- package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
- package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
- package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
- package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
- package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
- package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
- package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
- package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
- package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
- package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
- package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
- package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
- package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
- package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
- package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
- package/templates/nextjs-standalone/src/lib/config.ts +38 -40
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
- package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +104 -1
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +14 -8
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
- package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
- package/templates/nextjs-standalone/src/lib/security.ts +24 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
- package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +1 -1
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
- package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/nextjs-standalone/tsconfig.json +0 -1
- package/templates/nextjs-standalone/vercel.json +4 -1
- package/templates/docker/render.yaml +0 -34
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/docker/src/lib/db/duckdb.ts +0 -122
- package/templates/docker/src/lib/db/salesforce.ts +0 -342
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/docker/src/lib/tools/salesforce.ts +0 -138
- package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
|
@@ -28,6 +28,8 @@ interface DatasourceShape {
|
|
|
28
28
|
create(): Promise<{ query(sql: string, timeoutMs?: number): Promise<unknown>; close(): Promise<void> }> | { query(sql: string, timeoutMs?: number): Promise<unknown>; close(): Promise<void> };
|
|
29
29
|
dbType: string;
|
|
30
30
|
validate?(query: string): { valid: boolean; reason?: string };
|
|
31
|
+
parserDialect?: string;
|
|
32
|
+
forbiddenPatterns?: RegExp[];
|
|
31
33
|
};
|
|
32
34
|
entities?: unknown[] | (() => Promise<unknown[]> | unknown[]);
|
|
33
35
|
dialect?: string;
|
|
@@ -51,7 +53,7 @@ interface InteractionShape {
|
|
|
51
53
|
|
|
52
54
|
function hasContextProvider(p: PluginLike): p is PluginLike & ContextShape {
|
|
53
55
|
return (
|
|
54
|
-
p.
|
|
56
|
+
p.types.includes("context") &&
|
|
55
57
|
typeof (p as Record<string, unknown>).contextProvider === "object" &&
|
|
56
58
|
(p as Record<string, unknown>).contextProvider !== null &&
|
|
57
59
|
typeof ((p as Record<string, unknown>).contextProvider as Record<string, unknown>)?.load === "function"
|
|
@@ -60,18 +62,18 @@ function hasContextProvider(p: PluginLike): p is PluginLike & ContextShape {
|
|
|
60
62
|
|
|
61
63
|
function hasDatasource(p: PluginLike): p is PluginLike & DatasourceShape {
|
|
62
64
|
return (
|
|
63
|
-
p.
|
|
65
|
+
p.types.includes("datasource") &&
|
|
64
66
|
typeof (p as Record<string, unknown>).connection === "object" &&
|
|
65
67
|
(p as Record<string, unknown>).connection !== null
|
|
66
68
|
);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
function hasActions(p: PluginLike): p is PluginLike & ActionShape {
|
|
70
|
-
return p.
|
|
72
|
+
return p.types.includes("action") && Array.isArray((p as Record<string, unknown>).actions);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
function hasRoutes(p: PluginLike): p is PluginLike & InteractionShape {
|
|
74
|
-
return p.
|
|
76
|
+
return p.types.includes("interaction") && typeof (p as Record<string, unknown>).routes === "function";
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
// ---------------------------------------------------------------------------
|
|
@@ -106,12 +108,16 @@ export async function wireDatasourcePlugins(
|
|
|
106
108
|
}
|
|
107
109
|
try {
|
|
108
110
|
const conn = await plugin.connection.create();
|
|
111
|
+
const meta = (plugin.connection.parserDialect || plugin.connection.forbiddenPatterns)
|
|
112
|
+
? { parserDialect: plugin.connection.parserDialect, forbiddenPatterns: plugin.connection.forbiddenPatterns }
|
|
113
|
+
: undefined;
|
|
109
114
|
await connRegistry.registerDirect(
|
|
110
115
|
plugin.id,
|
|
111
116
|
conn as Parameters<ConnectionRegistry["registerDirect"]>[1],
|
|
112
117
|
plugin.connection.dbType as Parameters<ConnectionRegistry["registerDirect"]>[2],
|
|
113
118
|
plugin.name ?? plugin.id,
|
|
114
119
|
plugin.connection.validate,
|
|
120
|
+
meta,
|
|
115
121
|
);
|
|
116
122
|
wired.push(plugin.id);
|
|
117
123
|
log.info({ pluginId: plugin.id, dbType: plugin.connection.dbType }, "Datasource plugin wired");
|
|
@@ -254,6 +260,109 @@ export async function wireInteractionPlugins(
|
|
|
254
260
|
return { wired, failed };
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Sandbox plugins
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Duplicated from @useatlas/plugin-sdk/types to avoid runtime SDK dependency.
|
|
269
|
+
* Keep in sync — explore-sdk-compat.test.ts verifies structural equivalence.
|
|
270
|
+
*/
|
|
271
|
+
const SANDBOX_DEFAULT_PRIORITY = 60;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Minimal interface for a sandbox execution backend.
|
|
275
|
+
* Structurally identical to ExploreBackend in explore.ts — duplicated here
|
|
276
|
+
* to avoid a circular dependency (explore.ts imports from wiring.ts).
|
|
277
|
+
* Changes to either interface should be mirrored.
|
|
278
|
+
*/
|
|
279
|
+
export interface SandboxExecBackend {
|
|
280
|
+
exec(command: string): Promise<{ stdout: string; stderr: string; exitCode: number }>;
|
|
281
|
+
close?(): Promise<void>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
interface SandboxShape {
|
|
285
|
+
sandbox: {
|
|
286
|
+
create(root: string): Promise<SandboxExecBackend> | SandboxExecBackend;
|
|
287
|
+
priority?: number;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function hasSandbox(p: PluginLike): p is PluginLike & SandboxShape {
|
|
292
|
+
return (
|
|
293
|
+
p.types.includes("sandbox") &&
|
|
294
|
+
typeof (p as Record<string, unknown>).sandbox === "object" &&
|
|
295
|
+
(p as Record<string, unknown>).sandbox !== null &&
|
|
296
|
+
typeof ((p as Record<string, unknown>).sandbox as Record<string, unknown>)?.create === "function"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Discover sandbox plugins, sort by priority (highest first), and try to
|
|
302
|
+
* create a backend from each until one succeeds.
|
|
303
|
+
*
|
|
304
|
+
* Unlike other wire functions, sandbox plugins are not registered into a
|
|
305
|
+
* global registry — the caller receives a single backend instance directly.
|
|
306
|
+
*
|
|
307
|
+
* NOTE: In practice, called lazily from getExploreBackend() on the first
|
|
308
|
+
* explore command (not at startup), because the explore backend is cached
|
|
309
|
+
* as a singleton and depends on runtime environment detection.
|
|
310
|
+
*/
|
|
311
|
+
export async function wireSandboxPlugins(
|
|
312
|
+
pluginRegistry: PluginRegistry,
|
|
313
|
+
semanticRoot: string,
|
|
314
|
+
): Promise<{
|
|
315
|
+
backend: SandboxExecBackend | null;
|
|
316
|
+
pluginId: string | null;
|
|
317
|
+
failed: Array<{ pluginId: string; error: string }>;
|
|
318
|
+
}> {
|
|
319
|
+
const sandboxPlugins = pluginRegistry.getByType("sandbox");
|
|
320
|
+
const failed: Array<{ pluginId: string; error: string }> = [];
|
|
321
|
+
|
|
322
|
+
if (sandboxPlugins.length === 0) {
|
|
323
|
+
return { backend: null, pluginId: null, failed };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Filter to valid sandbox plugins, sort by priority descending
|
|
327
|
+
const valid = sandboxPlugins.filter(hasSandbox);
|
|
328
|
+
if (valid.length === 0) {
|
|
329
|
+
for (const sp of sandboxPlugins) {
|
|
330
|
+
log.warn({ pluginId: sp.id }, "Sandbox plugin missing sandbox.create() — skipped");
|
|
331
|
+
}
|
|
332
|
+
return { backend: null, pluginId: null, failed };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const sorted = [...valid].sort((a, b) => {
|
|
336
|
+
const pa = a.sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
|
|
337
|
+
const pb = b.sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
|
|
338
|
+
return pb - pa;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
for (const sp of sorted) {
|
|
342
|
+
try {
|
|
343
|
+
const backend = await sp.sandbox.create(semanticRoot);
|
|
344
|
+
if (!backend || typeof backend.exec !== "function") {
|
|
345
|
+
const msg = "create() returned invalid backend (missing exec method)";
|
|
346
|
+
failed.push({ pluginId: sp.id, error: msg });
|
|
347
|
+
log.error({ pluginId: sp.id }, msg);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
log.info({ pluginId: sp.id }, "Using sandbox plugin for explore backend");
|
|
351
|
+
return { backend, pluginId: sp.id, failed };
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
354
|
+
failed.push({ pluginId: sp.id, error: msg });
|
|
355
|
+
log.error(
|
|
356
|
+
{ pluginId: sp.id, err: err instanceof Error ? err : new Error(String(err)) },
|
|
357
|
+
"Sandbox plugin create() failed, trying next",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
log.error({ count: sorted.length }, "All sandbox plugins failed to create a backend");
|
|
363
|
+
return { backend: null, pluginId: null, failed };
|
|
364
|
+
}
|
|
365
|
+
|
|
257
366
|
/**
|
|
258
367
|
* For each healthy context plugin, call `contextProvider.load()` and collect
|
|
259
368
|
* the returned text fragments. Fragments are injected into the agent system
|
|
@@ -34,6 +34,11 @@ const PROVIDER_DEFAULTS: Record<ConfigProvider, string> = {
|
|
|
34
34
|
gateway: "anthropic/claude-opus-4.6",
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
/** Returns the default provider string based on runtime environment. */
|
|
38
|
+
export function getDefaultProvider(): ConfigProvider {
|
|
39
|
+
return process.env.VERCEL ? "gateway" : "anthropic";
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
function isBedrockAnthropicModel(modelId: string): boolean {
|
|
38
43
|
return modelId.includes("anthropic") || modelId.includes("claude");
|
|
39
44
|
}
|
|
@@ -43,7 +48,7 @@ function isBedrockAnthropicModel(modelId: string): boolean {
|
|
|
43
48
|
* Returns the validated config provider string and the resolved model ID.
|
|
44
49
|
*/
|
|
45
50
|
function resolveProvider(): { provider: ConfigProvider; modelId: string } {
|
|
46
|
-
const raw = process.env.ATLAS_PROVIDER ??
|
|
51
|
+
const raw = process.env.ATLAS_PROVIDER ?? getDefaultProvider();
|
|
47
52
|
if (!VALID_PROVIDERS.has(raw as ConfigProvider)) {
|
|
48
53
|
throw new Error(
|
|
49
54
|
`Unknown provider "${raw}". Supported: ${[...VALID_PROVIDERS].join(", ")}`
|
|
@@ -9,3 +9,27 @@
|
|
|
9
9
|
|
|
10
10
|
export const SENSITIVE_PATTERNS =
|
|
11
11
|
/password|secret|credential|connection.?string|pg_hba\.conf|SSL|certificate|Access denied for user|ER_ACCESS_DENIED_ERROR|ER_DBACCESS_DENIED_ERROR|ER_BAD_HOST_ERROR|ER_HOST_NOT_PRIVILEGED|ER_SPECIFIC_ACCESS_DENIED_ERROR|PROTOCOL_CONNECTION_LOST|Can't connect to MySQL server|Authentication failed|DB::Exception.*Authentication|UNKNOWN_USER|WRONG_PASSWORD|REQUIRED_PASSWORD|IP_ADDRESS_NOT_ALLOWED|ALL_CONNECTION_TRIES_FAILED|CLIENT_HAS_CONNECTED_TO_WRONG_PORT|AUTHENTICATION_FAILED|INVALID_SESSION_ID|LOGIN_MUST_USE_SECURITY_TOKEN|INVALID_LOGIN|INVALID_CLIENT_ID/i;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Mask credentials in a database connection URL.
|
|
15
|
+
* Returns "<invalid-url>" for unparseable URLs to avoid leaking raw strings.
|
|
16
|
+
*/
|
|
17
|
+
const SENSITIVE_PARAMS = /^(password|secret|token|key|credential|auth)$/i;
|
|
18
|
+
|
|
19
|
+
export function maskConnectionUrl(url: string): string {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(url);
|
|
22
|
+
if (parsed.username || parsed.password) {
|
|
23
|
+
parsed.username = "***";
|
|
24
|
+
parsed.password = "";
|
|
25
|
+
}
|
|
26
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
27
|
+
if (SENSITIVE_PARAMS.test(key)) {
|
|
28
|
+
parsed.searchParams.set(key, "***");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return parsed.toString();
|
|
32
|
+
} catch {
|
|
33
|
+
return "<invalid-url>";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -86,6 +86,8 @@ function loadEntitiesFromDir(
|
|
|
86
86
|
for (const file of files) {
|
|
87
87
|
try {
|
|
88
88
|
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
89
|
+
// js-yaml v4+ yaml.load() uses DEFAULT_SCHEMA (JSON + core YAML types) which is
|
|
90
|
+
// safe — it does not instantiate arbitrary JS objects (unlike v3's yaml.load).
|
|
89
91
|
const raw = yaml.load(content);
|
|
90
92
|
const parsed = EntityShape.safeParse(raw);
|
|
91
93
|
if (!parsed.success) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the sidecar HTTP contract.
|
|
3
3
|
* Used by both the sidecar server (packages/sandbox-sidecar) and
|
|
4
|
-
* the sidecar
|
|
4
|
+
* the sidecar clients (explore-sidecar.ts, python-sidecar.ts).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export interface SidecarExecRequest {
|
|
@@ -14,3 +14,14 @@ export interface SidecarExecResponse {
|
|
|
14
14
|
stderr: string;
|
|
15
15
|
exitCode: number;
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
// --- Python execution ---
|
|
19
|
+
|
|
20
|
+
export interface SidecarPythonRequest {
|
|
21
|
+
code: string;
|
|
22
|
+
data?: { columns: string[]; rows: unknown[][] };
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Wire-format alias — canonical type lives in python.ts. */
|
|
27
|
+
export type { PythonResult as SidecarPythonResponse } from "@atlas/api/lib/tools/python";
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import * as path from "path";
|
|
10
|
-
import { detectDBType } from "./db/connection";
|
|
10
|
+
import { detectDBType, resolveDatasourceUrl } from "./db/connection";
|
|
11
|
+
import { maskConnectionUrl } from "./security";
|
|
12
|
+
import { getDefaultProvider } from "./providers";
|
|
11
13
|
import { detectAuthMode, getAuthModeSource } from "./auth/detect";
|
|
12
14
|
import { createLogger } from "./logger";
|
|
13
15
|
|
|
@@ -18,7 +20,8 @@ export type DiagnosticCode =
|
|
|
18
20
|
| "MISSING_SEMANTIC_LAYER" | "INVALID_SCHEMA" | "INTERNAL_DB_UNREACHABLE"
|
|
19
21
|
| "WEAK_AUTH_SECRET" | "INVALID_JWKS_URL" | "MISSING_AUTH_ISSUER"
|
|
20
22
|
| "MISSING_AUTH_PREREQ"
|
|
21
|
-
| "ACTIONS_REQUIRE_AUTH" | "ACTIONS_MISSING_CREDENTIALS"
|
|
23
|
+
| "ACTIONS_REQUIRE_AUTH" | "ACTIONS_MISSING_CREDENTIALS"
|
|
24
|
+
| "INVALID_CONFIG";
|
|
22
25
|
|
|
23
26
|
export interface DiagnosticError {
|
|
24
27
|
code: DiagnosticCode;
|
|
@@ -33,6 +36,12 @@ const PROVIDER_KEY_MAP: Record<string, string> = {
|
|
|
33
36
|
gateway: "AI_GATEWAY_API_KEY",
|
|
34
37
|
};
|
|
35
38
|
|
|
39
|
+
const PROVIDER_SIGNUP_URL: Record<string, string> = {
|
|
40
|
+
anthropic: "https://console.anthropic.com/settings/keys",
|
|
41
|
+
openai: "https://platform.openai.com/api-keys",
|
|
42
|
+
gateway: "https://vercel.com/~/ai/api-keys",
|
|
43
|
+
};
|
|
44
|
+
|
|
36
45
|
let _cached: DiagnosticError[] | null = null;
|
|
37
46
|
let _cachedAt = 0;
|
|
38
47
|
const _startupWarnings: string[] = [];
|
|
@@ -67,14 +76,23 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
67
76
|
|
|
68
77
|
const errors: DiagnosticError[] = [];
|
|
69
78
|
|
|
70
|
-
// 1.
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
// 1. Analytics datasource — resolve from ATLAS_DATASOURCE_URL or Neon fallback
|
|
80
|
+
const resolvedDatasourceUrl = resolveDatasourceUrl();
|
|
81
|
+
if (!resolvedDatasourceUrl) {
|
|
82
|
+
if (process.env.ATLAS_DEMO_DATA === "true") {
|
|
83
|
+
const msg =
|
|
84
|
+
"ATLAS_DEMO_DATA=true but neither DATABASE_URL_UNPOOLED nor DATABASE_URL is set. " +
|
|
85
|
+
"The Neon integration may not have provisioned a database. " +
|
|
86
|
+
"Check your Vercel project's storage integrations.";
|
|
87
|
+
log.error(msg);
|
|
88
|
+
errors.push({ code: "MISSING_DATASOURCE_URL", message: msg });
|
|
89
|
+
} else if (process.env.DATABASE_URL) {
|
|
73
90
|
const msg =
|
|
74
91
|
"DATABASE_URL is set but ATLAS_DATASOURCE_URL is not. " +
|
|
75
92
|
"As of v0.5, the analytics datasource uses ATLAS_DATASOURCE_URL. " +
|
|
76
93
|
"DATABASE_URL is now reserved for Atlas's internal Postgres. " +
|
|
77
|
-
"Rename your analytics connection to ATLAS_DATASOURCE_URL
|
|
94
|
+
"Rename your analytics connection to ATLAS_DATASOURCE_URL, " +
|
|
95
|
+
"or set ATLAS_DEMO_DATA=true to use the same database for demo data.";
|
|
78
96
|
log.error(msg);
|
|
79
97
|
errors.push({ code: "MISSING_DATASOURCE_URL", message: msg });
|
|
80
98
|
} else {
|
|
@@ -87,19 +105,23 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
87
105
|
}
|
|
88
106
|
log.warn(msg);
|
|
89
107
|
}
|
|
108
|
+
} else if (!process.env.ATLAS_DATASOURCE_URL && process.env.ATLAS_DEMO_DATA === "true") {
|
|
109
|
+
const source = process.env.DATABASE_URL_UNPOOLED ? "DATABASE_URL_UNPOOLED" : "DATABASE_URL";
|
|
110
|
+
log.info("Demo mode: using %s as analytics datasource", source);
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
// 2. API key for configured provider
|
|
93
|
-
const provider = process.env.ATLAS_PROVIDER ??
|
|
114
|
+
const provider = process.env.ATLAS_PROVIDER ?? getDefaultProvider();
|
|
94
115
|
const requiredKey = PROVIDER_KEY_MAP[provider];
|
|
95
116
|
|
|
96
117
|
if (requiredKey === undefined) {
|
|
97
118
|
// Unknown provider — providers.ts will throw a descriptive error at model init,
|
|
98
119
|
// so we don't duplicate that check here.
|
|
99
120
|
} else if (requiredKey && !process.env[requiredKey]) {
|
|
100
|
-
let message = `${requiredKey} is not set. Atlas needs an API key for the ${provider} provider.`;
|
|
101
|
-
|
|
102
|
-
|
|
121
|
+
let message = `${requiredKey} is not set. Atlas needs an API key for the "${provider}" provider. Set it in your .env file.`;
|
|
122
|
+
const signupUrl = PROVIDER_SIGNUP_URL[provider];
|
|
123
|
+
if (signupUrl) {
|
|
124
|
+
message += ` Get one at ${signupUrl}`;
|
|
103
125
|
}
|
|
104
126
|
errors.push({ code: "MISSING_API_KEY", message });
|
|
105
127
|
}
|
|
@@ -129,11 +151,11 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
129
151
|
});
|
|
130
152
|
}
|
|
131
153
|
|
|
132
|
-
// 4. Datasource connectivity (only if
|
|
133
|
-
if (
|
|
154
|
+
// 4. Datasource connectivity (only if a datasource URL is resolved)
|
|
155
|
+
if (resolvedDatasourceUrl) {
|
|
134
156
|
let dbType: ReturnType<typeof detectDBType> | null = null;
|
|
135
157
|
try {
|
|
136
|
-
dbType = detectDBType();
|
|
158
|
+
dbType = detectDBType(resolvedDatasourceUrl);
|
|
137
159
|
} catch (err) {
|
|
138
160
|
const detail = err instanceof Error ? err.message : String(err);
|
|
139
161
|
log.error({ err: detail }, "Unsupported datasource URL");
|
|
@@ -142,7 +164,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
142
164
|
|
|
143
165
|
if (dbType === "mysql") {
|
|
144
166
|
// MySQL: URL validation + connection test
|
|
145
|
-
if (!isValidUrl(
|
|
167
|
+
if (!isValidUrl(resolvedDatasourceUrl)) {
|
|
146
168
|
errors.push({
|
|
147
169
|
code: "DB_UNREACHABLE",
|
|
148
170
|
message: "ATLAS_DATASOURCE_URL appears malformed. Expected format: mysql://user:pass@host:3306/dbname",
|
|
@@ -153,7 +175,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
153
175
|
let pool;
|
|
154
176
|
try {
|
|
155
177
|
pool = mysql.createPool({
|
|
156
|
-
uri:
|
|
178
|
+
uri: resolvedDatasourceUrl,
|
|
157
179
|
connectionLimit: 1,
|
|
158
180
|
connectTimeout: 5000,
|
|
159
181
|
});
|
|
@@ -163,7 +185,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
163
185
|
const detail = err instanceof Error ? err.message : "";
|
|
164
186
|
log.error({ err: detail }, "MySQL connection check failed");
|
|
165
187
|
|
|
166
|
-
let message =
|
|
188
|
+
let message = `Cannot connect to ${maskConnectionUrl(resolvedDatasourceUrl)}. Check the connection string and ensure the database is running.`;
|
|
167
189
|
|
|
168
190
|
if (/ECONNREFUSED/i.test(detail)) {
|
|
169
191
|
message += " The connection was refused — is the MySQL server running?";
|
|
@@ -184,47 +206,6 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
184
206
|
}
|
|
185
207
|
}
|
|
186
208
|
}
|
|
187
|
-
} else if (dbType === "clickhouse") {
|
|
188
|
-
// ClickHouse: connectivity test via SELECT 1
|
|
189
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
190
|
-
let createClient: any = null;
|
|
191
|
-
try {
|
|
192
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
193
|
-
({ createClient } = require("@clickhouse/client"));
|
|
194
|
-
} catch {
|
|
195
|
-
errors.push({
|
|
196
|
-
code: "DB_UNREACHABLE",
|
|
197
|
-
message: "ClickHouse support requires the @clickhouse/client package. Install it with: bun add @clickhouse/client",
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (createClient) {
|
|
202
|
-
const { rewriteClickHouseUrl } = await import("./db/connection");
|
|
203
|
-
const httpUrl = rewriteClickHouseUrl(process.env.ATLAS_DATASOURCE_URL!);
|
|
204
|
-
const client = createClient({ url: httpUrl });
|
|
205
|
-
try {
|
|
206
|
-
await client.query({ query: "SELECT 1", format: "JSON" });
|
|
207
|
-
} catch (err) {
|
|
208
|
-
const detail = err instanceof Error ? err.message : "";
|
|
209
|
-
log.error({ err: detail }, "ClickHouse connection check failed");
|
|
210
|
-
|
|
211
|
-
let message = "Cannot connect to ClickHouse. Check that the server is running and the connection string is correct.";
|
|
212
|
-
|
|
213
|
-
if (/ECONNREFUSED/i.test(detail)) {
|
|
214
|
-
message += " The connection was refused — is the ClickHouse server running?";
|
|
215
|
-
} else if (/Authentication/i.test(detail) || /AUTHENTICATION_FAILED/i.test(detail)) {
|
|
216
|
-
message += " Authentication failed — check your username and password.";
|
|
217
|
-
} else if (/timeout/i.test(detail)) {
|
|
218
|
-
message += " The connection timed out — check network/firewall settings.";
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
errors.push({ code: "DB_UNREACHABLE", message });
|
|
222
|
-
} finally {
|
|
223
|
-
await client.close().catch((err: unknown) => {
|
|
224
|
-
log.warn({ err: err instanceof Error ? err.message : String(err) }, "ClickHouse client cleanup warning");
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
209
|
} else if (dbType === "postgres") {
|
|
229
210
|
// PostgreSQL: existing URL validation + connection test + schema validation
|
|
230
211
|
const atlasSchema = process.env.ATLAS_SCHEMA;
|
|
@@ -238,7 +219,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
238
219
|
});
|
|
239
220
|
}
|
|
240
221
|
|
|
241
|
-
if (!isValidUrl(
|
|
222
|
+
if (!isValidUrl(resolvedDatasourceUrl)) {
|
|
242
223
|
errors.push({
|
|
243
224
|
code: "DB_UNREACHABLE",
|
|
244
225
|
message: "ATLAS_DATASOURCE_URL appears malformed. Expected format: postgresql://user:pass@host:5432/dbname",
|
|
@@ -247,7 +228,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
247
228
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
248
229
|
const { Pool } = require("pg");
|
|
249
230
|
const pool = new Pool({
|
|
250
|
-
connectionString:
|
|
231
|
+
connectionString: resolvedDatasourceUrl,
|
|
251
232
|
max: 1,
|
|
252
233
|
connectionTimeoutMillis: 5000,
|
|
253
234
|
});
|
|
@@ -281,7 +262,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
281
262
|
const detail = err instanceof Error ? err.message : "";
|
|
282
263
|
log.error({ err: detail }, "DB connection check failed");
|
|
283
264
|
|
|
284
|
-
let message =
|
|
265
|
+
let message = `Cannot connect to ${maskConnectionUrl(resolvedDatasourceUrl)}. Check the connection string and ensure the database is running.`;
|
|
285
266
|
|
|
286
267
|
if (/ECONNREFUSED/i.test(detail)) {
|
|
287
268
|
message += " The connection was refused — is the database server running?";
|
|
@@ -298,46 +279,14 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
298
279
|
});
|
|
299
280
|
}
|
|
300
281
|
}
|
|
301
|
-
} else if (dbType === "salesforce") {
|
|
302
|
-
// Salesforce: test login + listObjects
|
|
303
|
-
try {
|
|
304
|
-
const { parseSalesforceURL, createSalesforceDataSource } = await import("./db/salesforce");
|
|
305
|
-
const config = parseSalesforceURL(process.env.ATLAS_DATASOURCE_URL!);
|
|
306
|
-
const source = createSalesforceDataSource(config);
|
|
307
|
-
try {
|
|
308
|
-
await source.listObjects();
|
|
309
|
-
} finally {
|
|
310
|
-
await source.close().catch((err: unknown) => {
|
|
311
|
-
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Salesforce cleanup warning");
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
} catch (err) {
|
|
315
|
-
const detail = err instanceof Error ? err.message : "";
|
|
316
|
-
log.error({ err: detail }, "Salesforce connection check failed");
|
|
317
|
-
|
|
318
|
-
let message = "Cannot connect to Salesforce. Check that your credentials and connection string are correct.";
|
|
319
|
-
|
|
320
|
-
if (/LOGIN_MUST_USE_SECURITY_TOKEN/i.test(detail)) {
|
|
321
|
-
message += " A security token is required — add ?token=YOUR_TOKEN to the connection URL.";
|
|
322
|
-
} else if (/INVALID_LOGIN/i.test(detail)) {
|
|
323
|
-
message += " Authentication failed — check your username and password.";
|
|
324
|
-
} else if (/timeout/i.test(detail)) {
|
|
325
|
-
message += " The connection timed out — check network/firewall settings.";
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
errors.push({ code: "DB_UNREACHABLE", message });
|
|
329
|
-
}
|
|
330
282
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
"
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
_startupWarnings.push(msg);
|
|
339
|
-
}
|
|
340
|
-
log.warn(msg);
|
|
283
|
+
// Non-core database types are validated by their respective datasource plugins.
|
|
284
|
+
if (dbType && dbType !== "postgres" && dbType !== "mysql") {
|
|
285
|
+
log.info(
|
|
286
|
+
{ dbType },
|
|
287
|
+
"Non-core datasource type '%s' — connectivity validation deferred to plugin initialize()",
|
|
288
|
+
dbType,
|
|
289
|
+
);
|
|
341
290
|
}
|
|
342
291
|
}
|
|
343
292
|
|
|
@@ -363,7 +312,7 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
363
312
|
const detail = err instanceof Error ? err.message : "";
|
|
364
313
|
log.error({ err: detail }, "Internal DB connection check failed");
|
|
365
314
|
|
|
366
|
-
let message =
|
|
315
|
+
let message = `Cannot connect to the internal database at ${maskConnectionUrl(process.env.DATABASE_URL!)}. Check the connection string and ensure the database is running.`;
|
|
367
316
|
if (/ECONNREFUSED/i.test(detail)) {
|
|
368
317
|
message += " The connection was refused — is the database server running?";
|
|
369
318
|
} else if (/timeout/i.test(detail)) {
|
|
@@ -394,6 +343,18 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
394
343
|
errors.push({ code: "INTERNAL_DB_UNREACHABLE", message: migrationErr });
|
|
395
344
|
}
|
|
396
345
|
|
|
346
|
+
// 5.5. Config file validation (atlas.config.ts)
|
|
347
|
+
try {
|
|
348
|
+
const configMod = await import("@atlas/api/lib/config");
|
|
349
|
+
if (typeof configMod.loadConfig === "function" && !configMod.getConfig()) {
|
|
350
|
+
await configMod.loadConfig();
|
|
351
|
+
}
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
354
|
+
log.error({ err: detail }, "Config validation failed");
|
|
355
|
+
errors.push({ code: "INVALID_CONFIG", message: detail });
|
|
356
|
+
}
|
|
357
|
+
|
|
397
358
|
// 6. Auth mode diagnostics
|
|
398
359
|
const authMode = detectAuthMode();
|
|
399
360
|
const authSource = getAuthModeSource();
|
|
@@ -441,7 +402,8 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
441
402
|
errors.push({
|
|
442
403
|
code: "WEAK_AUTH_SECRET",
|
|
443
404
|
message:
|
|
444
|
-
|
|
405
|
+
`BETTER_AUTH_SECRET must be at least 32 characters (currently ${secret.length}). ` +
|
|
406
|
+
"Generate one with: openssl rand -base64 32",
|
|
445
407
|
});
|
|
446
408
|
}
|
|
447
409
|
if (!process.env.BETTER_AUTH_URL) {
|
|
@@ -490,6 +452,14 @@ export async function validateEnvironment(): Promise<DiagnosticError[]> {
|
|
|
490
452
|
"ATLAS_AUTH_ISSUER is required for BYOT auth mode. Set it to your identity provider's issuer URL (e.g. https://your-idp.com/).",
|
|
491
453
|
});
|
|
492
454
|
}
|
|
455
|
+
|
|
456
|
+
if (process.env.ATLAS_AUTH_AUDIENCE === "") {
|
|
457
|
+
const msg =
|
|
458
|
+
"ATLAS_AUTH_AUDIENCE is set to an empty string — audience validation will be skipped. " +
|
|
459
|
+
"Remove the variable entirely if audience checking is not needed, or set it to a valid audience value.";
|
|
460
|
+
if (!_startupWarnings.includes(msg)) _startupWarnings.push(msg);
|
|
461
|
+
log.warn(msg);
|
|
462
|
+
}
|
|
493
463
|
}
|
|
494
464
|
|
|
495
465
|
// Warn about orphaned auth env vars that suggest misconfiguration
|
|
@@ -72,6 +72,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
|
|
|
72
72
|
getTargetHost: () => "localhost",
|
|
73
73
|
list: () => ["default", "salesforce-plugin"],
|
|
74
74
|
getValidator: (id: string) => validatorMap.get(id),
|
|
75
|
+
getParserDialect: () => undefined,
|
|
76
|
+
getForbiddenPatterns: () => [],
|
|
75
77
|
},
|
|
76
78
|
detectDBType: () => "postgres",
|
|
77
79
|
}));
|