plugin(amplication): add Amplication plugins to community plugins (#2919)

* merge branch 'backstage:main' into main
* feat: add Amplication plugins for Backstage catalog and scaffolder
* chore: Remove trailing whitespace in Amplication catalog-info.yaml
* feat: add amplication backend modules for Backstage catalog and scaffolder
* chore: add repository and backstage configuration to Amplication backend modules
* test: simplify AmplicationTemplatesProcessor test entities and assertions
* test: remove unused imports from AmplicationTemplatesProcessor test
* docs: update Amplication plugin documentation and refactor processor
* fix: Remove unnecessary error logging in Amplication template scaffolding

Signed-off-by: Itai Nathaniel <itainathaniel@gmail.com>
This commit is contained in:
Itai Nathaniel 2025-03-07 02:05:11 +02:00 committed by GitHub
parent 6465acfe5f
commit 9b6048a045
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 24561 additions and 0 deletions

View File

@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"privatePackages": {
"tag": false,
"version": false
}
}

View File

@ -0,0 +1,5 @@
---
'@backstage-community/plugin-catalog-backend-module-amplication': minor
---
A backend module plugin for Amplication (https://amplication.com) has been created

View File

@ -0,0 +1,5 @@
---
'@backstage-community/plugin-scaffolder-backend-module-amplication': minor
---
A scaffolder plugin for Amplication (https://amplication.com) has been created

View File

@ -0,0 +1,8 @@
.git
.yarn/cache
.yarn/install-state.gz
node_modules
packages/*/src
packages/*/node_modules
plugins
*.local.yaml

View File

@ -0,0 +1 @@
playwright.config.ts

View File

@ -0,0 +1 @@
module.exports = require('../../.eslintrc.cjs');

54
workspaces/amplication/.gitignore vendored Normal file
View File

@ -0,0 +1,54 @@
# macOS
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Coverage directory generated when running tests with coverage
coverage
# Dependencies
node_modules/
# Yarn 3 files
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Node version directives
.nvmrc
# dotenv environment variables file
.env
.env.test
# Build output
dist
dist-types
# Temporary change files created by Vim
*.swp
# MkDocs build output
site
# Local configuration files
*.local.yaml
# Sensitive credentials
*-credentials.yaml
# vscode database functionality support files
*.session.sql
# E2E test reports
e2e-test-report/

View File

@ -0,0 +1,5 @@
dist
dist-types
coverage
.vscode
.eslintrc.js

View File

@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@ -0,0 +1 @@
{ "version": "1.35.0" }

View File

@ -0,0 +1,13 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: amplication
description: An example of a Backstage application.
# Example for optional annotations
# annotations:
# github.com/project-slug: backstage/backstage
# backstage.io/techdocs-ref: dir:.
spec:
type: website
owner: john@example.com
lifecycle: experimental

View File

@ -0,0 +1,65 @@
{
"name": "@internal/amplication",
"version": "1.0.0",
"private": true,
"engines": {
"node": "18 || 20"
},
"scripts": {
"tsc": "tsc",
"tsc:full": "tsc --skipLibCheck false --incremental false",
"build:all": "backstage-cli repo build --all",
"build:api-reports": "yarn build:api-reports:only --tsc",
"build:api-reports:only": "backstage-repo-tools api-reports -o ae-wrong-input-file-type,ae-undocumented --validate-release-tags",
"clean": "backstage-cli repo clean",
"test": "backstage-cli repo test",
"test:all": "backstage-cli repo test --coverage",
"fix": "backstage-cli repo fix",
"lint": "backstage-cli repo lint --since origin/main",
"lint:all": "backstage-cli repo lint",
"prettier:check": "prettier --check .",
"new": "backstage-cli new --scope @backstage-community",
"postinstall": "cd ../../ && yarn install"
},
"workspaces": {
"packages": [
"packages/*",
"plugins/*"
]
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/community-plugins",
"directory": "workspaces/amplication"
},
"devDependencies": {
"@backstage/cli": "^0.29.5",
"@backstage/e2e-test-utils": "^0.1.1",
"@backstage/repo-tools": "^0.12.1",
"@changesets/cli": "^2.27.1",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"knip": "^5.27.4",
"msw": "^2.7.0",
"node-gyp": "^10.0.0",
"prettier": "^2.3.2",
"typescript": "~5.3.0"
},
"resolutions": {
"@types/react": "^18",
"@types/react-dom": "^18"
},
"prettier": "@backstage/cli/config/prettier",
"lint-staged": {
"*.{js,jsx,ts,tsx,mjs,cjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md}": [
"prettier --write"
]
},
"dependencies": {
"@material-ui/core": "^4.12.4"
}
}

View File

@ -0,0 +1,9 @@
# The Plugins Folder
This is where your own plugins and their associated modules live, each in a
separate folder of its own.
If you want to create a new plugin here, go to your project root directory, run
the command `yarn new`, and follow the on-screen instructions.
You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!

View File

@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

View File

@ -0,0 +1,91 @@
# Amplication backend plugin for Backstage
The Amplication backend plugin integrates Amplication templates into Backstage.
## Capabilities
The Amplication backend plugin has the following capabilities:
- Synchronization of Amplication templates in a workspace
## Installation
Run the following command to install the catalog backend module in your Backstage project
```bash
yarn workspace backend add @backstage-community/plugin-catalog-backend-module-amplication
```
## Configuration
1. Add the following configuration to the app-config.yaml file, and customize the schedule to fit your needs:
```yaml title="app-config.yaml"
catalog:
locations:
- type: amplication
target: https://server.amplication.com/graphql
```
2. Register the plugin in the `packages/backend/src/index.ts` file:
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
/* highlight-add-next-line */
backend.add(
import('@backstage-community/plugin-catalog-backend-module-amplication'),
);
backend.start();
```
# Amplication Processor plugin
Welcome to the Amplication Processor plugin!
This plugin is designed to process amplication templates from the Amplication API and add them to the Backstage catalog, alongside with three other entities:
- Kind: user:amplication-bot
- Kind: System
- Kind: API
## Prerequisites
- Amplication Token
## Usage
Install the plugin by running the following command **from your Backstage root directory**
`yarn --cwd packages/app add @backstage-community/plugin-catalog-backend-module-amplication`
Add the collator to your backend instance, along with the search plugin itself:
```tsx
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(
import('@backstage-community/plugin-catalog-backend-module-amplication'),
);
backend.start();
```
## Configuration
Configure your `app-config.yaml` with Amplication's configuration
```yaml title="app-config.yaml"
amplication:
appUrl: https://app.amplication.com
apiUrl: https://server.amplication.com/graphql
token: ${AMPLICATION_TOKEN}
```
## Troubleshooting
If you encounter any issues, please refer to the [Amplication documentation](https://docs.amplication.com) for more information.
If the issue persists, please contact the Amplication support team.

View File

@ -0,0 +1,46 @@
{
"name": "@backstage-community/plugin-catalog-backend-module-amplication",
"description": "The amplication backend module for the catalog plugin.",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/community-plugins",
"directory": "workspaces/amplication/plugins/catalog-backend-module-amplication"
},
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
"pluginPackage": "@backstage/plugin-catalog-backend"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/backend-plugin-api": "^1.1.1",
"@backstage/catalog-model": "^1.7.3",
"@backstage/plugin-catalog-common": "^1.1.3",
"@backstage/plugin-catalog-node": "^1.5.1"
},
"devDependencies": {
"@backstage/backend-test-utils": "^1.2.1",
"@backstage/cli": "^0.29.5",
"msw": "^2.7.0"
},
"files": [
"dist"
]
}

View File

@ -0,0 +1,13 @@
## API Report File for "@backstage-community/plugin-catalog-backend-module-amplication"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
// @public
const catalogModuleAmplicationGraphqlProcessor: BackendFeature;
export default catalogModuleAmplicationGraphqlProcessor;
// (No @packageDocumentation comment for this package)
```

View File

@ -0,0 +1,17 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { catalogModuleAmplicationGraphqlProcessor as default } from './module';

View File

@ -0,0 +1,43 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { AmplicationTemplatesProcessor } from './processor/AmplicationTemplatesProcessor';
/**
* The processor module for the amplication plugin.
*
* @public
*/
export const catalogModuleAmplicationGraphqlProcessor = createBackendModule({
pluginId: 'catalog',
moduleId: 'amplication-graphql-processor',
register(reg) {
reg.registerInit({
deps: {
config: coreServices.rootConfig,
logger: coreServices.logger,
catalog: catalogProcessingExtensionPoint,
},
async init({ logger, catalog, config }) {
catalog.addProcessor(new AmplicationTemplatesProcessor(logger, config));
},
});
},
});

View File

@ -0,0 +1,163 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AmplicationTemplatesProcessor } from './AmplicationTemplatesProcessor';
import {
LoggerService,
RootConfigService,
} from '@backstage/backend-plugin-api';
import { mockServices } from '@backstage/backend-test-utils';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import { CatalogProcessorCache } from '@backstage/plugin-catalog-node/index';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
describe('AmplicationTemplatesProcessor Name', () => {
it('runs successfully', async () => {
const processor = buildProcessor();
expect(processor.getProcessorName()).toBe('AmplicationTemplatesProcessor');
});
});
describe('Calling the API', () => {
const handlers = [
http.post('http://example.com/api/templates', () => {
return HttpResponse.json({
data: {
catalog: {
totalCount: 2,
data: [
{
id: 'cm6gs6t6v000fjx5te4xlrcfx',
name: 'Resource Itai-template',
description: 'Template created from an existing resource',
resourceType: 'ServiceTemplate',
project: {
id: 'cm6gb3j0a000q14gzlq9m7h1o',
name: 'Sample Project',
},
blueprint: {
id: 'cm6grdfol000bjx5t7gixjk0q',
name: "Itai's Blueprint",
},
},
{
id: 'cm6sbna88000xutjte7zu96ee',
name: 'pick-me-template',
description: 'Template created from an existing resource',
resourceType: 'ServiceTemplate',
project: {
id: 'cm6hv1ugf005hjx5tr087j8d3',
name: 'ma-yesh-beze',
},
blueprint: {
id: 'cm6gb3j00000p14gz2n11otq4',
name: "Node.js Service (Amplication's Standard)",
},
},
],
},
},
});
}),
];
const server = setupServer(...handlers);
server.listen();
it('returns false if location is not a graphql', async () => {
const processor = buildProcessor();
const location: LocationSpec = {
type: 'url',
target: 'http://example.com/api/templates',
};
const emit = jest.fn();
const parser = jest.fn();
const cache: CatalogProcessorCache = {
get: jest.fn(),
set: jest.fn(),
};
const result = await processor.readLocation(
location,
true,
emit,
parser,
cache,
);
expect(result).toBe(false);
});
it('returns true if location is a graphql', async () => {
const processor = buildProcessor();
const location: LocationSpec = {
type: 'amplication',
target: 'http://example.com/api/templates',
};
const emit = jest.fn();
const parser = jest.fn();
const cache: CatalogProcessorCache = {
get: jest.fn(),
set: jest.fn(),
};
const result = await processor.readLocation(
location,
true,
emit,
parser,
cache,
);
expect(result).toBe(true);
});
it('emit() is called with the correct arguments', async () => {
const processor = buildProcessor();
const location: LocationSpec = {
type: 'amplication',
target: 'http://example.com/api/templates',
};
const emit = jest.fn();
const parser = jest.fn();
const cache: CatalogProcessorCache = {
get: jest.fn(),
set: jest.fn(),
};
const result = await processor.readLocation(
location,
true,
emit,
parser,
cache,
);
expect(result).toBe(true);
expect(cache.get).toHaveBeenCalled();
expect(emit).toHaveBeenCalled();
expect(emit).toHaveBeenCalledTimes(1);
});
});
function buildProcessor() {
const logger: LoggerService = mockServices.logger.mock();
const config: RootConfigService = mockServices.rootConfig.mock();
return new AmplicationTemplatesProcessor(logger, config);
}

View File

@ -0,0 +1,188 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity, EntityMeta } from '@backstage/catalog-model';
import {
processingResult,
CatalogProcessor,
CatalogProcessorEmit,
CatalogProcessorCache,
CatalogProcessorParser,
} from '@backstage/plugin-catalog-node';
import {
LoggerService,
RootConfigService,
} from '@backstage/backend-plugin-api';
import { LocationSpec } from '@backstage/plugin-catalog-common';
const CACHE_KEY = 'v1';
type CacheItem = {
etag: string;
json: any;
};
export class AmplicationTemplatesProcessor implements CatalogProcessor {
constructor(
private readonly logger: LoggerService,
private readonly config: RootConfigService,
) {
this.logger.info(`${this.getProcessorName()} is loading...`);
}
getProcessorName(): string {
return 'AmplicationTemplatesProcessor';
}
async readLocation(
location: LocationSpec,
_optional: boolean,
emit: CatalogProcessorEmit,
_parser: CatalogProcessorParser,
cache: CatalogProcessorCache,
): Promise<boolean> {
if (location.type !== 'amplication') {
return false;
}
const cacheItem = await cache.get<CacheItem>(CACHE_KEY);
try {
const response = await this.getCatalog(location);
const etag = response.headers.get('etag') || 'NO ETAG';
if (etag !== cacheItem?.etag) {
const json = await response.json();
json.data.catalog.data.forEach((item: any) => {
this.emitComponent(
item,
emit,
location,
json.data.currentWorkspace.id,
);
});
await cache.set<CacheItem>(CACHE_KEY, {
etag: etag,
json: json,
});
}
} catch (error) {
const message = `Unable to read ${location.type}, ${error}`;
emit(processingResult.generalError(location, message));
}
return true;
}
private async getCatalog(location: LocationSpec) {
const token = this.config.getString('amplication.token');
return await fetch(location.target, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
variables: {
take: 100,
skip: 0,
where: { resourceType: { in: ['ServiceTemplate'] } },
},
query: `query searchCatalog($where: ResourceWhereInputWithPropertiesFilter, $take: Int, $skip: Int) {
currentWorkspace {
id
}
catalog(where: $where, take: $take, skip: $skip) {
totalCount
data {
id
name
description
resourceType
project {
id
name
}
blueprint {
id
name
}
serviceTemplate {
id
name
projectId
}
gitRepository {
name
gitOrganization {
name
provider
}
}
}
__typename
}
}`,
}),
});
}
private getSanitizedName(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9-_]/g, '-') // Replace any non-alphanumeric characters with a hyphen
.replace(/-+/g, '-') // Replace multiple consecutive hyphens with a single hyphen
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
.slice(0, 63); // Truncate to 63 characters
}
private emitComponent(
item: any,
emit: CatalogProcessorEmit,
location: LocationSpec,
workspaceId: string,
) {
const metadata: EntityMeta = {
name: this.getSanitizedName(item.name),
title: item.name,
description: item.description,
tags: ['amplication', item.resourceType.toLowerCase()],
};
const spec = {
id: item.id,
type: item.resourceType,
project: item.project.name,
project_id: item.project.id,
blueprint: item.blueprint?.name,
blueprint_id: item.blueprint?.id,
lifecycle: 'production',
providesApis: ['amplication-api'],
system: 'amplication',
owner: 'user:amplication-bot',
workspace: workspaceId,
} as any;
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: metadata,
spec: spec,
};
emit(processingResult.entity(location, entity));
}
}

