Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c77abb1
provide DatePipe in deploy commit list wrapper
norman-abramovitz Apr 15, 2026
838e001
clear prior deployer on deploy step 3 re-entry
norman-abramovitz Apr 15, 2026
32f0d7f
provide DatePipe on standalone delete/detach dialogs
norman-abramovitz Apr 15, 2026
2394c20
fix deploy retry: log viewer + button state reset
norman-abramovitz Apr 16, 2026
ba9ba6e
add page-header copyToken spec coverage
norman-abramovitz Apr 16, 2026
b39b6c8
unblock step2 spec and cover GHE+PAT behavior
norman-abramovitz Apr 16, 2026
2b165e7
cover async service binding display components
norman-abramovitz Apr 16, 2026
2b19c90
bump cfapppush CLI pin to v8.18.2
norman-abramovitz Apr 16, 2026
e6b9dd9
harden vcs.go argv option-smuggling
norman-abramovitz Apr 16, 2026
34ea643
add form valueChanges test for GHE+PAT wiring
norman-abramovitz Apr 16, 2026
cc3b0c5
cover csi-mode SERVICES_WALL_MODE bind-hidden branch
norman-abramovitz Apr 16, 2026
eba55ae
harden k8s analyzer against shell injection and traversal
norman-abramovitz Apr 16, 2026
500ecb6
fix go vet issues in kubernetes plugin
norman-abramovitz Apr 16, 2026
0de6e10
migrate jetstream log streaming to loggregator v9
norman-abramovitz Apr 16, 2026
6d97bee
fix go vet issues in analysis/container
norman-abramovitz Apr 16, 2026
300674f
Bump version to v5.0.0-dev.9
norman-abramovitz Apr 16, 2026
a834352
Fix SSH viewer reconnect and OnPush bugs
norman-abramovitz Apr 16, 2026
1d0c7b2
Fix CF SSH to use process GUID not app GUID
norman-abramovitz Apr 17, 2026
82ca0e3
Fix app instances page duplicate rows and button displacement
norman-abramovitz Apr 17, 2026
1a3ebc9
Fix PostCSS nesting warning and deploy-step2 test crash
norman-abramovitz Apr 17, 2026
7adcb42
Bump version to v5.0.0-dev.10
norman-abramovitz Apr 17, 2026
d3d199d
FWT-929: Add cross-tab navigation to app summary cards
norman-abramovitz Apr 17, 2026
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stratos",
"version": "v5.0.0-dev.8+build.20260415.52d8eaf981",
"version": "v5.0.0-dev.10",
"type": "module",
"description": "Stratos Console",
"main": "index.js",
Expand Down
24 changes: 19 additions & 5 deletions postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
const postcss = require('postcss');
const nested = require('postcss-nested');

// postcss-nested uses PostCSS v8 visitor API (phase 2), but Tailwind's
// detectNesting runs in phase 1 (runOnRoot). Wrapping in an Once handler
// forces nesting to be expanded in phase 1, before Tailwind sees it.
const nestedOnce = {
postcssPlugin: 'postcss-nested-once',
Once(root) {
postcss([nested]).process(root).sync();
},
}
};

