Skip to content

Commit

Permalink
Merge pull request #946 from microsoft/sandy081/pub-sign
Browse files Browse the repository at this point in the history
Support publishing signed extensions
  • Loading branch information
sandy081 committed Apr 4, 2024
2 parents a080290 + e75abdc commit 1ae7a0c
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 27 deletions.
Empty file added dummy.js
Empty file.
112 changes: 105 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -38,10 +38,11 @@
"node": ">= 14"
},
"dependencies": {
"azure-devops-node-api": "^11.0.1",
"azure-devops-node-api": "^12.5.0",
"chalk": "^2.4.2",
"cheerio": "^1.0.0-rc.9",
"commander": "^6.2.1",
"form-data": "^4.0.0",
"glob": "^7.0.6",
"hosted-git-info": "^4.0.2",
"jsonc-parser": "^3.2.0",
Expand Down Expand Up @@ -91,4 +92,4 @@
"watch-files": "src/**",
"spec": "src/test/**/*.ts"
}
}
}
3 changes: 3 additions & 0 deletions src/main.ts
Expand Up @@ -193,6 +193,7 @@ module.exports = function (argv: string[]): void {
)
.option('--no-update-package-json', 'Do not update `package.json`. Valid only when [version] is provided.')
.option('-i, --packagePath <paths...>', 'Publish the provided VSIX packages.')
.option('--sigzipPath <paths...>', 'Signature archives to publish alongside the VSIX packages.')
.option(
'--githubBranch <branch>',
'The GitHub branch used to infer relative links in README.md. Can be overridden by --baseContentUrl and --baseImagesUrl.'
Expand Down Expand Up @@ -230,6 +231,7 @@ module.exports = function (argv: string[]): void {
gitTagVersion,
updatePackageJson,
packagePath,
sigzipPath,
githubBranch,
gitlabBranch,
baseContentUrl,
Expand Down Expand Up @@ -259,6 +261,7 @@ module.exports = function (argv: string[]): void {
gitTagVersion,
updatePackageJson,
packagePath,
sigzipPath,
githubBranch,
gitlabBranch,
baseContentUrl,
Expand Down
61 changes: 45 additions & 16 deletions src/publish.ts
Expand Up @@ -9,6 +9,9 @@ import { getGalleryAPI, read, getPublishedUrl, log, getHubUrl, patchOptionsWithM
import { Manifest } from './manifest';
import { readVSIXPackage } from './zip';
import { validatePublisher } from './validation';
import { GalleryApi } from 'azure-devops-node-api/GalleryApi';
import FormData from 'form-data';
import { basename } from 'path';

const tmpName = promisify(tmp.tmpName);

Expand Down Expand Up @@ -69,6 +72,8 @@ export interface IPublishOptions {
readonly allowMissingRepository?: boolean;
readonly skipDuplicate?: boolean;
readonly skipLicense?: boolean;

readonly sigzipPath?: string[];
}

export async function publish(options: IPublishOptions = {}): Promise<any> {
Expand All @@ -81,7 +86,8 @@ export async function publish(options: IPublishOptions = {}): Promise<any> {
);
}

for (const packagePath of options.packagePath) {
for (let index = 0; index < options.packagePath.length; index++) {
const packagePath = options.packagePath[index];
const vsix = await readVSIXPackage(packagePath);
let target: string | undefined;

Expand All @@ -107,7 +113,7 @@ export async function publish(options: IPublishOptions = {}): Promise<any> {
}
}

await _publish(packagePath, vsix.manifest, { ...options, target });
await _publish(packagePath, options.sigzipPath?.[index], vsix.manifest, { ...options, target });
}
} else {
const cwd = options.cwd || process.cwd();
Expand All @@ -121,12 +127,12 @@ export async function publish(options: IPublishOptions = {}): Promise<any> {
for (const target of options.targets) {
const packagePath = await tmpName();
const packageResult = await pack({ ...options, target, packagePath });
await _publish(packagePath, packageResult.manifest, { ...options, target });
await _publish(packagePath, undefined, packageResult.manifest, { ...options, target });
}
} else {
const packagePath = await tmpName();
const packageResult = await pack({ ...options, packagePath });
await _publish(packagePath, packageResult.manifest, options);
await _publish(packagePath, undefined, packageResult.manifest, options);
}
}
}
Expand All @@ -141,7 +147,7 @@ export interface IInternalPublishOptions {
readonly skipDuplicate?: boolean;
}

async function _publish(packagePath: string, manifest: Manifest, options: IInternalPublishOptions) {
async function _publish(packagePath: string, sigzipPath: string | undefined, manifest: Manifest, options: IInternalPublishOptions) {
validatePublisher(manifest.publisher);

if (manifest.enableProposedApi && !options.allowAllProposedApis && !options.noVerify) {
Expand Down Expand Up @@ -202,22 +208,30 @@ async function _publish(packagePath: string, manifest: Manifest, options: IInter

}

try {
await api.updateExtension(undefined, packageStream, manifest.publisher, manifest.name);
} catch (err: any) {
if (err.statusCode === 409) {
if (options.skipDuplicate) {
log.done(`Version ${manifest.version} is already published. Skipping publish.`);
return;
if (sigzipPath) {
await _publishSignedPackage(api, basename(packagePath), packageStream, basename(sigzipPath), fs.createReadStream(sigzipPath), manifest);
} else {
try {
await api.updateExtension(undefined, packageStream, manifest.publisher, manifest.name);
} catch (err: any) {
if (err.statusCode === 409) {
if (options.skipDuplicate) {
log.done(`Version ${manifest.version} is already published. Skipping publish.`);
return;
} else {
throw new Error(`${description} already exists.`);
}
} else {
throw new Error(`${description} already exists.`);
throw err;
}
} else {
throw err;
}
}
} else {
await api.createExtension(undefined, packageStream);
if (sigzipPath) {
await _publishSignedPackage(api, basename(packagePath), packageStream, basename(sigzipPath), fs.createReadStream(sigzipPath), manifest);
} else {
await api.createExtension(undefined, packageStream);
}
}
} catch (err: any) {
const message = (err && err.message) || '';
Expand All @@ -236,6 +250,21 @@ async function _publish(packagePath: string, manifest: Manifest, options: IInter
log.done(`Published ${description}.`);
}

async function _publishSignedPackage(api: GalleryApi, packageName: string, packageStream: fs.ReadStream, sigzipName: string, sigzipStream: fs.ReadStream, manifest: Manifest) {
const extensionType = 'Visual Studio Code';
const form = new FormData();
const lineBreak = '\r\n';
form.setBoundary('0f411892-ef48-488f-89d3-4f0546e84723');
form.append('vsix', packageStream, {
header: `--${form.getBoundary()}${lineBreak}Content-Disposition: attachment; name=vsix; filename=${packageName}${lineBreak}Content-Type: application/octet-stream${lineBreak}${lineBreak}`
});
form.append('sigzip', sigzipStream, {
header: `--${form.getBoundary()}${lineBreak}Content-Disposition: attachment; name=sigzip; filename=${sigzipName}${lineBreak}Content-Type: application/octet-stream${lineBreak}${lineBreak}`
});

return await api.publishExtensionWithPublisherSignature(undefined, form, manifest.publisher, manifest.name, extensionType);
}

export interface IUnpublishOptions extends IPublishOptions {
id?: string;
force?: boolean;
Expand Down
4 changes: 2 additions & 2 deletions src/store.ts
@@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import { homedir } from 'os';
import { read, getGalleryAPI, getSecurityRolesAPI, log } from './util';
import { read, getGalleryAPI, getSecurityRolesAPI, log, getMarketplaceUrl } from './util';
import { validatePublisher } from './validation';
import { readManifest } from './package';

Expand Down Expand Up @@ -142,7 +142,7 @@ export async function verifyPat(pat: string, publisherName?: string): Promise<vo
}

async function requestPAT(publisherName: string): Promise<string> {
console.log('https://marketplace.visualstudio.com/manage/publishers/');
console.log(`${getMarketplaceUrl()}/manage/publishers/`);

const pat = await read(`Personal Access Token for publisher '${publisherName}':`, { silent: true, replace: '*' });
await verifyPat(pat, publisherName);
Expand Down
4 changes: 4 additions & 0 deletions src/util.ts
Expand Up @@ -23,6 +23,10 @@ export function getPublishedUrl(extension: string): string {
return `${marketplaceUrl}/items?itemName=${extension}`;
}

export function getMarketplaceUrl(): string {
return marketplaceUrl;
}

export function getHubUrl(publisher: string, name: string): string {
return `${marketplaceUrl}/manage/publishers/${publisher}/extensions/${name}/hub`;
}
Expand Down

0 comments on commit 1ae7a0c

Please sign in to comment.