@stackgenhq/backstage-plugin-stackgen-backend 0.3.6-beta.1

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.
Files changed (66) hide show
  1. package/README.md +485 -0
  2. package/config.d.ts +39 -0
  3. package/dist/actions/core/assign_project_members_action.cjs.js +227 -0
  4. package/dist/actions/core/assign_project_members_action.cjs.js.map +1 -0
  5. package/dist/actions/core/create_appstack_action.cjs.js +953 -0
  6. package/dist/actions/core/create_appstack_action.cjs.js.map +1 -0
  7. package/dist/actions/core/create_project_action.cjs.js +243 -0
  8. package/dist/actions/core/create_project_action.cjs.js.map +1 -0
  9. package/dist/actions/core/download_iac_action.cjs.js +133 -0
  10. package/dist/actions/core/download_iac_action.cjs.js.map +1 -0
  11. package/dist/actions/core/set_environment_variable_values_action.cjs.js +121 -0
  12. package/dist/actions/core/set_environment_variable_values_action.cjs.js.map +1 -0
  13. package/dist/actions/core/set_state_backend_action.cjs.js +174 -0
  14. package/dist/actions/core/set_state_backend_action.cjs.js.map +1 -0
  15. package/dist/actions/exporter/export_iac_to_git_action.cjs.js +290 -0
  16. package/dist/actions/exporter/export_iac_to_git_action.cjs.js.map +1 -0
  17. package/dist/actions/exporter/list_vault_secrets_action.cjs.js +39 -0
  18. package/dist/actions/exporter/list_vault_secrets_action.cjs.js.map +1 -0
  19. package/dist/config/constants.cjs.js +23 -0
  20. package/dist/config/constants.cjs.js.map +1 -0
  21. package/dist/exporter/exportOverrides.cjs.js +27 -0
  22. package/dist/exporter/exportOverrides.cjs.js.map +1 -0
  23. package/dist/exporter/gitExporterConfigHttp.cjs.js +105 -0
  24. package/dist/exporter/gitExporterConfigHttp.cjs.js.map +1 -0
  25. package/dist/exporter/http.cjs.js +14 -0
  26. package/dist/exporter/http.cjs.js.map +1 -0
  27. package/dist/exporter/paths.cjs.js +25 -0
  28. package/dist/exporter/paths.cjs.js.map +1 -0
  29. package/dist/exporter/projectGitExporterTemplate.cjs.js +62 -0
  30. package/dist/exporter/projectGitExporterTemplate.cjs.js.map +1 -0
  31. package/dist/exporter/topologyResolve.cjs.js +71 -0
  32. package/dist/exporter/topologyResolve.cjs.js.map +1 -0
  33. package/dist/index.cjs.js +31 -0
  34. package/dist/index.cjs.js.map +1 -0
  35. package/dist/index.d.ts +224 -0
  36. package/dist/lib/extractArchive.cjs.js +62 -0
  37. package/dist/lib/extractArchive.cjs.js.map +1 -0
  38. package/dist/modules/stackgenExporterScaffolderModule.cjs.js +29 -0
  39. package/dist/modules/stackgenExporterScaffolderModule.cjs.js.map +1 -0
  40. package/dist/modules/stackgenUiProxiesModule.cjs.js +366 -0
  41. package/dist/modules/stackgenUiProxiesModule.cjs.js.map +1 -0
  42. package/dist/plugin/plugin.cjs.js +136 -0
  43. package/dist/plugin/plugin.cjs.js.map +1 -0
  44. package/dist/plugin/router.cjs.js +341 -0
  45. package/dist/plugin/router.cjs.js.map +1 -0
  46. package/dist/services/MappingsService/mappingsService.cjs.js +149 -0
  47. package/dist/services/MappingsService/mappingsService.cjs.js.map +1 -0
  48. package/dist/services/StackgenService/stackgenService.cjs.js +267 -0
  49. package/dist/services/StackgenService/stackgenService.cjs.js.map +1 -0
  50. package/dist/utils/api-objects.cjs.js +205 -0
  51. package/dist/utils/api-objects.cjs.js.map +1 -0
  52. package/dist/vendor/stackgen-api-client/index.cjs.js +47681 -0
  53. package/dist/vendor/stackgen-api-client/index.cjs.js.map +1 -0
  54. package/migrations/20250131055302_init.js +24 -0
  55. package/migrations/20250305123456_alter_type_associations.js +23 -0
  56. package/package.json +86 -0
  57. package/templates/appstack-configure-environment.yaml +81 -0
  58. package/templates/appstack-from-template.yaml +76 -0
  59. package/templates/appstack-with-modules.yaml +69 -0
  60. package/templates/appstack-with-resources.yaml +81 -0
  61. package/templates/export-iac.yaml +97 -0
  62. package/templates/full-infra-setup.yaml +156 -0
  63. package/templates/git_export_exporter_multi_scm.yaml +123 -0
  64. package/templates/project-advanced.yaml +77 -0
  65. package/templates/project-basic.yaml +39 -0
  66. package/templates/project-with-members.yaml +86 -0
