Skip to content

feat: 🎸 edit project configuration #87

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: feature/workspace-settings
Choose a base branch
from
Open
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
13 changes: 13 additions & 0 deletions apps/cli-daemon/src/app/workspace/dto/schema.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { JSONSchema7 } from 'json-schema';

export class SchemaDto implements JSONSchema7 {
properties: JSONSchema7['properties'];
title: JSONSchema7['title'];
description: JSONSchema7['title'];

constructor({ title, properties, description }: JSONSchema7) {
this.properties = properties;
this.title = title;
this.description = description;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface ExtractValuePaths {
path: string;
overridePath?: string;
}
export const configurationsPaths: ExtractValuePaths[] = [
{ path: 'definitions.project.properties.prefix', overridePath: 'prefix' },
{ path: 'definitions.project.properties.root', overridePath: 'root' },
{
path: 'definitions.project.properties.sourceRoot',
overridePath: 'sourceRoot',
},
];
6 changes: 6 additions & 0 deletions apps/cli-daemon/src/app/workspace/workspace.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Patch,
Post,
} from '@nestjs/common';
import { JSONSchema7 } from 'json-schema';

import { ExecResult } from '../generators/dto';
import { GeneratorsService } from '../generators/generators.service';
Expand Down Expand Up @@ -60,6 +61,11 @@ export class WorkspaceController {
return this.workspaceService.readWorkspaceProjectNames();
}

@Get('workspace-configuration')
async getWorkspaceConfiguration(): Promise<JSONSchema7> {
return this.workspaceService.getWorkspaceConfiguration();
}

@Get('project/:projectName')
async getProject(
@Param('projectName') projectName: string
Expand Down
18 changes: 18 additions & 0 deletions apps/cli-daemon/src/app/workspace/workspace.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ import {
Logger,
NotFoundException,
} from '@nestjs/common';
import { JSONSchema7 } from 'json-schema';
import { Draft07 } from 'json-schema-library';
import * as angularSchema from 'node_modules/@angular/cli/lib/config/schema.json';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this import is from 'node_modules' and not from @angular/cli like the others?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be read in runtime. Currently you're bundling & reading our installed angular pkg, instead you need to read the installed angular pkg in the currently active user's workspace by using node's fs module (readFile, readFileSync).

After this change you can revert the changes you made in the tsconfig file


import { SessionService } from '../session/session.service';

import { ProjectDto, UpdateProjectDto } from './dto';
import { SchemaDto } from './dto/schema.dto';
import {
ANGULAR_WORKSPACE_NOT_FOUND_EXCEPTION,
BAD_PATH_EXCEPTION,
Expand Down Expand Up @@ -157,4 +161,18 @@ export class WorkspaceService {
return new InternalServerErrorException(err);
}
}

getWorkspaceConfiguration(): Partial<JSONSchema7> {
const jsonSchema = new Draft07(angularSchema);

const { root, prefix, sourceRoot } =
jsonSchema.getSchema()['definitions']['project']['properties'];
const properties = new Draft07({ root, prefix, sourceRoot }).getSchema();

return new SchemaDto({
title: 'Angular CLI Workspace Configuration',
description: 'Browser target options',
properties: properties,
});
}
}
3 changes: 2 additions & 1 deletion apps/cli-daemon/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it needed? I didnt see that you import json anywhere, if its for jest, move it to tsconfig.spec.json

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im importing angular.json file.

}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { WorkspaceSettingsService } from './workspace-settings.service';

@Component({
selector: 'cli-workspace-settings',
standalone: true,
Expand All @@ -11,10 +9,4 @@ import { WorkspaceSettingsService } from './workspace-settings.service';
styleUrls: ['./workspace-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WorkspaceSettingsComponent {
angularJson$ = this.workspaceSettingsService.readWorkspaceProjectNames();

constructor(
private readonly workspaceSettingsService: WorkspaceSettingsService
) {}
}
export class WorkspaceSettingsComponent {}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
<p>configuration works!</p>
<h1>Edit project configuration</h1>

<ng-container *ngIf="formly$ | async as formly">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<formly-form
[model]="formly.formData"
[fields]="formly.formFields"
[form]="form"
></formly-form>
<button mat-button type="submit">Submit</button>
</form>
</ng-container>
Original file line number Diff line number Diff line change
@@ -1,22 +1,58 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

import {
createWorkspaceSettingsServiceMockk,
workspaceConfigurationMock,
} from '../../testing';
import { WorkspaceSettingsService } from '../services';

import { ConfigurationComponent } from './configuration.component';

describe('ConfigurationComponent', () => {
let component: ConfigurationComponent;
let fixture: ComponentFixture<ConfigurationComponent>;
let service: WorkspaceSettingsService;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ConfigurationComponent],
imports: [ConfigurationComponent, NoopAnimationsModule],
providers: [
{
provide: WorkspaceSettingsService,
useValue: createWorkspaceSettingsServiceMockk(),
},
],
}).compileComponents();

fixture = TestBed.createComponent(ConfigurationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
service = TestBed.inject(WorkspaceSettingsService);
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should formly create descriptions for fields', () => {
const { root, sourceRoot, prefix } = workspaceConfigurationMock.properties;
const innerHTML = fixture.debugElement.nativeElement.innerHTML;
expect(innerHTML).toContain(root.description);
expect(innerHTML).toContain(sourceRoot.description);
expect(innerHTML).toContain(prefix.description);
});