View File

@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

View File

@ -0,0 +1,92 @@
# Amplication actions for Backstage
This module provides [Backstage](https://backstage.io/) template [actions](https://backstage.io/docs/features/software-templates/builtin-actions) for [Amplication](https://amplication.com/).
The following actions are currently supported in this module:
- Scaffold a new service
## Setup
You need to setup the [Amplication catalog backend plugin](../catalog-backend-module-amplication/README.md) before you move forward with any of the following steps if you haven't already.
## Installation
Run the following command to install the action package in your Backstage project
```bash
yarn workspace backend add @backstage-community/plugin-scaffolder-backend-module-amplication
```
### Installing the action on the backend
Add the following to your `packages/backend/src/index.ts` file:
```ts title="packages/backend/src/index.ts"
const backend = createBackend();
// Add the following line
backend.add(
import('@backstage-community/plugin-scaffolder-backend-module-amplication'),
);
backend.start();
```
## Configuration
Configure your `app-config.yaml` with Amplication's configuration
```yaml title="app-config.yaml"
amplication:
appUrl: https://app.amplication.com
apiUrl: https://server.amplication.com/graphql
token: ${AMPLICATION_TOKEN}
```
Add the Amplication actions to your templates, see the [example](./src/template/template.yaml) file in this repository for complete usage examples
```yaml
id: scaffoldService
name: Scaffold a New Service
action: amplication:scaffold-service
input:
name: fooService
description: fooService description
project_id: 1a2b3c4d5e6f7g8h9i0j
serviceTemplate_id: 9i8h7g6f5e4d3c2b1a0
workspace_id: f1e2d3c4b5a6978685d4
```
## Usage
### Action: amplication:scaffold-service
#### Input
| Parameter Name | Type | Required | Description | Example |
| ------------------ | :----: | :------: | ------------------- | -------------------- |
| name | string | Yes | Service name | foo |
| description | string | No | Service description | foo description |
| project_id | string | Yes | Project ID | 1a2b3c4d5e6f7g8h9i0j |
| serviceTemplate_id | string | Yes | Service Template ID | 9i8h7g6f5e4d3c2b1a0 |
| workspace_id | string | Yes | Workspace ID | f1e2d3c4b5a6978685d4 |
#### Output
This action doesn't have any outputs.
## Discover new services
If you're creating a new service with the right plugin installed, it will be created with a `catalog-info.yaml` file in the root of the service. With the following configuration, you could auto-discover the service and add it to your catalog:
```yaml
catalog:
providers:
github:
providerId:
organization: <YOUR_GITHUB_ORGANIZATION>
catalogPath: '**/catalog-info.yaml'
filters:
repository: '.*'
```

View File

@ -0,0 +1,44 @@
{
"name": "@backstage-community/plugin-scaffolder-backend-module-amplication",
"description": "The amplication module for @backstage/plugin-scaffolder-backend",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/community-plugins",
"directory": "workspaces/amplication/plugins/scaffolder-backend-module-amplication"
},
"backstage": {
"role": "backend-plugin-module",
"pluginId": "scaffolder",
"pluginPackage": "@backstage/plugin-scaffolder-backend"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/backend-plugin-api": "^1.1.1",
"@backstage/config": "^1.3.2",
"@backstage/plugin-scaffolder-node": "^0.6.3"
},
"devDependencies": {
"@backstage/cli": "^0.29.5",
"@backstage/plugin-scaffolder-node-test-utils": "^0.1.18"
},
"files": [
"dist"
]
}