package/README.md ADDED
@@ -0,0 +1,485 @@
1
+ # Backstage Plugin StackGen Backend
2
+
3
+ Published to **[npmjs.org](https://www.npmjs.com/package/@stackgenhq/backstage-plugin-stackgen-backend)** as `@stackgenhq/backstage-plugin-stackgen-backend`. Install works without a GitHub PAT. Release process: [docs/PUBLISH.md](docs/PUBLISH.md).
4
+
5
+ ## Installation
6
+
7
+ This plugin is installed via the `@stackgenhq/backstage-plugin-stackgen-backend` package. To install it to your backend package, run the following command:
8
+
9
+ ```bash
10
+ # From your root directory
11
+ yarn --cwd packages/backend add @stackgenhq/backstage-plugin-stackgen-backend
12
+ ```
13
+
14
+ No `.yarnrc.yml` GitHub Packages scope is required for released versions.
15
+ Then add the plugin to your backend in `packages/backend/src/index.ts`:
16
+
17
+ ```ts
18
+ const backend = createBackend();
19
+ // ...
20
+ backend.add(import('@stackgenhq/backstage-plugin-stackgen-backend'));
21
+ ```
22
+
23
+
24
+ add configuration required for plugin in backstage app-config.yaml
25
+ ```yaml
26
+ stackGen:
27
+ # The base URL for the StackGen API. Default: `https://cloud.stackgen.com`
28
+ baseUrl: "${BACKSTAGE_ADAPTER_URL}"
29
+ apiToken: "${STACKGEN_PAT}"
30
+ # Optional: role assigned when createAppStack auto-adds PAT user to project/team
31
+ # Defaults to admin role 00000000-0000-0000-0000-000000000002
32
+ projectMemberRoleId: "${STACKGEN_PROJECT_MEMBER_ROLE_ID}"
33
+ allowedTeams: ""
34
+ ```
35
+
36
+ allowedTeams is optional so by default, all the teams the STACKGEN_PAT has access will be available.
37
+ Access can be restricted to a few teams by adding their ids in the configuration. exa:
38
+
39
+ ```yaml
40
+ stackGen:
41
+ baseUrl: "${BACKSTAGE_ADAPTER_URL}"
42
+ apiToken: "${STACKGEN_PAT}"
43
+ allowedTeams: "20f0e211-15ce-4d2c-1e12-0555bffee7bd,20f0e211-15ce-4d2c-2e22-0555bffee7bd"
44
+ ```
45
+
46
+ ## API specification
47
+
48
+ The canonical Integrations Gateway API spec is maintained in the [integrations](https://github.com/appcd-dev/integrations) repo:
49
+
50
+ **https://github.com/appcd-dev/integrations/blob/main/api-docs/api-spec.yml**
51
+
52
+ Use that spec as the source of truth for request/response schemas and endpoints. This plugin is not generated from the spec; implementation is in code and may be updated independently.
53
+
54
+ ## Compatibility
55
+
56
+ | Package | Tested / required notes |
57
+ |---------|-------------------------|
58
+ | `@backstage/plugin-scaffolder-node` | Declared dependency **`^0.12.5`** (tested). Schema declarations use the **function form** (`schema.input: () => zodObject` or `(z) => z.object(…)`) required by `parseSchemas`. Do not pass raw `ZodObject` instances — modern scaffolder-node ignores them and leaves `schema.input` / `schema.output` undefined. Older scaffolder-node **0.6.x** accepted raw Zod objects via `safeParseAsync` detection; that path is obsolete. |
59
+ | Node.js | `>=20` |
60
+
61
+ **ENG-3394:** Releases **≤0.3.2** (built against scaffolder-node 0.6.x with raw ZodObject schemas) show blank input fields on Backstage “Installed actions” when paired with scaffolder-node **≥0.8** (including **0.12.5**) and skip scaffolder upfront validation. Fixed in **0.3.3** (package track `0.3.3-beta.3+`): function-form schemas + scaffolder-node `^0.12.5`.
62
+
63
+ ## Available Actions
64
+
65
+ ### Setup
66
+
67
+ To use these actions, add the following code in your backend in `packages/backend/src/index.ts`:
68
+
69
+ ```ts
70
+ import { createAppStackAction, createProjectAction, downloadIaCAction, exportAppStackToGitAction } from '@stackgenhq/backstage-plugin-stackgen-backend';
71
+ import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node';
72
+ import { coreServices, createBackendModule } from '@backstage/backend-plugin-api';
73
+
74
+ ...
75
+
76
+ export const scaffolderCustomExtension = createBackendModule({
77
+ pluginId: 'scaffolder',
78
+ moduleId: 'custom-extensions',
79
+ register(env) {
80
+ env.registerInit({
81
+ deps: {
82
+ scaffolder: scaffolderActionsExtensionPoint,
83
+ config: coreServices.rootConfig,
84
+ logger: coreServices.logger,
85
+ },
86
+ async init({ scaffolder, config, logger }) {
87
+ scaffolder.addActions(
88
+ createProjectAction(config, logger),
89
+ createAppStackAction(config, logger),
90
+ downloadIaCAction(config, logger),
91
+ exportAppStackToGitAction(config, logger),
92
+ // ...other actions
93
+ );
94
+ },
95
+ });
96
+ },
97
+ });
98
+
99
+ ...
100
+
101
+ backend.add(scaffolderCustomExtension);
102
+ ```
103
+
104
+ ### stackGen:createProject
105
+
106
+ Creates a Project in StackGen with optional git and environment configuration.
107
+
108
+ A project in StackGen maps to a team. After creation, the `teamId` output can be passed to `stackGen:createAppStack` to create an appstack under that project/team.
109
+
110
+ **Inputs:**
111
+
112
+ | Field | Type | Required | Description |
113
+ |-------|------|----------|-------------|
114
+ | `project.name` | string | yes | Project name |
115
+ | `project.description` | string | yes | Project description |
116
+ | `project.gitConfig` | object | no | Default git configuration (scmType, repoUrl, targetBranch, path, vaultReference) |
117
+ | `project.environmentConfig` | object | no | Default environment configuration (templates, variables, overrides) |
118
+
119
+ **Outputs:**
120
+
121
+ | Field | Type | Description |
122
+ |-------|------|-------------|
123
+ | `projectId` | string | UUID of the created project |
124
+ | `projectName` | string | Name of the project |
125
+ | `teamId` | string | UUID of the team (same as project; use this for appstack creation) |
126
+ | `alreadyExisted` | boolean | `true` if a project with this name already existed (409), `false` if newly created (201) |
127
+
128
+ **Status code handling:**
129
+
130
+ - **201** -- Project created. Outputs `projectId`, `projectName`, `teamId`, `alreadyExisted: false`.
131
+ - **409** -- Project already exists. Treated as success. Outputs `projectId`, `projectName`, `teamId`, `alreadyExisted: true`.
132
+ - **403** -- Throws error: invalid token permissions.
133
+ - **400** -- Throws error: invalid project name.
134
+
135
+ Important behavior for project initialization:
136
+
137
+ - Auxiliary initialization settings (for example environment templates, variables, overrides, default configs like `gitConfig`/`environmentConfig`) are only applied when the plugin actually creates a new project.
138
+ - If the requested project already exists (`409` path), the action returns that project/team identifiers and proceeds, but it does **not** patch/update the existing project with those extra settings.
139
+ - Updating existing projects with those parameters is planned for a future update.
140
+
141
+ Example:
142
+ ```yaml
143
+ steps:
144
+ - id: create-project
145
+ name: Create StackGen Project
146
+ action: stackGen:createProject
147
+ input:
148
+ project:
149
+ name: ${{ parameters.projectName }}
150
+ description: "Project created via Backstage"
151
+ ```
152
+
153
+ Example with git and environment config:
154
+ ```yaml
155
+ steps:
156
+ - id: create-project
157
+ name: Create StackGen Project
158
+ action: stackGen:createProject
159
+ input:
160
+ project:
161
+ name: ${{ parameters.projectName }}
162
+ description: "Project created via Backstage"
163
+ gitConfig:
164
+ name: "default-git-config"
165
+ type: "git"
166
+ parameters:
167
+ scmType: "GITHUB"
168
+ repoUrl: "https://github.com/org/repo.git"
169
+ targetBranch: "main"
170
+ vaultReference: "550e8400-e29b-41d4-a716-446655440000"
171
+ default: true
172
+ environmentConfig:
173
+ name: "default-env-config"
174
+ environmentTemplates:
175
+ - name: "production"
176
+ color: "#FF0000"
177
+ stateBackendTemplate:
178
+ type: "S3"
179
+ configJson: '{"bucket": "my-terraform-state"}'
180
+ variables:
181
+ - name: "region"
182
+ type: "string"
183
+ defaultValueJson: '"us-east-1"'
184
+ description: "AWS region for deployment"
185
+ variableOverrides:
186
+ - variableName: "region"
187
+ environmentTemplateName: "production"
188
+ valueJson: '"us-west-2"'
189
+ ```
190
+
191
+ Environment variable value types (`defaultValueJson` and `valueJson`):
192
+
193
+ - These fields are **JSON literals encoded as strings** in YAML.
194
+ - The plugin passes them through to StackGen, so you can use non-string JSON values.
195
+ - Set `variables[].type` to match your intended semantic type in StackGen.
196
+
197
+ Examples:
198
+
199
+ ```yaml
200
+ variables:
201
+ - name: "region"
202
+ type: "string"
203
+ defaultValueJson: '"us-east-1"'
204
+ - name: "replicas"
205
+ type: "number"
206
+ defaultValueJson: '3'
207
+ - name: "enableAutoscaling"
208
+ type: "boolean"
209
+ defaultValueJson: 'true'
210
+ - name: "subnetIds"
211
+ type: "list"
212
+ defaultValueJson: '["subnet-1","subnet-2"]'
213
+ - name: "extraTags"
214
+ type: "object"
215
+ defaultValueJson: '{"team":"platform","env":"dev"}'
216
+
217
+ variableOverrides:
218
+ - variableName: "replicas"
219
+ environmentTemplateName: "prod"
220
+ valueJson: '5'
221
+ ```
222
+
223
+ ### stackGen:createAppStack
224
+
225
+ Creates an AppStack in StackGen for a given team with specified resources and configurations.
226
+
227
+ Behavior by input type:
228
+
229
+ - **Before appstack creation**: action resolves the PAT user via `GET /appcd/api/v1/auth/me` and attempts to assign that user to the target project/team (`appstack.teamId`) via `POST /appcd/api/v1/orgs/{projectId}/users` using role `projectMemberRoleId` (default admin). If this preflight assignment fails, appstack creation still proceeds and the membership error is logged as a warning—this usually happens if the PAT user was already a member of the project. If the PAT was newly added, it **stays** a project member after the action completes (no automatic removal).
230
+ - **When no `resourcePackId` is present**: uses the legacy create path `POST /stackgen/v1/appstacks` with payload shape `name`, `coreConfig` (provider, targetCompute), `resources`, and `teamId`.
231
+ - **When `templateAppstackId` is set** (and `resources` is empty): loads the source AppStack via `GET /appcd/api/v1/appstacks/{uuid}`, creates a blank appstack through integrations, then clones topology with `POST /iac-gen/v1/topologies?orgId=…` (`appstackRefId`). Deployment type and IaC type come from the source `coreConfig` (defaults: `k8s`, `Terraform` if missing).
232
+ - **When any `resourcePackId` is present**: uses a two-step integrations-compatible flow:
233
+ 1. Create a blank appstack via `POST /integrations/api/v1/projects/{teamId}/appstacks` (empty topology)
234
+ 2. Add each resource pack into that topology via `POST /iac-gen/v1/topologies/{topologyId}/resources?orgId={orgScope}`, preserving `resourcePackId` semantics.
235
+
236
+ **Inputs:**
237
+
238
+ | Field | Type | Required | Description |
239
+ |-------|------|----------|-------------|
240
+ | `appstack.name` or `appstack.appstackName` | string | yes | AppStack name |
241
+ | `appstack.teamId` | string | yes | Team UUID (use `teamId` output from `stackGen:createProject`) |
242
+ | `appstack.cloudProvider` | string | conditional | Required unless `templateAppstackId` is set (then read from appcd `coreConfig.provider`) |
243
+ | `appstack.templateAppstackId` | string (UUID) | no | If set: load source AppStack from appcd, then clone topology via iac-gen (`appstackRefId`). Cannot be used with a non-empty `resources` array |
244
+ | `appstack.resources` | array | yes | List of resources (can be empty `[]`) |
245
+
246
+ Integrations `POST …/appstacks` only accepts `name`, `cloudProvider`, and `topology` (no extra metadata fields).
247
+
248
+ Each resource in the array:
249
+
250
+ | Field | Type | Required | Description |
251
+ |-------|------|----------|-------------|
252
+ | `resourceType` | string | yes | Valid StackGen resource type |
253
+ | `configuration` | object | no | Resource configuration (mutually exclusive with `resourcePackId`) |
254
+ | `tfVars` | object | no | Terraform variables |
255
+ | `resourcePackId` | string | no | Resource pack ID (mutually exclusive with `configuration`) |
256
+ | `children` | array | no | Child resources for group resource types |
257
+
258
+ **Outputs:**
259
+
260
+ | Field | Type | Description |
261
+ |-------|------|-------------|
262
+ | `appstackId` | string | UUID of the created appstack |
263
+ | `appstackName` | string | Name of the created appstack |
264
+ | `appStackURL` | string | Direct URL to the created appstack in StackGen |
265
+
266
+ Example (chained with project creation):
267
+ ```yaml
268
+ steps:
269
+ - id: create-project
270
+ name: Create StackGen Project
271
+ action: stackGen:createProject
272
+ input:
273
+ project:
274
+ name: ${{ parameters.projectName }}
275
+ description: "Project created via Backstage"
276
+
277
+ - id: stackGen
278
+ name: Create AppStack
279
+ action: stackGen:createAppStack
280
+ input:
281
+ appstack:
282
+ name: "MyAppStack"
283
+ # teamId comes from the output of the create-project step
284
+ teamId: ${{ steps['create-project'].output.teamId }}
285
+ cloudProvider: "aws"
286
+ resources:
287
+ - resourceType: "aws_s3"
288
+ # Only one of 'configuration' or 'resourcePackId' should be provided
289
+ configuration:
290
+ bucket_name: "mybucket"
291
+ - resourceType: "resourcePack"
292
+ resourcePackId: "20f0e212-15ce-4d2c-1e12-0555bffee7bd"
293
+ # No configuration here because resourcePackId is used (legacy path preserves resourcePackId)
294
+ - resourceType: "helm_workload"
295
+ children:
296
+ - resourceType: "helm_workload"
297
+ configuration:
298
+ name: test-workload
299
+ image: test-image
300
+ - resourceType: helm_service
301
+ configuration:
302
+ name: test-service
303
+ ```
304
+
305
+ Example output:
306
+ ```yaml
307
+ output:
308
+ links:
309
+ - title: 'View Created AppStack in StackGen'
310
+ url: '${{ steps.stackGen.output.appStackURL }}'
311
+ ```
312
+
313
+ ## Example templates walkthrough
314
+
315
+ The templates in `examples/template/` demonstrate end-to-end StackGen scaffolding flows:
316
+
317
+ | Template | Description |
318
+ |----------|-------------|
319
+ | `stackgen-export-iac` | Manually fetch IaC from an AppStack and push it to GitHub via Backstage — useful when you prefer Backstage to handle the git push instead of StackGen's native git integration. |
320
+ | `stackgen-appstack-with-resources` | Creates a project with environments, then an AppStack with a resource pack, standalone S3 bucket, and custom Terraform module (via `templateId`). |
321
+ | `stackgen-full-setup` | The most comprehensive template: project + Git config + AppStack (resource pack + S3 + custom module) + production/staging environments with per-environment TF variables + S3 state backends. |
322
+ | `stackgen-project-basic` | Creates an empty project — a blank starting point. |
323
+ | `stackgen-project-advanced` | Creates a project with Git integration, environments, variables, and per-environment overrides. |
324
+
325
+ Project initialization in these templates can include:
326
+
327
+ - Environment templates (for example `dev`, `stage`, `prod`).
328
+ - Default variables and per-environment variable overrides.
329
+ - Git configuration via `project.gitConfig`.
330
+ - Per-environment storage backend configuration via `stateBackendTemplate`.
331
+
332
+ ## Disabling the Frontend Plugin
333
+
334
+ The StackGen plugin includes an optional frontend component that adds a "StackGen" item to the Backstage sidebar. If you only need the backend scaffolder actions (templates) and don't want the sidebar entry, you can toggle it off by commenting out two lines:
335
+
336
+ **1. Remove the sidebar item** in `packages/app/src/components/Root/Root.tsx`:
337
+ ```tsx
338
+ // Comment out this line:
339
+ {/* <SidebarItem icon={StackGenIcon} to="stackgen" text="StackGen"/> */}
340
+ ```
341
+
342
+ **2. Remove the route** in `packages/app/src/App.tsx`:
343
+ ```tsx
344
+ // Comment out this line:
345
+ {/* <Route path="/stackgen" element={<StackGenPage />} /> */}
346
+ ```
347
+
348
+ The imports can remain in place — they'll be unused but won't cause errors. To re-enable the frontend later, simply uncomment both lines and restart Backstage.
349
+
350
+
351
+ ### stackGen:downloadIaC
352
+
353
+ This action downloads and extracts the Infrastructure as Code (IaC) files for a specified AppStack from StackGen.
354
+
355
+ Example input:
356
+ ```yaml
357
+ steps:
358
+ - id: download-iac
359
+ name: Download IaC
360
+ action: stackGen:downloadIaC
361
+ input:
362
+ appstackName: "MyAppStack"
363
+ # Optional: teamId can be omitted if not needed
364
+ teamId: "team-123"
365
+ # Optional: path where IaC files should be extracted, defaults to "./"
366
+ extractedIaCPath: "./infrastructure"
367
+ ```
368
+ ### stackGen:exportToGit
369
+
370
+ This action is a compatibility wrapper over the new stack-exporter flow.
371
+ It resolves topology for `teamId + appstackName`, selects an existing git exporter
372
+ config for the team (or uses an explicit id), and triggers exporter `POST /export`.
373
+
374
+ **Inputs (high level)**
375
+
376
+ | Field | Required | Description |
377
+ |-------|----------|-------------|
378
+ | `appstackName`, `teamId` | yes | AppStack and team / org id |
379
+ | `configId` | no | When set (non-empty), used as stack-exporter git `config_id`; skips listing, `chooseConfig`, and bootstrap. **Precedence:** `configId` overrides `gitConfig`. In Software Templates, map from the frontend field **`existingExporterConfigId`** (`ExporterGitConfigSelect`). |
380
+ | `topologyId`, `appstackId` | no | Optional topology / appstack hints for resolution |
381
+ | `gitConfig` | no | Bootstrap shape (same as `createProject.project.gitConfig`). Ignored when `configId` is set. |
382
+ | `baseBranch` | no | Branch to base the change on (PR base / commit destination). Maps to the exporter `target_branch` override. Blank uses the git config default. Prefill via `ExporterBaseBranch`. |
383
+ | `pushBranch` | no | Branch StackGen pushes the generated IaC to (source/head branch). Maps to the exporter `branch_format` override. Blank creates a new one. Prefill via `ExporterPushBranch.branch`. |
384
+ | `useSameBranch` | no | When `true`, sets exporter `use_same_branch=true` (reuse previous export head). Set by `ExporterPushBranch` when the history suggestion is left unchanged. |
385
+ | `createPr` | no | Boolean, default `true`. When `false`, changes are committed directly to the branch without opening a PR (exporter `commit_only`). |
386
+ | `overrides` | no | Raw exporter overrides (e.g. `pr_title`, `commit_msg`). An explicit entry wins over the derived key for the same override. |
387
+
388
+ These give the Backstage plugin parity with the StackGen UI git export (base branch, push branch, create PR). `baseBranch`, `pushBranch`, `useSameBranch`, and `createPr` are translated to exporter override keys in one place (`buildExportOverrides`), so templates do not need to know the internal keys.
389
+
390
+ **Outputs**
391
+
392
+ | Field | When present | Description |
393
+ |-------|--------------|-------------|
394
+ | `pullRequestUrl`, `externalLink` | PR created (`createPr` true) | URL of the opened pull request |
395
+ | `committedBranch` | commit-only (`createPr` false) | Branch StackGen committed the changes to |
396
+ | `notes` | always (when returned) | Human-readable export notes (e.g. "committed to branch X") |
397
+
398
+ > A commit-only export (`createPr: false`) does not open a pull request, so `pullRequestUrl` is absent; use `committedBranch` / `notes` in that case.
399
+
400
+ Notes:
401
+ - **Explicit `configId`:** Use when the user picks a config in Backstage (`ExporterGitConfigSelect` writes the config uuid to `existingExporterConfigId`; pass it as `configId` in the action input). **Precedence:** `configId` overrides `gitConfig`.
402
+ - **Listing configs:** If `configId` and `gitConfig` are omitted, `GET .../exporter/.../config?orgId={teamId}&type=git&appstackId={appstackId}` — if a single/default git config exists for that AppStack, it is used for `POST .../export`. Multiple configs require `configId`.
403
+ - **Bootstrap (no git config for the AppStack yet):** Project-level `gitConfig` from `stackGen:createProject` is stored as stack-exporter **templates** (`GET .../exporter/.../config/template?orgId={teamId}`). When the list step finds nothing usable, this action loads that template and `POST .../exporter/.../config?orgId={teamId}` **with `appstack_id`** so the config is scoped to the AppStack.
404
+ - **Optional `gitConfig` on this action:** Same shape as `createProject.project.gitConfig`. When provided (and `configId` is not set), creates a new appstack-scoped config from that input and uses it. Not used when `configId` is set.
405
+ - **`appstackId`:** Optional. When omitted, resolved from `teamId` + `appstackName` via appcd `GET /appstacks` (`appstackId` for exporter binding, `uuid` for topology).
406
+
407
+ Example input:
408
+ ```yaml
409
+ steps:
410
+ - id: exportToGit
411
+ name: Export AppStack to Git via StackGen
412
+ action: stackGen:exportToGit
413
+ input:
414
+ appstackName: ${{ parameters.appstackName }}
415
+ teamId: ${{ parameters.teamId }}
416
+ # Optional: explicit exporter git config (from ExporterGitConfigSelect → existingExporterConfigId)
417
+ configId: ${{ parameters.existingExporterConfigId }}
418
+ # gitConfig omitted: uses project template from createProject when no appstack config exists
419
+ # First-class git export controls (parity with the StackGen UI):
420
+ baseBranch: ${{ parameters.baseBranch }} # PR base / commit destination (target_branch)
421
+ pushBranch: ${{ parameters.pushBranch.branch }} # branch StackGen pushes to (branch_format)
422
+ useSameBranch: ${{ parameters.pushBranch.useSameBranch }} # reuse history head when unchanged
423
+ createPr: ${{ parameters.createPr }} # false => commit_only (no PR)
424
+ overrides:
425
+ - key: pr_title
426
+ value: Export IaC from Backstage
427
+ - key: commit_msg
428
+ value: chore: export iac
429
+ ```
430
+
431
+
432
+ Example output:
433
+ ```
434
+ output:
435
+ links:
436
+ - title: 'Pull Request'
437
+ url: '${{ steps.exportToGit.output.pullRequestUrl }}'
438
+ ```
439
+
440
+ ## Development
441
+
442
+ Use the [stackgen-backstage](https://github.com/appcd-dev/stackgen-backstage) app to run Backstage with your local backend plugin build.
443
+
444
+ 1. **Lay out repositories** so the backend plugin and the Backstage app are siblings (adjust paths below if yours differ):
445
+
446
+ ```text
447
+ <workspace>/
448
+ stackgen-backstage/
449
+ backstage-plugin-stackgen-backend/
450
+ ```
451
+
452
+ 2. **Point the backend at your local plugin** in `stackgen-backstage/packages/backend/package.json`:
453
+
454
+ Set the dependency to a Yarn link relative to `packages/backend` (three levels up to the parent of `stackgen-backstage`, then into the backend plugin repo):
455
+
456
+ ```json
457
+ "@stackgenhq/backstage-plugin-stackgen-backend": "link:../../../backstage-plugin-stackgen-backend"
458
+ ```
459
+
460
+ If your clone lives elsewhere, change the `link:` path accordingly (it must resolve from `packages/backend` to the root of `backstage-plugin-stackgen-backend`).
461
+
462
+ 3. **Build the backend plugin**:
463
+
464
+ ```bash
465
+ yarn install
466
+ yarn tsc && yarn build
467
+ ```
468
+
469
+ 4. **Install and run stackgen-backstage** from the Backstage repo root:
470
+
471
+ ```bash
472
+ yarn install
473
+ ```
474
+
475
+ Configure `app-config.yaml` (or local overrides) with `stackGen.baseUrl`, `stackGen.apiToken`, and any optional keys you need (see [Installation](#installation)). The stackgen-backstage README also documents env vars such as `STACKGEN_API_TOKEN` if you wire config that way.
476
+
477
+ 5. **Start the app** (frontend + backend in parallel):
478
+
479
+ ```bash
480
+ yarn dev
481
+ ```
482
+
483
+ Alternatively, run workspaces separately: `yarn start-backend` and `yarn start` (app only), as described in the stackgen-backstage `package.json` scripts.
484
+
485
+ 6. **After backend plugin code changes**, rebuild the plugin (`yarn tsc && yarn build` in `backstage-plugin-stackgen-backend`), then install the dependencies again.
package/config.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ export interface Config {
2
+ /**
3
+ * @visibility backend
4
+ */
5
+ stackGen: {
6
+ /**
7
+ * @visibility secret
8
+ */
9
+ baseUrl: string;
10
+ /**
11
+ * @visibility secret
12
+ */
13
+ apiToken: string;
14
+ /**
15
+ * @visibility backend
16
+ */
17
+ allowedTeams: string;
18
+ /**
19
+ * Default StackGen org id when actions omit orgId (optional).
20
+ * @visibility backend
21
+ */
22
+ orgId?: string;
23
+ /**
24
+ * Base path for iac-gen HTTP API (default /iac-gen).
25
+ * @visibility backend
26
+ */
27
+ iacGenBasePath?: string;
28
+ /**
29
+ * Base path for stack-exporter HTTP API (default /exporter/api/v1).
30
+ * @visibility backend
31
+ */
32
+ exporterBasePath?: string;
33
+ /**
34
+ * Base path for stackgen-vault HTTP API (default /api/vault/v1).
35
+ * @visibility backend
36
+ */
37
+ vaultBasePath?: string;
38
+ };
39
+ }