Skip to content

Commit 033d602

Browse files
authored
Merge pull request #1100 from stevedlawrence:reproducible-vsix
Allow for reproducible .vsix packages
2 parents 0a22db7 + c367404 commit 033d602

File tree

2 files changed

+59
-12
lines changed

2 files changed

+59
-12
lines changed

src/package.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ export class ManifestProcessor extends BaseProcessor {
505505
if (!minEngineVersion) {
506506
throw new Error('Failed to get minVersion of engines.vscode')
507507
}
508-
508+
509509
if (target) {
510510
if (engineSemver.version !== 'latest' && !semver.satisfies(minEngineVersion, '>=1.61', { includePrerelease: true })) {
511511
throw new Error(
@@ -1804,12 +1804,20 @@ function writeVsix(files: IFile[], packagePath: string): Promise<void> {
18041804
() =>
18051805
new Promise((c, e) => {
18061806
const zip = new yazl.ZipFile();
1807+
const zipOptions: Partial<yazl.Options> = {};
1808+
1809+
// reproducible zip files
1810+
const sde = process.env.SOURCE_DATE_EPOCH;
1811+
if (sde) {
1812+
const epoch = parseInt(sde);
1813+
zipOptions.mtime = new Date(epoch * 1000);
1814+
files = files.sort((a, b) => a.path.localeCompare(b.path))
1815+
}
1816+
18071817
files.forEach(f =>
18081818
isInMemoryFile(f)
1809-
? zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, f.path, {
1810-
mode: f.mode,
1811-
})
1812-
: zip.addFile(f.localPath, f.path, { mode: f.mode })
1819+
? zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, f.path, { ...zipOptions, mode: f.mode })
1820+
: zip.addFile(f.localPath, f.path, { ...zipOptions, mode: f.mode })
18131821
);
18141822
zip.end();
18151823

src/test/package.test.ts

+46-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
versionBump,
1515
VSIX,
1616
LicenseProcessor,
17-
printAndValidatePackagedFiles,
17+
printAndValidatePackagedFiles, pack
1818
} from '../package';
1919
import { ManifestPackage } from '../manifest';
2020
import * as path from 'path';
@@ -2286,13 +2286,13 @@ describe('ManifestProcessor', () => {
22862286
});
22872287

22882288
it('should not throw error for engine version with x (e.g. 1.95.x)', async () => {
2289-
const root = fixture('uuid');
2290-
const manifest = JSON.parse(await fs.promises.readFile(path.join(root, 'package.json'), 'utf8'));
2291-
manifest.engines.vscode = '1.95.x'; // Non-strict semver, but acceptable
2289+
const root = fixture('uuid');
2290+
const manifest = JSON.parse(await fs.promises.readFile(path.join(root, 'package.json'), 'utf8'));
2291+
manifest.engines.vscode = '1.95.x'; // Non-strict semver, but acceptable
22922292

2293-
assert.doesNotThrow(() => new ManifestProcessor(manifest, { target: 'web' }));
2294-
assert.doesNotThrow(() => new ManifestProcessor(manifest, { preRelease: true }));
2295-
});
2293+
assert.doesNotThrow(() => new ManifestProcessor(manifest, { target: 'web' }));
2294+
assert.doesNotThrow(() => new ManifestProcessor(manifest, { preRelease: true }));
2295+
});
22962296
});
22972297

22982298
describe('MarkdownProcessor', () => {
@@ -3196,3 +3196,42 @@ describe('version', function () {
31963196
assert.strictEqual(newManifest.version, '1.0.0');
31973197
});
31983198
});
3199+
3200+
describe('writeVsix', function () {
3201+
this.timeout(60_000);
3202+
3203+
it('should be reproducible', async () => {
3204+
const exampleProject = fixture('manifestFiles');
3205+
const fixtureDir = fixture('');
3206+
3207+
const testDir = tmp.dirSync({ unsafeCleanup: true, tmpdir: fixtureDir });
3208+
const cwd = testDir.name
3209+
3210+
try {
3211+
fs.cpSync(exampleProject, cwd, { recursive: true });
3212+
3213+
const createVsix = async (vsixPath: string, epoch: number) => {
3214+
process.env["SOURCE_DATE_EPOCH"] = `${epoch}`;
3215+
await pack({ cwd, packagePath: vsixPath });
3216+
}
3217+
3218+
const vsix1 = testDir.name + '/vsix1.vsix';
3219+
const vsix2 = testDir.name + '/vsix2.vsix';
3220+
const vsix3 = testDir.name + '/vsix3.vsix';
3221+
3222+
await createVsix(vsix1, 1000000000);
3223+
await createVsix(vsix2, 1000000000);
3224+
await createVsix(vsix3, 1000000002);
3225+
3226+
const vsix1bytes = fs.readFileSync(vsix1);
3227+
const vsix2bytes = fs.readFileSync(vsix2);
3228+
const vsix3bytes = fs.readFileSync(vsix3);
3229+
3230+
assert.deepStrictEqual(vsix1bytes, vsix2bytes);
3231+
assert.notDeepStrictEqual(vsix1bytes, vsix3bytes);
3232+
3233+
} finally {
3234+
testDir.removeCallback();
3235+
}
3236+
});
3237+
});

0 commit comments

Comments
 (0)