View File

@ -0,0 +1,13 @@
## API Report File for "@backstage-community/plugin-scaffolder-backend-module-amplication"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
// @public (undocumented)
const amplicationModule: BackendFeature;
export default amplicationModule;
// (No @packageDocumentation comment for this package)
```

View File

@ -0,0 +1,123 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Config } from '@backstage/config';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
/**
* Creates an action that creates an application in MTA.
*/
/** @public */
export function scaffoldResourceFromAmplicationTemplate({
config,
}: {
config: Config;
}) {
return createTemplateAction<{
name: string;
description: string;
project_id: string;
serviceTemplate_id: string;
workspace_id: string;
}>({
id: 'amplication:scaffold-service',
description: 'Scaffold a new service from a template (using Amplication)',
schema: {
input: {
type: 'object',
required: ['name', 'project_id', 'serviceTemplate_id'],
properties: {
name: {
title: 'Service Name',
description: 'The name of the service',
type: 'string',
},
description: {
title: 'Service Description',
description: 'The description of the service',
type: 'string',
},
project_id: {
title: 'Project ID',
description: 'The ID of the project',
type: 'string',
},
serviceTemplate_id: {
title: 'Service Template ID',
description: 'The ID of the service template',
type: 'string',
},
},
},
},
async handler(ctx) {
ctx.logger.info(
`Running amplication's service scaffolder for ${ctx.input.name}`,
);
const appUrl = config.getString('amplication.appUrl');
const apiUrl = config.getString('amplication.apiUrl');
const token = config.getString('amplication.token');
const httpOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
operationName: 'createServiceFromTemplate',
variables: {
data: {
name: ctx.input.name,
description: ctx.input.description || '',
project: {
connect: {
id: ctx.input.project_id,
},
},
serviceTemplate: {
id: ctx.input.serviceTemplate_id,
},
buildAfterCreation: true,
},
},
query:
'mutation createServiceFromTemplate($data: ResourceFromTemplateCreateInput!) {\n createResourceFromTemplate(data: $data) {\n id\n name\n description\n __typename\n }\n}',
}),
};
try {
const response = await fetch(apiUrl, httpOptions);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const body = await response.json();
if (body.errors) {
throw new Error(`Error! ${body.errors[0].message}`);
}
ctx.logger.info('Service created successfully');
ctx.output(
'url',
`${appUrl}/${ctx.input.workspace_id}/${ctx.input.project_id}/${body.data.createResourceFromTemplate.id}`,
);
} catch (e) {
throw new Error(`There was an issue with the request: ${e}`);
}
},
});
}

