Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions .changeset/weak-pets-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@sap-ux/store': minor
'@sap-ux/odata-service-inquirer': patch
---

migrate backend system file from .fioritools to .sapdevelopmenttools
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@sap-ux/btp-utils';
import { ERROR_TYPE } from '@sap-ux/inquirer-common';
import type { OdataVersion } from '@sap-ux/odata-service-writer';
import { type BackendSystemKey, type BackendSystem, SystemService } from '@sap-ux/store';
import { type BackendSystemKey, type BackendSystem } from '@sap-ux/store';
import type { ListChoiceOptions } from 'inquirer';
import { t } from '../../../../i18n';
import type { ConnectedSystem, DestinationFilters } from '../../../../types';
Expand All @@ -19,6 +19,7 @@ import type { ConnectionValidator } from '../../../connectionValidator';
import LoggerHelper from '../../../logger-helper';
import type { ValidationResult } from '../../../types';
import { getBackendSystemDisplayName } from '@sap-ux/fiori-generator-shared';
import { getBackendSystemService } from '../../../../utils/store';

// New system choice value is a hard to guess string to avoid conflicts with existing system names or user named systems
// since it will be used as a new system value in the system selection prompt.
Expand Down Expand Up @@ -51,7 +52,9 @@ export async function connectWithBackendSystem(
// Create a new connection with the selected system
PromptState.resetConnectedSystem();
let connectValResult: ValidationResult = false;
const backendSystem = await new SystemService(LoggerHelper.logger).read(backendKey);

const backendService = await getBackendSystemService();
const backendSystem = await backendService.read(backendKey);

if (backendSystem) {
// Backend systems validation supports using a cached service provider to prevent re-authentication (e.g. re-opening a browser window)
Expand Down Expand Up @@ -238,7 +241,8 @@ export async function createSystemChoices(
};
}
} else {
const backendSystems = await new SystemService(LoggerHelper.logger).getAll({ includeSensitiveData: false });
const backendService = await getBackendSystemService();
const backendSystems = await backendService.getAll({ includeSensitiveData: false });
// Cache the backend systems
PromptState.backendSystemsCache = backendSystems;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { t } from '../../../i18n';
import { SystemService } from '@sap-ux/store';
import LoggerHelper from '../../logger-helper';
import type { ServiceInfo } from '@sap-ux/btp-utils';
import { readFileSync } from 'node:fs';
import { getBackendSystemService } from '../../../utils/store';

/**
* Check if the system name is already in use.
Expand All @@ -11,7 +10,10 @@ import { readFileSync } from 'node:fs';
* @returns true if the system name is already in use, otherwise false
*/
async function isSystemNameInUse(systemName: string): Promise<boolean> {
const backendSystems = await new SystemService(LoggerHelper.logger).getAll({ includeSensitiveData: false });
const backendService = await getBackendSystemService();
const backendSystems = await backendService.getAll({
includeSensitiveData: false
});
return !!backendSystems.find((system) => system.name === systemName);
}

Expand Down
13 changes: 13 additions & 0 deletions packages/odata-service-inquirer/src/utils/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getService, type BackendSystem, type BackendSystemKey, type Service } from '@sap-ux/store';

/**
* Get the backend system service instance.
*
* @returns the backend system service instance
*/
export async function getBackendSystemService(): Promise<Service<BackendSystem, BackendSystemKey>> {
const backendService = await getService<BackendSystem, BackendSystemKey>({
entityName: 'system'
});
return backendService;
}
11 changes: 7 additions & 4 deletions packages/odata-service-inquirer/test/unit/index-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jest.mock('../../src/prompts/datasources/sap-system/system-selection', () => ({
...jest.requireActual('../../src/prompts/datasources/sap-system/system-selection')
}));

