Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/web-admin-config-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baseplate-dev/project-builder-lib': patch
---

Add schema migration for web admin configuration support. This migration converts existing admin apps to web apps with adminConfig enabled, and adds the adminConfig field to existing web apps. This enables backward compatibility when upgrading projects to the unified web admin interface.
2 changes: 2 additions & 0 deletions packages/project-builder-lib/src/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { migration012MigrateAuthConfig } from './migration-012-migrate-auth-conf
import { migration013MoveGeneralSettings } from './migration-013-move-general-settings.js';
import { migration014MigratePluginIds } from './migration-014-migrate-plugin-ids.js';
import { migration015NullParentRefs } from './migration-015-null-parent-refs.js';
import { migration016WebAdminConfig } from './migration-016-web-admin-config.js';

export const SCHEMA_MIGRATIONS: SchemaMigration[] = [
migration005PrimaryUniqueRefs,
Expand All @@ -25,6 +26,7 @@ export const SCHEMA_MIGRATIONS: SchemaMigration[] = [
migration013MoveGeneralSettings,
migration014MigratePluginIds,
migration015NullParentRefs,
migration016WebAdminConfig,
];

export function isMigrateableProjectDefinition(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createSchemaMigration } from './types.js';

interface OldConfig {
apps?: {
id: string;
type: string;
name: string;
[key: string]: unknown;
}[];
[key: string]: unknown;
}

interface NewConfig {
apps?: {
id: string;
type: string;
name: string;
adminApp?: {
enabled: boolean;
pathPrefix: string;
allowedRoles?: { id: string }[];
sections?: {
id: string;
name: string;
type: string;
[key: string]: unknown;
}[];
};
[key: string]: unknown;
}[];
[key: string]: unknown;
}

export const migration016WebAdminConfig = createSchemaMigration<
OldConfig,
NewConfig
>({
version: 16,
name: 'webAdminApp',
description:
'Convert admin apps to web apps with adminApp and add adminApp to existing web apps',
migrate: (config) => {
if (!config.apps) {
return config as NewConfig;
}

const apps = config.apps.map((app) => {
// Convert admin apps to web apps with adminConfig enabled
if (app.type === 'admin') {
const { sections, allowedRoles, ...restApp } = app;
return {
...restApp,
type: 'web',
adminApp: {
enabled: true,
pathPrefix: '/admin',
sections: sections ?? [],
allowedRoles: allowedRoles ?? undefined,
},
};
}

// Add adminApp to web apps that don't already have it
if (app.type === 'web' && !('adminApp' in app)) {
return {
...app,
adminApp: {
enabled: false,
pathPrefix: '/admin',
},
};
}

return app;
});

return {
...config,
apps,
} as NewConfig;
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { describe, expect, it } from 'vitest';

import { migration016WebAdminConfig } from './migration-016-web-admin-config.js';

describe('migration016WebAdminConfig', () => {
it('converts admin apps to web apps with adminApp enabled', () => {
const oldConfig = {
apps: [
{
id: 'admin-app-1',
type: 'admin',
name: 'My Admin App',
sections: [
{ id: 'section-1', name: 'Users', type: 'crud' },
{ id: 'section-2', name: 'Posts', type: 'crud' },
],
allowedRoles: [{ id: 'admin-role' }],
},
],
};

const result = migration016WebAdminConfig.migrate(oldConfig);

expect(result.apps?.[0]).toEqual({
id: 'admin-app-1',
type: 'web',
name: 'My Admin App',
adminApp: {
enabled: true,
pathPrefix: '/admin',
sections: [
{ id: 'section-1', name: 'Users', type: 'crud' },
{ id: 'section-2', name: 'Posts', type: 'crud' },
],
allowedRoles: [{ id: 'admin-role' }],
},
});
});

it('converts admin apps without sections or allowedRoles', () => {
const oldConfig = {
apps: [
{
id: 'admin-app-2',
type: 'admin',
name: 'Simple Admin App',
},
],
};

const result = migration016WebAdminConfig.migrate(oldConfig);

expect(result.apps?.[0]).toEqual({
id: 'admin-app-2',
type: 'web',
name: 'Simple Admin App',
adminApp: {
enabled: true,
pathPrefix: '/admin',
sections: [],
allowedRoles: undefined,
},
});
});

it('adds adminApp to web apps', () => {
const oldConfig = {
apps: [
{
id: 'web-app-1',
type: 'web',
name: 'My Web App',
title: 'My App',
description: 'A web application',
},
{
id: 'api-app-1',
type: 'api',
name: 'My API',
},
],
};

const result = migration016WebAdminConfig.migrate(oldConfig);

expect(result.apps?.[0]).toEqual({
id: 'web-app-1',
type: 'web',
name: 'My Web App',
title: 'My App',
description: 'A web application',
adminApp: {
enabled: false,
pathPrefix: '/admin',
},
});

// API app should be unchanged
expect(result.apps?.[1]).toEqual({
id: 'api-app-1',
type: 'api',
name: 'My API',
});
});

it('handles missing apps array gracefully', () => {
const configWithoutApps = {};
const result = migration016WebAdminConfig.migrate(configWithoutApps);
expect(result).toEqual({});
});

it('handles empty apps array', () => {
const configWithEmptyApps = { apps: [] };
const result = migration016WebAdminConfig.migrate(configWithEmptyApps);
expect(result).toEqual({ apps: [] });
});

it('preserves other properties', () => {
const configWithOtherProps = {
apps: [
{
id: 'web-app-1',
type: 'web',
name: 'My Web App',
},
],
models: [{ id: 'model-1', name: 'User' }],
features: [{ id: 'feature-1', name: 'Auth' }],
};

const result = migration016WebAdminConfig.migrate(configWithOtherProps);

expect((result as { models?: unknown }).models).toEqual([
{ id: 'model-1', name: 'User' },
]);
expect((result as { features?: unknown }).features).toEqual([
{ id: 'feature-1', name: 'Auth' },
]);
});

it('does not modify web apps that already have adminApp', () => {
const configWithExistingAdminApp = {
apps: [
{
id: 'web-app-1',
type: 'web',
name: 'My Web App',
adminApp: {
enabled: true,
pathPrefix: '/custom-admin',
sections: [],
},
},
],
};

const result = migration016WebAdminConfig.migrate(
configWithExistingAdminApp,
);

expect(result.apps?.[0].adminApp).toEqual({
enabled: true,
pathPrefix: '/custom-admin',
sections: [],
});
});

it('handles non-web apps correctly', () => {
const configWithNonWebApps = {
apps: [
{
id: 'api-app-1',
type: 'api',
name: 'My API',
},
{
id: 'cli-app-1',
type: 'cli',
name: 'My CLI',
},
],
};

const result = migration016WebAdminConfig.migrate(configWithNonWebApps);

// Non-web apps should remain unchanged
expect(result.apps?.[0]).toEqual({
id: 'api-app-1',
type: 'api',
name: 'My API',
});
expect(result.apps?.[1]).toEqual({
id: 'cli-app-1',
type: 'cli',
name: 'My CLI',
});
});
});
49 changes: 0 additions & 49 deletions packages/project-builder-lib/src/schema/apps/admin/app.ts

This file was deleted.

4 changes: 3 additions & 1 deletion packages/project-builder-lib/src/schema/apps/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './admin/index.js';
export * from './backend/index.js';
export * from './base.js';
export * from './types.js';
// Re-export admin types from web admin for backward compatibility
export * from './web/admin/index.js';

export * from './web/index.js';
Loading