module.exports = {
plugins: [
nestedOnce,
require('tailwindcss'),
require('autoprefixer'),
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
LoadingPageComponent,
],
providers: [
DatePipe,
CfAppRoutesListConfigService,
AppServiceBindingListConfigService
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideZonelessChangeDetection, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { provideRouter } from '@angular/router';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { of } from 'rxjs';
import { Store } from '@ngrx/store';
Expand Down Expand Up @@ -54,6 +55,7 @@ describe('InstancesTabComponent', () => {
provideHttpClient(),
provideHttpClientTesting(),
provideZonelessChangeDetection(),
provideRouter([]),
{ provide: Store, useValue: mockStore },
{ provide: PaginationMonitorFactory, useValue: mockPmf },
{ provide: ApplicationService, useClass: ApplicationServiceMock },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
import { GitCommit } from '@stratosui/git';
import { Observable } from 'rxjs';
Expand All @@ -18,6 +19,7 @@ import {
ListComponent
],
providers: [
DatePipe,
{
provide: ListConfig,
useFactory: () => new GithubCommitsListConfigServiceDeploy(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { provideMockStore } from '@ngrx/store/testing';
import { describe, it, expect, beforeEach } from 'vitest';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ActivatedRoute } from '@angular/router';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Subject, of } from 'rxjs';
import { StoreModule } from '@ngrx/store';
import { deployAppReducer } from '../../../../store/reducers/deploy-app.reducer';

import { getGitHubAPIURL, GITHUB_API_URL, GitSCMService } from '@stratosui/git';
import { STORE_TEST_PROVIDERS } from '@stratosui/store/testing';
import { getGitHubAPIURL, GITHUB_API_URL, GitSCMService, GitHubSCM } from '@stratosui/git';
import {
EntityCatalogHelper,
EntityCatalogHelpers,
EntityCatalogTestModule,
TEST_CATALOGUE_ENTITIES,
generateStratosEntities,
} from '@stratosui/store';
import { createBasicStoreModule, STORE_TEST_PROVIDERS } from '@stratosui/store/testing';
import { generateCFEntities } from '../../../../cf-entity-generator';
import { ApplicationDeploySourceTypes } from '../deploy-application-steps.types';
import { DeployApplicationStep2Component } from './deploy-application-step2.component';

Expand All @@ -15,22 +28,148 @@ describe('DeployApplicationStep2Component', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
DeployApplicationStep2Component
DeployApplicationStep2Component,
createBasicStoreModule(),
StoreModule.forFeature('deployApplication', deployAppReducer),
EntityCatalogTestModule,
],
providers: [
provideMockStore(),
...STORE_TEST_PROVIDERS,
{
provide: TEST_CATALOGUE_ENTITIES,
useValue: [
...generateStratosEntities(),
...generateCFEntities(),
],
},
GitSCMService,
ApplicationDeploySourceTypes,
{ provide: GITHUB_API_URL, useFactory: getGitHubAPIURL },
provideZonelessChangeDetection()
]
{
provide: ActivatedRoute,
useValue: { snapshot: { queryParams: {}, data: {}, params: {} } },
},
provideZonelessChangeDetection(),
provideHttpClient(),
provideHttpClientTesting(),
],
});

const helper = TestBed.inject(EntityCatalogHelper);
EntityCatalogHelpers.SetEntityCatalogHelper(helper);
});

// TODO: Fix EntityCatalogHelper initialization to enable component creation test
// The component requires EntityCatalogHelper to be initialized, which needs proper entity catalog setup
it('should be defined', () => {
expect(DeployApplicationStep2Component).toBeDefined();
});

it('creates the component', () => {
fixture = TestBed.createComponent(DeployApplicationStep2Component);
component = fixture.componentInstance;
expect(component).toBeTruthy();
});

