Skip to content

Commit

Permalink
SALTO-6747: Support checking for and creating SFDX projects (#6644)
Browse files Browse the repository at this point in the history
  • Loading branch information
yelly authored Oct 15, 2024
1 parent 524abfb commit c70f061
Show file tree
Hide file tree
Showing 21 changed files with 732 additions and 85 deletions.
27 changes: 25 additions & 2 deletions packages/adapter-api/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ export type ConfigCreator = {
getConfig: (options?: InstanceElement) => Promise<InstanceElement>
}

export type IsInitializedFolderArgs = {
baseDir: string
}

export type IsInitializedFolderResult = {
result: boolean
errors: ReadonlyArray<SaltoError>
}

export type InitFolderArgs = {
baseDir: string
}

export type InitFolderResult = {
errors: ReadonlyArray<SaltoError>
}

export type LoadElementsFromFolderArgs = {
baseDir: string
} & AdapterBaseContext
Expand Down Expand Up @@ -235,16 +252,22 @@ export type ReferenceInfo = {

export type GetCustomReferencesFunc = (elements: Element[], adapterConfig?: InstanceElement) => Promise<ReferenceInfo[]>

export type AdapterFormat = {
isInitializedFolder?: (args: IsInitializedFolderArgs) => Promise<IsInitializedFolderResult>
initFolder?: (args: InitFolderArgs) => Promise<InitFolderResult>
loadElementsFromFolder?: (args: LoadElementsFromFolderArgs) => Promise<FetchResult>
dumpElementsToFolder?: (args: DumpElementsToFolderArgs) => Promise<DumpElementsResult>
}

export type Adapter = {
operations: (context: AdapterOperationsContext) => AdapterOperations
validateCredentials: (config: Readonly<InstanceElement>) => Promise<AccountInfo>
authenticationMethods: AdapterAuthentication
configType?: ObjectType
configCreator?: ConfigCreator
install?: () => Promise<AdapterInstallResult>
loadElementsFromFolder?: (args: LoadElementsFromFolderArgs) => Promise<FetchResult>
dumpElementsToFolder?: (args: DumpElementsToFolderArgs) => Promise<DumpElementsResult>
getAdditionalReferences?: GetAdditionalReferencesFunc
adapterFormat?: AdapterFormat
getCustomReferences?: GetCustomReferencesFunc
}

Expand Down
33 changes: 31 additions & 2 deletions packages/cli/src/commands/adapter_format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import _ from 'lodash'
import { logger } from '@salto-io/logging'
import { Workspace } from '@salto-io/workspace'
import { collections } from '@salto-io/lowerdash'
import { calculatePatch, syncWorkspaceToFolder } from '@salto-io/core'
import { calculatePatch, syncWorkspaceToFolder, initFolder, isInitializedFolder } from '@salto-io/core'
import { WorkspaceCommandAction, createWorkspaceCommand, createCommandGroupDef } from '../command_builder'
import { outputLine, errorOutputLine } from '../outputer'
import { validateWorkspace, formatWorkspaceErrors } from '../workspace/workspace'
import { CliExitCode, CliOutput } from '../types'
import { UpdateModeArg, UPDATE_MODE_OPTION } from './common/update_mode'
import { formatFetchWarnings, formatSyncToWorkspaceErrors } from '../formatter'
import { getUserBooleanInput } from '../callbacks'

const log = logger(module)
const { awu } = collections.asynciterable
Expand Down Expand Up @@ -161,13 +162,34 @@ const applyPatchCmd = createWorkspaceCommand({
type SyncWorkspaceToFolderArgs = {
toDir: string
accountName: 'salesforce'
force: boolean
}
export const syncWorkspaceToFolderAction: WorkspaceCommandAction<SyncWorkspaceToFolderArgs> = async ({
workspace,
input,
output,
}) => {
const { accountName, toDir } = input
const { accountName, toDir, force } = input

const initializedResult = await isInitializedFolder({ workspace, accountName, baseDir: toDir })
if (initializedResult.errors.length > 0) {
outputLine(formatSyncToWorkspaceErrors(initializedResult.errors), output)
return CliExitCode.AppError
}

if (!initializedResult.result) {
if (force || (await getUserBooleanInput('The folder is no initialized for the adapter format, initialize?'))) {
outputLine(`Initializing adapter format folder at ${toDir}`, output)
const initResult = await initFolder({ workspace, accountName, baseDir: toDir })
if (initResult.errors.length > 0) {
outputLine(formatSyncToWorkspaceErrors(initResult.errors), output)
return CliExitCode.AppError
}
} else {
outputLine('Folder not initialized for adapter format, aborting', output)
return CliExitCode.UserInputError
}
}

outputLine(`Synchronizing content of workspace to folder at ${toDir}`, output)
const result = await syncWorkspaceToFolder({ workspace, accountName, baseDir: toDir })
Expand Down Expand Up @@ -199,6 +221,13 @@ const syncToWorkspaceCmd = createWorkspaceCommand({
choices: ['salesforce'],
default: 'salesforce',
},
{
name: 'force',
type: 'boolean',
alias: 'f',
description: 'Initialize the folder for adapter format if needed',
default: false,
},
],
},
action: syncWorkspaceToFolderAction,
Expand Down
79 changes: 76 additions & 3 deletions packages/cli/test/commands/adapter_format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { Element, InstanceElement, ObjectType, toChange } from '@salto-io/adapter-api'
import { detailedCompare } from '@salto-io/adapter-utils'
import { calculatePatch, syncWorkspaceToFolder } from '@salto-io/core'
import { calculatePatch, initFolder, isInitializedFolder, syncWorkspaceToFolder } from '@salto-io/core'
import { merger, updateElementsWithAlternativeAccount } from '@salto-io/workspace'
import * as mocks from '../mocks'
import { applyPatchAction, syncWorkspaceToFolderAction } from '../../src/commands/adapter_format'
Expand All @@ -19,11 +19,15 @@ jest.mock('@salto-io/core', () => {
...actual,
calculatePatch: jest.fn().mockImplementation(actual.calculatePatch),
syncWorkspaceToFolder: jest.fn().mockImplementation(actual.syncWorkspaceToFolder),
isInitializedFolder: jest.fn().mockImplementation(actual.isInitializedFolder),
initFolder: jest.fn().mockImplementation(actual.initFolder),
}
})

const mockCalculatePatch = calculatePatch as jest.MockedFunction<typeof calculatePatch>
const mockSyncWorkspaceToFolder = syncWorkspaceToFolder as jest.MockedFunction<typeof syncWorkspaceToFolder>
const mockIsInitializedFolder = isInitializedFolder as jest.MockedFunction<typeof isInitializedFolder>
const mockInitFolder = initFolder as jest.MockedFunction<typeof initFolder>

describe('apply-patch command', () => {
const commandName = 'apply-patch'
Expand Down Expand Up @@ -238,16 +242,83 @@ describe('sync-to-workspace command', () => {
})
})

describe('when core sync returns without errors', () => {
describe('when isFolderInitialized returns errors', () => {
let result: CliExitCode
beforeEach(async () => {
mockIsInitializedFolder.mockResolvedValueOnce({
result: false,
errors: [{ severity: 'Error', message: 'Not supported', detailedMessage: 'detailed Not Supported' }],
})
result = await syncWorkspaceToFolderAction({
...cliCommandArgs,
workspace,
input: {
accountName: 'salesforce',
toDir: 'someDir',
force: true,
},
})
})
it('should return non-success exit code', () => {
expect(result).toEqual(CliExitCode.AppError)
})
})

describe('when folder is not initialized and initFolder returns errors', () => {
let result: CliExitCode
beforeEach(async () => {
mockIsInitializedFolder.mockResolvedValueOnce({ result: false, errors: [] })
mockInitFolder.mockResolvedValueOnce({
errors: [{ severity: 'Error', message: 'Not supported', detailedMessage: 'detailed Not Supported' }],
})
result = await syncWorkspaceToFolderAction({
...cliCommandArgs,
workspace,
input: {
accountName: 'salesforce',
toDir: 'someDir',
force: true,
},
})
})
it('should return non-success exit code', () => {
expect(result).toEqual(CliExitCode.AppError)
})
})

describe('when folder is initialized and core sync returns without errors', () => {
let result: CliExitCode
beforeEach(async () => {
mockSyncWorkspaceToFolder.mockResolvedValueOnce({ errors: [] })
mockIsInitializedFolder.mockResolvedValueOnce({ result: true, errors: [] })
result = await syncWorkspaceToFolderAction({
...cliCommandArgs,
workspace,
input: {
accountName: 'salesforce',
toDir: 'someDir',
force: true,
},
})
})
it('should return success exit code', () => {
expect(result).toEqual(CliExitCode.Success)
})
})

describe('when folder is not initialized and folder init and core sync returns without errors', () => {
let result: CliExitCode
beforeEach(async () => {
mockSyncWorkspaceToFolder.mockResolvedValueOnce({ errors: [] })
mockIsInitializedFolder.mockResolvedValueOnce({ result: false, errors: [] })
mockInitFolder.mockResolvedValueOnce({ errors: [] })
result = await syncWorkspaceToFolderAction({
...cliCommandArgs,
workspace,
input: {
accountName: 'salesforce',
toDir: 'someDir',
force: true,
},
})
})
Expand All @@ -256,18 +327,20 @@ describe('sync-to-workspace command', () => {
})
})

describe('when core sync returns with errors', () => {
describe('when folder is initialized and core sync returns with errors', () => {
let result: CliExitCode
beforeEach(async () => {
mockSyncWorkspaceToFolder.mockResolvedValueOnce({
errors: [{ severity: 'Error', message: 'Not supported', detailedMessage: 'detailed Not Supported' }],
})
mockIsInitializedFolder.mockResolvedValueOnce({ result: true, errors: [] })
result = await syncWorkspaceToFolderAction({
...cliCommandArgs,
workspace,
input: {
accountName: 'salesforce',
toDir: 'someDir',
force: true,
},
})
})
Expand Down
8 changes: 7 additions & 1 deletion packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ export {
} from './src/local-workspace/remote_map'
export { NoWorkspaceConfig } from './src/local-workspace/errors'
export * from './src/types'
export { calculatePatch, syncWorkspaceToFolder, updateElementFolder } from './src/core/adapter_format'
export {
calculatePatch,
syncWorkspaceToFolder,
updateElementFolder,
isInitializedFolder,
initFolder,
} from './src/core/adapter_format'
Loading

0 comments on commit c70f061

Please sign in to comment.