Skip to content

Commit 0fb9ece

Browse files
authored
Merge pull request #24 from jcatt-sf/w-21770329-zip-ts-port
feat: port zip command from Python to native TypeScript
2 parents 02c0a47 + 59d140b commit 0fb9ece

7 files changed

Lines changed: 922 additions & 134 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"dependencies": {
66
"@oclif/core": "^4",
77
"@salesforce/core": "^8.2.7",
8-
"@salesforce/sf-plugins-core": "^12"
8+
"@salesforce/sf-plugins-core": "^12",
9+
"jszip": "^3.10.1"
910
},
1011
"devDependencies": {
1112
"@oclif/plugin-command-snapshot": "^5.2.3",

src/base/zipBase.ts

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,34 @@
1616
import { existsSync } from 'node:fs';
1717
import { SfCommand } from '@salesforce/sf-plugins-core';
1818
import { Messages, SfError } from '@salesforce/core';
19-
import { DatacodeBinaryExecutor, type DatacodeZipExecutionResult } from '../utils/datacodeBinaryExecutor.js';
20-
import { checkEnvironment } from '../utils/environmentChecker.js';
21-
import { type SharedResultProps } from './types.js';
19+
import { zipWithSfError, type ZipResult as ZipBuilderResult } from '../utils/zipBuilder.js';
2220

2321
export type BaseZipFlags = {
2422
'package-dir': string;
2523
network?: string;
2624
};
2725

28-
export type ZipResult = SharedResultProps & {
29-
archivePath?: string;
30-
executionResult?: DatacodeZipExecutionResult;
26+
export type ZipResult = {
27+
success: boolean;
28+
codeType: 'script' | 'function';
29+
packageDir: string;
30+
archivePath: string;
31+
fileCount: number;
32+
archiveSizeBytes: number;
33+
message: string;
3134
};
3235

36+
function formatBytes(bytes: number): string {
37+
const units = ['B', 'KB', 'MB', 'GB'];
38+
let value = bytes;
39+
let unit = 0;
40+
while (value >= 1024 && unit < units.length - 1) {
41+
value /= 1024;
42+
unit += 1;
43+
}
44+
return `${value.toFixed(unit === 0 ? 0 : 2)} ${units[unit]}`;
45+
}
46+
3347
// eslint-disable-next-line sf-plugin/command-summary, sf-plugin/command-example
3448
export abstract class ZipBase extends SfCommand<ZipResult> {
3549
public static enableJsonFlag = false;
@@ -39,7 +53,7 @@ export abstract class ZipBase extends SfCommand<ZipResult> {
3953
const codeType = this.getCodeType();
4054
const messages = this.getMessages();
4155
const packageDir = flags['package-dir'];
42-
const network = flags.network;
56+
const network = flags.network ?? 'default';
4357

4458
if (!existsSync(packageDir)) {
4559
throw new SfError(
@@ -50,45 +64,25 @@ export abstract class ZipBase extends SfCommand<ZipResult> {
5064
}
5165

5266
try {
53-
const { pythonInfo, packageInfo, binaryInfo } = await checkEnvironment(
54-
this.spinner,
55-
this.log.bind(this),
56-
messages
57-
);
58-
5967
this.spinner.start(messages.getMessage('info.executingZip'));
60-
const executionResult = await DatacodeBinaryExecutor.executeBinaryZip(packageDir, network);
61-
68+
const result: ZipBuilderResult = await zipWithSfError(packageDir, network, this.log.bind(this));
6269
this.spinner.stop();
6370

64-
if (executionResult.archivePath) {
65-
this.log(messages.getMessage('info.archiveCreated', [executionResult.archivePath]));
66-
}
67-
68-
if (executionResult.fileCount !== undefined) {
69-
this.log(messages.getMessage('info.filesIncluded', [executionResult.fileCount.toString()]));
70-
}
71-
72-
if (executionResult.archiveSize) {
73-
this.log(messages.getMessage('info.archiveSize', [executionResult.archiveSize]));
74-
}
71+
this.log(messages.getMessage('info.archiveCreated', [result.archivePath]));
72+
this.log(messages.getMessage('info.filesIncluded', [result.fileCount.toString()]));
73+
this.log(messages.getMessage('info.archiveSize', [formatBytes(result.archiveSizeBytes)]));
7574

7675
return {
7776
success: true,
78-
pythonVersion: pythonInfo,
79-
packageInfo,
80-
binaryInfo,
8177
codeType,
8278
packageDir,
83-
archivePath: executionResult.archivePath,
84-
executionResult,
79+
archivePath: result.archivePath,
80+
fileCount: result.fileCount,
81+
archiveSizeBytes: result.archiveSizeBytes,
8582
message: messages.getMessage('info.zipCompleted'),
8683
};
8784
} catch (error) {
8885
this.spinner.stop();
89-
90-
// The error will be properly handled by the Salesforce CLI framework
91-
// as an SfError with actions, so we just throw it
9286
throw error;
9387
}
9488
}

src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export { PipChecker, type PipPackageInfo } from './utils/pipChecker.js';
1919
export { DatacodeBinaryChecker, type DatacodeBinaryInfo } from './utils/datacodeBinaryChecker.js';
2020
export {
2121
DatacodeBinaryExecutor,
22-
type DatacodeZipExecutionResult,
2322
type DatacodeDeployExecutionResult,
2423
type DatacodeRunExecutionResult,
2524
} from './utils/datacodeBinaryExecutor.js';
@@ -39,3 +38,11 @@ export {
3938
type ScanPermissions,
4039
} from './utils/nativeScan.js';
4140
export type { ScanResult } from './base/scanBase.js';
41+
export {
42+
createZip,
43+
hasNonemptyRequirementsFile,
44+
prepareDependencyArchive,
45+
zip as zipPackage,
46+
zipWithSfError,
47+
type ZipResult as ZipBuilderResult,
48+
} from './utils/zipBuilder.js';

src/utils/datacodeBinaryExecutor.ts

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@ import { spawnAsync, type SpawnError } from './spawnHelper.js';
2424
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
2525
const messages = Messages.loadMessages('@salesforce/plugin-data-code-extension', 'datacodeBinaryExecutor');
2626

27-
export type DatacodeZipExecutionResult = {
28-
stdout: string;
29-
stderr: string;
30-
archivePath?: string;
31-
fileCount?: number;
32-
archiveSize?: string;
33-
};
34-
3527
export type DatacodeDeployExecutionResult = {
3628
stdout: string;
3729
stderr: string;
@@ -48,43 +40,6 @@ export type DatacodeRunExecutionResult = {
4840
};
4941

5042
export class DatacodeBinaryExecutor {
51-
/**
52-
* Executes datacustomcode zip with the specified parameters.
53-
*
54-
* @param packageDir The directory containing the initialized package to zip
55-
* @param network Optional network configuration for Jupyter notebooks
56-
* @returns Execution result with stdout, stderr, and archive information
57-
* @throws SfError if execution fails
58-
*/
59-
public static async executeBinaryZip(packageDir: string, network?: string): Promise<DatacodeZipExecutionResult> {
60-
const args = ['zip'];
61-
62-
if (network) {
63-
args.push('--network', network);
64-
}
65-
66-
args.push(packageDir);
67-
68-
try {
69-
const { stdout, stderr } = await spawnAsync('datacustomcode', args, {
70-
timeout: 120_000,
71-
});
72-
73-
return {
74-
stdout: stdout.trim(),
75-
stderr: stderr.trim(),
76-
};
77-
} catch (error) {
78-
const spawnError = error as SpawnError;
79-
const binaryOutput = spawnError.stderr?.trim() ?? (error instanceof Error ? error.message : String(error));
80-
throw new SfError(
81-
messages.getMessage('error.zipExecutionFailed', [packageDir, binaryOutput]),
82-
'ZipExecutionFailed',
83-
messages.getMessages('actions.zipExecutionFailed')
84-
);
85-
}
86-
}
87-
8843
/**
8944
* Executes datacustomcode deploy with the specified parameters.
9045
*

0 commit comments

Comments
 (0)