@topogram/cli 0.3.63 → 0.3.65
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/package.json +1 -1
- package/src/adoption/plan/index.js +703 -0
- package/src/adoption/plan.d.ts +6 -0
- package/src/adoption/plan.js +12 -703
- package/src/adoption/reporting.d.ts +10 -0
- package/src/adoption/review-groups.d.ts +6 -0
- package/src/agent-brief.d.ts +3 -0
- package/src/agent-brief.js +495 -0
- package/src/agent-ops/query-builders/auth.js +375 -0
- package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
- package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
- package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
- package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
- package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
- package/src/agent-ops/query-builders/change-risk.js +25 -0
- package/src/agent-ops/query-builders/common.js +149 -0
- package/src/agent-ops/query-builders/maintained-risk.js +539 -0
- package/src/agent-ops/query-builders/maintained-shared.js +120 -0
- package/src/agent-ops/query-builders/multi-agent.js +547 -0
- package/src/agent-ops/query-builders/projection-impacts.js +514 -0
- package/src/agent-ops/query-builders/work-packets.js +417 -0
- package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
- package/src/agent-ops/query-builders/workflow-context.js +398 -0
- package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -0
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/archive/archive.d.ts +2 -0
- package/src/archive/compact.d.ts +1 -0
- package/src/archive/unarchive.d.ts +1 -0
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +60 -0
- package/src/catalog/diagnostics.js +15 -0
- package/src/catalog/entries.js +42 -0
- package/src/catalog/files.js +67 -0
- package/src/catalog/provenance.js +122 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +12 -0
- package/src/catalog.js +18 -750
- package/src/cli/catalog-alias.d.ts +1 -0
- package/src/cli/command-parser.js +38 -0
- package/src/cli/command-parsers/core.js +102 -0
- package/src/cli/command-parsers/generator.js +39 -0
- package/src/cli/command-parsers/import.js +44 -0
- package/src/cli/command-parsers/legacy-workflow.js +21 -0
- package/src/cli/command-parsers/project.js +47 -0
- package/src/cli/command-parsers/sdlc.js +47 -0
- package/src/cli/command-parsers/shared.js +51 -0
- package/src/cli/command-parsers/template.js +48 -0
- package/src/cli/commands/agent.js +47 -0
- package/src/cli/commands/catalog/check.js +31 -0
- package/src/cli/commands/catalog/copy.js +59 -0
- package/src/cli/commands/catalog/doctor.js +248 -0
- package/src/cli/commands/catalog/help.js +21 -0
- package/src/cli/commands/catalog/list.js +52 -0
- package/src/cli/commands/catalog/runner.js +92 -0
- package/src/cli/commands/catalog/shared.js +17 -0
- package/src/cli/commands/catalog/show.js +134 -0
- package/src/cli/commands/catalog.js +32 -0
- package/src/cli/commands/check.js +268 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/emit.js +149 -0
- package/src/cli/commands/generate.js +96 -0
- package/src/cli/commands/generator-policy/package-info.js +162 -0
- package/src/cli/commands/generator-policy/payloads.js +372 -0
- package/src/cli/commands/generator-policy/printers.js +159 -0
- package/src/cli/commands/generator-policy/runner.js +81 -0
- package/src/cli/commands/generator-policy/shared.js +39 -0
- package/src/cli/commands/generator-policy.js +17 -0
- package/src/cli/commands/generator.js +443 -0
- package/src/cli/commands/import/adopt.js +170 -0
- package/src/cli/commands/import/check.js +91 -0
- package/src/cli/commands/import/diff.js +84 -0
- package/src/cli/commands/import/help.js +47 -0
- package/src/cli/commands/import/paths.js +277 -0
- package/src/cli/commands/import/plan.js +284 -0
- package/src/cli/commands/import/refresh.js +470 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +230 -0
- package/src/cli/commands/import-runner.js +157 -0
- package/src/cli/commands/import.js +35 -0
- package/src/cli/commands/inspect.js +55 -0
- package/src/cli/commands/new.js +94 -0
- package/src/cli/commands/package/constants.js +17 -0
- package/src/cli/commands/package/doctor.js +240 -0
- package/src/cli/commands/package/help.js +27 -0
- package/src/cli/commands/package/lockfile.js +135 -0
- package/src/cli/commands/package/npm.js +97 -0
- package/src/cli/commands/package/reporting.js +35 -0
- package/src/cli/commands/package/runner.js +33 -0
- package/src/cli/commands/package/shared.js +9 -0
- package/src/cli/commands/package/update-cli.js +252 -0
- package/src/cli/commands/package/versions.js +35 -0
- package/src/cli/commands/package.js +31 -0
- package/src/cli/commands/query/change-plan.js +68 -0
- package/src/cli/commands/query/definitions.js +202 -0
- package/src/cli/commands/query/import-adopt.js +121 -0
- package/src/cli/commands/query/runner/artifacts.js +102 -0
- package/src/cli/commands/query/runner/boundaries.js +211 -0
- package/src/cli/commands/query/runner/change.js +182 -0
- package/src/cli/commands/query/runner/import-adopt.js +111 -0
- package/src/cli/commands/query/runner/index.js +31 -0
- package/src/cli/commands/query/runner/output.js +12 -0
- package/src/cli/commands/query/runner/workflow.js +241 -0
- package/src/cli/commands/query/runner.js +3 -0
- package/src/cli/commands/query/workflow-context.js +5 -0
- package/src/cli/commands/query/workspace.js +274 -0
- package/src/cli/commands/query.js +11 -0
- package/src/cli/commands/release-rollout.js +257 -0
- package/src/cli/commands/release-shared.js +528 -0
- package/src/cli/commands/release-status.js +429 -0
- package/src/cli/commands/release.js +107 -0
- package/src/cli/commands/sdlc.js +168 -0
- package/src/cli/commands/setup.js +76 -0
- package/src/cli/commands/source.js +291 -0
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +466 -0
- package/src/cli/commands/template/constants.js +8 -0
- package/src/cli/commands/template/diagnostics.js +26 -0
- package/src/cli/commands/template/help.js +28 -0
- package/src/cli/commands/template/lifecycle.js +404 -0
- package/src/cli/commands/template/list-show.js +287 -0
- package/src/cli/commands/template/policy.js +422 -0
- package/src/cli/commands/template/shared.js +127 -0
- package/src/cli/commands/template/updates.js +352 -0
- package/src/cli/commands/template-runner.js +198 -0
- package/src/cli/commands/template.js +43 -0
- package/src/cli/commands/trust.js +219 -0
- package/src/cli/commands/version.js +40 -0
- package/src/cli/commands/widget.js +168 -0
- package/src/cli/commands/workflow.js +63 -0
- package/src/cli/dispatcher.js +392 -0
- package/src/cli/help-dispatch.js +188 -0
- package/src/cli/help.js +296 -0
- package/src/cli/migration-guidance.js +59 -0
- package/src/cli/options.js +96 -0
- package/src/cli/output-safety.js +107 -0
- package/src/cli/path-normalization.js +29 -0
- package/src/cli.js +47 -11711
- package/src/example-implementation.d.ts +2 -0
- package/src/format.d.ts +1 -0
- package/src/generator/api/contracts.js +497 -0
- package/src/generator/api/metadata.js +221 -0
- package/src/generator/api/openapi.js +559 -0
- package/src/generator/api/schema.js +124 -0
- package/src/generator/api/types.d.ts +98 -0
- package/src/generator/api.js +3 -1195
- package/src/generator/check.d.ts +1 -0
- package/src/generator/context/bundle.d.ts +1 -0
- package/src/generator/context/shared/domain-sdlc.js +282 -0
- package/src/generator/context/shared/maintained-boundary.js +665 -0
- package/src/generator/context/shared/metrics.js +85 -0
- package/src/generator/context/shared/primitives.js +64 -0
- package/src/generator/context/shared/relationships.js +453 -0
- package/src/generator/context/shared/summaries.js +263 -0
- package/src/generator/context/shared/types.d.ts +207 -0
- package/src/generator/context/shared.d.ts +44 -0
- package/src/generator/context/shared.js +80 -1390
- package/src/generator/context/slice/core.js +397 -0
- package/src/generator/context/slice/sdlc.js +417 -0
- package/src/generator/context/slice/ui-packets.js +183 -0
- package/src/generator/context/slice.js +2 -859
- package/src/generator/native/parity-bundle.js +2 -1
- package/src/generator/registry/index.js +507 -0
- package/src/generator/registry.js +18 -504
- package/src/generator/runtime/environment/index.js +666 -0
- package/src/generator/runtime/environment.js +4 -666
- package/src/generator/runtime/runtime-check/index.js +554 -0
- package/src/generator/runtime/runtime-check.js +4 -554
- package/src/generator/runtime/shared/index.js +572 -0
- package/src/generator/runtime/shared.js +19 -570
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/shared.d.ts +3 -0
- package/src/generator/surfaces/web/html-escape.js +22 -0
- package/src/generator/surfaces/web/react.js +10 -8
- package/src/generator/surfaces/web/sveltekit.js +7 -5
- package/src/generator/surfaces/web/vanilla.js +8 -4
- package/src/generator/widget-conformance/behavior-report.js +258 -0
- package/src/generator/widget-conformance/checks.js +371 -0
- package/src/generator/widget-conformance/projection-context.js +200 -0
- package/src/generator/widget-conformance/report.js +166 -0
- package/src/generator/widget-conformance/types.d.ts +121 -0
- package/src/generator/widget-conformance.js +3 -824
- package/src/generator.d.ts +2 -0
- package/src/github-client.js +520 -0
- package/src/import/core/context.d.ts +3 -0
- package/src/import/core/contracts.d.ts +1 -0
- package/src/import/core/registry.d.ts +4 -0
- package/src/import/core/runner/candidates.js +217 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +50 -0
- package/src/import/core/runner/run.js +79 -0
- package/src/import/core/runner/tracks.js +150 -0
- package/src/import/core/runner/ui-drafts.js +337 -0
- package/src/import/core/runner.js +3 -698
- package/src/import/core/shared/api-routes.js +221 -0
- package/src/import/core/shared/candidates.js +97 -0
- package/src/import/core/shared/files.js +177 -0
- package/src/import/core/shared/next-app.js +389 -0
- package/src/import/core/shared/types.d.ts +51 -0
- package/src/import/core/shared/ui-routes.js +230 -0
- package/src/import/core/shared.js +67 -910
- package/src/import/extractors/api/flutter-dio.js +4 -8
- package/src/import/extractors/api/react-native-repository.js +4 -8
- package/src/import/index.d.ts +4 -0
- package/src/import/provenance.d.ts +4 -0
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +83 -0
- package/src/new-project/json.js +28 -0
- package/src/new-project/metadata.js +96 -0
- package/src/new-project/package-spec.js +161 -0
- package/src/new-project/project-files.js +348 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +368 -0
- package/src/new-project/template-snapshots.js +430 -0
- package/src/new-project/template-updates.js +512 -0
- package/src/new-project/types.d.ts +83 -0
- package/src/new-project.js +6 -2188
- package/src/npm-safety.js +79 -0
- package/src/parser.d.ts +87 -0
- package/src/parser.js +118 -0
- package/src/path-helpers.d.ts +1 -0
- package/src/path-helpers.js +20 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +564 -0
- package/src/project-config.js +19 -560
- package/src/reconcile/docs.d.ts +8 -0
- package/src/reconcile/journeys.d.ts +1 -0
- package/src/resolver/enrich/acceptance-criterion.js +2 -0
- package/src/resolver/enrich/bug.js +2 -0
- package/src/resolver/enrich/pitch.js +2 -0
- package/src/resolver/enrich/requirement.js +2 -0
- package/src/resolver/enrich/task.js +2 -0
- package/src/resolver/index.js +19 -2089
- package/src/resolver/normalize.js +384 -1
- package/src/resolver/plans.js +168 -0
- package/src/resolver/projections-api.js +494 -0
- package/src/resolver/projections-db.js +133 -0
- package/src/resolver/projections-ui.js +317 -0
- package/src/resolver/shapes.js +251 -0
- package/src/resolver/shared.js +278 -0
- package/src/resolver/widgets.js +132 -0
- package/src/resolver.d.ts +1 -0
- package/src/runtime-support.js +29 -0
- package/src/sdlc/adopt.d.ts +1 -0
- package/src/sdlc/check.d.ts +1 -0
- package/src/sdlc/explain.d.ts +1 -0
- package/src/sdlc/release.d.ts +1 -0
- package/src/sdlc/scaffold.d.ts +1 -0
- package/src/sdlc/transition.d.ts +1 -0
- package/src/template-trust/constants.js +62 -0
- package/src/template-trust/content.js +258 -0
- package/src/template-trust/diff.js +92 -0
- package/src/template-trust/policy.js +61 -0
- package/src/template-trust/record.js +90 -0
- package/src/template-trust/status.js +182 -0
- package/src/template-trust.js +24 -687
- package/src/text-helpers.d.ts +7 -0
- package/src/text-helpers.js +245 -0
- package/src/topogram-config.js +306 -0
- package/src/topogram-types.d.ts +69 -0
- package/src/validator/common.js +488 -0
- package/src/validator/data-model.js +237 -0
- package/src/validator/docs.js +167 -0
- package/src/validator/expressions.js +146 -1
- package/src/validator/index.d.ts +23 -0
- package/src/validator/index.js +32 -3585
- package/src/validator/kinds.d.ts +41 -0
- package/src/validator/kinds.js +2 -0
- package/src/validator/model-helpers.js +46 -0
- package/src/validator/per-kind/acceptance-criterion.js +5 -0
- package/src/validator/per-kind/bug.js +6 -0
- package/src/validator/per-kind/domain.js +15 -2
- package/src/validator/per-kind/pitch.js +7 -0
- package/src/validator/per-kind/requirement.js +5 -0
- package/src/validator/per-kind/task.js +7 -0
- package/src/validator/per-kind/widget.js +14 -0
- package/src/validator/projections/api-http-async.js +410 -0
- package/src/validator/projections/api-http-authz.js +88 -0
- package/src/validator/projections/api-http-core.js +205 -0
- package/src/validator/projections/api-http-policies.js +339 -0
- package/src/validator/projections/api-http-responses.js +233 -0
- package/src/validator/projections/api-http.js +44 -0
- package/src/validator/projections/db.js +353 -0
- package/src/validator/projections/generator-defaults.js +45 -0
- package/src/validator/projections/helpers.js +87 -0
- package/src/validator/projections/ui-helpers.js +214 -0
- package/src/validator/projections/ui-navigation.js +344 -0
- package/src/validator/projections/ui-structure.js +364 -0
- package/src/validator/projections/ui-widgets.js +493 -0
- package/src/validator/projections/ui.js +46 -0
- package/src/validator/registry.js +48 -1
- package/src/validator/utils.d.ts +20 -0
- package/src/validator/utils.js +115 -12
- package/src/validator.d.ts +2 -0
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/adoption/index.js +26 -0
- package/src/workflows/docs-generate.js +262 -0
- package/src/workflows/docs-scan.js +703 -0
- package/src/workflows/docs.js +15 -0
- package/src/workflows/import-app/api/collect.js +221 -0
- package/src/workflows/import-app/api/openapi.js +257 -0
- package/src/workflows/import-app/api/routes.js +327 -0
- package/src/workflows/import-app/api/sources.js +22 -0
- package/src/workflows/import-app/api.js +4 -0
- package/src/workflows/import-app/db.js +538 -0
- package/src/workflows/import-app/index.js +30 -0
- package/src/workflows/import-app/shared.js +218 -0
- package/src/workflows/import-app/ui.js +443 -0
- package/src/workflows/import-app/workflow.js +159 -0
- package/src/workflows/reconcile/adoption-plan/build.js +208 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
- package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
- package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
- package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
- package/src/workflows/reconcile/adoption-plan.js +32 -0
- package/src/workflows/reconcile/auth/closures.js +115 -0
- package/src/workflows/reconcile/auth/formatters.js +142 -0
- package/src/workflows/reconcile/auth/inference.js +330 -0
- package/src/workflows/reconcile/auth/roles.js +122 -0
- package/src/workflows/reconcile/auth.js +37 -0
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +14 -0
- package/src/workflows/reconcile/bundle-shared.js +75 -0
- package/src/workflows/reconcile/candidate-model.js +477 -0
- package/src/workflows/reconcile/canonical-surface.js +264 -0
- package/src/workflows/reconcile/gap-report.js +333 -0
- package/src/workflows/reconcile/ids.js +6 -0
- package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
- package/src/workflows/reconcile/impacts/indexes.js +101 -0
- package/src/workflows/reconcile/impacts/patches.js +252 -0
- package/src/workflows/reconcile/impacts/reports.js +80 -0
- package/src/workflows/reconcile/impacts.js +16 -0
- package/src/workflows/reconcile/index.js +7 -0
- package/src/workflows/reconcile/renderers.js +461 -0
- package/src/workflows/reconcile/summary.js +90 -0
- package/src/workflows/reconcile/workflow.js +309 -0
- package/src/workflows/shared.js +189 -0
- package/src/workflows/types.d.ts +93 -0
- package/src/workflows.d.ts +1 -0
- package/src/workflows.js +10 -7652
- package/src/workspace-docs.d.ts +29 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import childProcess from "node:child_process";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import { assertSafeNpmSpec, localNpmrcEnv } from "../npm-safety.js";
|
|
9
|
+
import { GENERATOR_LABELS, SURFACE_ORDER, TEMPLATE_MANIFEST, unsupportedTemplateSymlinkMessage } from "./constants.js";
|
|
10
|
+
import { isLocalTemplateSpec, packageNameFromSpec } from "./package-spec.js";
|
|
11
|
+
|
|
12
|
+
/** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
|
|
13
|
+
/** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
|
|
14
|
+
/** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
|
|
15
|
+
/** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
|
|
16
|
+
/** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
|
|
17
|
+
/** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
|
|
18
|
+
/** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
|
|
19
|
+
/** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
|
|
20
|
+
/** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
|
|
21
|
+
/** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
|
|
22
|
+
/** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {unknown} value
|
|
26
|
+
* @returns {TemplateManifest}
|
|
27
|
+
*/
|
|
28
|
+
export function validateTemplateManifest(value) {
|
|
29
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30
|
+
throw new Error(`${TEMPLATE_MANIFEST} must contain a JSON object.`);
|
|
31
|
+
}
|
|
32
|
+
const manifest = /** @type {Record<string, unknown>} */ (value);
|
|
33
|
+
for (const field of ["id", "version", "kind", "topogramVersion"]) {
|
|
34
|
+
if (typeof manifest[field] !== "string" || !manifest[field]) {
|
|
35
|
+
throw new Error(`${TEMPLATE_MANIFEST} is missing required string field '${field}'.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (manifest.kind !== "starter") {
|
|
39
|
+
throw new Error(`${TEMPLATE_MANIFEST} kind must be 'starter'.`);
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
Object.prototype.hasOwnProperty.call(manifest, "includesExecutableImplementation") &&
|
|
43
|
+
typeof manifest.includesExecutableImplementation !== "boolean"
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(`${TEMPLATE_MANIFEST} field 'includesExecutableImplementation' must be a boolean.`);
|
|
46
|
+
}
|
|
47
|
+
if (Object.prototype.hasOwnProperty.call(manifest, "starterScripts")) {
|
|
48
|
+
if (!manifest.starterScripts || typeof manifest.starterScripts !== "object" || Array.isArray(manifest.starterScripts)) {
|
|
49
|
+
throw new Error(`${TEMPLATE_MANIFEST} field 'starterScripts' must be an object of package.json script names to commands.`);
|
|
50
|
+
}
|
|
51
|
+
for (const [scriptName, command] of Object.entries(manifest.starterScripts)) {
|
|
52
|
+
if (typeof scriptName !== "string" || !scriptName.trim() || scriptName.startsWith("-") || scriptName.includes("\n")) {
|
|
53
|
+
throw new Error(`${TEMPLATE_MANIFEST} starterScripts contains an invalid script name.`);
|
|
54
|
+
}
|
|
55
|
+
if (typeof command !== "string" || !command.trim()) {
|
|
56
|
+
throw new Error(`${TEMPLATE_MANIFEST} starterScripts.${scriptName} must be a non-empty string.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return /** @type {TemplateManifest} */ (/** @type {unknown} */ (manifest));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} templateRoot
|
|
65
|
+
* @returns {TemplateManifest}
|
|
66
|
+
*/
|
|
67
|
+
export function readTemplateManifest(templateRoot) {
|
|
68
|
+
const manifestPath = path.join(templateRoot, TEMPLATE_MANIFEST);
|
|
69
|
+
if (!fs.existsSync(manifestPath)) {
|
|
70
|
+
throw new Error(`Template at '${templateRoot}' is missing ${TEMPLATE_MANIFEST}.`);
|
|
71
|
+
}
|
|
72
|
+
return validateTemplateManifest(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} root
|
|
77
|
+
* @param {string} currentDir
|
|
78
|
+
* @param {string} label
|
|
79
|
+
* @param {string} templateId
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
function assertTemplateTreeHasNoSymlinks(root, currentDir, label, templateId) {
|
|
83
|
+
const rootStat = fs.lstatSync(currentDir);
|
|
84
|
+
const relativeRoot = path.relative(root, currentDir).replace(/\\/g, "/") || label;
|
|
85
|
+
if (rootStat.isSymbolicLink()) {
|
|
86
|
+
throw new Error(unsupportedTemplateSymlinkMessage(templateId, relativeRoot));
|
|
87
|
+
}
|
|
88
|
+
if (!rootStat.isDirectory()) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
|
92
|
+
const entryPath = path.join(currentDir, entry.name);
|
|
93
|
+
const relativePath = path.relative(root, entryPath).replace(/\\/g, "/");
|
|
94
|
+
if (entry.isSymbolicLink()) {
|
|
95
|
+
throw new Error(unsupportedTemplateSymlinkMessage(templateId, relativePath));
|
|
96
|
+
}
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
assertTemplateTreeHasNoSymlinks(root, entryPath, label, templateId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} templateRoot
|
|
105
|
+
* @returns {TemplateManifest}
|
|
106
|
+
*/
|
|
107
|
+
export function validateTemplateRoot(templateRoot) {
|
|
108
|
+
const manifest = readTemplateManifest(templateRoot);
|
|
109
|
+
const topogramRoot = path.join(templateRoot, "topogram");
|
|
110
|
+
const projectConfigPath = path.join(templateRoot, "topogram.project.json");
|
|
111
|
+
if (fs.existsSync(topogramRoot) && fs.lstatSync(topogramRoot).isSymbolicLink()) {
|
|
112
|
+
throw new Error(unsupportedTemplateSymlinkMessage(manifest.id, "topogram"));
|
|
113
|
+
}
|
|
114
|
+
if (fs.existsSync(projectConfigPath) && fs.lstatSync(projectConfigPath).isSymbolicLink()) {
|
|
115
|
+
throw new Error(unsupportedTemplateSymlinkMessage(manifest.id, "topogram.project.json"));
|
|
116
|
+
}
|
|
117
|
+
if (!fs.existsSync(topogramRoot) || !fs.statSync(topogramRoot).isDirectory()) {
|
|
118
|
+
throw new Error(`Template '${manifest.id}' is missing topogram/.`);
|
|
119
|
+
}
|
|
120
|
+
if (!fs.existsSync(projectConfigPath) || !fs.statSync(projectConfigPath).isFile()) {
|
|
121
|
+
throw new Error(`Template '${manifest.id}' is missing topogram.project.json.`);
|
|
122
|
+
}
|
|
123
|
+
assertTemplateTreeHasNoSymlinks(templateRoot, topogramRoot, "topogram", manifest.id);
|
|
124
|
+
if (manifest.includesExecutableImplementation) {
|
|
125
|
+
const implementationRoot = path.join(templateRoot, "implementation");
|
|
126
|
+
if (fs.existsSync(implementationRoot) && fs.lstatSync(implementationRoot).isSymbolicLink()) {
|
|
127
|
+
throw new Error(unsupportedTemplateSymlinkMessage(manifest.id, "implementation"));
|
|
128
|
+
}
|
|
129
|
+
if (!fs.existsSync(implementationRoot) || !fs.statSync(implementationRoot).isDirectory()) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Template '${manifest.id}' declares executable implementation code but is missing implementation/.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
assertTemplateTreeHasNoSymlinks(templateRoot, implementationRoot, "implementation", manifest.id);
|
|
135
|
+
} else {
|
|
136
|
+
const implementationRoot = path.join(templateRoot, "implementation");
|
|
137
|
+
if (fs.existsSync(implementationRoot) && fs.statSync(implementationRoot).isDirectory()) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Template '${manifest.id}' contains implementation/ but ${TEMPLATE_MANIFEST} does not declare includesExecutableImplementation: true.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return manifest;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} generatorId
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
function generatorLabel(generatorId) {
|
|
151
|
+
return GENERATOR_LABELS.get(generatorId) || generatorId.replace(/^topogram\//, "");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {string} templateRoot
|
|
156
|
+
* @returns {TemplateTopologySummary}
|
|
157
|
+
*/
|
|
158
|
+
export function summarizeTemplateTopology(templateRoot) {
|
|
159
|
+
const projectConfigPath = path.join(templateRoot, "topogram.project.json");
|
|
160
|
+
const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
|
|
161
|
+
const rawRuntimes = /** @type {any[]} */ (
|
|
162
|
+
Array.isArray(projectConfig.topology?.runtimes) ? projectConfig.topology.runtimes : []
|
|
163
|
+
);
|
|
164
|
+
/** @type {Array<Record<string, any>>} */
|
|
165
|
+
const runtimes = [];
|
|
166
|
+
for (const runtime of rawRuntimes) {
|
|
167
|
+
if (runtime && typeof runtime === "object" && typeof runtime.kind === "string") {
|
|
168
|
+
runtimes.push(/** @type {Record<string, any>} */ (runtime));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const sortedRuntimes = [...runtimes].sort((a, b) => {
|
|
172
|
+
const aOrder = SURFACE_ORDER.get(a.kind) ?? 100;
|
|
173
|
+
const bOrder = SURFACE_ORDER.get(b.kind) ?? 100;
|
|
174
|
+
return aOrder - bOrder;
|
|
175
|
+
});
|
|
176
|
+
const surfaces = [...new Set(sortedRuntimes.map((runtime) => String(runtime.kind)))];
|
|
177
|
+
const generators = [
|
|
178
|
+
...new Set(
|
|
179
|
+
sortedRuntimes
|
|
180
|
+
.map((runtime) => runtime.generator?.id)
|
|
181
|
+
.filter((generatorId) => typeof generatorId === "string")
|
|
182
|
+
.map((generatorId) => String(generatorId))
|
|
183
|
+
)
|
|
184
|
+
];
|
|
185
|
+
return {
|
|
186
|
+
surfaces,
|
|
187
|
+
generators,
|
|
188
|
+
stack: generators.map(generatorLabel).join(" + ")
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {string} templateSpec
|
|
194
|
+
* @returns {string}
|
|
195
|
+
*/
|
|
196
|
+
export function installPackageSpec(templateSpec) {
|
|
197
|
+
assertSafeNpmSpec(templateSpec);
|
|
198
|
+
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), "topogram-template-"));
|
|
199
|
+
const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
200
|
+
const result = childProcess.spawnSync(
|
|
201
|
+
npmBin,
|
|
202
|
+
[
|
|
203
|
+
"install",
|
|
204
|
+
"--prefix",
|
|
205
|
+
installRoot,
|
|
206
|
+
"--ignore-scripts",
|
|
207
|
+
"--no-audit",
|
|
208
|
+
"--no-fund",
|
|
209
|
+
"--package-lock=false",
|
|
210
|
+
"--",
|
|
211
|
+
templateSpec
|
|
212
|
+
],
|
|
213
|
+
{
|
|
214
|
+
encoding: "utf8",
|
|
215
|
+
env: {
|
|
216
|
+
...process.env,
|
|
217
|
+
...localNpmrcEnv(process.cwd()),
|
|
218
|
+
PATH: process.env.PATH || ""
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
if (result.status !== 0) {
|
|
223
|
+
throw new Error(formatPackageInstallError(templateSpec, result));
|
|
224
|
+
}
|
|
225
|
+
const packageRoot = path.join(installRoot, "node_modules", packageNameFromSpec(templateSpec));
|
|
226
|
+
if (fs.existsSync(packageRoot)) {
|
|
227
|
+
return packageRoot;
|
|
228
|
+
}
|
|
229
|
+
return findInstalledTemplatePackageRoot(installRoot, templateSpec);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {string} templateSpec
|
|
234
|
+
* @param {any} result
|
|
235
|
+
* @returns {string}
|
|
236
|
+
*/
|
|
237
|
+
function formatPackageInstallError(templateSpec, result) {
|
|
238
|
+
const output = [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n").trim();
|
|
239
|
+
const normalized = output.toLowerCase();
|
|
240
|
+
const npmrcHint = "Ensure npm can access the registry required by this template package. Topogram ignores project .npmrc files unless TOPOGRAM_ALLOW_LOCAL_NPMRC=1 or --allow-local-npmrc is used.";
|
|
241
|
+
const packageAccessHint = "For private package registries, configure a token with package read access.";
|
|
242
|
+
const authHint = "For private template packages, configure npm auth for the package registry before installing.";
|
|
243
|
+
const doctorHint = "Run `topogram doctor` to check Node.js, npm, package, and catalog access.";
|
|
244
|
+
if (result.error?.code === "ENOENT") {
|
|
245
|
+
return [
|
|
246
|
+
`Failed to install template package '${templateSpec}': npm was not found.`,
|
|
247
|
+
"Install Node.js/npm and retry."
|
|
248
|
+
].join("\n");
|
|
249
|
+
}
|
|
250
|
+
if (/\b(e401|eneedauth)\b/.test(normalized) || normalized.includes("unauthenticated") || normalized.includes("authentication required")) {
|
|
251
|
+
return [
|
|
252
|
+
`Authentication is required to install template package '${templateSpec}'.`,
|
|
253
|
+
authHint,
|
|
254
|
+
npmrcHint,
|
|
255
|
+
packageAccessHint,
|
|
256
|
+
doctorHint,
|
|
257
|
+
output
|
|
258
|
+
].filter(Boolean).join("\n");
|
|
259
|
+
}
|
|
260
|
+
if (/\be403\b/.test(normalized) || normalized.includes("forbidden") || normalized.includes("permission")) {
|
|
261
|
+
return [
|
|
262
|
+
`Package access was denied while installing template package '${templateSpec}'.`,
|
|
263
|
+
authHint,
|
|
264
|
+
packageAccessHint,
|
|
265
|
+
doctorHint,
|
|
266
|
+
output
|
|
267
|
+
].filter(Boolean).join("\n");
|
|
268
|
+
}
|
|
269
|
+
if (/\b(e404|404)\b/.test(normalized) || normalized.includes("not found")) {
|
|
270
|
+
return [
|
|
271
|
+
`Template package '${templateSpec}' was not found, or the current token does not have access to it.`,
|
|
272
|
+
"Check the package name/version and registry access.",
|
|
273
|
+
packageAccessHint,
|
|
274
|
+
doctorHint,
|
|
275
|
+
output
|
|
276
|
+
].filter(Boolean).join("\n");
|
|
277
|
+
}
|
|
278
|
+
if (/\beintegrity\b/.test(normalized) || normalized.includes("integrity checksum failed")) {
|
|
279
|
+
return [
|
|
280
|
+
`Package integrity failed while installing template package '${templateSpec}'.`,
|
|
281
|
+
"Refresh package-lock.json from the published registry tarball instead of a local npm pack tarball.",
|
|
282
|
+
output
|
|
283
|
+
].filter(Boolean).join("\n");
|
|
284
|
+
}
|
|
285
|
+
return `Failed to install template package '${templateSpec}'.\n${output || "unknown error"}`.trim();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @param {string} installRoot
|
|
290
|
+
* @param {string} templateSpec
|
|
291
|
+
* @returns {string}
|
|
292
|
+
*/
|
|
293
|
+
function findInstalledTemplatePackageRoot(installRoot, templateSpec) {
|
|
294
|
+
const nodeModules = path.join(installRoot, "node_modules");
|
|
295
|
+
if (!fs.existsSync(nodeModules)) {
|
|
296
|
+
throw new Error(`Template package '${templateSpec}' did not create node_modules.`);
|
|
297
|
+
}
|
|
298
|
+
/** @type {string[]} */
|
|
299
|
+
const candidates = [];
|
|
300
|
+
for (const entry of fs.readdirSync(nodeModules)) {
|
|
301
|
+
if (entry === ".bin") {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const entryPath = path.join(nodeModules, entry);
|
|
305
|
+
if (entry.startsWith("@")) {
|
|
306
|
+
for (const scopedEntry of fs.readdirSync(entryPath)) {
|
|
307
|
+
candidates.push(path.join(entryPath, scopedEntry));
|
|
308
|
+
}
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
candidates.push(entryPath);
|
|
312
|
+
}
|
|
313
|
+
const templateRoots = candidates.filter((candidate) =>
|
|
314
|
+
fs.existsSync(path.join(candidate, TEMPLATE_MANIFEST))
|
|
315
|
+
);
|
|
316
|
+
if (templateRoots.length === 1) {
|
|
317
|
+
return templateRoots[0];
|
|
318
|
+
}
|
|
319
|
+
if (templateRoots.length > 1) {
|
|
320
|
+
throw new Error(`Template package '${templateSpec}' installed multiple template manifests.`);
|
|
321
|
+
}
|
|
322
|
+
throw new Error(`Template package '${templateSpec}' did not install a package with ${TEMPLATE_MANIFEST}.`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @param {string} templateName
|
|
327
|
+
* @param {string} templatesRoot
|
|
328
|
+
* @returns {ResolvedTemplate}
|
|
329
|
+
*/
|
|
330
|
+
export function resolveTemplate(templateName, templatesRoot) {
|
|
331
|
+
void templatesRoot;
|
|
332
|
+
|
|
333
|
+
if (isLocalTemplateSpec(templateName)) {
|
|
334
|
+
const templateRoot = path.resolve(templateName);
|
|
335
|
+
if (!fs.existsSync(templateRoot)) {
|
|
336
|
+
throw new Error(`Local template path '${templateName}' does not exist.`);
|
|
337
|
+
}
|
|
338
|
+
if (!fs.statSync(templateRoot).isDirectory()) {
|
|
339
|
+
const packageTemplateRoot = installPackageSpec(templateName);
|
|
340
|
+
return {
|
|
341
|
+
requested: templateName,
|
|
342
|
+
root: packageTemplateRoot,
|
|
343
|
+
manifest: validateTemplateRoot(packageTemplateRoot),
|
|
344
|
+
source: "package",
|
|
345
|
+
packageSpec: templateName
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
requested: templateName,
|
|
350
|
+
root: templateRoot,
|
|
351
|
+
manifest: validateTemplateRoot(templateRoot),
|
|
352
|
+
source: "local",
|
|
353
|
+
packageSpec: null
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const templateRoot = installPackageSpec(templateName);
|
|
358
|
+
if (!fs.existsSync(templateRoot)) {
|
|
359
|
+
throw new Error(`Template package '${templateName}' did not install to '${templateRoot}'.`);
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
requested: templateName,
|
|
363
|
+
root: templateRoot,
|
|
364
|
+
manifest: validateTemplateRoot(templateRoot),
|
|
365
|
+
source: "package",
|
|
366
|
+
packageSpec: templateName
|
|
367
|
+
};
|
|
368
|
+
}
|