Skip to content

Commit 5dd8d86

Browse files
mshanemcshetzel
andauthored
Sm/sdr-mdapi-zip (#516)
* fix: use the other coverageReport * feat: add type omniInteractionConfig * fix: dataMapping is mcf * chore: skip other unsupported features * chore: adding types for SDR * feat: zip and mdapiDir * refactor: stream2buffer on streams class * fix: throw when no zip buffer possible * test: ut for stream2buffer Co-authored-by: Steve Hetzel <[email protected]>
1 parent e36d531 commit 5dd8d86

File tree

6 files changed

+91
-5
lines changed

6 files changed

+91
-5
lines changed

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2021, Salesforce.com, Inc.
1+
Copyright (c) 2022, Salesforce.com, Inc.
22
All rights reserved.
33

44
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

src/client/metadataApiDeploy.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
*/
77
import { basename, dirname, extname, join } from 'path';
88
import { isString } from '@salesforce/ts-types';
9+
import { create as createArchive } from 'archiver';
10+
import * as fs from 'graceful-fs';
911
import { MetadataConverter } from '../convert';
1012
import { ComponentLike, SourceComponent } from '../resolve';
1113
import { normalizeToArray } from '../utils';
1214
import { ComponentSet } from '../collections';
1315
import { registry } from '../registry';
1416
import { MissingJobIdError } from '../errors';
17+
import { stream2buffer } from '../convert/streams';
1518
import { MetadataTransfer, MetadataTransferOptions } from './metadataTransfer';
1619
import {
1720
AsyncResult,
@@ -23,7 +26,6 @@ import {
2326
MetadataTransferResult,
2427
} from './types';
2528
import { DiagnosticUtil } from './diagnosticUtil';
26-
2729
export class DeployResult implements MetadataTransferResult {
2830
public readonly response: MetadataApiDeployStatus;
2931
public readonly components: ComponentSet;
@@ -208,6 +210,14 @@ export class DeployResult implements MetadataTransferResult {
208210

209211
export interface MetadataApiDeployOptions extends MetadataTransferOptions {
210212
apiOptions?: ApiOptions;
213+
/**
214+
* Path to a zip file containing mdapi-formatted code and a package.xml
215+
*/
216+
zipPath?: string;
217+
/**
218+
* Path to a directory containing mdapi-formatted code and a package.xml
219+
*/
220+
mdapiPath?: string;
211221
}
212222

213223
export class MetadataApiDeploy extends MetadataTransfer<MetadataApiDeployStatus, DeployResult> {
@@ -302,8 +312,7 @@ export class MetadataApiDeploy extends MetadataTransfer<MetadataApiDeployStatus,
302312
}
303313

304314
protected async pre(): Promise<AsyncResult> {
305-
const converter = new MetadataConverter();
306-
const { zipBuffer } = await converter.convert(this.components, 'metadata', { type: 'zip' });
315+
const zipBuffer = await this.getZipBuffer();
307316
const connection = await this.getConnection();
308317
await this.maybeSaveTempDirectory('metadata');
309318
return connection.deploy(zipBuffer, this.options.apiOptions);
@@ -313,4 +322,32 @@ export class MetadataApiDeploy extends MetadataTransfer<MetadataApiDeployStatus,
313322
protected async post(result: MetadataApiDeployStatus): Promise<DeployResult> {
314323
return new DeployResult(result, this.components);
315324
}
325+
326+
private async getZipBuffer(): Promise<Buffer> {
327+
if (this.options.mdapiPath) {
328+
if (!fs.existsSync(this.options.mdapiPath) || !fs.lstatSync(this.options.mdapiPath).isDirectory()) {
329+
throw new Error(`Deploy directory ${this.options.mdapiPath} does not exist or is not a directory`);
330+
}
331+
// make a zip from the given directory
332+
const zip = createArchive('zip', { zlib: { level: 9 } });
333+
// anywhere not at the root level is fine
334+
zip.directory(this.options.mdapiPath, 'zip');
335+
await zip.finalize();
336+
return stream2buffer(zip);
337+
}
338+
// read the zip into a buffer
339+
if (this.options.zipPath) {
340+
if (!fs.existsSync(this.options.zipPath)) {
341+
throw new Error(`Zip file ${this.options.zipPath} does not exist`);
342+
}
343+
// does encoding matter for zip files? I don't know
344+
return fs.promises.readFile(this.options.zipPath);
345+
}
346+
if (this.options.components) {
347+
const converter = new MetadataConverter();
348+
const { zipBuffer } = await converter.convert(this.components, 'metadata', { type: 'zip' });
349+
return zipBuffer;
350+
}
351+
throw new Error('Options should include components, zipPath, or mdapiPath');
352+
}
316353
}

src/convert/streams.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77
import { basename, dirname, isAbsolute, join } from 'path';
8-
import { pipeline as cbPipeline, Readable, Transform, Writable } from 'stream';
8+
import { pipeline as cbPipeline, Readable, Transform, Writable, Stream } from 'stream';
99
import { promisify } from 'util';
1010
import { Archiver, create as createArchive } from 'archiver';
1111
import { createWriteStream, existsSync } from 'graceful-fs';
@@ -24,6 +24,17 @@ import { SfdxFileFormat, WriteInfo, WriterFormat } from './types';
2424

2525
export const pipeline = promisify(cbPipeline);
2626

27+
export const stream2buffer = async (stream: Stream): Promise<Buffer> => {
28+
return new Promise<Buffer>((resolve, reject) => {
29+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
30+
const buf = Array<any>();
31+
32+
stream.on('data', (chunk) => buf.push(chunk));
33+
stream.on('end', () => resolve(Buffer.concat(buf)));
34+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
35+
stream.on('error', (err) => reject(`error converting stream - ${err}`));
36+
});
37+
};
2738
export class ComponentReader extends Readable {
2839
private iter: Iterator<SourceComponent>;
2940

test/client/metadataApiDeploy.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ describe('MetadataApiDeploy', () => {
3535

3636
describe('Lifecycle', () => {
3737
describe('start', () => {
38+
it('should not convert zip, but read from fs', async () => {});
39+
it('should not mdapiDir, but generate zip buffer from it', async () => {});
40+
3841
it('should convert to metadata format and create zip', async () => {
3942
const components = new ComponentSet([matchingContentFile.COMPONENT]);
4043
const { operation, convertStub } = await stubMetadataDeploy(env, {
@@ -756,6 +759,26 @@ describe('MetadataApiDeploy', () => {
756759
});
757760

758761
describe('Constructor', () => {
762+
it('should allow zip file', () => {
763+
const mdApiDeploy = new MetadataApiDeploy({
764+
usernameOrConnection: 'testing',
765+
zipPath: 'foo/myZip.zip',
766+
});
767+
// @ts-ignore testing private property
768+
const mdOpts = mdApiDeploy.options;
769+
expect(mdOpts.zipPath).to.equal('foo/myZip.zip');
770+
});
771+
772+
it('should allow mdapi path', () => {
773+
const mdApiDeploy = new MetadataApiDeploy({
774+
usernameOrConnection: 'testing',
775+
mdapiPath: 'foo/myDir',
776+
});
777+
// @ts-ignore testing private property
778+
const mdOpts = mdApiDeploy.options;
779+
expect(mdOpts.mdapiPath).to.equal('foo/myDir');
780+
});
781+
759782
it('should merge default API options', () => {
760783
const mdApiDeploy = new MetadataApiDeploy({
761784
usernameOrConnection: 'testing',

test/convert/streams.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,4 +518,17 @@ describe('Streams', () => {
518518
expect(jsToXml.read().toString()).to.be.equal(expectedBody);
519519
});
520520
});
521+
522+
describe('stream2buffer', () => {
523+
it('returns the stream content in a buffer', async () => {
524+
const stream = new Readable();
525+
stream._read = (): void => {
526+
stream.push('foo');
527+
stream.push(null);
528+
};
529+
const buffer = await streams.stream2buffer(stream);
530+
expect(buffer).to.be.instanceof(Buffer);
531+
expect(buffer.toString()).to.equal('foo');
532+
});
533+
});
521534
});

test/mock/client/transferOperations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export const MOCK_RECENTLY_VALIDATED_ID_SOAP = '0987654321';
4040

4141
interface DeployStubOptions {
4242
components?: ComponentSet;
43+
zipPath?: string;
44+
mdapiPath?: string;
4345
componentSuccesses?: Partial<DeployMessage> | Array<Partial<DeployMessage>>;
4446
componentFailures?: Partial<DeployMessage> | Array<Partial<DeployMessage>>;
4547
apiOptions?: MetadataApiDeployOptions;

0 commit comments

Comments
 (0)