@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,456 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateCompileCheckBundle
|
|
3
|
+
} from "./compile-check.js";
|
|
4
|
+
import {
|
|
5
|
+
generateDeploymentBundle
|
|
6
|
+
} from "./deployment.js";
|
|
7
|
+
import {
|
|
8
|
+
generateEnvironmentBundle
|
|
9
|
+
} from "./environment.js";
|
|
10
|
+
import {
|
|
11
|
+
generateRuntimeCheckBundle
|
|
12
|
+
} from "./runtime-check.js";
|
|
13
|
+
import {
|
|
14
|
+
generateRuntimeSmokeBundle
|
|
15
|
+
} from "./smoke.js";
|
|
16
|
+
import {
|
|
17
|
+
buildVerificationSummary,
|
|
18
|
+
getDefaultEnvironmentProjections,
|
|
19
|
+
resolveRuntimeTopology,
|
|
20
|
+
runtimePorts,
|
|
21
|
+
runtimeUrls
|
|
22
|
+
} from "./shared.js";
|
|
23
|
+
import { getExampleImplementation } from "../../example-implementation.js";
|
|
24
|
+
import { mergeNamedBundles, renderLoadEnvScript, renderNestedBundleShellScript } from "./bundle-shared.js";
|
|
25
|
+
|
|
26
|
+
function runtimeReferenceFor(graph, options = {}) {
|
|
27
|
+
try {
|
|
28
|
+
return structuredClone(getExampleImplementation(graph, options).runtime.reference);
|
|
29
|
+
} catch {
|
|
30
|
+
return {
|
|
31
|
+
appBundle: {
|
|
32
|
+
name: "Topogram App Bundle",
|
|
33
|
+
demoContainerName: "Hello workflow",
|
|
34
|
+
demoPrimaryTitle: "Hello page"
|
|
35
|
+
},
|
|
36
|
+
environment: { databaseName: "topogram_app", envExample: "" },
|
|
37
|
+
ports: { server: 3000, web: 5173 },
|
|
38
|
+
demoEnv: { userId: "11111111-1111-4111-8111-111111111111" },
|
|
39
|
+
smoke: { webPath: "/", expectText: "Topogram" },
|
|
40
|
+
runtimeCheck: {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildAppBundlePlan(graph, options = {}) {
|
|
46
|
+
const runtimeReference = runtimeReferenceFor(graph, options);
|
|
47
|
+
const topology = resolveRuntimeTopology(graph, options);
|
|
48
|
+
const { apiProjection, uiProjection, dbProjection } = getDefaultEnvironmentProjections(graph, options);
|
|
49
|
+
const environmentProfile = options.profileId || "local_process";
|
|
50
|
+
const deployProfile = options.deployProfileId || "fly_io";
|
|
51
|
+
const smokeVerification = buildVerificationSummary(graph, ["smoke", "journey"]);
|
|
52
|
+
const runtimeVerification = buildVerificationSummary(graph, ["runtime", "contract", "journey"]);
|
|
53
|
+
if (smokeVerification) {
|
|
54
|
+
runtimeReference.smoke.verification = smokeVerification;
|
|
55
|
+
}
|
|
56
|
+
if (runtimeVerification) {
|
|
57
|
+
runtimeReference.runtimeCheck.verification = runtimeVerification;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
type: "app_bundle_plan",
|
|
61
|
+
name: runtimeReference.appBundle.name,
|
|
62
|
+
projections: {
|
|
63
|
+
api: apiProjection?.id || null,
|
|
64
|
+
ui: uiProjection?.id || null,
|
|
65
|
+
db: dbProjection?.id || null
|
|
66
|
+
},
|
|
67
|
+
topology: {
|
|
68
|
+
components: topology.components.map((component) => ({
|
|
69
|
+
id: component.id,
|
|
70
|
+
type: component.type,
|
|
71
|
+
projection: component.projection.id,
|
|
72
|
+
generator: component.generator,
|
|
73
|
+
port: component.port ?? null,
|
|
74
|
+
api: component.api || null,
|
|
75
|
+
database: component.database || null
|
|
76
|
+
}))
|
|
77
|
+
},
|
|
78
|
+
runtimeReference,
|
|
79
|
+
profiles: {
|
|
80
|
+
environment: environmentProfile,
|
|
81
|
+
deployment: deployProfile
|
|
82
|
+
},
|
|
83
|
+
commands: {
|
|
84
|
+
bootstrap: "./scripts/bootstrap.sh",
|
|
85
|
+
dev: "./scripts/dev.sh",
|
|
86
|
+
compile: "./scripts/compile-check.sh",
|
|
87
|
+
runtime: "./scripts/runtime.sh",
|
|
88
|
+
smoke: "./scripts/smoke.sh",
|
|
89
|
+
runtimeCheck: "./scripts/runtime-check.sh",
|
|
90
|
+
deployCheck: "./scripts/deploy-check.sh"
|
|
91
|
+
},
|
|
92
|
+
layout: {
|
|
93
|
+
apps: "apps",
|
|
94
|
+
deploy: "deploy",
|
|
95
|
+
smoke: "smoke",
|
|
96
|
+
runtimeCheck: "runtime-check",
|
|
97
|
+
compile: "compile",
|
|
98
|
+
services: "apps/services",
|
|
99
|
+
web: "apps/web",
|
|
100
|
+
db: "apps/db",
|
|
101
|
+
native: "apps/native"
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderAppBundleEnvExample(plan) {
|
|
107
|
+
const demo = plan.runtimeReference.demoEnv;
|
|
108
|
+
const databaseName = plan.runtimeReference.environment.databaseName || "topogram_app";
|
|
109
|
+
const topology = {
|
|
110
|
+
primaryApi: { port: plan.topology.components.find((component) => component.type === "api")?.port },
|
|
111
|
+
primaryWeb: { port: plan.topology.components.find((component) => component.type === "web")?.port }
|
|
112
|
+
};
|
|
113
|
+
const ports = runtimePorts(plan.runtimeReference, topology);
|
|
114
|
+
const urls = runtimeUrls(plan.runtimeReference, topology);
|
|
115
|
+
if (!plan.projections.dbPlatform) {
|
|
116
|
+
return `# App bundle defaults
|
|
117
|
+
TOPOGRAM_ENVIRONMENT_PROFILE=${plan.profiles.environment}
|
|
118
|
+
TOPOGRAM_DEPLOY_PROFILE=${plan.profiles.deployment}
|
|
119
|
+
|
|
120
|
+
# Local runtime defaults
|
|
121
|
+
${plan.projections.api ? `SERVER_PORT=${ports.server}\n` : ""}${plan.projections.ui ? `WEB_PORT=${ports.web}\n` : ""}${plan.projections.api && plan.projections.ui ? `PUBLIC_TOPOGRAM_API_BASE_URL=${urls.api}\n` : ""}PUBLIC_TOPOGRAM_DEMO_USER_ID=${demo.userId}
|
|
122
|
+
TOPOGRAM_DEMO_USER_ID=${demo.userId}
|
|
123
|
+
${plan.runtimeReference.environment.envExample || ""}
|
|
124
|
+
|
|
125
|
+
# Smoke-test defaults
|
|
126
|
+
${plan.projections.api ? `TOPOGRAM_API_BASE_URL=${urls.api}\n` : ""}${plan.projections.ui ? `TOPOGRAM_WEB_BASE_URL=${urls.web}\n` : ""}`;
|
|
127
|
+
}
|
|
128
|
+
if (plan.projections.dbPlatform === "db_sqlite") {
|
|
129
|
+
return `# App bundle defaults
|
|
130
|
+
TOPOGRAM_ENVIRONMENT_PROFILE=${plan.profiles.environment}
|
|
131
|
+
TOPOGRAM_DEPLOY_PROFILE=${plan.profiles.deployment}
|
|
132
|
+
|
|
133
|
+
# Local runtime defaults
|
|
134
|
+
SERVER_PORT=${ports.server}
|
|
135
|
+
WEB_PORT=${ports.web}
|
|
136
|
+
DATABASE_URL=file:./var/${databaseName}.sqlite
|
|
137
|
+
PUBLIC_TOPOGRAM_API_BASE_URL=${urls.api}
|
|
138
|
+
PUBLIC_TOPOGRAM_DEMO_USER_ID=${demo.userId}
|
|
139
|
+
TOPOGRAM_DEMO_USER_ID=${demo.userId}
|
|
140
|
+
${plan.runtimeReference.environment.envExample || ""}
|
|
141
|
+
TOPOGRAM_SEED_DEMO=true
|
|
142
|
+
|
|
143
|
+
# Smoke-test defaults
|
|
144
|
+
TOPOGRAM_API_BASE_URL=${urls.api}
|
|
145
|
+
TOPOGRAM_WEB_BASE_URL=${urls.web}
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
return `# App bundle defaults
|
|
149
|
+
TOPOGRAM_ENVIRONMENT_PROFILE=${plan.profiles.environment}
|
|
150
|
+
TOPOGRAM_DEPLOY_PROFILE=${plan.profiles.deployment}
|
|
151
|
+
|
|
152
|
+
# Local runtime defaults
|
|
153
|
+
DB_PORT=5432
|
|
154
|
+
SERVER_PORT=${ports.server}
|
|
155
|
+
WEB_PORT=${ports.web}
|
|
156
|
+
POSTGRES_DB=${databaseName}
|
|
157
|
+
POSTGRES_USER=\${USER:-postgres}
|
|
158
|
+
POSTGRES_PASSWORD=postgres
|
|
159
|
+
DATABASE_URL=postgresql://\${POSTGRES_USER}@localhost:5432/${databaseName}
|
|
160
|
+
DATABASE_ADMIN_URL=postgresql://\${POSTGRES_USER}@localhost:5432/postgres
|
|
161
|
+
PUBLIC_TOPOGRAM_API_BASE_URL=${urls.api}
|
|
162
|
+
PUBLIC_TOPOGRAM_DEMO_USER_ID=${demo.userId}
|
|
163
|
+
TOPOGRAM_DEMO_USER_ID=${demo.userId}
|
|
164
|
+
${plan.runtimeReference.environment.envExample || ""}
|
|
165
|
+
TOPOGRAM_SEED_DEMO=true
|
|
166
|
+
|
|
167
|
+
# Smoke-test defaults
|
|
168
|
+
TOPOGRAM_API_BASE_URL=${urls.api}
|
|
169
|
+
TOPOGRAM_WEB_BASE_URL=${urls.web}
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function renderAppBundleReadme(plan) {
|
|
174
|
+
const urls = runtimeUrls(plan.runtimeReference, {
|
|
175
|
+
primaryApi: { port: plan.topology.components.find((component) => component.type === "api")?.port },
|
|
176
|
+
primaryWeb: { port: plan.topology.components.find((component) => component.type === "web")?.port }
|
|
177
|
+
});
|
|
178
|
+
return `# ${plan.name}
|
|
179
|
+
|
|
180
|
+
This is the polished generated app bundle for Topogram v0.1.
|
|
181
|
+
|
|
182
|
+
It includes:
|
|
183
|
+
- \`apps/services/<api-id>/\`: generated API service scaffolds
|
|
184
|
+
- \`apps/web/<web-id>/\`: generated web scaffolds
|
|
185
|
+
- \`apps/db/<db-id>/\`: generated DB lifecycle bundles
|
|
186
|
+
- \`apps/native/<native-id>/\`: generated native app scaffolds
|
|
187
|
+
- \`deploy/\`: deployment packaging
|
|
188
|
+
- \`compile/\`: generated compile verification
|
|
189
|
+
- \`smoke/\`: minimal runtime confidence check
|
|
190
|
+
- \`runtime-check/\`: richer staged runtime verification with JSON reporting
|
|
191
|
+
|
|
192
|
+
## Start Here
|
|
193
|
+
|
|
194
|
+
1. Copy \`.env.example\` to \`.env\` if you want to customize defaults
|
|
195
|
+
2. Bootstrap the app:
|
|
196
|
+
- \`bash ${plan.commands.bootstrap.replace("./", "")}\`
|
|
197
|
+
- this provisions or migrates the database and seeds demo data by default
|
|
198
|
+
3. Run the app:
|
|
199
|
+
- \`bash ${plan.commands.dev.replace("./", "")}\`
|
|
200
|
+
4. Compile-check it:
|
|
201
|
+
- \`bash ${plan.commands.compile.replace("./", "")}\`
|
|
202
|
+
5. Run self-contained local runtime verification:
|
|
203
|
+
- \`bash ${plan.commands.runtime.replace("./", "")}\`
|
|
204
|
+
|
|
205
|
+
Or, with the app still running, run richer staged runtime checks in another terminal:
|
|
206
|
+
- \`bash ${plan.commands.runtimeCheck.replace("./", "")}\`
|
|
207
|
+
Then run the lightweight smoke check:
|
|
208
|
+
- \`bash ${plan.commands.smoke.replace("./", "")}\`
|
|
209
|
+
|
|
210
|
+
## Golden Path
|
|
211
|
+
|
|
212
|
+
For the default generated bundle:
|
|
213
|
+
|
|
214
|
+
1. Use the \`${plan.profiles.environment}\` environment profile
|
|
215
|
+
2. Run \`bash ${plan.commands.bootstrap.replace("./", "")}\`
|
|
216
|
+
3. Run \`bash ${plan.commands.dev.replace("./", "")}\`
|
|
217
|
+
4. Open the web app at \`${urls.web}${plan.runtimeReference.smoke.webPath}\`
|
|
218
|
+
5. Confirm the seeded "${plan.runtimeReference.appBundle.demoContainerName}" and "${plan.runtimeReference.appBundle.demoPrimaryTitle}" flow through the stack
|
|
219
|
+
6. Run \`bash ${plan.commands.compile.replace("./", "")}\`
|
|
220
|
+
7. Run \`bash ${plan.commands.runtime.replace("./", "")}\`
|
|
221
|
+
|
|
222
|
+
## Deployment
|
|
223
|
+
|
|
224
|
+
- Validate deploy configuration:
|
|
225
|
+
- \`bash ${plan.commands.deployCheck.replace("./", "")}\`
|
|
226
|
+
- Then use the generated deployment bundle under \`deploy/\`
|
|
227
|
+
|
|
228
|
+
## Notes
|
|
229
|
+
|
|
230
|
+
- The default generated app profile is \`${plan.profiles.environment}\`
|
|
231
|
+
- The default generated deployment profile is \`${plan.profiles.deployment}\`
|
|
232
|
+
- Demo data is seeded during bootstrap unless \`TOPOGRAM_SEED_DEMO=false\`
|
|
233
|
+
- If \`.env\` is missing, generated scripts fall back to \`.env.example\`
|
|
234
|
+
- You can regenerate other environment or deployment profiles from the Topogram source project
|
|
235
|
+
- The generated server exposes \`GET /health\` for liveness and \`GET /ready\` for DB-backed readiness
|
|
236
|
+
- \`compile/\` is self-contained and does not require the app to be running
|
|
237
|
+
- \`smoke/\` and \`runtime-check/\` are probes against a running local stack
|
|
238
|
+
- \`scripts/runtime.sh\` starts the local stack, waits for readiness, runs the probes, and stops the stack
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function renderAppBundlePackageJson() {
|
|
243
|
+
return `${JSON.stringify({
|
|
244
|
+
name: "topogram-app-bundle",
|
|
245
|
+
private: true,
|
|
246
|
+
scripts: {
|
|
247
|
+
check: "npm run compile",
|
|
248
|
+
bootstrap: "bash ./scripts/bootstrap.sh",
|
|
249
|
+
dev: "bash ./scripts/dev.sh",
|
|
250
|
+
compile: "bash ./scripts/compile-check.sh",
|
|
251
|
+
runtime: "bash ./scripts/runtime.sh",
|
|
252
|
+
"runtime-check": "bash ./scripts/runtime-check.sh",
|
|
253
|
+
smoke: "bash ./scripts/smoke.sh",
|
|
254
|
+
probe: "npm run smoke && npm run runtime-check",
|
|
255
|
+
"deploy:check": "bash ./scripts/deploy-check.sh"
|
|
256
|
+
}
|
|
257
|
+
}, null, 2)}\n`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function renderAppBundleLoadEnvScript() {
|
|
261
|
+
return renderLoadEnvScript();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function renderAppBundleBootstrapScript() {
|
|
265
|
+
return renderNestedBundleShellScript("apps", "scripts/bootstrap-db.sh");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function renderAppBundleDevScript() {
|
|
269
|
+
return renderNestedBundleShellScript("apps", "scripts/stack-dev.sh");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function renderAppBundleRuntimeScript() {
|
|
273
|
+
return `#!/usr/bin/env bash
|
|
274
|
+
set -euo pipefail
|
|
275
|
+
|
|
276
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
277
|
+
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
278
|
+
STACK_PID=""
|
|
279
|
+
|
|
280
|
+
. "$SCRIPT_DIR/load-env.sh"
|
|
281
|
+
|
|
282
|
+
kill_tree() {
|
|
283
|
+
local pid="$1"
|
|
284
|
+
local child
|
|
285
|
+
while IFS= read -r child; do
|
|
286
|
+
[[ -n "$child" ]] && kill_tree "$child"
|
|
287
|
+
done < <(pgrep -P "$pid" 2>/dev/null || true)
|
|
288
|
+
kill "$pid" >/dev/null 2>&1 || true
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
cleanup() {
|
|
292
|
+
if [[ -n "$STACK_PID" ]]; then
|
|
293
|
+
kill_tree "$STACK_PID"
|
|
294
|
+
wait "$STACK_PID" >/dev/null 2>&1 || true
|
|
295
|
+
fi
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
trap cleanup EXIT INT TERM
|
|
299
|
+
|
|
300
|
+
bash "$SCRIPT_DIR/bootstrap.sh"
|
|
301
|
+
|
|
302
|
+
TOPOGRAM_SKIP_STACK_BOOTSTRAP=true bash "$SCRIPT_DIR/dev.sh" &
|
|
303
|
+
STACK_PID=$!
|
|
304
|
+
|
|
305
|
+
node "$SCRIPT_DIR/wait-for-stack.mjs"
|
|
306
|
+
bash "$SCRIPT_DIR/smoke.sh"
|
|
307
|
+
bash "$SCRIPT_DIR/runtime-check.sh"
|
|
308
|
+
`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function renderAppBundleWaitForStackScript(plan) {
|
|
312
|
+
const topology = {
|
|
313
|
+
primaryApi: { port: plan.topology.components.find((component) => component.type === "api")?.port },
|
|
314
|
+
primaryWeb: { port: plan.topology.components.find((component) => component.type === "web")?.port }
|
|
315
|
+
};
|
|
316
|
+
const ports = runtimePorts(plan.runtimeReference, topology);
|
|
317
|
+
const urls = runtimeUrls(plan.runtimeReference, topology);
|
|
318
|
+
const endpoints = [
|
|
319
|
+
...(plan.projections.api ? [
|
|
320
|
+
{ label: "api health", url: `${urls.api}/health`, expectJson: "ok" },
|
|
321
|
+
{ label: "api readiness", url: `${urls.api}/ready`, expectJson: "ready" }
|
|
322
|
+
] : []),
|
|
323
|
+
...(plan.projections.ui ? [
|
|
324
|
+
{ label: "web app", url: `${urls.web}${plan.runtimeReference.smoke.webPath || "/"}` }
|
|
325
|
+
] : [])
|
|
326
|
+
];
|
|
327
|
+
return `const endpoints = ${JSON.stringify(endpoints, null, 2)};
|
|
328
|
+
const timeoutMs = Number(process.env.TOPOGRAM_RUNTIME_WAIT_MS || "60000");
|
|
329
|
+
const intervalMs = Number(process.env.TOPOGRAM_RUNTIME_WAIT_INTERVAL_MS || "500");
|
|
330
|
+
const startedAt = Date.now();
|
|
331
|
+
|
|
332
|
+
function envUrl(url) {
|
|
333
|
+
return String(url)
|
|
334
|
+
.replace("http://localhost:${ports.server}", process.env.TOPOGRAM_API_BASE_URL || process.env.PUBLIC_TOPOGRAM_API_BASE_URL || "http://localhost:${ports.server}")
|
|
335
|
+
.replace("http://localhost:${ports.web}", process.env.TOPOGRAM_WEB_BASE_URL || process.env.PUBLIC_TOPOGRAM_WEB_BASE_URL || \`http://localhost:\${process.env.WEB_PORT || "${ports.web}"}\`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function sleep(ms) {
|
|
339
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function isReady(endpoint) {
|
|
343
|
+
const url = envUrl(endpoint.url);
|
|
344
|
+
try {
|
|
345
|
+
const response = await fetch(url);
|
|
346
|
+
if (response.status !== 200) {
|
|
347
|
+
return { ok: false, message: \`\${endpoint.label} returned \${response.status}\` };
|
|
348
|
+
}
|
|
349
|
+
if (endpoint.expectJson) {
|
|
350
|
+
const body = await response.json().catch(() => null);
|
|
351
|
+
if (body?.[endpoint.expectJson] !== true) {
|
|
352
|
+
return { ok: false, message: \`\${endpoint.label} did not report \${endpoint.expectJson}=true\` };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return { ok: true };
|
|
356
|
+
} catch (error) {
|
|
357
|
+
const code = error?.cause?.code || error?.code || (error instanceof Error ? error.message : String(error));
|
|
358
|
+
return { ok: false, message: \`\${endpoint.label} not reachable: \${code}\` };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (endpoints.length === 0) {
|
|
363
|
+
console.log("No runtime endpoints are configured; skipping readiness wait.");
|
|
364
|
+
process.exit(0);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let lastMessage = "";
|
|
368
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
369
|
+
const results = await Promise.all(endpoints.map((endpoint) => isReady(endpoint)));
|
|
370
|
+
if (results.every((result) => result.ok)) {
|
|
371
|
+
console.log("Generated stack is ready.");
|
|
372
|
+
process.exit(0);
|
|
373
|
+
}
|
|
374
|
+
lastMessage = results.filter((result) => !result.ok).map((result) => result.message).join("; ");
|
|
375
|
+
await sleep(intervalMs);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.error(\`Generated stack did not become ready within \${timeoutMs}ms. \${lastMessage}\`);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function renderAppBundleSmokeScript() {
|
|
384
|
+
return renderNestedBundleShellScript("smoke", "scripts/smoke.sh");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function renderAppBundleRuntimeCheckScript() {
|
|
388
|
+
return renderNestedBundleShellScript("runtime-check", "scripts/check.sh");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function renderAppBundleCompileScript() {
|
|
392
|
+
return renderNestedBundleShellScript("compile", "scripts/check.sh");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function renderAppBundleDeployCheckScript() {
|
|
396
|
+
return renderNestedBundleShellScript("deploy", "scripts/deploy-check.sh");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function noopBundle(name, message) {
|
|
400
|
+
return {
|
|
401
|
+
"README.md": `# ${name}\n\n${message}\n`,
|
|
402
|
+
"scripts/check.sh": `#!/usr/bin/env bash\nset -euo pipefail\necho ${JSON.stringify(message)}\n`,
|
|
403
|
+
"scripts/smoke.sh": `#!/usr/bin/env bash\nset -euo pipefail\necho ${JSON.stringify(message)}\n`,
|
|
404
|
+
"scripts/deploy-check.sh": `#!/usr/bin/env bash\nset -euo pipefail\necho ${JSON.stringify(message)}\n`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function generateAppBundle(graph, options = {}) {
|
|
409
|
+
const plan = buildAppBundlePlan(graph, options);
|
|
410
|
+
const topology = resolveRuntimeTopology(graph, options);
|
|
411
|
+
const projections = getDefaultEnvironmentProjections(graph, options);
|
|
412
|
+
plan.projections.dbPlatform = projections.dbProjection?.platform || null;
|
|
413
|
+
const fullStack = topology.apiComponents.length > 0 && topology.webComponents.length > 0 && topology.dbComponents.length > 0;
|
|
414
|
+
const envBundle = generateEnvironmentBundle(graph, { ...options, profileId: plan.profiles.environment });
|
|
415
|
+
const deployBundle = fullStack
|
|
416
|
+
? generateDeploymentBundle(graph, { ...options, profileId: plan.profiles.deployment })
|
|
417
|
+
: noopBundle("Deployment Check", "No deployment bundle is generated for this partial topology.");
|
|
418
|
+
const smokeBundle = fullStack
|
|
419
|
+
? generateRuntimeSmokeBundle(graph, options)
|
|
420
|
+
: noopBundle("Runtime Smoke", "No runtime smoke bundle is generated for this partial topology.");
|
|
421
|
+
const runtimeCheckBundle = fullStack
|
|
422
|
+
? generateRuntimeCheckBundle(graph, options)
|
|
423
|
+
: noopBundle("Runtime Check", "No runtime check bundle is generated for this partial topology.");
|
|
424
|
+
const compileBundle = generateCompileCheckBundle(graph, options);
|
|
425
|
+
|
|
426
|
+
const files = {
|
|
427
|
+
".env.example": renderAppBundleEnvExample(plan),
|
|
428
|
+
".gitignore": "node_modules/\n.env\n**/node_modules/\n**/package-lock.json\n",
|
|
429
|
+
"README.md": renderAppBundleReadme(plan),
|
|
430
|
+
"package.json": renderAppBundlePackageJson(),
|
|
431
|
+
"app-bundle-plan.json": `${JSON.stringify(plan, null, 2)}\n`,
|
|
432
|
+
"scripts/load-env.sh": renderAppBundleLoadEnvScript(),
|
|
433
|
+
"scripts/bootstrap.sh": renderAppBundleBootstrapScript(),
|
|
434
|
+
"scripts/dev.sh": renderAppBundleDevScript(),
|
|
435
|
+
"scripts/runtime.sh": renderAppBundleRuntimeScript(),
|
|
436
|
+
"scripts/wait-for-stack.mjs": renderAppBundleWaitForStackScript(plan),
|
|
437
|
+
"scripts/compile-check.sh": renderAppBundleCompileScript(),
|
|
438
|
+
"scripts/runtime-check.sh": renderAppBundleRuntimeCheckScript(),
|
|
439
|
+
"scripts/smoke.sh": renderAppBundleSmokeScript(),
|
|
440
|
+
"scripts/deploy-check.sh": renderAppBundleDeployCheckScript()
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
mergeNamedBundles(files, {
|
|
444
|
+
apps: envBundle,
|
|
445
|
+
deploy: deployBundle,
|
|
446
|
+
smoke: smokeBundle,
|
|
447
|
+
"runtime-check": runtimeCheckBundle,
|
|
448
|
+
compile: compileBundle
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
return files;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function generateAppBundlePlan(graph, options = {}) {
|
|
455
|
+
return buildAppBundlePlan(graph, options);
|
|
456
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export function mergeBundleFiles(files, prefix, bundle) {
|
|
2
|
+
for (const [filePath, contents] of Object.entries(bundle || {})) {
|
|
3
|
+
files[`${prefix}/${filePath}`] = contents;
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function mergeNamedBundles(files, bundles) {
|
|
8
|
+
for (const [prefix, bundle] of Object.entries(bundles || {})) {
|
|
9
|
+
mergeBundleFiles(files, prefix, bundle);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function renderLoadEnvScript(options = {}) {
|
|
14
|
+
const searchParentEnv = Boolean(options.searchParentEnv);
|
|
15
|
+
const rootDir = options.rootDirExpression || 'ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"';
|
|
16
|
+
const base = [
|
|
17
|
+
"#!/usr/bin/env bash",
|
|
18
|
+
"set -euo pipefail",
|
|
19
|
+
"",
|
|
20
|
+
rootDir,
|
|
21
|
+
'DEFAULT_ENV_FILE="$ROOT_DIR/.env"',
|
|
22
|
+
'DEFAULT_ENV_EXAMPLE_FILE="$ROOT_DIR/.env.example"'
|
|
23
|
+
];
|
|
24
|
+
if (searchParentEnv) {
|
|
25
|
+
base.push(
|
|
26
|
+
'FALLBACK_ENV_FILE="$(cd "$ROOT_DIR/.." && pwd)/.env"',
|
|
27
|
+
'FALLBACK_ENV_EXAMPLE_FILE="$(cd "$ROOT_DIR/.." && pwd)/.env.example"'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
base.push('ENV_FILE="${TOPOGRAM_ENV_FILE:-$DEFAULT_ENV_FILE}"', "");
|
|
31
|
+
if (searchParentEnv) {
|
|
32
|
+
base.push(
|
|
33
|
+
'if [[ ! -f "$ENV_FILE" && -z "${TOPOGRAM_ENV_FILE:-}" && -f "$FALLBACK_ENV_FILE" ]]; then',
|
|
34
|
+
' ENV_FILE="$FALLBACK_ENV_FILE"',
|
|
35
|
+
"fi",
|
|
36
|
+
""
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
base.push(
|
|
40
|
+
'if [[ ! -f "$ENV_FILE" && -z "${TOPOGRAM_ENV_FILE:-}" && -f "$DEFAULT_ENV_EXAMPLE_FILE" ]]; then',
|
|
41
|
+
' ENV_FILE="$DEFAULT_ENV_EXAMPLE_FILE"',
|
|
42
|
+
"fi",
|
|
43
|
+
""
|
|
44
|
+
);
|
|
45
|
+
if (searchParentEnv) {
|
|
46
|
+
base.push(
|
|
47
|
+
'if [[ ! -f "$ENV_FILE" && -z "${TOPOGRAM_ENV_FILE:-}" && -f "$FALLBACK_ENV_EXAMPLE_FILE" ]]; then',
|
|
48
|
+
' ENV_FILE="$FALLBACK_ENV_EXAMPLE_FILE"',
|
|
49
|
+
"fi",
|
|
50
|
+
""
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
base.push(
|
|
54
|
+
'if [[ -f "$ENV_FILE" ]]; then',
|
|
55
|
+
" set -a",
|
|
56
|
+
' . "$ENV_FILE"',
|
|
57
|
+
" set +a",
|
|
58
|
+
"fi",
|
|
59
|
+
"",
|
|
60
|
+
'if [[ -n "${DATABASE_URL:-}" ]]; then',
|
|
61
|
+
' if [[ "$DATABASE_URL" == file:* ]]; then',
|
|
62
|
+
' database_path="${DATABASE_URL#file:}"',
|
|
63
|
+
' if [[ "$database_path" != /* ]]; then',
|
|
64
|
+
` DATABASE_URL="file:$ROOT_DIR/$(printf '%s' "$database_path" | sed 's#^./##')"`,
|
|
65
|
+
" export DATABASE_URL",
|
|
66
|
+
" fi",
|
|
67
|
+
' elif [[ "$DATABASE_URL" != /* && "$DATABASE_URL" != postgresql:* ]]; then',
|
|
68
|
+
` DATABASE_URL="$ROOT_DIR/$(printf '%s' "$DATABASE_URL" | sed 's#^./##')"`,
|
|
69
|
+
" export DATABASE_URL",
|
|
70
|
+
" fi",
|
|
71
|
+
"fi",
|
|
72
|
+
""
|
|
73
|
+
);
|
|
74
|
+
return base.join("\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function renderEnvAwareShellScript(bodyLines, options = {}) {
|
|
78
|
+
const rootDir = options.rootDirExpression || 'ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"';
|
|
79
|
+
const loadEnvScript = options.loadEnvScript || '"$SCRIPT_DIR/load-env.sh"';
|
|
80
|
+
const lines = [
|
|
81
|
+
"#!/usr/bin/env bash",
|
|
82
|
+
"set -euo pipefail",
|
|
83
|
+
"",
|
|
84
|
+
'SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
|
|
85
|
+
rootDir,
|
|
86
|
+
`. ${loadEnvScript}`,
|
|
87
|
+
"",
|
|
88
|
+
...(bodyLines || [])
|
|
89
|
+
];
|
|
90
|
+
return `${lines.join("\n")}\n`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function renderRootShellScript(bodyLines, options = {}) {
|
|
94
|
+
const includeScriptDir = options.includeScriptDir !== false;
|
|
95
|
+
const blankLineAfterRoot = options.blankLineAfterRoot !== false;
|
|
96
|
+
const rootDir = options.rootDirExpression || (includeScriptDir
|
|
97
|
+
? 'ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"'
|
|
98
|
+
: 'ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"');
|
|
99
|
+
const lines = [
|
|
100
|
+
"#!/usr/bin/env bash",
|
|
101
|
+
"set -euo pipefail",
|
|
102
|
+
""
|
|
103
|
+
];
|
|
104
|
+
if (includeScriptDir) {
|
|
105
|
+
lines.push('SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"');
|
|
106
|
+
}
|
|
107
|
+
lines.push(rootDir);
|
|
108
|
+
if (blankLineAfterRoot) {
|
|
109
|
+
lines.push("");
|
|
110
|
+
}
|
|
111
|
+
lines.push(...(bodyLines || []));
|
|
112
|
+
return `${lines.join("\n")}\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function renderRootEnvFileShellScript(bodyLines, options = {}) {
|
|
116
|
+
return renderRootShellScript([
|
|
117
|
+
'ENV_FILE="${TOPOGRAM_ENV_FILE:-$ROOT_DIR/.env}"',
|
|
118
|
+
"",
|
|
119
|
+
'if [[ -f "$ENV_FILE" ]]; then',
|
|
120
|
+
" set -a",
|
|
121
|
+
' . "$ENV_FILE"',
|
|
122
|
+
" set +a",
|
|
123
|
+
"fi",
|
|
124
|
+
"",
|
|
125
|
+
...(bodyLines || [])
|
|
126
|
+
], options);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function renderNestedBundleShellScript(bundleDir, scriptPath, options = {}) {
|
|
130
|
+
return renderEnvAwareShellScript([`(cd "$ROOT_DIR/${bundleDir}" && bash ./${scriptPath})`], options);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function renderNodeScriptRunner(scriptFileName, options = {}) {
|
|
134
|
+
const searchParentEnv = Boolean(options.searchParentEnv);
|
|
135
|
+
const lines = [
|
|
136
|
+
"#!/usr/bin/env bash",
|
|
137
|
+
"set -euo pipefail",
|
|
138
|
+
"",
|
|
139
|
+
'SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
|
|
140
|
+
'ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"',
|
|
141
|
+
'DEFAULT_ENV_FILE="$ROOT_DIR/.env"'
|
|
142
|
+
];
|
|
143
|
+
if (searchParentEnv) {
|
|
144
|
+
lines.push('FALLBACK_ENV_FILE="$(cd "$ROOT_DIR/.." && pwd)/.env"');
|
|
145
|
+
}
|
|
146
|
+
lines.push('ENV_FILE="${TOPOGRAM_ENV_FILE:-$DEFAULT_ENV_FILE}"', "");
|
|
147
|
+
if (searchParentEnv) {
|
|
148
|
+
lines.push(
|
|
149
|
+
'if [[ ! -f "$ENV_FILE" && -z "${TOPOGRAM_ENV_FILE:-}" && -f "$FALLBACK_ENV_FILE" ]]; then',
|
|
150
|
+
' ENV_FILE="$FALLBACK_ENV_FILE"',
|
|
151
|
+
"fi",
|
|
152
|
+
""
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
lines.push(
|
|
156
|
+
'if [[ -f "$ENV_FILE" ]]; then',
|
|
157
|
+
" set -a",
|
|
158
|
+
' . "$ENV_FILE"',
|
|
159
|
+
" set +a",
|
|
160
|
+
"fi",
|
|
161
|
+
"",
|
|
162
|
+
`node "$SCRIPT_DIR/${scriptFileName}"`,
|
|
163
|
+
""
|
|
164
|
+
);
|
|
165
|
+
return lines.join("\n");
|
|
166
|
+
}
|