@topogram/cli 0.3.34
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/ARCHITECTURE.md +67 -0
- package/CHANGELOG.md +240 -0
- package/README.md +223 -0
- package/package.json +51 -0
- package/src/adoption/index.js +3 -0
- package/src/adoption/plan.js +702 -0
- package/src/adoption/reporting.js +464 -0
- package/src/adoption/review-groups.js +313 -0
- package/src/agent-ops/query-builders.js +5012 -0
- package/src/archive/archive.js +141 -0
- package/src/archive/compact.js +26 -0
- package/src/archive/jsonl.js +70 -0
- package/src/archive/resolver-bridge.js +82 -0
- package/src/archive/schema.js +87 -0
- package/src/archive/unarchive.js +108 -0
- package/src/catalog.js +752 -0
- package/src/cli/catalog-alias.js +166 -0
- package/src/cli.js +9738 -0
- package/src/component-behavior.js +173 -0
- package/src/example-implementation.js +91 -0
- package/src/format.js +19 -0
- package/src/generator/adapters.d.ts +4 -0
- package/src/generator/adapters.js +325 -0
- package/src/generator/api.d.ts +1 -0
- package/src/generator/api.js +1196 -0
- package/src/generator/check.js +355 -0
- package/src/generator/component-conformance.js +767 -0
- package/src/generator/components.js +39 -0
- package/src/generator/context/bundle.js +291 -0
- package/src/generator/context/diff.js +256 -0
- package/src/generator/context/digest.js +182 -0
- package/src/generator/context/domain-coverage.js +94 -0
- package/src/generator/context/domain-page.js +137 -0
- package/src/generator/context/index.js +42 -0
- package/src/generator/context/report.js +121 -0
- package/src/generator/context/shared.js +1397 -0
- package/src/generator/context/slice.js +703 -0
- package/src/generator/context/task-mode.js +466 -0
- package/src/generator/docs.js +327 -0
- package/src/generator/index.js +161 -0
- package/src/generator/native/parity-bundle.js +311 -0
- package/src/generator/output.js +300 -0
- package/src/generator/registry.js +482 -0
- package/src/generator/runtime/app-bundle.js +456 -0
- package/src/generator/runtime/bundle-shared.js +166 -0
- package/src/generator/runtime/compile-check.js +163 -0
- package/src/generator/runtime/deployment.js +287 -0
- package/src/generator/runtime/environment.js +635 -0
- package/src/generator/runtime/index.js +32 -0
- package/src/generator/runtime/runtime-check.js +554 -0
- package/src/generator/runtime/shared.js +515 -0
- package/src/generator/runtime/smoke.js +219 -0
- package/src/generator/schema.js +204 -0
- package/src/generator/sdlc/board.js +66 -0
- package/src/generator/sdlc/doc-page.js +53 -0
- package/src/generator/sdlc/index.js +23 -0
- package/src/generator/sdlc/release-notes.js +62 -0
- package/src/generator/sdlc/traceability-matrix.js +65 -0
- package/src/generator/shared.js +29 -0
- package/src/generator/surfaces/contracts.js +146 -0
- package/src/generator/surfaces/databases/contract.js +40 -0
- package/src/generator/surfaces/databases/index.js +84 -0
- package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
- package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
- package/src/generator/surfaces/databases/migration-plan.js +281 -0
- package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
- package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
- package/src/generator/surfaces/databases/postgres/index.js +9 -0
- package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
- package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
- package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
- package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
- package/src/generator/surfaces/databases/shared.d.ts +1 -0
- package/src/generator/surfaces/databases/shared.js +350 -0
- package/src/generator/surfaces/databases/snapshot.js +96 -0
- package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
- package/src/generator/surfaces/databases/sqlite/index.js +8 -0
- package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
- package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
- package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
- package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
- package/src/generator/surfaces/index.js +25 -0
- package/src/generator/surfaces/native/swiftui-app.js +38 -0
- package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
- package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
- package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
- package/src/generator/surfaces/services/express.d.ts +1 -0
- package/src/generator/surfaces/services/express.js +766 -0
- package/src/generator/surfaces/services/hono.d.ts +1 -0
- package/src/generator/surfaces/services/hono.js +204 -0
- package/src/generator/surfaces/services/index.js +42 -0
- package/src/generator/surfaces/services/persistence-wiring.js +240 -0
- package/src/generator/surfaces/services/runtime-helpers.js +631 -0
- package/src/generator/surfaces/services/server-contract.js +80 -0
- package/src/generator/surfaces/services/stateless.d.ts +1 -0
- package/src/generator/surfaces/services/stateless.js +97 -0
- package/src/generator/surfaces/shared.js +64 -0
- package/src/generator/surfaces/web/api-client.js +1 -0
- package/src/generator/surfaces/web/forms.js +1 -0
- package/src/generator/surfaces/web/index.d.ts +2 -0
- package/src/generator/surfaces/web/index.js +53 -0
- package/src/generator/surfaces/web/react-components.js +248 -0
- package/src/generator/surfaces/web/react.js +538 -0
- package/src/generator/surfaces/web/routes.js +1 -0
- package/src/generator/surfaces/web/screens.js +1 -0
- package/src/generator/surfaces/web/shared.js +369 -0
- package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
- package/src/generator/surfaces/web/sveltekit-components.js +234 -0
- package/src/generator/surfaces/web/sveltekit.js +426 -0
- package/src/generator/surfaces/web/ui-web-contract.js +65 -0
- package/src/generator/surfaces/web/vanilla.js +239 -0
- package/src/generator/verification.js +84 -0
- package/src/generator.js +1 -0
- package/src/import/core/context.js +52 -0
- package/src/import/core/contracts.js +23 -0
- package/src/import/core/registry.js +81 -0
- package/src/import/core/runner.js +646 -0
- package/src/import/core/shared.js +910 -0
- package/src/import/enrichers/auth-session.js +18 -0
- package/src/import/enrichers/django-rest.js +226 -0
- package/src/import/enrichers/doc-linking.js +20 -0
- package/src/import/enrichers/rails-controllers.js +246 -0
- package/src/import/enrichers/rails-models.js +130 -0
- package/src/import/enrichers/workflow-target-state.js +10 -0
- package/src/import/extractors/api/aspnet-core.js +304 -0
- package/src/import/extractors/api/django-routes.js +318 -0
- package/src/import/extractors/api/express.js +154 -0
- package/src/import/extractors/api/fastify.js +371 -0
- package/src/import/extractors/api/flutter-dio.js +135 -0
- package/src/import/extractors/api/generic-route-fallback.js +90 -0
- package/src/import/extractors/api/graphql-code-first.js +565 -0
- package/src/import/extractors/api/graphql-sdl.js +309 -0
- package/src/import/extractors/api/jaxrs.js +303 -0
- package/src/import/extractors/api/micronaut.js +213 -0
- package/src/import/extractors/api/next-route.js +50 -0
- package/src/import/extractors/api/next-server-action.js +51 -0
- package/src/import/extractors/api/nextauth.js +52 -0
- package/src/import/extractors/api/openapi-code.js +242 -0
- package/src/import/extractors/api/openapi.js +232 -0
- package/src/import/extractors/api/rails-routes.js +230 -0
- package/src/import/extractors/api/react-native-repository.js +128 -0
- package/src/import/extractors/api/retrofit.js +103 -0
- package/src/import/extractors/api/spring-web.js +372 -0
- package/src/import/extractors/api/swift-webapi.js +116 -0
- package/src/import/extractors/api/trpc.js +212 -0
- package/src/import/extractors/db/django-models.js +232 -0
- package/src/import/extractors/db/dotnet-models.js +93 -0
- package/src/import/extractors/db/drizzle.js +242 -0
- package/src/import/extractors/db/ef-core.js +221 -0
- package/src/import/extractors/db/flutter-entities.js +120 -0
- package/src/import/extractors/db/jpa.js +120 -0
- package/src/import/extractors/db/liquibase.js +180 -0
- package/src/import/extractors/db/mybatis-xml.js +145 -0
- package/src/import/extractors/db/prisma.js +185 -0
- package/src/import/extractors/db/rails-schema.js +175 -0
- package/src/import/extractors/db/react-native-entities.js +95 -0
- package/src/import/extractors/db/room.js +193 -0
- package/src/import/extractors/db/snapshot.js +112 -0
- package/src/import/extractors/db/sql.js +180 -0
- package/src/import/extractors/db/swiftdata.js +137 -0
- package/src/import/extractors/ui/android-compose.js +230 -0
- package/src/import/extractors/ui/backend-only.js +70 -0
- package/src/import/extractors/ui/blazor.js +227 -0
- package/src/import/extractors/ui/flutter-screens.js +152 -0
- package/src/import/extractors/ui/maui-xaml.js +135 -0
- package/src/import/extractors/ui/next-app-router.js +83 -0
- package/src/import/extractors/ui/next-pages-router.js +141 -0
- package/src/import/extractors/ui/razor-pages.js +181 -0
- package/src/import/extractors/ui/react-native-screens.js +166 -0
- package/src/import/extractors/ui/react-router.js +139 -0
- package/src/import/extractors/ui/sveltekit.js +123 -0
- package/src/import/extractors/ui/swiftui.js +193 -0
- package/src/import/extractors/ui/uikit.js +175 -0
- package/src/import/extractors/verification/generic.js +290 -0
- package/src/import/extractors/workflows/generic.js +137 -0
- package/src/import/index.js +7 -0
- package/src/import/provenance.js +158 -0
- package/src/new-project.js +2107 -0
- package/src/parser.js +439 -0
- package/src/policy/review-boundaries.js +165 -0
- package/src/project-config.js +535 -0
- package/src/proofs/backend-parity.js +19 -0
- package/src/proofs/contract-audit.js +220 -0
- package/src/proofs/ios-parity.js +7 -0
- package/src/proofs/issues-parity.js +10 -0
- package/src/proofs/web-parity.js +50 -0
- package/src/realization/api/build-api-realization.js +5 -0
- package/src/realization/api/index.js +1 -0
- package/src/realization/backend/build-backend-runtime-realization.js +82 -0
- package/src/realization/backend/index.d.ts +1 -0
- package/src/realization/backend/index.js +4 -0
- package/src/realization/db/build-db-realization.js +17 -0
- package/src/realization/db/index.js +3 -0
- package/src/realization/db/migration-plan.js +5 -0
- package/src/realization/db/snapshot.js +5 -0
- package/src/realization/ui/build-ui-shared-realization.js +305 -0
- package/src/realization/ui/build-web-realization.js +189 -0
- package/src/realization/ui/index.js +2 -0
- package/src/reconcile/docs.js +280 -0
- package/src/reconcile/index.js +3 -0
- package/src/reconcile/journeys.js +441 -0
- package/src/resolver/docs.js +1 -0
- package/src/resolver/enrich/acceptance-criterion.js +14 -0
- package/src/resolver/enrich/bug.js +12 -0
- package/src/resolver/enrich/component.js +2 -0
- package/src/resolver/enrich/index.js +1 -0
- package/src/resolver/enrich/pitch.js +18 -0
- package/src/resolver/enrich/requirement.js +20 -0
- package/src/resolver/enrich/task.js +16 -0
- package/src/resolver/expressions.js +1 -0
- package/src/resolver/index.js +2422 -0
- package/src/resolver/normalize.js +1 -0
- package/src/resolver.js +1 -0
- package/src/sdlc/adopt.js +65 -0
- package/src/sdlc/check.js +86 -0
- package/src/sdlc/dod/acceptance-criterion.js +22 -0
- package/src/sdlc/dod/bug.js +26 -0
- package/src/sdlc/dod/document.js +23 -0
- package/src/sdlc/dod/index.js +25 -0
- package/src/sdlc/dod/pitch.js +23 -0
- package/src/sdlc/dod/requirement.js +34 -0
- package/src/sdlc/dod/task.js +39 -0
- package/src/sdlc/explain.js +116 -0
- package/src/sdlc/history.js +80 -0
- package/src/sdlc/paths.js +11 -0
- package/src/sdlc/release.js +106 -0
- package/src/sdlc/scaffold.js +89 -0
- package/src/sdlc/status-filter.js +54 -0
- package/src/sdlc/transition.js +112 -0
- package/src/sdlc/transitions/acceptance-criterion.js +28 -0
- package/src/sdlc/transitions/bug.js +31 -0
- package/src/sdlc/transitions/document.js +29 -0
- package/src/sdlc/transitions/index.js +56 -0
- package/src/sdlc/transitions/pitch.js +34 -0
- package/src/sdlc/transitions/requirement.js +31 -0
- package/src/sdlc/transitions/task.js +34 -0
- package/src/template-trust.js +597 -0
- package/src/validator/expressions.js +1 -0
- package/src/validator/index.js +3424 -0
- package/src/validator/kinds.js +346 -0
- package/src/validator/per-kind/acceptance-criterion.js +91 -0
- package/src/validator/per-kind/bug.js +77 -0
- package/src/validator/per-kind/component.js +274 -0
- package/src/validator/per-kind/domain.js +205 -0
- package/src/validator/per-kind/pitch.js +101 -0
- package/src/validator/per-kind/requirement.js +75 -0
- package/src/validator/per-kind/task.js +96 -0
- package/src/validator/registry.js +1 -0
- package/src/validator/utils.js +12 -0
- package/src/validator.js +1 -0
- package/src/workflows.js +7597 -0
- package/src/workspace-docs.js +265 -0
- package/template-helpers/react.js +5 -0
- package/template-helpers/sveltekit.js +5 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateServerBundle,
|
|
3
|
+
generateWebBundle,
|
|
4
|
+
getDefaultEnvironmentProjections,
|
|
5
|
+
resolveRuntimeTopology,
|
|
6
|
+
runtimeUrls
|
|
7
|
+
} from "./shared.js";
|
|
8
|
+
import { getExampleImplementation } from "../../example-implementation.js";
|
|
9
|
+
import { mergeBundleFiles } from "./bundle-shared.js";
|
|
10
|
+
|
|
11
|
+
function compileCheckName(graph, options = {}) {
|
|
12
|
+
try {
|
|
13
|
+
return getExampleImplementation(graph, options).runtime.reference.compileCheck.name;
|
|
14
|
+
} catch {
|
|
15
|
+
return "Topogram Compile Check";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runtimeReferenceFor(graph, options = {}) {
|
|
20
|
+
try {
|
|
21
|
+
return getExampleImplementation(graph, options).runtime.reference;
|
|
22
|
+
} catch {
|
|
23
|
+
return { environment: { databaseName: "topogram_app", envExample: "" }, demoEnv: { userId: "" }, ports: { server: 3000, web: 5173 } };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildCompileCheckPlan(graph, options = {}) {
|
|
28
|
+
const topology = resolveRuntimeTopology(graph, options);
|
|
29
|
+
const { apiProjection, uiProjection, dbProjection } = getDefaultEnvironmentProjections(graph, options);
|
|
30
|
+
const apiChecks = topology.apiComponents.map((component, index) => ({
|
|
31
|
+
id: index === 0 ? "server_typecheck" : `server_typecheck_${component.id}`,
|
|
32
|
+
cwd: topology.serviceDir(component),
|
|
33
|
+
install: "npm install --no-audit --no-fund",
|
|
34
|
+
command: "npm run check"
|
|
35
|
+
}));
|
|
36
|
+
const webChecks = topology.webComponents.flatMap((component, index) => [
|
|
37
|
+
{
|
|
38
|
+
id: index === 0 ? "web_typecheck" : `web_typecheck_${component.id}`,
|
|
39
|
+
cwd: topology.webDir(component),
|
|
40
|
+
install: "npm install --no-audit --no-fund",
|
|
41
|
+
command: "npm run check"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: index === 0 ? "web_build" : `web_build_${component.id}`,
|
|
45
|
+
cwd: topology.webDir(component),
|
|
46
|
+
install: "npm install --no-audit --no-fund",
|
|
47
|
+
command: "npm run build"
|
|
48
|
+
}
|
|
49
|
+
]);
|
|
50
|
+
return {
|
|
51
|
+
type: "compile_check_plan",
|
|
52
|
+
name: compileCheckName(graph, options),
|
|
53
|
+
projections: {
|
|
54
|
+
api: apiProjection?.id || null,
|
|
55
|
+
ui: uiProjection?.id || null,
|
|
56
|
+
db: dbProjection?.id || null
|
|
57
|
+
},
|
|
58
|
+
topology: {
|
|
59
|
+
components: topology.components.map((component) => ({
|
|
60
|
+
id: component.id,
|
|
61
|
+
type: component.type,
|
|
62
|
+
projection: component.projection.id,
|
|
63
|
+
generator: component.generator
|
|
64
|
+
}))
|
|
65
|
+
},
|
|
66
|
+
checks: [...apiChecks, ...webChecks]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function renderCompileCheckEnvExample(graph, options = {}) {
|
|
71
|
+
const runtimeReference = runtimeReferenceFor(graph, options);
|
|
72
|
+
const topology = resolveRuntimeTopology(graph, options);
|
|
73
|
+
const { dbProjection } = getDefaultEnvironmentProjections(graph, options);
|
|
74
|
+
const urls = runtimeUrls(runtimeReference, topology);
|
|
75
|
+
if (dbProjection?.platform === "db_sqlite") {
|
|
76
|
+
return `DATABASE_URL=./var/${runtimeReference.environment.databaseName || "topogram_app"}.sqlite
|
|
77
|
+
PUBLIC_TOPOGRAM_API_BASE_URL=${urls.api}
|
|
78
|
+
PUBLIC_TOPOGRAM_DEMO_USER_ID=${runtimeReference.demoEnv.userId}
|
|
79
|
+
${runtimeReference.environment.envExample || ""}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
return `DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${runtimeReference.environment.databaseName || "topogram_app"}?schema=public
|
|
83
|
+
PUBLIC_TOPOGRAM_API_BASE_URL=${urls.api}
|
|
84
|
+
PUBLIC_TOPOGRAM_DEMO_USER_ID=${runtimeReference.demoEnv.userId}
|
|
85
|
+
${runtimeReference.environment.envExample || ""}
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function renderCompileCheckReadme(graph, options = {}) {
|
|
90
|
+
return `# ${compileCheckName(graph, options).replace("Plan", "Bundle")}
|
|
91
|
+
|
|
92
|
+
This bundle verifies that the generated server and web projects typecheck and build.
|
|
93
|
+
|
|
94
|
+
## Checks
|
|
95
|
+
|
|
96
|
+
- server TypeScript check
|
|
97
|
+
- web TypeScript check
|
|
98
|
+
- web production build
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
1. Copy \`.env.example\` to \`.env\` if needed
|
|
103
|
+
2. Run \`bash ./scripts/check.sh\`
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderCompileCheckScript(plan) {
|
|
108
|
+
const lines = [
|
|
109
|
+
"#!/usr/bin/env bash",
|
|
110
|
+
"set -euo pipefail",
|
|
111
|
+
"",
|
|
112
|
+
'SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
|
|
113
|
+
'ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"',
|
|
114
|
+
'ENV_FILE="${TOPOGRAM_ENV_FILE:-$ROOT_DIR/.env}"',
|
|
115
|
+
"",
|
|
116
|
+
'if [[ -f "$ENV_FILE" ]]; then',
|
|
117
|
+
" set -a",
|
|
118
|
+
' . "$ENV_FILE"',
|
|
119
|
+
" set +a",
|
|
120
|
+
"fi",
|
|
121
|
+
""
|
|
122
|
+
];
|
|
123
|
+
if (plan.checks.length === 0) {
|
|
124
|
+
lines.push('echo "No API or web components are configured; compile check is a no-op."');
|
|
125
|
+
}
|
|
126
|
+
for (const check of plan.checks) {
|
|
127
|
+
const label = check.id.includes("web")
|
|
128
|
+
? check.id.includes("build") ? "Building generated web" : "Checking generated web"
|
|
129
|
+
: "Checking generated server";
|
|
130
|
+
lines.push(`echo "${label} (${check.cwd})..."`);
|
|
131
|
+
lines.push(`echo "Installing dependencies (${check.cwd})..."`);
|
|
132
|
+
lines.push(`(cd "$ROOT_DIR/${check.cwd}" && ${check.install})`);
|
|
133
|
+
lines.push(`echo "Running ${check.command} (${check.cwd})..."`);
|
|
134
|
+
lines.push(`(cd "$ROOT_DIR/${check.cwd}" && ${check.command})`);
|
|
135
|
+
lines.push("");
|
|
136
|
+
}
|
|
137
|
+
lines.push('echo "Compile checks passed."');
|
|
138
|
+
return `${lines.join("\n")}\n`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function generateCompileCheckBundle(graph, options = {}) {
|
|
142
|
+
const plan = buildCompileCheckPlan(graph, options);
|
|
143
|
+
const topology = resolveRuntimeTopology(graph, options);
|
|
144
|
+
const files = {
|
|
145
|
+
".env.example": renderCompileCheckEnvExample(graph, options),
|
|
146
|
+
"README.md": renderCompileCheckReadme(graph, options),
|
|
147
|
+
"compile-check-plan.json": `${JSON.stringify(plan, null, 2)}\n`,
|
|
148
|
+
"scripts/check.sh": renderCompileCheckScript(plan)
|
|
149
|
+
};
|
|
150
|
+
for (const component of topology.apiComponents) {
|
|
151
|
+
const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
|
|
152
|
+
mergeBundleFiles(files, topology.serviceDir(component), serverBundle);
|
|
153
|
+
}
|
|
154
|
+
for (const component of topology.webComponents) {
|
|
155
|
+
const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
|
|
156
|
+
mergeBundleFiles(files, topology.webDir(component), webBundle);
|
|
157
|
+
}
|
|
158
|
+
return files;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function generateCompileCheckPlan(graph, options = {}) {
|
|
162
|
+
return buildCompileCheckPlan(graph, options);
|
|
163
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateDbBundle,
|
|
3
|
+
generateServerBundle,
|
|
4
|
+
generateWebBundle,
|
|
5
|
+
getDefaultEnvironmentProjections,
|
|
6
|
+
resolveRuntimeTopology
|
|
7
|
+
} from "./shared.js";
|
|
8
|
+
import { getExampleImplementation } from "../../example-implementation.js";
|
|
9
|
+
import { mergeNamedBundles, renderRootEnvFileShellScript, renderRootShellScript } from "./bundle-shared.js";
|
|
10
|
+
import { generatorProfile as manifestGeneratorProfile } from "../registry.js";
|
|
11
|
+
|
|
12
|
+
function projectionHintProfile(projection, fallback) {
|
|
13
|
+
for (const entry of projection.generatorDefaults || []) {
|
|
14
|
+
if (entry.key === "profile" && entry.value != null) {
|
|
15
|
+
return entry.value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function slugifyAppName(name) {
|
|
22
|
+
return String(name || "topogram-app")
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
25
|
+
.replace(/^-+|-+$/g, "")
|
|
26
|
+
.slice(0, 60) || "topogram-app";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildDeploymentPlan(graph, options = {}) {
|
|
30
|
+
const runtimeReference = getExampleImplementation(graph, options).runtime.reference;
|
|
31
|
+
const topology = resolveRuntimeTopology(graph, options);
|
|
32
|
+
const { apiProjection, uiProjection, dbProjection } = getDefaultEnvironmentProjections(graph, options);
|
|
33
|
+
const profile = options.profileId || "fly_io";
|
|
34
|
+
const supportedProfiles = ["fly_io", "railway"];
|
|
35
|
+
const webProfile = manifestGeneratorProfile(topology.primaryWeb?.generator?.id, null) || projectionHintProfile(uiProjection, "sveltekit");
|
|
36
|
+
const databaseTarget = dbProjection.platform === "db_sqlite"
|
|
37
|
+
? "sqlite_file"
|
|
38
|
+
: profile === "fly_io"
|
|
39
|
+
? "managed_postgres"
|
|
40
|
+
: "railway_postgres";
|
|
41
|
+
if (!supportedProfiles.includes(profile)) {
|
|
42
|
+
throw new Error(`Unsupported deployment profile '${profile}'`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
type: "deployment_plan",
|
|
47
|
+
deployment: {
|
|
48
|
+
name: runtimeReference.appBundle.name.replace("App Bundle", "Deployment Stack"),
|
|
49
|
+
profile
|
|
50
|
+
},
|
|
51
|
+
projections: {
|
|
52
|
+
api: apiProjection.id,
|
|
53
|
+
ui: uiProjection.id,
|
|
54
|
+
db: dbProjection.id
|
|
55
|
+
},
|
|
56
|
+
topology: {
|
|
57
|
+
components: topology.components.map((component) => ({
|
|
58
|
+
id: component.id,
|
|
59
|
+
type: component.type,
|
|
60
|
+
projection: component.projection.id,
|
|
61
|
+
generator: component.generator,
|
|
62
|
+
port: component.port ?? null,
|
|
63
|
+
api: component.api || null,
|
|
64
|
+
database: component.database || null
|
|
65
|
+
}))
|
|
66
|
+
},
|
|
67
|
+
directories: {
|
|
68
|
+
server: topology.serviceDir(topology.primaryApi),
|
|
69
|
+
web: topology.webDir(topology.primaryWeb),
|
|
70
|
+
db: topology.dbDir(topology.primaryDb)
|
|
71
|
+
},
|
|
72
|
+
runtime: {
|
|
73
|
+
server: manifestGeneratorProfile(topology.primaryApi?.generator?.id, "hono"),
|
|
74
|
+
web: webProfile,
|
|
75
|
+
orm: "prisma",
|
|
76
|
+
serverPort: topology.primaryApi?.port || 3000
|
|
77
|
+
},
|
|
78
|
+
targets: {
|
|
79
|
+
server: profile === "fly_io" ? "fly.io" : "railway",
|
|
80
|
+
web: "vercel",
|
|
81
|
+
database: databaseTarget
|
|
82
|
+
},
|
|
83
|
+
requiredEnv: ["DATABASE_URL", "PUBLIC_TOPOGRAM_API_BASE_URL"],
|
|
84
|
+
recommendedCommands: {
|
|
85
|
+
deployServer: profile === "fly_io" ? "fly deploy" : "railway up",
|
|
86
|
+
deployWeb: "vercel deploy",
|
|
87
|
+
migrate: "npm run db:migrate"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderDeploymentEnvExample(plan) {
|
|
93
|
+
return `# Deployment profile
|
|
94
|
+
TOPOGRAM_DEPLOY_PROFILE=${plan.deployment.profile}
|
|
95
|
+
|
|
96
|
+
# Shared runtime variables
|
|
97
|
+
DATABASE_URL=
|
|
98
|
+
PUBLIC_TOPOGRAM_API_BASE_URL=
|
|
99
|
+
|
|
100
|
+
# Optional server runtime values
|
|
101
|
+
PORT=${plan.runtime.serverPort}
|
|
102
|
+
NODE_ENV=production
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderDeploymentReadme(plan) {
|
|
107
|
+
const platformNotes = plan.deployment.profile === "fly_io"
|
|
108
|
+
? `## Fly.io Server Deploy
|
|
109
|
+
|
|
110
|
+
- Review \`fly.toml\`
|
|
111
|
+
- Set secrets with \`fly secrets set DATABASE_URL=...\`
|
|
112
|
+
- Deploy with \`${plan.recommendedCommands.deployServer}\`
|
|
113
|
+
`
|
|
114
|
+
: `## Railway Server Deploy
|
|
115
|
+
|
|
116
|
+
- Review \`railway.json\`
|
|
117
|
+
- Set environment variables in Railway
|
|
118
|
+
- Deploy with \`${plan.recommendedCommands.deployServer}\`
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
return `# ${plan.deployment.name}
|
|
122
|
+
|
|
123
|
+
This bundle packages deployment helpers for the generated runtime.
|
|
124
|
+
|
|
125
|
+
- \`${plan.directories.server}/\`: generated Hono + Prisma server scaffold
|
|
126
|
+
- \`${plan.directories.web}/\`: generated ${plan.runtime.web === "react" ? "Vite + React Router" : "SvelteKit"} web scaffold
|
|
127
|
+
- platform deployment files for \`${plan.deployment.profile}\`
|
|
128
|
+
- a Vercel config for the web app
|
|
129
|
+
|
|
130
|
+
${platformNotes}
|
|
131
|
+
## Web Deploy
|
|
132
|
+
|
|
133
|
+
- Review \`${plan.directories.web}/vercel.json\`
|
|
134
|
+
- Set \`PUBLIC_TOPOGRAM_API_BASE_URL\`
|
|
135
|
+
- Deploy with \`${plan.recommendedCommands.deployWeb}\`
|
|
136
|
+
|
|
137
|
+
## Database Migrations
|
|
138
|
+
|
|
139
|
+
- Run \`${plan.recommendedCommands.migrate}\` against the target database before or during deploy
|
|
140
|
+
- The generated server bundle includes Prisma schema and DB lifecycle scripts for greenfield or brownfield environments
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function renderDeploymentPackageJson(plan) {
|
|
145
|
+
return `${JSON.stringify({
|
|
146
|
+
name: "topogram-deployment-bundle",
|
|
147
|
+
private: true,
|
|
148
|
+
scripts: {
|
|
149
|
+
"deploy:check": "bash ./scripts/deploy-check.sh",
|
|
150
|
+
"deploy:server": plan.recommendedCommands.deployServer,
|
|
151
|
+
"deploy:web": plan.recommendedCommands.deployWeb,
|
|
152
|
+
"db:migrate": "bash ./scripts/deploy-migrate.sh"
|
|
153
|
+
}
|
|
154
|
+
}, null, 2)}\n`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function renderDeploymentCheckScript(plan) {
|
|
158
|
+
return renderRootEnvFileShellScript([
|
|
159
|
+
"for name in DATABASE_URL PUBLIC_TOPOGRAM_API_BASE_URL; do",
|
|
160
|
+
' if [[ -z "${!name:-}" ]]; then',
|
|
161
|
+
' echo "Missing required deployment variable: $name" >&2',
|
|
162
|
+
" exit 1",
|
|
163
|
+
" fi",
|
|
164
|
+
"done",
|
|
165
|
+
"",
|
|
166
|
+
`echo "Deployment configuration looks ready for ${plan.deployment.profile}."`
|
|
167
|
+
], {
|
|
168
|
+
blankLineAfterRoot: false,
|
|
169
|
+
includeScriptDir: false,
|
|
170
|
+
rootDirExpression: 'ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderDeploymentMigrateScript(plan) {
|
|
175
|
+
return renderRootShellScript([
|
|
176
|
+
`cd "$ROOT_DIR/${plan.directories.server}"`,
|
|
177
|
+
"npm install",
|
|
178
|
+
"npm exec -- prisma generate --schema prisma/schema.prisma",
|
|
179
|
+
"npm exec -- prisma db push --schema prisma/schema.prisma --skip-generate"
|
|
180
|
+
]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function renderServerDockerfile(plan) {
|
|
184
|
+
return `FROM node:22-alpine
|
|
185
|
+
WORKDIR /app
|
|
186
|
+
|
|
187
|
+
COPY package.json ./
|
|
188
|
+
RUN npm install
|
|
189
|
+
|
|
190
|
+
COPY tsconfig.json ./
|
|
191
|
+
COPY prisma ./prisma
|
|
192
|
+
COPY src ./src
|
|
193
|
+
|
|
194
|
+
RUN npm exec -- prisma generate --schema prisma/schema.prisma
|
|
195
|
+
|
|
196
|
+
ENV PORT=${plan.runtime.serverPort}
|
|
197
|
+
EXPOSE ${plan.runtime.serverPort}
|
|
198
|
+
|
|
199
|
+
CMD ["npm", "run", "dev"]
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderFlyToml(plan) {
|
|
204
|
+
return `app = "${slugifyAppName(plan.deployment.name)}"
|
|
205
|
+
primary_region = "ord"
|
|
206
|
+
|
|
207
|
+
[build]
|
|
208
|
+
dockerfile = "${plan.directories.server}/Dockerfile"
|
|
209
|
+
|
|
210
|
+
[env]
|
|
211
|
+
PORT = "${plan.runtime.serverPort}"
|
|
212
|
+
|
|
213
|
+
[http_service]
|
|
214
|
+
internal_port = ${plan.runtime.serverPort}
|
|
215
|
+
force_https = true
|
|
216
|
+
auto_stop_machines = "stop"
|
|
217
|
+
auto_start_machines = true
|
|
218
|
+
min_machines_running = 0
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function renderRailwayJson(plan) {
|
|
223
|
+
return `${JSON.stringify({
|
|
224
|
+
"$schema": "https://railway.app/railway.schema.json",
|
|
225
|
+
build: {
|
|
226
|
+
builder: "DOCKERFILE",
|
|
227
|
+
dockerfilePath: `${plan.directories.server}/Dockerfile`
|
|
228
|
+
},
|
|
229
|
+
deploy: {
|
|
230
|
+
startCommand: "npm run dev",
|
|
231
|
+
restartPolicyType: "ON_FAILURE",
|
|
232
|
+
restartPolicyMaxRetries: 10
|
|
233
|
+
}
|
|
234
|
+
}, null, 2)}\n`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function renderVercelJson(plan) {
|
|
238
|
+
return `${JSON.stringify({
|
|
239
|
+
framework: plan.runtime.web === "react" ? "vite" : "sveltekit"
|
|
240
|
+
}, null, 2)}\n`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function generateDeploymentBundle(graph, options = {}) {
|
|
244
|
+
const plan = buildDeploymentPlan(graph, options);
|
|
245
|
+
const topology = resolveRuntimeTopology(graph, options);
|
|
246
|
+
const files = {
|
|
247
|
+
".env.example": renderDeploymentEnvExample(plan),
|
|
248
|
+
"README.md": renderDeploymentReadme(plan),
|
|
249
|
+
"package.json": renderDeploymentPackageJson(plan),
|
|
250
|
+
"scripts/deploy-check.sh": renderDeploymentCheckScript(plan),
|
|
251
|
+
"scripts/deploy-migrate.sh": renderDeploymentMigrateScript(plan),
|
|
252
|
+
[`${plan.directories.server}/Dockerfile`]: renderServerDockerfile(plan),
|
|
253
|
+
[`${plan.directories.web}/vercel.json`]: renderVercelJson(plan)
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
if (plan.deployment.profile === "fly_io") {
|
|
257
|
+
files["fly.toml"] = renderFlyToml(plan);
|
|
258
|
+
}
|
|
259
|
+
if (plan.deployment.profile === "railway") {
|
|
260
|
+
files["railway.json"] = renderRailwayJson(plan);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const component of topology.apiComponents) {
|
|
264
|
+
const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
|
|
265
|
+
mergeNamedBundles(files, {
|
|
266
|
+
[topology.serviceDir(component)]: serverBundle
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
for (const component of topology.webComponents) {
|
|
270
|
+
const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
|
|
271
|
+
mergeNamedBundles(files, {
|
|
272
|
+
[topology.webDir(component)]: webBundle
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
for (const component of topology.dbComponents) {
|
|
276
|
+
const dbBundle = generateDbBundle(graph, component.projection.id, { ...options, component });
|
|
277
|
+
mergeNamedBundles(files, {
|
|
278
|
+
[topology.dbDir(component)]: dbBundle
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return files;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function generateDeploymentPlan(graph, options = {}) {
|
|
286
|
+
return buildDeploymentPlan(graph, options);
|
|
287
|
+
}
|