jest.mock('@sap-ux/store', () => ({
__esModule: true, // Workaround for spyOn TypeError: Jest cannot redefine property
jest.mock('../../src/utils/store', () => ({
__esModule: true, // Workaround to for spyOn TypeError: Jest cannot redefine property
...jest.requireActual('@sap-ux/store'),
SystemService: jest.fn().mockImplementation(() => ({
getBackendSystemService: jest.fn().mockImplementation(() => ({
getAll: jest.fn().mockResolvedValue([
{
name: 'storedSystem1',
Expand All @@ -42,6 +42,10 @@ describe('API tests', () => {
jest.restoreAllMocks();
});

afterEach(() => {
jest.clearAllMocks();
});

test('getPrompts', async () => {
jest.spyOn(prompts, 'getQuestions').mockResolvedValue([
{
Expand Down Expand Up @@ -102,7 +106,6 @@ describe('API tests', () => {

test('getPrompts, i18n is loaded', async () => {
const { prompts: questions } = await getPrompts(undefined, undefined, true, undefined, true);

expect(questions).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { getQuestions } from '../../../src/prompts';
import { DatasourceType } from '../../../src/types';
import * as utils from '../../../src/utils';
import { hostEnvironment } from '@sap-ux/fiori-generator-shared';
import { isFeatureEnabled } from '@sap-ux/feature-toggle';

/**
* Workaround to for spyOn TypeError: Jest cannot redefine property
Expand All @@ -16,10 +15,10 @@ jest.mock('@sap-ux/btp-utils', () => {
};
});

jest.mock('@sap-ux/store', () => ({
jest.mock('../../../src/utils/store', () => ({
__esModule: true, // Workaround to for spyOn TypeError: Jest cannot redefine property
...jest.requireActual('@sap-ux/store'),
SystemService: jest.fn().mockImplementation(() => ({
getBackendSystemService: jest.fn().mockImplementation(() => ({
getAll: jest.fn().mockResolvedValue([
{
name: 'storedSystem1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import * as sapSystemValidators from '../../../../../src/prompts/datasources/sap
import { PromptState } from '../../../../../src/utils';
import { ConnectionValidator } from '../../../../../src/prompts/connectionValidator';

jest.mock('@sap-ux/store', () => ({
jest.mock('../../../../../src/utils/store', () => ({
__esModule: true, // Workaround to for spyOn TypeError: Jest cannot redefine property
...jest.requireActual('@sap-ux/store'),
SystemService: jest.fn().mockImplementation(() => ({
getBackendSystemService: jest.fn().mockImplementation(() => ({
getAll: jest.fn().mockResolvedValue([{ name: 'http://abap.on.prem:1234' }])
}))
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { initI18nOdataServiceInquirer } from '../../../../src/i18n';
import { suggestSystemName } from '../../../../src/prompts/datasources/sap-system/prompt-helpers';

jest.mock('@sap-ux/store', () => ({
jest.mock('../../../../src/utils/store', () => ({
__esModule: true, // Workaround to for spyOn TypeError: Jest cannot redefine property
...jest.requireActual('@sap-ux/store'),
SystemService: jest.fn().mockImplementation(() => ({
getBackendSystemService: jest.fn().mockImplementation(() => ({
getAll: jest.fn().mockResolvedValue([{ name: 'system1' }, { name: 'system2' }, { name: 'system2 (1)' }])
}))
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
findDefaultSystemSelectionIndex,
NewSystemChoice
} from '../../../../../src/prompts/datasources/sap-system/system-selection/prompt-helpers';
import type { AuthenticationType, BackendSystem } from '@sap-ux/store';
import type { BackendSystem } from '@sap-ux/store';
import type { Destination, Destinations } from '@sap-ux/btp-utils';
import type { AxiosError } from '@sap-ux/axios-extension';

Expand Down Expand Up @@ -38,11 +38,10 @@ const mockAxiosError403 = {
message: 'Request failed with status code 403'
} as AxiosError;

jest.mock('@sap-ux/store', () => ({
jest.mock('../../../../../src/utils/store', () => ({
__esModule: true, // Workaround to for spyOn TypeError: Jest cannot redefine property
...jest.requireActual('@sap-ux/store'),
// Mock store access
SystemService: jest.fn().mockImplementation(() => ({
getBackendSystemService: jest.fn().mockImplementation(() => ({
getAll: jest.fn().mockResolvedValueOnce(backendSystems),
partialUpdate: jest.fn().mockImplementation((system: BackendSystem) => {
return Promise.resolve(system);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import LoggerHelper from '../../../../../src/prompts/logger-helper';
import type { ConnectedSystem } from '../../../../../src/types';
import { promptNames } from '../../../../../src/types';
import { getPromptHostEnvironment, PromptState } from '../../../../../src/utils';
import { isFeatureEnabled } from '@sap-ux/feature-toggle';

jest.mock('../../../../../src/utils', () => ({
...jest.requireActual('../../../../../src/utils'),
Expand Down Expand Up @@ -70,11 +69,10 @@ const systemServiceMock = {
read: systemServiceReadMock
} as Partial<SystemService>;

jest.mock('@sap-ux/store', () => ({
jest.mock('../../../../../src/utils/store', () => ({
__esModule: true, // Workaround to for spyOn TypeError: Jest cannot redefine property
...jest.requireActual('@sap-ux/store'),
// Mock store access
SystemService: jest.fn().mockImplementation(() => systemServiceMock)
getBackendSystemService: jest.fn().mockImplementation(() => systemServiceMock)
}));

jest.mock('@sap-ux/btp-utils', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { initI18nOdataServiceInquirer, t } from '../../../../src/i18n';
import type { BackendSystem } from '@sap-ux/store';
import type { ServiceInfo } from '@sap-ux/btp-utils';

jest.mock('@sap-ux/store', () => ({
SystemService: jest.fn().mockImplementation(() => ({
jest.mock('../../../../src/utils/store', () => ({
getBackendSystemService: jest.fn().mockImplementation(() => ({
getAll: jest.fn().mockResolvedValue([{ name: 'new system' } as BackendSystem])
}))
}));
Expand Down
17 changes: 1 addition & 16 deletions packages/store/src/data-access/filesystem.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import path from 'node:path';
import type { FSWatcher } from 'node:fs';
import fs, { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { plural } from 'pluralize';
import type { DataAccess } from '.';
import type { Logger } from '@sap-ux/logger';
import { errorInstance, getFioriToolsDirectory } from '../utils';
import { errorInstance, getEntityFileName, getFioriToolsDirectory, toPersistenceName } from '../utils';
import type { ServiceOptions } from '../types';
import os from 'node:os';
import type { Entity } from '../constants';
Expand Down Expand Up @@ -220,20 +219,6 @@ class FilesystemStore<E extends object> implements DataAccess<E> {
}
}

/**
* Trims, lowercases and returns plural if a non-empty string
*
* @param s
*/
function toPersistenceName(s: string): string | undefined {
const t = s?.trim().toLowerCase();
return t && plural(t);
}

function getEntityFileName(entityName: string): string {
return toPersistenceName(entityName) + '.json';
}

/** Return an FSWatcher for a given entity name
* The client is responsible for disposing of the FSWatcher
*/
Expand Down
37 changes: 36 additions & 1 deletion packages/store/src/services/backend-system.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type { Logger } from '@sap-ux/logger';
import type { Service, ServiceRetrievalOptions } from '.';
import type { DataProvider } from '../data-provider';
import type { ServiceOptions } from '../types';
import { SystemDataProvider } from '../data-provider/backend-system';
import { BackendSystem, BackendSystemKey } from '../entities/backend-system';
import { text } from '../i18n';
import type { ServiceOptions } from '../types';
import { existsSync, copyFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { getFioriToolsDirectory, getSapDevToolsDirectory, getEntityFileName } from '../utils';
import { Entity } from '../constants';

/**
* Should not be used directly, use factory method `getService` instead.
* Data integrity cannot be guaranteed when using this class directly.
*/
export class SystemService implements Service<BackendSystem, BackendSystemKey> {
private readonly dataProvider: DataProvider<BackendSystem, BackendSystemKey>;
private readonly logger: Logger;
Expand All @@ -14,6 +22,7 @@ export class SystemService implements Service<BackendSystem, BackendSystemKey> {
this.logger = logger;
this.dataProvider = new SystemDataProvider(this.logger, options);
}

public async partialUpdate(
key: BackendSystemKey,
entity: Partial<BackendSystem>
Expand Down Expand Up @@ -81,5 +90,31 @@ export class SystemService implements Service<BackendSystem, BackendSystemKey> {
}

export function getInstance(logger: Logger, options: ServiceOptions = {}): SystemService {
if (!options.baseDirectory) {
ensureSettingsMigrated();
options.baseDirectory = getSapDevToolsDirectory();
}
return new SystemService(logger, options);
}

/**
* Ensure settings are migrated from the old fiori tools directory to the new sap development tools directory.
*/
function ensureSettingsMigrated(): void {
const sapDevToolsDir = getSapDevToolsDirectory();
const migrationFlag = join(sapDevToolsDir, '.migrated');

if (existsSync(migrationFlag)) {
return;
}

const systemFileName = getEntityFileName(Entity.BackendSystem);
const legacyPath = join(getFioriToolsDirectory(), systemFileName);
const newPath = join(sapDevToolsDir, systemFileName);

if (existsSync(legacyPath)) {
mkdirSync(dirname(newPath), { recursive: true });
copyFileSync(legacyPath, newPath);
writeFileSync(migrationFlag, new Date().toISOString());
}
}
23 changes: 23 additions & 0 deletions packages/store/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { homedir } from 'node:os';
import path from 'node:path';
import { plural } from 'pluralize';

/** Pick the properties listed and return a new object with a shallow-copy */
export const pick = <T>(target: T, ...props: Array<keyof T>): Partial<T> | undefined => {
Expand Down Expand Up @@ -34,9 +35,31 @@ export enum FioriToolsSettings {
dir = '.fioritools'
}

export enum SapDevTools {
dir = '.sapdevelopmenttools'
}

export const getFioriToolsDirectory = (): string => {
return path.join(homedir(), FioriToolsSettings.dir);
};

export const getSapDevToolsDirectory = (): string => {
return path.join(homedir(), SapDevTools.dir);
};

/**
* Trims, lowercases and returns plural if a non-empty string
*
* @param s
*/
export function toPersistenceName(s: string): string | undefined {
const t = s?.trim().toLowerCase();
return t && plural(t);
}

export function getEntityFileName(entityName: string): string {
return toPersistenceName(entityName) + '.json';
}

export * from './app-studio';
export * from './backend';
Loading
Loading