Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4ef79ba
Strip RequireAuth component
kingston Jul 8, 2025
66d977f
Refactor generators around
kingston Jul 8, 2025
810320f
Upgrade React Hook Form to 7.60.0
kingston Jul 8, 2025
0257da8
Fix bug where we were re-using new app IDs
kingston Jul 8, 2025
7ec68a8
Fix template extractor defaults
kingston Jul 8, 2025
05e7ea8
Support saving without dirty and center field items
kingston Jul 8, 2025
8ca6f2d
Throw error if duplicate IDs detected
kingston Jul 8, 2025
f6c3a06
Set up placeholder auth plugin
kingston Jul 8, 2025
62ce229
Rename to placeholder auth module generator
kingston Jul 8, 2025
e75bc8e
Fix generator names
kingston Jul 8, 2025
58856da
Move placeholder generators to auth plugin
kingston Jul 8, 2025
4fef7f3
Add support for globs in onlyIfChanged command filter
kingston Jul 8, 2025
cf6971f
Auto register gql files in react apollo
kingston Jul 8, 2025
bc85105
Rename placeholder auth hooks to kebab case
kingston Jul 8, 2025
199441b
Fix up router context error
kingston Jul 8, 2025
c111398
Conform plugin-auth to new placeholder generators
kingston Jul 8, 2025
307c823
Remove unused placeholder auth hooks generator
kingston Jul 8, 2025
a49b4e9
Rename core auth module generator
kingston Jul 8, 2025
c8aaceb
Simplify auth module generator with renderers :)
kingston Jul 8, 2025
956bb79
Add support for default handlers for context parsing
kingston Jul 9, 2025
0d1d612
Fix typings for default handler
kingston Jul 9, 2025
ff25d6e
Fix up default handling
kingston Jul 9, 2025
15cf913
Fix types better
kingston Jul 9, 2025
593a754
Set up placeholder hooks
kingston Jul 9, 2025
092304d
Update model for baseplate
kingston Jul 9, 2025
9d2c9e9
Fix import
kingston Jul 9, 2025
2c11ecd
Handle unused variables in error formatter
kingston Jul 9, 2025
982b9f6
Fix generator parsing for multilevel names
kingston Jul 9, 2025
04874b5
Add full support on backend for auth module
kingston Jul 9, 2025
979341b
Rename react auth to react auth routes
kingston Jul 9, 2025
99da3f1
Incorporate react session changes
kingston Jul 9, 2025
39ec8d6
Add auth routes generator
kingston Jul 9, 2025
9f6ecfe
Add changeset
kingston Jul 9, 2025
71aa065
Fix typings for graphql compiler
kingston Jul 9, 2025
22f7014
Add barrel exports
kingston Jul 9, 2025
2f2a670
Fix knip
kingston Jul 9, 2025
b61bf54
Remove unused cn file
kingston Jul 9, 2025
cf73b74
Fix styles to prefix with auth correctly
kingston Jul 9, 2025
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/all-goats-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baseplate-dev/sync': patch
---

Add support for globs in onlyIfChanged command filter
5 changes: 5 additions & 0 deletions .changeset/brave-parts-see.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baseplate-dev/plugin-auth': patch
---

Support front and backend of new auth plugin
5 changes: 5 additions & 0 deletions .changeset/easy-donuts-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baseplate-dev/project-builder-web': patch
---

Fix bug in app creation that was re-using the same ID
7 changes: 7 additions & 0 deletions .changeset/plenty-actors-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@baseplate-dev/project-builder-web': patch
'@baseplate-dev/react-generators': patch
'@baseplate-dev/ui-components': patch
---