describe('applyGithubEnterpriseAndToken (GHE + PAT port)', () => {
let scmSpy: {
setPublicApi: ReturnType<typeof vi.fn>;
setAccessToken: ReturnType<typeof vi.fn>;
clearAccessToken: ReturnType<typeof vi.fn>;
getType: ReturnType<typeof vi.fn>;
};

beforeEach(() => {
fixture = TestBed.createComponent(DeployApplicationStep2Component);
component = fixture.componentInstance;

scmSpy = {
setPublicApi: vi.fn(),
setAccessToken: vi.fn(),
clearAccessToken: vi.fn(),
getType: vi.fn().mockReturnValue('github'),
};
// Inject a mock SCM so applyGithubEnterpriseAndToken's side effects are observable
// without driving the full deploy-step form.
component.scm = scmSpy as unknown as GitHubSCM;
});

const invoke = (url: string | undefined, token: string | undefined) =>
(component as unknown as {
applyGithubEnterpriseAndToken(u?: string, t?: string): void;
}).applyGithubEnterpriseAndToken(url, token);

it('flags an invalid enterprise URL and does not set the public API', () => {
invoke('not-a-url', undefined);

expect(component.isInvalidGithubEnterpriseUrl).toBe(true);
expect(scmSpy.setPublicApi).not.toHaveBeenCalled();
});

it('sets the public API endpoint for a valid enterprise URL', () => {
invoke('https://github.example.com/api/v3', undefined);

expect(component.isInvalidGithubEnterpriseUrl).toBe(false);
expect(scmSpy.setPublicApi).toHaveBeenCalledWith('https://github.example.com/api/v3');
});

it('sets the access token when one is provided', () => {
invoke('https://github.example.com/api/v3', 'pat-abc123');

expect(scmSpy.setAccessToken).toHaveBeenCalledWith('pat-abc123');
expect(scmSpy.clearAccessToken).not.toHaveBeenCalled();
});

it('clears the access token when none is provided', () => {
invoke('https://github.example.com/api/v3', '');

expect(scmSpy.clearAccessToken).toHaveBeenCalled();
expect(scmSpy.setAccessToken).not.toHaveBeenCalled();
});

it('ignores an empty enterprise URL and leaves the public API untouched', () => {
invoke('', 'pat-abc123');

expect(component.isInvalidGithubEnterpriseUrl).toBe(false);
expect(scmSpy.setPublicApi).not.toHaveBeenCalled();
});

it('skips token handling when the active SCM is not GitHub (e.g. GitLab)', () => {
scmSpy.getType.mockReturnValue('gitlab');
invoke('https://gitlab.example.com/api/v4', 'pat-should-be-ignored');

expect(scmSpy.setAccessToken).not.toHaveBeenCalled();
expect(scmSpy.clearAccessToken).not.toHaveBeenCalled();
});

it('wires applyGithubEnterpriseAndToken to the sourceSelectionForm valueChanges stream', () => {
// Mock the NgForm ViewChild with a Subject so we can emit form values
// without standing up the full template-driven reactive form. Also mock
// setupForGit's other upstream collaborators just enough to reach the
// suggestedRepos$ assignment where the tap callback is registered.
const valueChanges = new Subject<Record<string, string | undefined>>();
(component as unknown as { sourceSelectionForm: { valueChanges: Subject<unknown> } }).sourceSelectionForm = {
valueChanges,
};
// updateSuggestedRepositories is called inside switchMap; stub it to
// return an empty result so the pipe completes without network work.
vi.spyOn(
component as unknown as { updateSuggestedRepositories: (n: string) => unknown },
'updateSuggestedRepositories',
).mockReturnValue(of([]));

// Invoke setupForGit so the valueChanges subscription gets wired up.
(component as unknown as { setupForGit(): void }).setupForGit();
// Subscribe to keep the pipe hot; the tap callback only fires on
// subscribed observables.
component.suggestedRepos$.subscribe();

valueChanges.next({
githubEnterpriseUrl: 'https://github.example.com/api/v3',
githubAccessToken: 'pat-from-form',
projectName: 'org/repo',
});

expect(scmSpy.setPublicApi).toHaveBeenCalledWith('https://github.example.com/api/v3');
expect(scmSpy.setAccessToken).toHaveBeenCalledWith('pat-from-form');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ export class DeployApplicationStep3Component implements OnDestroy {
}

onEnter = (fsDeployer: DeployApplicationDeployer) => {
// Re-entry after Previous: tear down the prior deployer's subscriptions
// and websocket so its stale status$ cannot replay the earlier error
// snackbar on top of the fresh attempt.
this.destroyDeployer();
if (this.deployer && this.deployer !== fsDeployer) {
this.deployer.close();
}

// Reset UI state so buttons reflect "deploy not yet started" rather
// than carrying over the prior attempt's terminal state (e.g. "Go to
// App Summary" staying active from a previous successful push).
this.validSubject.next(false);
this.closeableSubject.next(false);
this.showOverlaySubject.next(false);
this.disablePreviousSubject.next(true);
this.error.set(false);

// If we were passed data, then we came from the File System step
if (fsDeployer) {
this.deployer = fsDeployer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ import { DetachAppsComponent } from './detach-apps/detach-apps.component';
StepComponent,
DetachAppsComponent,
AppActionMonitorComponent
]
],
providers: [DatePipe]
})
export class DetachServiceInstanceComponent {
private store = inject<Store<CFAppState>>(Store);
Expand Down
Loading
Loading