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
611 changes: 611 additions & 0 deletions src/m365/spfx/commands/SpfxCompatibilityMatrix.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/m365/spfx/commands/project/DeployWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const workflow: GitHubWorkflow = {
"build-and-deploy": {
"runs-on": "ubuntu-latest",
env: {
NodeVersion: "22.x"
NodeVersion: ""
},
steps: [
{
Expand Down Expand Up @@ -115,7 +115,7 @@ export const pipeline: AzureDevOpsPipeline = {
},
{
name: "NodeVersion",
value: "22.x"
value: ""
}
],
stages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { Logger } from '../../../../cli/Logger.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { spfx } from '../../../../utils/spfx.js';
import { session } from '../../../../utils/session.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import commands from '../../commands.js';
Expand All @@ -22,6 +23,7 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
before(() => {
sinon.stub(telemetry, 'trackEvent').resolves();
sinon.stub(pid, 'getProcessName').callsFake(() => '');
sinon.stub(spfx, 'getHighestNodeVersion').returns('22.0.x');
sinon.stub(session, 'getId').callsFake(() => '');
commandInfo = cli.getCommandInfo(command);
});
Expand All @@ -44,6 +46,7 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
afterEach(() => {
sinonUtil.restore([
(command as any).getProjectRoot,
(command as any).getProjectVersion,
fs.existsSync,
fs.readFileSync,
fs.writeFileSync
Expand Down Expand Up @@ -89,6 +92,8 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
return '';
});

sinon.stub(command as any, 'getProjectVersion').returns('1.16.0');

const writeFileSyncStub: sinon.SinonStub = sinon.stub(fs, 'writeFileSync').resolves({});

await command.action(logger, { options: { name: 'test', branchName: 'dev', skipFeatureDeployment: true, loginMethod: 'user', scope: 'sitecollection', siteUrl: 'https://contoso.sharepoint.com/sites/project' } } as any);
Expand Down Expand Up @@ -148,12 +153,74 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
return '';
});

sinon.stub(command as any, 'getProjectVersion').returns('1.21.1');

const writeFileSyncStub: sinon.SinonStub = sinon.stub(fs, 'writeFileSync').resolves({});

await command.action(logger, { options: { debug: true } } as any);
assert(writeFileSyncStub.calledWith(path.join(process.cwd(), projectPath, '/.azuredevops', 'pipelines', 'deploy-spfx-solution.yml')), 'workflow file not created');
});

it('handles error with unknown minor version of SPFx when missing minor version', async () => {
sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath));

sinon.stub(fs, 'readFileSync').callsFake((path, options) => {
if (path.toString().endsWith('package.json') && options === 'utf-8') {
return '{"name": "test"}';
}

return '';
});

sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
if (fakePath.toString().endsWith('.azuredevops')) {
return true;
}
else if (fakePath.toString().endsWith('pipelines')) {
return true;
}

return false;
});

sinon.stub(command as any, 'getProjectVersion').returns('');

sinon.stub(fs, 'writeFileSync').throws('error');

await assert.rejects(command.action(logger, { options: {} } as any),
new CommandError(`Unable to determine the version of the current SharePoint Framework project`));
});

it('handles error with not found node version', async () => {
sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath));

sinon.stub(fs, 'readFileSync').callsFake((path, options) => {
if (path.toString().endsWith('package.json') && options === 'utf-8') {
return '{"name": "test"}';
}

return '';
});

sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
if (fakePath.toString().endsWith('.azuredevops')) {
return true;
}
else if (fakePath.toString().endsWith('pipelines')) {
return true;
}

return false;
});

sinon.stub(command as any, 'getProjectVersion').returns('99.99.99');

sinon.stub(fs, 'writeFileSync').throws('error');

await assert.rejects(command.action(logger, { options: {} } as any),
new CommandError(`Could not find Node version for 99.99.99 of SharePoint Framework`));
});

it('handles unexpected error', async () => {
sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath));

Expand All @@ -176,6 +243,8 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
return false;
});

sinon.stub(command as any, 'getProjectVersion').returns('1.21.1');

sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; });