Upgrade react-hook-form to 7.60.0
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"watch:tsc:root": "tsc -b tsconfig.build.json --preserveWatchOutput -w"
},
"lint-staged": {
"*.(js|ts|tsx|jsx|json|md|mdx|css|scss|yaml|yml|toml|html|svg|yml|yaml|json)": "prettier --write"
"*.(js|ts|tsx|jsx|json|md|mdx|css|scss|yaml|yml|toml|html|yml|yaml|json)": "prettier --write"
},
"dependencies": {
"@changesets/cli": "2.28.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"importMapProviders": {},
"pathRootRelativePath": "{module-root}/services/password-hasher.service.ts",
"projectExports": { "createPasswordHash": {}, "verifyPasswordHash": {} },
"template": "password-hasher.service.ts",
"variables": {}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { AUTH_PASSWORD_HASHER_SERVICE_PATHS } from './template-paths.js';
import { AUTH_PASSWORD_HASHER_SERVICE_RENDERERS } from './template-renderers.js';
import { AUTH_PASSWORD_HASHER_SERVICE_IMPORTS } from './ts-import-providers.js';
import { AUTH_PASSWORD_HASHER_SERVICE_TEMPLATES } from './typed-templates.js';

export const AUTH_PASSWORD_HASHER_SERVICE_GENERATED = {
imports: AUTH_PASSWORD_HASHER_SERVICE_IMPORTS,
paths: AUTH_PASSWORD_HASHER_SERVICE_PATHS,
renderers: AUTH_PASSWORD_HASHER_SERVICE_RENDERERS,
templates: AUTH_PASSWORD_HASHER_SERVICE_TEMPLATES,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { RenderTsTemplateFileActionInput } from '@baseplate-dev/core-generators';
import type { BuilderAction } from '@baseplate-dev/sync';

import { typescriptFileProvider } from '@baseplate-dev/core-generators';
import { createGeneratorTask, createProviderType } from '@baseplate-dev/sync';

import { AUTH_PASSWORD_HASHER_SERVICE_PATHS } from './template-paths.js';
import { AUTH_PASSWORD_HASHER_SERVICE_TEMPLATES } from './typed-templates.js';

export interface AuthPasswordHasherServiceRenderers {
passwordHasherService: {
render: (
options: Omit<
RenderTsTemplateFileActionInput<
typeof AUTH_PASSWORD_HASHER_SERVICE_TEMPLATES.passwordHasherService
>,
'destination' | 'importMapProviders' | 'template'
>,
) => BuilderAction;
};
}

const authPasswordHasherServiceRenderers =
createProviderType<AuthPasswordHasherServiceRenderers>(
'auth-password-hasher-service-renderers',
);

const authPasswordHasherServiceRenderersTask = createGeneratorTask({
dependencies: {
paths: AUTH_PASSWORD_HASHER_SERVICE_PATHS.provider,
typescriptFile: typescriptFileProvider,
},
exports: {
authPasswordHasherServiceRenderers:
authPasswordHasherServiceRenderers.export(),
},
run({ paths, typescriptFile }) {
return {
providers: {
authPasswordHasherServiceRenderers: {
passwordHasherService: {
render: (options) =>
typescriptFile.renderTemplateFile({
template:
AUTH_PASSWORD_HASHER_SERVICE_TEMPLATES.passwordHasherService,
destination: paths.passwordHasherService,
...options,
}),
},
},
},
};
},
});

export const AUTH_PASSWORD_HASHER_SERVICE_RENDERERS = {
provider: authPasswordHasherServiceRenderers,
task: authPasswordHasherServiceRenderersTask,
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ import { BadRequestError } from './http-errors.js';
*/
export const handleZodRequestValidationError = (error: unknown): never => {
if (error instanceof ZodError) {
throw new BadRequestError('Validation failed', 'ZOD_VALIDATION_ERROR', {
errors: error.errors,
const formattedErrors = error.errors.map((err) => ({
path: err.path.join('.'),
message: err.message,
code: err.code,
}));

throw new BadRequestError('Validation failed', 'VALIDATION_ERROR', {
errors: formattedErrors,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const pothosPrismaFindQueryGenerator = createGenerator({
);`,
{
QUERY_EXPORT: `${lowerFirstModelName}Query`,
BUILDER: 'builder',
BUILDER: pothosTypesFile.getBuilderFragment(),
QUERY_NAME: quot(lowerFirstModelName),
OPTIONS: TsCodeUtils.mergeFragmentsAsObject(options, {
disableSort: true,
Expand Down
2 changes: 1 addition & 1 deletion packages/project-builder-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"immer": "10.1.1",
"inflection": "3.0.0",
"react": "catalog:",
"react-hook-form": "7.56.3",
"react-hook-form": "7.60.0",
"zod": "catalog:",
"zustand": "5.0.3"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import {
deserializeSchemaWithTransformedReferences,
fixRefDeletions,
serializeSchemaFromRefPayload,
serializeSchema,
} from '#src/references/index.js';
import { createProjectDefinitionSchema } from '#src/schema/index.js';

Expand Down Expand Up @@ -100,9 +100,15 @@ export class ProjectDefinitionContainer {
* @returns The serialized contents of the project definition
*/
toSerializedContents(): string {
return stringifyPrettyStable(
serializeSchemaFromRefPayload(this.refPayload),
const serializedContents = serializeSchema(
createProjectDefinitionSchema,
this.definition,
{
defaultMode: 'strip',
plugins: this.pluginStore,
},
);
return stringifyPrettyStable(serializedContents);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/project-builder-lib/src/plugins/metadata/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export const pluginMetadataSchema = z.object({
dependencies: z.array(pluginSpecDependencySchema).optional(),
})
.optional(),
/**
* Whether the plugin should be hidden in the project builder UI
*
* (It can be used once in the definition but cannot be added)
*/
hidden: z.boolean().optional(),
});

export type PluginMetadata = z.infer<typeof pluginMetadataSchema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ export function extractDefinitionRefs<T>(value: T): ZodRefPayload<T> {

const cleanData = extractDefinitionRefsRecursive(value, refContext, []);

// Simple sanity check to make sure we don't have duplicate IDs
const idSet = new Set<string>();
for (const entity of refContext.entitiesWithNameResolver) {
if (idSet.has(entity.id)) {
throw new Error(`Duplicate ID found: ${entity.id}`);
}
idSet.add(entity.id);
}

return {
data: cleanData as T,
references: refContext.references,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import type { ZodRefContext } from './extract-definition-refs.js';

import { createDefinitionEntityNameResolver } from './definition-ref-builder.js';
import { deserializeSchemaWithTransformedReferences } from './deserialize-schema.js';
import { extractDefinitionRefsRecursive } from './extract-definition-refs.js';
import {
extractDefinitionRefs,
extractDefinitionRefsRecursive,
} from './extract-definition-refs.js';
import {
DefinitionReferenceMarker,
REF_ANNOTATIONS_MARKER_SYMBOL,
Expand Down Expand Up @@ -860,5 +863,125 @@ describe('extract-definition-refs', () => {
});
});
});

describe('Duplicate ID Detection', () => {
it('should throw error when duplicate entity IDs are found', () => {
const testEntityType = new DefinitionEntityType('test', 'test');

const inputWithDuplicateIds = {
entity1: {
id: 'test:duplicate-id',
name: 'Entity One',
[REF_ANNOTATIONS_MARKER_SYMBOL]: {
entities: [
{
type: testEntityType,
getNameResolver: () => ({ resolveName: () => 'Entity One' }),
},
],
references: [],
contextPaths: [],
},
},
entity2: {
id: 'test:duplicate-id', // Same ID as entity1
name: 'Entity Two',
[REF_ANNOTATIONS_MARKER_SYMBOL]: {
entities: [
{
type: testEntityType,
getNameResolver: () => ({ resolveName: () => 'Entity Two' }),
},
],
references: [],
contextPaths: [],
},
},
};

expect(() => extractDefinitionRefs(inputWithDuplicateIds)).toThrow(
'Duplicate ID found: test:duplicate-id',
);
});
});
});

describe('Non-Recursive Function Tests (extractDefinitionRefs)', () => {
it('should process simple object with entity annotations', () => {
const testEntityType = new DefinitionEntityType('test', 'test');

const input = {
id: 'test:test-id',
name: 'Test Entity',
[REF_ANNOTATIONS_MARKER_SYMBOL]: {
entities: [
{
type: testEntityType,
getNameResolver: () => ({ resolveName: () => 'Test Entity' }),
},
],
references: [],
contextPaths: [],
},
};

const result = extractDefinitionRefs(input);

expect(result.data).toEqual({
id: 'test:test-id',
name: 'Test Entity',
});
expect(result.entitiesWithNameResolver).toHaveLength(1);
expect(result.entitiesWithNameResolver[0]).toMatchObject({
id: 'test:test-id',
type: testEntityType,
path: [],
idPath: ['id'],
});
expect(result.references).toHaveLength(0);
});

it('should process object with both entities and references', () => {
const testEntityType = new DefinitionEntityType('test', 'test');
const refEntityType = new DefinitionEntityType('ref', 'ref');

const input = {
entity: {
id: 'test:entity-id',
name: 'Test Entity',
[REF_ANNOTATIONS_MARKER_SYMBOL]: {
entities: [
{
type: testEntityType,
getNameResolver: () => ({ resolveName: () => 'Test Entity' }),
},
],
references: [],
contextPaths: [],
},
},
ref: new DefinitionReferenceMarker('ref:ref-id', {
type: refEntityType,
onDelete: 'RESTRICT',
}),
};

const result = extractDefinitionRefs(input);

expect(result.data).toEqual({
entity: {
id: 'test:entity-id',
name: 'Test Entity',
},
ref: 'ref:ref-id',
});
expect(result.entitiesWithNameResolver).toHaveLength(1);
expect(result.references).toHaveLength(1);
expect(result.references[0]).toMatchObject({
type: refEntityType,
path: ['ref'],
onDelete: 'RESTRICT',
});
});
});
});
Loading