View File

@ -0,0 +1,17 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { amplicationModule as default } from './module';

View File

@ -0,0 +1,42 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
import { scaffoldResourceFromAmplicationTemplate } from './actions/amplication';
/*
* A backend module that integrates with the scaffolder to provide Amplication application creation.
*/
/** @public */
export const amplicationModule = createBackendModule({
pluginId: 'scaffolder',
moduleId: 'amplication',
register({ registerInit }) {
registerInit({
deps: {
scaffolder: scaffolderActionsExtensionPoint,
config: coreServices.rootConfig,
},
async init({ scaffolder, config }) {
scaffolder.addActions(
scaffoldResourceFromAmplicationTemplate({ config }),
);
},
});
},
});

View File

@ -0,0 +1,55 @@
apiVersion: scaffolder.backstage.io/v1beta3
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
kind: Template
metadata:
name: scaffold-new-service-amplication
title: Scaffold a New Service
description: Scaffold a new service from a template (using Amplication)
tags:
- amplication
spec:
type: service
parameters:
- title: Service Information
required:
- name
- template
properties:
name:
title: Name
type: string
description: The name of the service
ui:autofocus: true
description:
title: Description
type: string
description: The description of the service
template:
title: Template
type: string
description: The template to use
ui:field: EntityPicker
ui:options:
allowedKinds: []
catalogFilter:
- kind: component
spec.type: ServiceTemplate
steps:
- id: fetch
name: Fetch catalog entity
action: catalog:fetch
input:
entityRef: ${{ parameters.template }}
- id: scaffoldService
name: Scaffold a New Service
action: amplication:scaffold-service
input:
name: ${{ parameters.name }}
description: ${{ parameters.description }}
project_id: ${{ steps.fetch.output.entity.spec.project_id }}
serviceTemplate_id: ${{ steps.fetch.output.entity.spec.id }}
workspace_id: ${{ steps.fetch.output.entity.spec.workspace }}
output:
links:
- title: Service on Amplication
url: ${{ steps.scaffoldService.output.url }}

View File

@ -0,0 +1,18 @@
{
"extends": "@backstage/cli/config/tsconfig.json",
"include": [
"packages/*/src",
"plugins/*/src",
"plugins/*/dev",
"plugins/*/migrations"
],
"files": ["node_modules/@backstage/cli/asset-types/asset-types.d.ts"],
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "dist-types",
"rootDir": ".",
"lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2022"],
"target": "ES2022",
"useUnknownInCatchVariables": false
}
}

File diff suppressed because it is too large Load Diff