Add changeset

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Add new package.json templates to `templatePackagePaths`

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/.yarn/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/examples/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/packages/backend/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/packages files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Copy over templates/default-app/plugins/* files

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Combine templates/default-app/app-config.yaml.hbs & packages/app-next/app-config.yaml

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Combine templates/default-app/packages/app/* & packages/app-next/*

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Align App.tsx with template's backend package

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Misc. changes that align templates/default-app & templates/default-app-next

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Add default-app-next's frontend packages to versions file and mocks

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Add `--experimental-frontend` CLI flag

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Fix default-app-next's frontend package name

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Fix spacing around `scripts` block

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Clarify changeset message

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Reduce to one default template with two frontend packages (1/2)

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Reduce to one default template with two frontend packages (2/2)

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

PR review: Trim down packages/app-next

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Bug: Normalize Windows paths while parsing them out

Signed-off-by: Lynette Lopez <lynettel@spotify.com>

Update packages/create-app/templates/default-app/packages/app-next/README.md

Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com>
Signed-off-by: Lynette Lopez <lynettelopez12@gmail.com>

Small follow up

Signed-off-by: Andre Wanlin <awanlin@spotify.com>

Fixed test

Signed-off-by: Andre Wanlin <awanlin@spotify.com>
This commit is contained in:
Lynette Lopez
2024-12-10 19:29:16 -05:00
committed by benjdlambert
parent 85b6f96f64
commit d7a3d04e82
30 changed files with 551 additions and 61 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Created a flag for scaffolding apps using the new frontend system.
+14
View File
@@ -19,6 +19,20 @@ yarn install
yarn backstage-create-app
```
## Testing Changes Locally
If you want to be able to test changes to `create-app` locally you can run the following command:
```sh
./path/to/your/backstage/fork/backstage/packages/create-app/bin/backstage-create-app
```
If your fork lives in `~/repos/forks` then it would be:
```sh
~/repos/forks/backstage/packages/create-app/bin/backstage-create-app
```
## Documentation
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
+1
View File
@@ -8,6 +8,7 @@
Usage: backstage-create-app [options]
Options:
--experimental-frontend
--path [directory]
--skip-install
--template-path [directory]
+32 -6
View File
@@ -57,7 +57,7 @@ describe('command entrypoint', () => {
jest.resetAllMocks();
});
test('should call expected tasks with no path option', async () => {
test('should call expected tasks with no `--path` option', async () => {
const cmd = {} as unknown as Command;
await createApp(cmd);
expect(checkAppExistsMock).toHaveBeenCalled();
@@ -74,11 +74,12 @@ describe('command entrypoint', () => {
expect(templatingMock.mock.lastCall?.[1]).toContain(
path.join(tmpdir(), 'MyApp'),
);
expect(templatingMock.mock.lastCall?.[3]).toEqual(['packages/app-next/']);
expect(moveAppMock).toHaveBeenCalled();
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with path option', async () => {
it('should call expected tasks with `--path` option', async () => {
const cmd = { path: 'myDirectory' } as unknown as Command;
await createApp(cmd);
expect(checkPathExistsMock).toHaveBeenCalled();
@@ -93,10 +94,33 @@ describe('command entrypoint', () => {
),
);
expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory');
expect(templatingMock.mock.lastCall?.[3]).toEqual(['packages/app-next/']);
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with relative --template-path option', async () => {
it('should call expected tasks when `--experimental-frontend` is supplied', async () => {
const cmd = { experimentalFrontend: true } as unknown as Command;
await createApp(cmd);
expect(checkAppExistsMock).toHaveBeenCalled();
expect(tryInitGitRepositoryMock).toHaveBeenCalled();
expect(templatingMock).toHaveBeenCalled();
expect(templatingMock.mock.lastCall?.[0]).toEqual(
findPaths(__dirname).resolveTarget(
'packages',
'create-app',
'templates',
'default-app',
),
);
expect(templatingMock.mock.lastCall?.[1]).toContain(
path.join(tmpdir(), 'MyApp'),
);
expect(templatingMock.mock.lastCall?.[3]).toEqual(['packages/app/']);
expect(moveAppMock).toHaveBeenCalled();
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with relative `--template-path` option', async () => {
const cmd = {
path: 'myDirectory',
templatePath: 'templateDirectory',
@@ -109,10 +133,11 @@ describe('command entrypoint', () => {
findPaths(__dirname).resolveTarget('templateDirectory'),
);
expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory');
expect(templatingMock.mock.lastCall?.[3]).toEqual([]);
expect(buildAppMock).toHaveBeenCalled();
});
it('should call expected tasks with absolute --template-path option', async () => {
it('should call expected tasks with absolute `--template-path` option', async () => {
const cmd = {
path: 'myDirectory',
templatePath: path.resolve('somewhere', 'templateDirectory'),
@@ -125,16 +150,17 @@ describe('command entrypoint', () => {
path.resolve('somewhere', 'templateDirectory'),
);
expect(templatingMock.mock.lastCall?.[1]).toEqual('myDirectory');
expect(templatingMock.mock.lastCall?.[3]).toEqual([]);
expect(buildAppMock).toHaveBeenCalled();
});
it('should not call `buildAppTask` when `skipInstall` is supplied', async () => {
it('should not call `buildAppTask()` when `--skip-install` is supplied', async () => {
const cmd = { skipInstall: true } as unknown as Command;
await createApp(cmd);
expect(buildAppMock).not.toHaveBeenCalled();
});
it('should not call `initGitRepository` when `gitConfig` is undefined', async () => {
it('should not call `initGitRepository()` when `gitConfig` is undefined', async () => {
const cmd = {} as unknown as Command;
readGitConfig.mockResolvedValue(undefined);
await createApp(cmd);
+25 -8
View File
@@ -65,10 +65,17 @@ export default async (opts: OptionValues): Promise<void> => {
},
]);
// Use `--template-path` argument as template when specified. Otherwise, use the default template.
const templateDir = opts.templatePath
? paths.resolveTarget(opts.templatePath)
: paths.resolveOwn('templates/default-app');
// If using the default template, filter out `packages/app` when the `--experimental-frontend` flag
// is used or `packages/app-next` when its not used.
const excludedDirs = !opts.templatePath
? [opts.experimentalFrontend ? 'packages/app/' : 'packages/app-next/']
: [];
// Use `--path` argument as application directory when specified, otherwise
// create a directory using `answers.name`
const appDir = opts.path
@@ -88,10 +95,15 @@ export default async (opts: OptionValues): Promise<void> => {
await checkPathExistsTask(appDir);
Task.section('Preparing files');
await templatingTask(templateDir, opts.path, {
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
});
await templatingTask(
templateDir,
opts.path,
{
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
},
excludedDirs,
);
} else {
// Template to temporary location, and then move files
@@ -102,10 +114,15 @@ export default async (opts: OptionValues): Promise<void> => {
const tempDir = await fs.mkdtemp(resolvePath(os.tmpdir(), answers.name));
Task.section('Preparing files');
await templatingTask(templateDir, tempDir, {
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
});
await templatingTask(
templateDir,
tempDir,
{
...answers,
defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH,
},
excludedDirs,
);
Task.section('Moving to final location');
await moveAppTask(tempDir, appDir, answers.name);
+4
View File
@@ -31,6 +31,10 @@ const main = (argv: string[]) => {
.name('backstage-create-app')
.version(version)
.description('Creates a new app in a new directory or specified path')
.option(
'--experimental-frontend',
'Use the default template with the experimental frontend',
)
.option(
'--path [directory]',
'Location to store the app defaulting to a new folder with the app name',
+47 -27
View File
@@ -49,59 +49,65 @@ jest.mock('child_process');
jest.mock('./versions', () => ({
packageVersions: {
root: '1.2.3',
'@backstage/cli': '1.0.0',
'@backstage/app-defaults': '1.0.0',
'@backstage/backend-defaults': '1.0.0',
'@backstage/backend-tasks': '1.0.0',
'@backstage/canon': '1.0.0',
'@backstage/catalog-model': '1.0.0',
'@backstage/catalog-client': '1.0.0',
'@backstage/catalog-model': '1.0.0',
'@backstage/cli': '1.0.0',
'@backstage/config': '1.0.0',
'@backstage/core-app-api': '1.0.0',
'@backstage/core-components': '1.0.0',
'@backstage/core-plugin-api': '1.0.0',
'@backstage/e2e-test-utils': '1.0.0',
'@backstage/frontend-defaults': '1.0.0',
'@backstage/frontend-plugin-api': '1.0.0',
'@backstage/integration-react': '1.0.0',
'@backstage/plugin-api-docs': '1.0.0',
'@backstage/plugin-app-backend': '1.0.0',
'@backstage/plugin-app-visualizer': '1.0.0',
'@backstage/plugin-auth-backend': '1.0.0',
'@backstage/plugin-auth-node': '1.0.0',
'@backstage/plugin-auth-backend-module-github-provider': '1.0.0',
'@backstage/plugin-auth-backend-module-guest-provider': '1.0.0',
'@backstage/plugin-auth-node': '1.0.0',
'@backstage/plugin-catalog': '1.0.0',
'@backstage/plugin-catalog-backend': '1.0.0',
'@backstage/plugin-catalog-backend-module-logs': '1.0.0',
'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': '1.0.0',
'@backstage/plugin-permission-common': '1.0.0',
'@backstage/plugin-permission-node': '1.0.0',
'@backstage/plugin-catalog-common': '1.0.0',
'@backstage/plugin-catalog-graph': '1.0.0',
'@backstage/plugin-catalog-import': '1.0.0',
'@backstage/plugin-catalog-react': '1.0.0',
'@backstage/plugin-home': '1.0.0',
'@backstage/plugin-kubernetes': '1.0.0',
'@backstage/plugin-kubernetes-backend': '1.0.0',
'@backstage/plugin-org': '1.0.0',
'@backstage/plugin-permission-backend': '1.0.0',
'@backstage/plugin-permission-backend-module-allow-all-policy': '1.0.0',
'@backstage/plugin-permission-common': '1.0.0',
'@backstage/plugin-permission-node': '1.0.0',
'@backstage/plugin-permission-react': '1.0.0',
'@backstage/plugin-proxy-backend': '1.0.0',
'@backstage/plugin-scaffolder': '1.0.0',
'@backstage/plugin-scaffolder-backend': '1.0.0',
'@backstage/plugin-scaffolder-backend-module-github': '1.0.0',
'@backstage/plugin-search': '1.0.0',
'@backstage/plugin-search-backend': '1.0.0',
'@backstage/plugin-search-backend-module-catalog': '1.0.0',
'@backstage/plugin-search-backend-module-pg': '1.0.0',
'@backstage/plugin-search-backend-module-techdocs': '1.0.0',
'@backstage/plugin-search-backend-node': '1.0.0',
'@backstage/plugin-techdocs-backend': '1.0.0',
'@backstage/app-defaults': '1.0.0',
'@backstage/core-app-api': '1.0.0',
'@backstage/core-components': '1.0.0',
'@backstage/core-plugin-api': '1.0.0',
'@backstage/e2e-test-utils': '1.0.0',
'@backstage/integration-react': '1.0.0',
'@backstage/plugin-api-docs': '1.0.0',
'@backstage/plugin-catalog': '1.0.0',
'@backstage/plugin-catalog-common': '1.0.0',
'@backstage/plugin-catalog-graph': '1.0.0',
'@backstage/plugin-catalog-import': '1.0.0',
'@backstage/plugin-catalog-react': '1.0.0',
'@backstage/plugin-kubernetes': '1.0.0',
'@backstage/plugin-kubernetes-backend': '1.0.0',
'@backstage/plugin-org': '1.0.0',
'@backstage/plugin-scaffolder': '1.0.0',
'@backstage/plugin-scaffolder-backend-module-github': '1.0.0',
'@backstage/plugin-permission-react': '1.0.0',
'@backstage/plugin-search': '1.0.0',
'@backstage/plugin-search-react': '1.0.0',
'@backstage/plugin-techdocs': '1.0.0',
'@backstage/plugin-techdocs-react': '1.0.0',
'@backstage/plugin-techdocs-backend': '1.0.0',
'@backstage/plugin-techdocs-module-addons-contrib': '1.0.0',
'@backstage/plugin-techdocs-react': '1.0.0',
'@backstage/plugin-user-settings': '1.0.0',
'@backstage/theme': '1.0.0',
'@backstage/test-utils': '1.0.0',
'@backstage/ui': '1.0.0',
'@backstage/theme': '1.0.0',
},
}));
@@ -267,7 +273,7 @@ describe('tasks', () => {
});
describe('templatingTask', () => {
it('should generate a project populating context parameters', async () => {
it('should generate a project and populate context parameters', async () => {
const templateDir = resolvePath(__dirname, '../../templates/default-app');
const destinationDir = 'templatedApp';
const context = {
@@ -291,6 +297,20 @@ describe('tasks', () => {
fs.readFile('templatedApp/packages/backend/package.json', 'utf-8'),
).resolves.toContain('sqlite3"');
});
it('should generate a project and skip excluded directories', async () => {
const templateDir = resolvePath(__dirname, '../../templates/default-app');
const destinationDir = 'templatedApp';
const context = { name: 'Backstage', dbTypeSqlite: true };
const excludedDirs = ['packages/app-next'];
await templatingTask(templateDir, destinationDir, context, excludedDirs);
expect(fs.existsSync('templatedApp/packages/app/package.json')).toBe(
true,
);
expect(fs.existsSync('templatedApp/packages/app-next/package.json')).toBe(
false,
);
});
});
describe('readGitConfig', () => {
+15 -4
View File
@@ -85,21 +85,32 @@ export class Task {
* @param templateDir - location containing template files
* @param destinationDir - location to save templated project
* @param context - template parameters
* @param excludedDirs - template files to exclude
*/
export async function templatingTask(
templateDir: string,
destinationDir: string,
context: any,
excludedDirs?: string[],
) {
const files = await recursive(templateDir).catch(error => {
throw new Error(`Failed to read template directory: ${error.message}`);
});
for (const file of files) {
const destinationFile = resolvePath(
destinationDir,
relativePath(templateDir, file),
);
const filePath = relativePath(templateDir, file);
if (
excludedDirs?.some(excludedDir => {
const normalizedFilePath = filePath.replace(/\\/g, '/');
const normalizedExcludedDir = excludedDir.replace(/\\/g, '/');
return normalizedFilePath.startsWith(normalizedExcludedDir);
})
) {
continue;
}
const destinationFile = resolvePath(destinationDir, filePath);
await fs.ensureDir(dirname(destinationFile));
if (file.endsWith('.hbs')) {
+21 -16
View File
@@ -33,6 +33,7 @@ import { version as root } from '../../../../package.json';
import { version as appDefaults } from '../../../app-defaults/package.json';
import { version as backendDefaults } from '../../../backend-defaults/package.json';
import { version as canon } from '../../../canon/package.json';
import { version as catalogClient } from '../../../catalog-client/package.json';
import { version as catalogModel } from '../../../catalog-model/package.json';
import { version as cli } from '../../../cli/package.json';
@@ -41,56 +42,58 @@ import { version as coreAppApi } from '../../../core-app-api/package.json';
import { version as coreComponents } from '../../../core-components/package.json';
import { version as corePluginApi } from '../../../core-plugin-api/package.json';
import { version as e2eTestUtils } from '../../../e2e-test-utils/package.json';
import { version as errors } from '../../../errors/package.json';
import { version as frontendDefaults } from '../../../frontend-defaults/package.json';
import { version as frontendPluginApi } from '../../../frontend-plugin-api/package.json';
import { version as integrationReact } from '../../../integration-react/package.json';
import { version as testUtils } from '../../../test-utils/package.json';
import { version as theme } from '../../../theme/package.json';
import { version as repoTools } from '../../../repo-tools/package.json';
import { version as ui } from '../../../ui/package.json';
import { version as pluginApiDocs } from '../../../../plugins/api-docs/package.json';
import { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json';
import { version as pluginAppVisualizer } from '../../../../plugins/app-visualizer/package.json';
import { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json';
import { version as pluginAuthBackendModuleGithubProvider } from '../../../../plugins/auth-backend-module-github-provider/package.json';
import { version as pluginAuthBackendModuleGuestProvider } from '../../../../plugins/auth-backend-module-guest-provider/package.json';
import { version as pluginAuthNode } from '../../../../plugins/auth-node/package.json';
import { version as pluginCatalog } from '../../../../plugins/catalog/package.json';
import { version as pluginCatalogCommon } from '../../../../plugins/catalog-common/package.json';
import { version as pluginCatalogReact } from '../../../../plugins/catalog-react/package.json';
import { version as pluginCatalogBackend } from '../../../../plugins/catalog-backend/package.json';
import { version as pluginCatalogBackendModuleLogs } from '../../../../plugins/catalog-backend-module-logs/package.json';
import { version as pluginCatalogBackendModuleScaffolderEntityModel } from '../../../../plugins/catalog-backend-module-scaffolder-entity-model/package.json';
import { version as pluginCatalogCommon } from '../../../../plugins/catalog-common/package.json';
import { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph/package.json';
import { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json';
import { version as pluginCatalogReact } from '../../../../plugins/catalog-react/package.json';
import { version as pluginHome } from '../../../../plugins/home/package.json';
import { version as pluginKubernetes } from '../../../../plugins/kubernetes/package.json';
import { version as pluginKubernetesBackend } from '../../../../plugins/kubernetes-backend/package.json';
import { version as pluginOrg } from '../../../../plugins/org/package.json';
import { version as pluginPermissionBackend } from '../../../../plugins/permission-backend/package.json';
import { version as pluginPermissionBackendModulePolicyAllowAll } from '../../../../plugins/permission-backend-module-policy-allow-all/package.json';
import { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json';
import { version as pluginPermissionReact } from '../../../../plugins/permission-react/package.json';
import { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json';
import { version as pluginPermissionReact } from '../../../../plugins/permission-react/package.json';
import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json';
import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json';
import { version as pluginScaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json';
import { version as pluginScaffolderBackendModuleGithub } from '../../../../plugins/scaffolder-backend-module-github/package.json';
import { version as pluginSearch } from '../../../../plugins/search/package.json';
import { version as pluginSearchReact } from '../../../../plugins/search-react/package.json';
import { version as pluginSearchBackend } from '../../../../plugins/search-backend/package.json';
import { version as pluginSearchBackendModuleCatalog } from '../../../../plugins/search-backend-module-catalog/package.json';
import { version as pluginSearchBackendModulePg } from '../../../../plugins/search-backend-module-pg/package.json';
import { version as pluginSearchBackendModuleTechdocs } from '../../../../plugins/search-backend-module-techdocs/package.json';
import { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json';
import { version as pluginSearchReact } from '../../../../plugins/search-react/package.json';
import { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json';
import { version as pluginTechdocsReact } from '../../../../plugins/techdocs-react/package.json';
import { version as pluginTechdocsModuleAddonsContrib } from '../../../../plugins/techdocs-module-addons-contrib/package.json';
import { version as pluginTechdocsBackend } from '../../../../plugins/techdocs-backend/package.json';
import { version as pluginTechdocsModuleAddonsContrib } from '../../../../plugins/techdocs-module-addons-contrib/package.json';
import { version as pluginTechdocsReact } from '../../../../plugins/techdocs-react/package.json';
import { version as pluginUserSettings } from '../../../../plugins/user-settings/package.json';
export const packageVersions = {
root,
'@backstage/app-defaults': appDefaults,
'@backstage/backend-defaults': backendDefaults,
'@backstage/canon': canon,
'@backstage/catalog-client': catalogClient,
'@backstage/catalog-model': catalogModel,
'@backstage/cli': cli,
@@ -99,11 +102,12 @@ export const packageVersions = {
'@backstage/core-components': coreComponents,
'@backstage/core-plugin-api': corePluginApi,
'@backstage/e2e-test-utils': e2eTestUtils,
'@backstage/errors': errors,
'@backstage/frontend-defaults': frontendDefaults,
'@backstage/frontend-plugin-api': frontendPluginApi,
'@backstage/integration-react': integrationReact,
'@backstage/repo-tools': repoTools,
'@backstage/plugin-api-docs': pluginApiDocs,
'@backstage/plugin-app-backend': pluginAppBackend,
'@backstage/plugin-app-visualizer': pluginAppVisualizer,
'@backstage/plugin-auth-backend': pluginAuthBackend,
'@backstage/plugin-auth-backend-module-github-provider':
pluginAuthBackendModuleGithubProvider,
@@ -111,15 +115,16 @@ export const packageVersions = {
pluginAuthBackendModuleGuestProvider,
'@backstage/plugin-auth-node': pluginAuthNode,
'@backstage/plugin-catalog': pluginCatalog,
'@backstage/plugin-catalog-common': pluginCatalogCommon,
'@backstage/plugin-catalog-react': pluginCatalogReact,
'@backstage/plugin-catalog-backend': pluginCatalogBackend,
'@backstage/plugin-catalog-backend-module-logs':
pluginCatalogBackendModuleLogs,
'@backstage/plugin-catalog-backend-module-scaffolder-entity-model':
pluginCatalogBackendModuleScaffolderEntityModel,
'@backstage/plugin-catalog-common': pluginCatalogCommon,
'@backstage/plugin-catalog-graph': pluginCatalogGraph,
'@backstage/plugin-catalog-import': pluginCatalogImport,
'@backstage/plugin-catalog-react': pluginCatalogReact,
'@backstage/plugin-home': pluginHome,
'@backstage/plugin-kubernetes': pluginKubernetes,
'@backstage/plugin-kubernetes-backend': pluginKubernetesBackend,
'@backstage/plugin-org': pluginOrg,
@@ -135,7 +140,6 @@ export const packageVersions = {
'@backstage/plugin-scaffolder-backend-module-github':
pluginScaffolderBackendModuleGithub,
'@backstage/plugin-search': pluginSearch,
'@backstage/plugin-search-react': pluginSearchReact,
'@backstage/plugin-search-backend': pluginSearchBackend,
'@backstage/plugin-search-backend-module-catalog':
pluginSearchBackendModuleCatalog,
@@ -143,11 +147,12 @@ export const packageVersions = {
'@backstage/plugin-search-backend-module-techdocs':
pluginSearchBackendModuleTechdocs,
'@backstage/plugin-search-backend-node': pluginSearchBackendNode,
'@backstage/plugin-search-react': pluginSearchReact,
'@backstage/plugin-techdocs': pluginTechdocs,
'@backstage/plugin-techdocs-react': pluginTechdocsReact,
'@backstage/plugin-techdocs-backend': pluginTechdocsBackend,
'@backstage/plugin-techdocs-module-addons-contrib':
pluginTechdocsModuleAddonsContrib,
'@backstage/plugin-techdocs-backend': pluginTechdocsBackend,
'@backstage/plugin-techdocs-react': pluginTechdocsReact,
'@backstage/plugin-user-settings': pluginUserSettings,
'@backstage/test-utils': testUtils,
'@backstage/theme': theme,
@@ -0,0 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
@@ -0,0 +1,9 @@
# example-app
This package is an example of a Backstage application using
the [New Frontend System](https://backstage.io/docs/frontend-system/).
To play with it, open a terminal and run the command: `yarn start`
**NOTE:** Don't forget to open a second terminal and to launch the backend there, using `yarn start`! The frontend
requires a backend to connect to.
@@ -0,0 +1,27 @@
/*
* Copyright 2020 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 { test, expect } from '@playwright/test';
test('App should render the welcome page', async ({ page }) => {
await page.goto('/');
const enterButton = page.getByRole('button', { name: 'Enter' });
await expect(enterButton).toBeVisible();
await enterButton.click();
await expect(page.getByText('My Company Catalog')).toBeVisible();
});
@@ -0,0 +1,64 @@
{
"name": "app",
"version": "0.0.0",
"private": true,
"bundled": true,
"backstage": {
"role": "frontend"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"test": "backstage-cli package test",
"lint": "backstage-cli package lint"
},
"dependencies": {
"@backstage/canon": "^{{version '@backstage/canon'}}",
"@backstage/cli": "^{{version '@backstage/cli'}}",
"@backstage/frontend-defaults": "^{{version '@backstage/frontend-defaults'}}",
"@backstage/frontend-plugin-api": "^{{version '@backstage/frontend-plugin-api'}}",
"@backstage/plugin-api-docs": "^{{version '@backstage/plugin-api-docs'}}",
"@backstage/plugin-app-visualizer": "^{{version '@backstage/plugin-app-visualizer'}}",
"@backstage/plugin-catalog": "^{{version '@backstage/plugin-catalog'}}",
"@backstage/plugin-catalog-graph": "^{{version '@backstage/plugin-catalog-graph'}}",
"@backstage/plugin-catalog-import": "^{{version '@backstage/plugin-catalog-import'}}",
"@backstage/plugin-home": "^{{version '@backstage/plugin-home'}}",
"@backstage/plugin-kubernetes": "^{{version '@backstage/plugin-kubernetes'}}",
"@backstage/plugin-org": "^{{version '@backstage/plugin-org'}}",
"@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}",
"@backstage/plugin-search": "^{{version '@backstage/plugin-search'}}",
"@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}",
"@backstage/plugin-techdocs-module-addons-contrib": "^{{version '@backstage/plugin-techdocs-module-addons-contrib'}}",
"@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}",
"@backstage/theme": "^{{version '@backstage/theme'}}",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0"
},
"devDependencies": {
"@backstage/test-utils": "^{{version '@backstage/test-utils'}}",
"@playwright/test": "^1.32.3",
"@testing-library/jest-dom": "^6.0.0",
"@types/react": "*",
"@types/react-dom": "*"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"files": [
"dist"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="favicon" viewBox="0 0 337.46 428.5"><title>Backstage favicon</title><style>#favicon path{fill:#121212}@media (prefers-color-scheme:dark){#favicon path{fill:#7df3e1}}</style><path d="M303,166.05a80.69,80.69,0,0,0,13.45-10.37c.79-.77,1.55-1.53,2.3-2.3a83.12,83.12,0,0,0,7.93-9.38A63.69,63.69,0,0,0,333,133.23a48.58,48.58,0,0,0,4.35-16.4c1.49-19.39-10-38.67-35.62-54.22L198.56,0,78.3,115.23,0,190.25l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.69,19.16-18.36,25.52-42.12,13.7-61.87a49.22,49.22,0,0,0-6.8-8.87A89.17,89.17,0,0,0,259,178.29h.15a85.08,85.08,0,0,0,31-5.79A80.88,80.88,0,0,0,303,166.05ZM202.45,225.86c-19.32,18.51-50.4,21.23-75.7,5.9L51.61,186.15l67.45-64.64,76.41,46.38C223,184.58,221.49,207.61,202.45,225.86Zm8.93-82.22-70.65-42.89L205.14,39,274.51,81.1c25.94,15.72,29.31,37,10.55,55A60.69,60.69,0,0,1,211.38,143.64Zm29.86,190c-19.57,18.75-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,282.52v24.67L108.6,373.1a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A87.27,87.27,0,0,1,241.24,333.68Zm0-39c-19.57,18.75-46.17,29.08-74.88,29.08a123.81,123.81,0,0,1-64.1-18.19L0,243.53v24.68l108.6,65.91a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.5v-1.78A87.27,87.27,0,0,1,241.24,294.7Zm0-39c-19.57,18.76-46.17,29.09-74.88,29.09a123.81,123.81,0,0,1-64.1-18.19L0,204.55v24.68l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.68,12.88-12.35,20-27.13,19.68-41.5v-1.82A86.09,86.09,0,0,1,241.24,255.71Zm83.7,25.74a94.15,94.15,0,0,1-60.2,25.86h0V334a81.6,81.6,0,0,0,51.74-22.37c14-13.38,21.14-28.11,21-42.64v-2.19A94.92,94.92,0,0,1,324.94,281.45Zm-83.7,91.21c-19.57,18.76-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,321.5v24.68l108.6,65.9a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.8,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A86.29,86.29,0,0,1,241.24,372.66ZM327,162.45c-.68.69-1.35,1.38-2.05,2.06a94.37,94.37,0,0,1-10.64,8.65,91.35,91.35,0,0,1-11.6,7,94.53,94.53,0,0,1-26.24,8.71,97.69,97.69,0,0,1-14.16,1.57c.5,1.61.9,3.25,1.25,4.9a53.27,53.27,0,0,1,1.14,12V217h.05a84.41,84.41,0,0,0,25.35-5.55,81,81,0,0,0,26.39-16.82c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,172.17a48.55,48.55,0,0,0,4.32-16.45c.09-1.23.2-2.47.19-3.7V150q-1.08,1.54-2.25,3.09A96.73,96.73,0,0,1,327,162.45Zm0,77.92c-.69.7-1.31,1.41-2,2.1a94.2,94.2,0,0,1-60.2,25.86h0l0,26.67h0a81.6,81.6,0,0,0,51.74-22.37A73.51,73.51,0,0,0,333,250.13a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.47.19-3.71v-2.19c-.74,1.07-1.46,2.15-2.27,3.21A95.68,95.68,0,0,1,327,240.37Zm0-39c-.69.7-1.31,1.41-2,2.1a93.18,93.18,0,0,1-10.63,8.65,91.63,91.63,0,0,1-11.63,7,95.47,95.47,0,0,1-37.94,10.18h0V256h0a81.65,81.65,0,0,0,51.74-22.37c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,211.15a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.48.19-3.71v-2.2c-.74,1.08-1.46,2.16-2.27,3.22A95.68,95.68,0,0,1,327,201.39Z"/></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Backstage is an open source framework for building developer portals"
/>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link
rel="manifest"
href="<%= publicPath %>/manifest.json"
crossorigin="use-credentials"
/>
<link rel="icon" href="<%= publicPath %>/favicon.ico" />
<link rel="shortcut icon" href="<%= publicPath %>/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="<%= publicPath %>/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="<%= publicPath %>/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="<%= publicPath %>/favicon-16x16.png"
/>
<link
rel="mask-icon"
href="<%= publicPath %>/safari-pinned-tab.svg"
color="#5bbad5"
/>
<title><%= config.getString('app.title') %></title>
<% if (config.has('app.datadogRum')) { %>
<script>
(function (h, o, u, n, d) {
h = h[d] = h[d] || {
q: [],
onReady: function (c) {
h.q.push(c);
},
};
d = o.createElement(u);
d.async = 1;
d.src = n;
n = o.getElementsByTagName(u)[0];
n.parentNode.insertBefore(d, n);
})(
window,
document,
'script',
'https://www.datadoghq-browser-agent.com/datadog-rum-v3.js',
'DD_RUM',
);
DD_RUM.onReady(function () {
DD_RUM.init({
clientToken: '<%= config.getString("app.datadogRum.clientToken") %>',
applicationId:
'<%= config.getString("app.datadogRum.applicationId") %>',
site: '<%= config.getOptionalString("app.datadogRum.site") || "datadoghq.com" %>',
service: 'backstage',
env: '<%= config.getString("app.datadogRum.env") %>',
sampleRate:
'<%= config.getOptionalNumber("app.datadogRum.sessionSampleRate") || 100 %>',
sessionReplaySampleRate:
'<%= config.getOptionalNumber("app.datadogRum.sessionReplaySampleRate") || 0 %>',
trackInteractions: true,
});
});
</script>
<% } %>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `yarn start`.
To create a production bundle, use `yarn build`.
-->
</body>
</html>
@@ -0,0 +1,15 @@
{
"short_name": "Backstage",
"name": "Backstage",
"icons": [
{
"src": "favicon.ico",
"sizes": "48x48",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
@@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="682.667" height="682.667" preserveAspectRatio="xMidYMid meet" version="1.0" viewBox="0 0 512 512"><metadata>Created by potrace 1.11, written by Peter Selinger 2001-2013</metadata><g fill="#000" stroke="none"><path d="M492 4610 c-4 -3 -8 -882 -7 -1953 l0 -1948 850 2 c898 1 945 3 1118 49 505 134 823 531 829 1037 2 136 -9 212 -44 323 -40 125 -89 218 -163 310 -35 43 -126 128 -169 157 -22 15 -43 30 -46 33 -12 13 -131 70 -188 91 l-64 22 60 28 c171 77 317 224 403 404 64 136 92 266 91 425 -5 424 -245 770 -642 923 -79 30 -105 39 -155 50 -11 3 -38 10 -60 15 -22 6 -60 13 -85 17 -25 3 -58 9 -75 12 -36 8 -1643 11 -1653 3z m1497 -743 c236 -68 352 -254 305 -486 -26 -124 -110 -224 -232 -277 -92 -40 -151 -46 -439 -49 l-283 -3 -1 27 c-1 36 -1 760 0 790 l1 23 298 -5 c226 -4 310 -9 351 -20z m-82 -1538 c98 -3 174 -19 247 -52 169 -78 257 -212 258 -395 0 -116 -36 -221 -100 -293 -64 -72 -192 -135 -314 -155 -23 -3 -181 -7 -350 -8 l-308 -2 -1 26 c-6 210 1 874 9 879 9 5 366 6 559 0z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/><path d="M4160 1789 c-275 -24 -499 -263 -503 -536 -1 -115 21 -212 66 -292 210 -369 697 -402 950 -65 77 103 110 199 111 329 0 50 -6 113 -13 140 -16 58 -62 155 -91 193 -33 43 -122 132 -132 132 -5 0 -26 11 -46 25 -85 56 -219 85 -342 74z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,51 @@
/*
* Copyright 2020 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 { renderWithEffects } from '@backstage/test-utils';
// Rarely, and only in windows CI, do these tests take slightly more than the
// default five seconds
jest.setTimeout(15_000);
describe('App', () => {
it('should render', async () => {
process.env = {
NODE_ENV: 'test',
APP_CONFIG: [
{
data: {
app: {
title: 'Test',
support: { url: 'http://localhost:7007/support' },
},
backend: { baseUrl: 'http://localhost:7007' },
lighthouse: {
baseUrl: 'http://localhost:3003',
},
techdocs: {
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
},
},
context: 'test',
},
] as any,
};
const { default: app } = await import('./App');
const rendered = await renderWithEffects(app);
expect(rendered.baseElement).toBeInTheDocument();
});
});
@@ -0,0 +1,67 @@
/*
* 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 { createApp } from '@backstage/frontend-defaults';
import apiDocsPlugin from '@backstage/plugin-api-docs/alpha';
import appVisualizerPlugin from '@backstage/plugin-app-visualizer';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
import catalogGraphPlugin from '@backstage/plugin-catalog-graph/alpha';
import catalogImportPlugin from '@backstage/plugin-catalog-import/alpha';
import homePlugin from '@backstage/plugin-home/alpha';
import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha';
import orgPlugin from '@backstage/plugin-org/alpha';
import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha';
import searchPlugin from '@backstage/plugin-search/alpha';
import techdocsPlugin from '@backstage/plugin-techdocs/alpha';
import userSettingsPlugin from '@backstage/plugin-user-settings/alpha';
import { createFrontendModule, PageBlueprint } from '@backstage/frontend-plugin-api';
import { Navigate } from 'react-router';
// This just defaults to `/catalog` instead of a blank page
const defaultPageExtension = PageBlueprint.make({
name: 'default',
params: {
defaultPath: '/',
loader: () => Promise.resolve(<Navigate to="catalog" />),
},
});
const app = createApp({
features: [
searchPlugin,
orgPlugin,
catalogPlugin,
apiDocsPlugin,
techdocsPlugin,
scaffolderPlugin,
homePlugin,
kubernetesPlugin,
catalogGraphPlugin,
catalogImportPlugin,
appVisualizerPlugin,
userSettingsPlugin,
createFrontendModule({
pluginId: 'app',
extensions: [
defaultPageExtension
],
}),
],
});
export default app.createRoot();
@@ -0,0 +1,22 @@
/*
* Copyright 2020 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 '@backstage/cli/asset-types';
import ReactDOM from 'react-dom/client';
import app from './App';
import '@backstage/canon/css/styles.css';
ReactDOM.createRoot(document.getElementById('root')!).render(app);
@@ -0,0 +1,21 @@
/*
* Copyright 2020 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.
*/
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
+1
View File
@@ -43,6 +43,7 @@ const templatePackagePaths = [
'packages/cli/templates/frontend-plugin/package.json.hbs',
'packages/create-app/templates/default-app/package.json.hbs',
'packages/create-app/templates/default-app/packages/app/package.json.hbs',
'packages/create-app/templates/default-app/packages/app-next/package.json.hbs',
'packages/create-app/templates/default-app/packages/backend/package.json.hbs',
];