it('should submit form', () => {
const updateWorkspaceProjectConfigurationSpy = jest.spyOn(
service,
'updateWorkspaceProjectConfiguration'
);
fixture.debugElement.nativeElement.querySelector('button').click();
expect(updateWorkspaceProjectConfigurationSpy).toBeCalledWith('name', {
prefix: 'prefix',
root: 'root',
sourceRoot: 'sourceRoot',
});
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,82 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { AsyncPipe, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { Project } from '@angular-cli-gui/shared/data';
import { FormlyFieldConfig, FormlyModule } from '@ngx-formly/core';
import { FormlyJsonschema } from '@ngx-formly/core/json-schema';
import { FormlyMaterialModule } from '@ngx-formly/material';
import {
combineLatest,
distinctUntilChanged,
map,
shareReplay,
switchMap,
} from 'rxjs';

import { WorkspaceSettingsService } from '../services';

@Component({
selector: 'cli-configuration',
standalone: true,
imports: [CommonModule],
imports: [
NgIf,
AsyncPipe,
FormlyModule,
FormlyMaterialModule,
ReactiveFormsModule,
],
templateUrl: './configuration.component.html',
styleUrls: ['./configuration.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ConfigurationComponent {}
export class ConfigurationComponent {
private readonly projectConfiguration$ = this.workspaceSettingsService
.readWorkspaceProjectConfiguration()
.pipe(
map(
(configuration) =>
this.schema.toFieldConfig(configuration)
.fieldGroup as FormlyFieldConfig[]
)
);

private readonly projectName$ = this.workspaceSettingsService
.readWorkspaceProjectNames()
.pipe(
map((names) => names[0]),
shareReplay({ bufferSize: 1, refCount: true })
);

private readonly workspaceProject$ = this.projectName$.pipe(
switchMap((currentProjectName: string) =>
this.workspaceSettingsService.readWorkspaceProject(currentProjectName)
),
distinctUntilChanged()
);

readonly formly$ = combineLatest([
this.projectConfiguration$,
this.workspaceProject$,
]).pipe(map(([formFields, formData]) => ({ formFields, formData })));

form = this.fb.group({});

constructor(
private workspaceSettingsService: WorkspaceSettingsService,
private fb: FormBuilder,
private schema: FormlyJsonschema
) {}

onSubmit(): void {
this.projectName$
.pipe(
switchMap((projectName) => {
return this.workspaceSettingsService.updateWorkspaceProjectConfiguration(
projectName,
this.form.value as Project
);
})
)
.subscribe();
}
}
1 change: 1 addition & 0 deletions libs/configuration/src/lib/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './workspace-settings';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './workspace-settings.service';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Project } from '@angular-cli-gui/shared/data';
import { JsonObject } from '@angular-devkit/core/src/json/utils';
import { JSONSchema7 } from 'json-schema';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class WorkspaceSettingsService {
constructor(private readonly http: HttpClient) {}

readWorkspaceProjectNames(): Observable<string[]> {
return this.http.get<string[]>(`/api/workspace/project-names`);
}

readWorkspaceProject(projectName: string): Observable<JsonObject> {
return this.http.get<JsonObject>(`/api/workspace/project/${projectName}`);
}

readWorkspaceProjectConfiguration(): Observable<JSONSchema7> {
return this.http.get<JSONSchema7>(`/api/workspace/workspace-configuration`);
}

updateWorkspaceProjectConfiguration(
projectName: string,
projectData: Project
): Observable<Project> {
return this.http.patch<Project>(`/api/workspace/project/${projectName}`, {
...projectData,
});
}
}
1 change: 1 addition & 0 deletions libs/configuration/src/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './workspace-settings.service.mock';
59 changes: 59 additions & 0 deletions libs/configuration/src/testing/workspace-settings.service.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { BehaviorSubject, Observable, Subject } from 'rxjs';

import { WorkspaceSettingsService } from '../lib/services';

type WorkspaceSettingsServiceMock = Partial<
Record<keyof WorkspaceSettingsService, jest.Mock<any, any> | Observable<any>>
>;

export const workspaceConfigurationMock = {
properties: {
root: {
type: 'string',
description: 'Root of the project files.',
},
prefix: {
type: 'string',
format: 'html-selector',
description: 'The prefix to apply to generated selectors.',
},
sourceRoot: {
type: 'string',
description:
'The root of the source files, assets and index.html file structure.',
},
},
title: 'Angular CLI Workspace Configuration',
description: 'Browser target options',
};

export const workspaceNamesMock = ['name'];

export const workspaceProjectMock = {
root: 'root',
prefix: 'prefix',
sourceRoot: 'sourceRoot',
extensions: {
projectType: 'application',
schematics: {
'@schematics/angular:component': {
style: 'scss',
},
},
},
};

export function createWorkspaceSettingsServiceMockk(): WorkspaceSettingsServiceMock {
return {
readWorkspaceProject: jest.fn(
() => new BehaviorSubject(workspaceProjectMock)
),
readWorkspaceProjectNames: jest.fn(
() => new BehaviorSubject(workspaceNamesMock)
),
readWorkspaceProjectConfiguration: jest.fn(
() => new BehaviorSubject(workspaceConfigurationMock)
),
updateWorkspaceProjectConfiguration: jest.fn(() => new Subject()),
};
}
Loading