Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ export interface IPublicGitHubRepositorySourceOptions {

// Matches https://<host>/<owner>/<repo>[.git] — HTTPS only, host-agnostic for GHE support.
const REPO_URL_REGEX: RegExp = /^https:\/\/([^/]+)\/([^/]+)\/([^/]+?)(\.git)?$/;
const OFFICIAL_SPFX_REPO_HOST: string = 'github.com';
const OFFICIAL_SPFX_REPO_OWNER: string = 'sharepoint';
const OFFICIAL_SPFX_REPO_NAME: string = 'spfx';
const OFFICIAL_SPFX_AVAILABLE_VERSIONS_MESSAGE: string = 'latest (1.22), next (1.23.0-beta)';

/**
* @public
Expand Down Expand Up @@ -208,13 +212,41 @@ export class PublicGitHubRepositorySource extends BaseSPFxTemplateRepositorySour
}
const response: Response = await fetch(downloadUrl, fetchInit);
if (!response.ok) {
const invalidSpfxVersionErrorMessage: string | undefined = this._getInvalidSpfxVersionErrorMessage(
response.status
);
if (invalidSpfxVersionErrorMessage) {
throw new Error(invalidSpfxVersionErrorMessage);
}

throw new Error(`Failed to download repository: ${response.status} ${response.statusText}`);
}

const zipBuffer: Buffer = Buffer.from(await response.arrayBuffer());
return this._extractZipBuffer(zipBuffer);
}

private _getInvalidSpfxVersionErrorMessage(status: number): string | undefined {
if (status !== 404 || !this._ref.startsWith('version/')) {
return undefined;
}

const { host, owner, repo } = this._parseRepoUrl();
if (
host !== OFFICIAL_SPFX_REPO_HOST ||
owner.toLowerCase() !== OFFICIAL_SPFX_REPO_OWNER ||
repo.toLowerCase() !== OFFICIAL_SPFX_REPO_NAME
) {
return undefined;
}

const requestedVersion: string = this._ref.slice('version/'.length);
return (
`Unsupported value for --spfx-version "${requestedVersion}". ` +
`Available versions: ${OFFICIAL_SPFX_AVAILABLE_VERSIONS_MESSAGE}.`
);
}

private _extractZipBuffer(zipBuffer: Buffer): Map<string, Buffer> {
const fileMap: Map<string, Buffer> = new Map<string, Buffer>();
const zip: AdmZip = new AdmZip(zipBuffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,24 @@ describe(PublicGitHubRepositorySource.name, () => {
);
});

it('should list available versions when --spfx-version does not exist in the official repo', async () => {
mockFetch.mockResolvedValue({
ok: false,
status: 404,
statusText: 'Not Found'
} as unknown as Response);

const source = new PublicGitHubRepositorySource({
repoUrl: 'https://github.com/SharePoint/spfx',
branch: 'version/1.21',
terminal
});

await expect(source.getTemplatesAsync()).rejects.toThrow(
/Unsupported value for --spfx-version "1.21"\. Available versions: latest \(1\.22\), next \(1\.23\.0-beta\)\./
);
});

it('should throw error when network request fails', async () => {
mockFetch.mockRejectedValue(new Error('Network error'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ exports[`PublicGitHubRepositorySource getTemplates should send Authorization hea

exports[`PublicGitHubRepositorySource getTemplates should throw error when download fails 1`] = `Array []`;

exports[`PublicGitHubRepositorySource getTemplates should list available versions when --spfx-version does not exist in the official repo 1`] = `Array []`;

exports[`PublicGitHubRepositorySource getTemplates should throw error when network request fails 1`] = `Array []`;

exports[`PublicGitHubRepositorySource getTemplates should use GHE API URL for enterprise hosts 1`] = `Array []`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/spfx-cli",
"comment": "Improve the unsupported --spfx-version error to list the currently available versions.",
"type": "none"
}
],
"packageName": "@microsoft/spfx-cli"
}