await assert.rejects(command.action(logger, { options: {} } as any),
Expand Down
18 changes: 18 additions & 0 deletions src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { pipeline } from './DeployWorkflow.js';
import { fsUtil } from '../../../../utils/fsUtil.js';
import { AzureDevOpsPipeline, AzureDevOpsPipelineStep } from './project-azuredevops-pipeline-model.js';
import GlobalOptions from '../../../../GlobalOptions.js';
import { versions } from '../SpfxCompatibilityMatrix.js';
import { spfx } from '../../../../utils/spfx.js';

interface CommandArgs {
options: Options;
Expand Down Expand Up @@ -155,6 +157,22 @@ class SpfxProjectAzureDevOpsPipelineAddCommand extends BaseProjectCommand {
pipeline.trigger.branches.include[0] = options.branchName;
}

const version = this.getProjectVersion();

if (!version) {
throw 'Unable to determine the version of the current SharePoint Framework project. Could not find the correct version based on @microsoft/generator-sharepoint property in the .yo-rc.json file.';
}

const versionRequirements = versions[version];

if (!versionRequirements) {
throw `Could not find Node version for version '${version}' of SharePoint Framework.`;
}

const nodeVersion: string = spfx.getHighestNodeVersion(versionRequirements.node.range);

this.assignPipelineVariables(pipeline, 'NodeVersion', nodeVersion);

const script = this.getScriptAction(pipeline);
if (script.script) {
if (options.loginMethod === 'user') {
Expand Down
70 changes: 70 additions & 0 deletions src/m365/spfx/commands/project/project-github-workflow-add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { Logger } from '../../../../cli/Logger.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { spfx } from '../../../../utils/spfx.js';
import { session } from '../../../../utils/session.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import commands from '../../commands.js';
Expand All @@ -22,6 +23,7 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
before(() => {
sinon.stub(telemetry, 'trackEvent').resolves();
sinon.stub(pid, 'getProcessName').callsFake(() => '');
sinon.stub(spfx, 'getHighestNodeVersion').returns('22.0.x');
sinon.stub(session, 'getId').callsFake(() => '');
commandInfo = cli.getCommandInfo(command);
});
Expand All @@ -44,6 +46,7 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
afterEach(() => {
sinonUtil.restore([
(command as any).getProjectRoot,
(command as any).getProjectVersion,
fs.existsSync,
fs.readFileSync,
fs.writeFileSync
Expand Down Expand Up @@ -116,6 +119,8 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
return '';
});

sinon.stub(command as any, 'getProjectVersion').returns('1.21.1');

const writeFileSyncStub: sinon.SinonStub = sinon.stub(fs, 'writeFileSync').resolves({});

await command.action(logger, { options: { debug: true } } as any);
Expand Down Expand Up @@ -149,12 +154,75 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
return '';
});

sinon.stub(command as any, 'getProjectVersion').returns('1.21.1');

const writeFileSyncStub: sinon.SinonStub = sinon.stub(fs, 'writeFileSync').resolves({});

await command.action(logger, { options: { name: 'test', branchName: 'dev', manuallyTrigger: true, skipFeatureDeployment: true, loginMethod: 'user', scope: 'sitecollection' } } as any);
assert(writeFileSyncStub.calledWith(path.join(process.cwd(), projectPath, '/.github', 'workflows', 'deploy-spfx-solution.yml')), 'workflow file not created');
});

it('handles error with unknown version of SPFx', async () => {
sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath));

sinon.stub(fs, 'readFileSync').callsFake((path, options) => {
if (path.toString().endsWith('package.json') && options === 'utf-8') {
return '{"name": "test"}';
}

return '';
});

sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
if (fakePath.toString().endsWith('.github')) {
return true;
}
else if (fakePath.toString().endsWith('workflows')) {
return true;
}

return false;
});

sinon.stub(command as any, 'getProjectVersion').returns(undefined);

sinon.stub(fs, 'writeFileSync').throws('error');

await assert.rejects(command.action(logger, { options: {} }),
new CommandError(`Unable to determine the version of the current SharePoint Framework project`));

});

it('handles error with not found node version', async () => {
sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath));

sinon.stub(fs, 'readFileSync').callsFake((path, options) => {
if (path.toString().endsWith('package.json') && options === 'utf-8') {
return '{"name": "test"}';
}

return '';
});

sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
if (fakePath.toString().endsWith('.github')) {
return true;
}
else if (fakePath.toString().endsWith('workflows')) {
return true;
}

return false;
});

sinon.stub(command as any, 'getProjectVersion').returns('99.99.99');

sinon.stub(fs, 'writeFileSync').throws('error');

await assert.rejects(command.action(logger, { options: {} }),
new CommandError(`Could not find Node version for 99.99.99 of SharePoint Framework`));
});

it('handles unexpected error', async () => {
sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath));

Expand All @@ -177,6 +245,8 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
return false;
});

sinon.stub(command as any, 'getProjectVersion').returns('1.21.1');

sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; });

await assert.rejects(command.action(logger, { options: {} } as any),
Expand Down
22 changes: 22 additions & 0 deletions src/m365/spfx/commands/project/project-github-workflow-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import commands from '../../commands.js';
import { workflow } from './DeployWorkflow.js';
import { BaseProjectCommand } from './base-project-command.js';
import { GitHubWorkflow, GitHubWorkflowStep } from './project-github-workflow-model.js';
import { versions } from '../SpfxCompatibilityMatrix.js';
import { spfx } from '../../../../utils/spfx.js';

interface CommandArgs {
options: Options;
Expand Down Expand Up @@ -155,6 +157,22 @@ class SpfxProjectGithubWorkflowAddCommand extends BaseProjectCommand {
workflow.on.push.branches[0] = options.branchName;
}

const version = this.getProjectVersion();

if (!version) {
throw 'Unable to determine the version of the current SharePoint Framework project. Could not find the correct version based on @microsoft/generator-sharepoint property in the .yo-rc.json file.';
}

const versionRequirements = versions[version];

if (!versionRequirements) {
throw `Could not find Node version for ${version} of SharePoint Framework`;
}

const nodeVersion: string = spfx.getHighestNodeVersion(versionRequirements.node.range);

this.assignNodeVersion(workflow, nodeVersion);

if (options.manuallyTrigger) {
// eslint-disable-next-line camelcase
workflow.on.workflow_dispatch = null;
Expand Down Expand Up @@ -184,6 +202,10 @@ class SpfxProjectGithubWorkflowAddCommand extends BaseProjectCommand {
}
}

private assignNodeVersion(workflow: GitHubWorkflow, nodeVersion: string): void {
workflow.jobs['build-and-deploy'].env.NodeVersion = nodeVersion;
}

private getLoginAction(workflow: GitHubWorkflow): GitHubWorkflowStep {
const steps = this.getWorkFlowSteps(workflow);
return steps.find(step => step.uses && step.uses.indexOf('action-cli-login') >= 0)!;
Expand Down
Loading
Loading