From c5c5e0b96becbe737e9f39b15afbc3f821d2c38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3jcik?= Date: Sat, 19 Apr 2025 00:47:41 +0200 Subject: [PATCH 1/2] Updates node version handling in ci cd commands. Closes #6242 --- .../spfx/commands/project/DeployWorkflow.ts | 11 +- .../project-azuredevops-pipeline-add.spec.ts | 128 ------------------ .../project-azuredevops-pipeline-add.ts | 25 ---- .../project-github-workflow-add.spec.ts | 127 ----------------- .../project/project-github-workflow-add.ts | 22 --- .../project/project-github-workflow-model.ts | 3 + 6 files changed, 12 insertions(+), 304 deletions(-) diff --git a/src/m365/spfx/commands/project/DeployWorkflow.ts b/src/m365/spfx/commands/project/DeployWorkflow.ts index 300d1d76594..0d1a3b7d7f2 100644 --- a/src/m365/spfx/commands/project/DeployWorkflow.ts +++ b/src/m365/spfx/commands/project/DeployWorkflow.ts @@ -13,6 +13,9 @@ export const workflow: GitHubWorkflow = { jobs: { "build-and-deploy": { "runs-on": "ubuntu-latest", + env: { + NodeVersion: "22.x" + }, steps: [ { name: "Checkout", @@ -22,7 +25,7 @@ export const workflow: GitHubWorkflow = { name: "Use Node.js", uses: "actions/setup-node@v4", with: { - "node-version": "18.x" + "node-version": "${{ env.NodeVersion }}" } }, { @@ -109,6 +112,10 @@ export const pipeline: AzureDevOpsPipeline = { { name: "SiteAppCatalogUrl", value: "" + }, + { + name: "NodeVersion", + value: "22.x" } ], stages: [ @@ -122,7 +129,7 @@ export const pipeline: AzureDevOpsPipeline = { task: "NodeTool@0", displayName: "Use Node.js", inputs: { - versionSpec: "18.x" + versionSpec: "$(NodeVersion)" } }, { diff --git a/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.spec.ts b/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.spec.ts index a81e402535f..6a6a08bd04e 100644 --- a/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.spec.ts +++ b/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.spec.ts @@ -44,7 +44,6 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => { afterEach(() => { sinonUtil.restore([ (command as any).getProjectRoot, - (command as any).getProjectVersion, fs.existsSync, fs.readFileSync, fs.writeFileSync @@ -90,8 +89,6 @@ 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); @@ -132,7 +129,6 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => { it('creates a default workflow (debug)', async () => { sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath)); - sinon.stub(fs, 'existsSync').callsFake((fakePath) => { if (fakePath.toString().endsWith('.azuredevops')) { return true; @@ -152,38 +148,6 @@ 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: { debug: true } } as any); - assert(writeFileSyncStub.calledWith(path.join(process.cwd(), projectPath, '/.azuredevops', 'pipelines', 'deploy-spfx-solution.yml')), 'workflow file not created'); - }); - - it('creates a default workflow for SPFx 1.18.x', async () => { - sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath)); - - 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(fs, 'readFileSync').callsFake((path, options) => { - if (path.toString().endsWith('package.json') && options === 'utf-8') { - return '{"name": "test"}'; - } - - return ''; - }); - - sinon.stub(command as any, 'getProjectVersion').returns('1.18.0'); - const writeFileSyncStub: sinon.SinonStub = sinon.stub(fs, 'writeFileSync').resolves({}); await command.action(logger, { options: { debug: true } } as any); @@ -212,101 +176,9 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => { return false; }); - sinon.stub(command as any, 'getProjectVersion').returns('1.18.0'); - sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; }); await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('error')); }); - - 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('.azuredevops')) { - return true; - } - else if (fakePath.toString().endsWith('pipelines')) { - return true; - } - - return false; - }); - - sinon.stub(command as any, 'getProjectVersion').returns(undefined); - - sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; }); - - await assert.rejects(command.action(logger, { options: {} } as any), - new CommandError(`Unable to determine the version of the current SharePoint Framework project`, undefined)); - }); - - 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('1'); - - sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; }); - - await assert.rejects(command.action(logger, { options: {} } as any), - new CommandError(`Unable to determine the minor version of the current SharePoint Framework project`, undefined)); - }); - - it('handles error with unknown minor version of SPFx when minor version is NaN', 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('1.aaa.0'); - - sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; }); - - await assert.rejects(command.action(logger, { options: {} } as any), - new CommandError(`Unable to determine the minor version of the current SharePoint Framework project`, undefined)); - }); }); \ No newline at end of file diff --git a/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts b/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts index 0b103f9b0a1..d8c0b671e40 100644 --- a/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts +++ b/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts @@ -10,7 +10,6 @@ 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 { parse } from 'semver'; interface CommandArgs { @@ -157,25 +156,6 @@ 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`; - } - - const minorVersion = parse(version)?.minor; - - if (minorVersion === undefined) { - throw `Unable to determine the minor version of the current SharePoint Framework project`; - } - - if (minorVersion < 18) { - const node = this.getNodeAction(pipeline); - if (node.inputs) { - node.inputs.versionSpec = '16.x'; - } - } - const script = this.getScriptAction(pipeline); if (script.script) { if (options.loginMethod === 'user') { @@ -229,11 +209,6 @@ class SpfxProjectAzureDevOpsPipelineAddCommand extends BaseProjectCommand { return steps.find(step => step.script)!; } - private getNodeAction(pipeline: AzureDevOpsPipeline): AzureDevOpsPipelineStep { - const steps = this.getPipelineSteps(pipeline); - return steps.find(step => step.task && step.task.indexOf('NodeTool') >= 0)!; - } - private getPipelineSteps(pipeline: AzureDevOpsPipeline): AzureDevOpsPipelineStep[] { return pipeline.stages[0].jobs[0].steps; } diff --git a/src/m365/spfx/commands/project/project-github-workflow-add.spec.ts b/src/m365/spfx/commands/project/project-github-workflow-add.spec.ts index 139f4ea5f49..856804c2be4 100644 --- a/src/m365/spfx/commands/project/project-github-workflow-add.spec.ts +++ b/src/m365/spfx/commands/project/project-github-workflow-add.spec.ts @@ -44,7 +44,6 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => { afterEach(() => { sinonUtil.restore([ (command as any).getProjectRoot, - (command as any).getProjectVersion, fs.existsSync, fs.readFileSync, fs.writeFileSync @@ -117,38 +116,6 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_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: { debug: true } } as any); - assert(writeFileSyncStub.calledWith(path.join(process.cwd(), projectPath, '/.github', 'workflows', 'deploy-spfx-solution.yml')), 'workflow file not created'); - }); - - it('creates a default workflow for SPFx 1.18.x', async () => { - sinon.stub(command as any, 'getProjectRoot').returns(path.join(process.cwd(), projectPath)); - - 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(fs, 'readFileSync').callsFake((path, options) => { - if (path.toString().endsWith('package.json') && options === 'utf-8') { - return '{"name": "test"}'; - } - - return ''; - }); - - sinon.stub(command as any, 'getProjectVersion').returns('1.18.0'); - const writeFileSyncStub: sinon.SinonStub = sinon.stub(fs, 'writeFileSync').resolves({}); await command.action(logger, { options: { debug: true } } as any); @@ -182,8 +149,6 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_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', manuallyTrigger: true, skipFeatureDeployment: true, loginMethod: 'user', scope: 'sitecollection' } } as any); @@ -212,101 +177,9 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => { return false; }); - sinon.stub(command as any, 'getProjectVersion').returns('1.18.0'); - sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; }); await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('error')); }); - - 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').callsFake(() => { throw 'error'; }); - - await assert.rejects(command.action(logger, { options: {} } as any), - new CommandError(`Unable to determine the version of the current SharePoint Framework project`, undefined)); - }); - - 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('.github')) { - return true; - } - else if (fakePath.toString().endsWith('workflows')) { - return true; - } - - return false; - }); - - sinon.stub(command as any, 'getProjectVersion').returns('1'); - - sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; }); - - await assert.rejects(command.action(logger, { options: {} } as any), - new CommandError(`Unable to determine the minor version of the current SharePoint Framework project`, undefined)); - }); - - it('handles error with unknown minor version of SPFx when minor version is NaN', 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('1.aaa.0'); - - sinon.stub(fs, 'writeFileSync').callsFake(() => { throw 'error'; }); - - await assert.rejects(command.action(logger, { options: {} } as any), - new CommandError(`Unable to determine the minor version of the current SharePoint Framework project`, undefined)); - }); }); \ No newline at end of file diff --git a/src/m365/spfx/commands/project/project-github-workflow-add.ts b/src/m365/spfx/commands/project/project-github-workflow-add.ts index 0ef39f20db1..200a5f2a01d 100644 --- a/src/m365/spfx/commands/project/project-github-workflow-add.ts +++ b/src/m365/spfx/commands/project/project-github-workflow-add.ts @@ -10,7 +10,6 @@ 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 { parse } from 'semver'; interface CommandArgs { options: Options; @@ -161,22 +160,6 @@ class SpfxProjectGithubWorkflowAddCommand extends BaseProjectCommand { workflow.on.workflow_dispatch = null; } - const version = this.getProjectVersion(); - - if (!version) { - throw `Unable to determine the version of the current SharePoint Framework project`; - } - - const minorVersion = parse(version)?.minor; - - if (minorVersion === undefined) { - throw `Unable to determine the minor version of the current SharePoint Framework project`; - } - - if (minorVersion < 18) { - this.getNodeAction(workflow).with!['node-version'] = '16.x'; - } - if (options.skipFeatureDeployment) { this.getDeployAction(workflow).with!.SKIP_FEATURE_DEPLOYMENT = true; } @@ -211,11 +194,6 @@ class SpfxProjectGithubWorkflowAddCommand extends BaseProjectCommand { return steps.find(step => step.uses && step.uses.indexOf('action-cli-deploy') >= 0)!; } - private getNodeAction(workflow: GitHubWorkflow): GitHubWorkflowStep { - const steps = this.getWorkFlowSteps(workflow); - return steps.find(step => step.uses && step.uses.indexOf('actions/setup-node@') >= 0)!; - } - private getWorkFlowSteps(workflow: GitHubWorkflow): GitHubWorkflowStep[] { return workflow.jobs['build-and-deploy'].steps; } diff --git a/src/m365/spfx/commands/project/project-github-workflow-model.ts b/src/m365/spfx/commands/project/project-github-workflow-model.ts index cf247d8da78..008ef6ec60c 100644 --- a/src/m365/spfx/commands/project/project-github-workflow-model.ts +++ b/src/m365/spfx/commands/project/project-github-workflow-model.ts @@ -9,6 +9,9 @@ export interface GitHubWorkflow { jobs: { "build-and-deploy": { "runs-on": string; + env: { + NodeVersion: string; + }; steps: GitHubWorkflowStep[]; }; }; From 49afc3c810963b25cc44cd59c53ba4106145baf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20W=C3=B3jcik?= <58668583+Adam-it@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:22:06 +0200 Subject: [PATCH 2/2] Update src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts --- .../spfx/commands/project/project-azuredevops-pipeline-add.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts b/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts index d8c0b671e40..6830ce245f9 100644 --- a/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts +++ b/src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts @@ -11,7 +11,6 @@ import { fsUtil } from '../../../../utils/fsUtil.js'; import { AzureDevOpsPipeline, AzureDevOpsPipelineStep } from './project-azuredevops-pipeline-model.js'; import GlobalOptions from '../../../../GlobalOptions.js'; - interface CommandArgs { options: Options; }