Skip to content

Commit dccb791

Browse files
milanholemansCopilot
andauthored
Updates 'spfx project github workflow add' sppkg path resolution. Closes pnp#7233
Co-authored-by: Copilot <copilot@github.com>
1 parent 8572e4d commit dccb791

6 files changed

Lines changed: 133 additions & 32 deletions

src/m365/spfx/commands/project/DeployWorkflow.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ export const workflow: GitHubWorkflow = {
1919
steps: [
2020
{
2121
name: "Checkout",
22-
uses: "actions/checkout@v4"
22+
uses: "actions/checkout@v6"
2323
},
2424
{
2525
name: "Use Node.js",
26-
uses: "actions/setup-node@v4",
26+
uses: "actions/setup-node@v6",
2727
with: {
2828
"node-version": "${{ env.NodeVersion }}"
2929
}
@@ -38,7 +38,7 @@ export const workflow: GitHubWorkflow = {
3838
},
3939
{
4040
name: "CLI for Microsoft 365 Login",
41-
uses: "pnp/action-cli-login@v2.2.4",
41+
uses: "pnp/action-cli-login@v4",
4242
with: {
4343
"CERTIFICATE_ENCODED": "${{ secrets.CERTIFICATE_ENCODED }}",
4444
"CERTIFICATE_PASSWORD": "${{ secrets.CERTIFICATE_PASSWORD }}",
@@ -48,9 +48,9 @@ export const workflow: GitHubWorkflow = {
4848
},
4949
{
5050
name: "CLI for Microsoft 365 Deploy App",
51-
uses: "pnp/action-cli-deploy@v4.0.0",
51+
uses: "pnp/action-cli-deploy@v6",
5252
with: {
53-
"APP_FILE_PATH": "sharepoint/solution/{{ solutionName }}.sppkg",
53+
"APP_FILE_PATH": "sharepoint/{{ sppkgPath }}",
5454
"SKIP_FEATURE_DEPLOYMENT": false,
5555
"OVERWRITE": true
5656
}
@@ -109,6 +109,10 @@ export const pipeline: AzureDevOpsPipeline = {
109109
name: "PackageName",
110110
value: ""
111111
},
112+
{
113+
name: "SppkgPath",
114+
value: ""
115+
},
112116
{
113117
name: "SiteAppCatalogUrl",
114118
value: ""

src/m365/spfx/commands/project/base-project-command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export abstract class BaseProjectCommand extends AnonymousCommand {
152152
return undefined;
153153
}
154154

155-
private readAndParseJsonFile(filePath: string, project: Project, keyPath: string): Project {
155+
protected readAndParseJsonFile(filePath: string, project: Project, keyPath: string): Project {
156156
if (fs.existsSync(filePath)) {
157157
try {
158158
const source = formatting.removeSingleLineComments(fs.readFileSync(filePath, 'utf-8'));

src/m365/spfx/commands/project/project-azuredevops-pipeline-add.spec.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
6969
sinon.stub(command as any, 'getProjectRoot').returns(projectPath);
7070

7171
sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
72-
if (fakePath.toString() === path.join(projectPath, '.azuredevops', 'pipelines')) {
72+
if (fakePath.toString() === path.join(projectPath, 'package.json')) {
73+
return true;
74+
}
75+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
76+
return true;
77+
}
78+
else if (fakePath.toString() === path.join(projectPath, '.azuredevops', 'pipelines')) {
7379
return true;
7480
}
7581

@@ -80,6 +86,9 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
8086
if (fakePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
8187
return '{"name": "test"}';
8288
}
89+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
90+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
91+
}
8392

8493
throw `Invalid path: ${fakePath}`;
8594
});
@@ -135,7 +144,13 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
135144
it('creates a default workflow (debug)', async () => {
136145
sinon.stub(command as any, 'getProjectRoot').returns(projectPath);
137146
sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
138-
if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
147+
if (fakePath.toString() === path.join(projectPath, 'package.json')) {
148+
return true;
149+
}
150+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
151+
return true;
152+
}
153+
else if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
139154
return true;
140155
}
141156
else if (fakePath.toString() === path.join(projectPath, '.azuredevops', 'pipelines')) {
@@ -149,6 +164,9 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
149164
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
150165
return '{"name": "test"}';
151166
}
167+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
168+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
169+
}
152170

153171
throw `Invalid path: ${filePath}`;
154172
});
@@ -168,12 +186,21 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
168186
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
169187
return '{"name": "test"}';
170188
}
189+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
190+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
191+
}
171192

172193
throw `Invalid path: ${filePath}`;
173194
});
174195

175196
sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
176-
if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
197+
if (fakePath.toString() === path.join(projectPath, 'package.json')) {
198+
return true;
199+
}
200+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
201+
return true;
202+
}
203+
else if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
177204
return true;
178205
}
179206
else if (fakePath.toString() === path.join(projectPath, '.azuredevops', 'pipelines')) {
@@ -198,12 +225,21 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
198225
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
199226
return '{"name": "test"}';
200227
}
228+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
229+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
230+
}
201231

202232
throw `Invalid path: ${filePath}`;
203233
});
204234

205235
sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
206-
if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
236+
if (fakePath.toString() === path.join(projectPath, 'package.json')) {
237+
return true;
238+
}
239+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
240+
return true;
241+
}
242+
else if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
207243
return true;
208244
}
209245
else if (fakePath.toString() === path.join(projectPath, '.azuredevops', 'pipelines')) {
@@ -228,12 +264,21 @@ describe(commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD, () => {
228264
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
229265
return '{"name": "test"}';
230266
}
267+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
268+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
269+
}
231270

232271
throw `Invalid path: ${filePath}`;
233272
});
234273

235274
sinon.stub(fs, 'existsSync').callsFake((fakePath) => {
236-
if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
275+
if (fakePath.toString() === path.join(projectPath, 'package.json')) {
276+
return true;
277+
}
278+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
279+
return true;
280+
}
281+
else if (fakePath.toString() === path.join(projectPath, '.azuredevops')) {
237282
return true;
238283
}
239284
else if (fakePath.toString() === path.join(projectPath, '.azuredevops', 'pipelines')) {

src/m365/spfx/commands/project/project-azuredevops-pipeline-add.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AzureDevOpsPipeline, AzureDevOpsPipelineStep } from './project-azuredev
1212
import GlobalOptions from '../../../../GlobalOptions.js';
1313
import { versions } from '../SpfxCompatibilityMatrix.js';
1414
import { spfx } from '../../../../utils/spfx.js';
15+
import { Project } from './project-model/index.js';
1516

1617
interface CommandArgs {
1718
options: Options;
@@ -117,16 +118,17 @@ class SpfxProjectAzureDevOpsPipelineAddCommand extends BaseProjectCommand {
117118
throw new CommandError(`Couldn't find project root folder`, SpfxProjectAzureDevOpsPipelineAddCommand.ERROR_NO_PROJECT_ROOT_FOLDER);
118119
}
119120

120-
const solutionPackageJsonFile: string = path.join(this.projectRootPath, 'package.json');
121-
const packageJson: string = fs.readFileSync(solutionPackageJsonFile, 'utf-8');
122-
const solutionName = JSON.parse(packageJson).name;
123-
124121
if (this.debug) {
125122
await logger.logToStderr(`Adding Azure DevOps pipeline in the current SPFx project`);
126123
}
127124

128125
try {
129-
this.updatePipeline(solutionName, pipeline, args.options);
126+
const project: Project = { path: this.projectRootPath };
127+
this.readAndParseJsonFile(path.join(this.projectRootPath, 'config', 'package-solution.json'), project, 'packageSolutionJson');
128+
129+
const sppkgPath = (project.packageSolutionJson as any)?.paths?.zippedPackage;
130+
131+
this.updatePipeline(sppkgPath, pipeline, args.options);
130132
this.savePipeline(pipeline);
131133
}
132134
catch (error: any) {
@@ -145,7 +147,7 @@ class SpfxProjectAzureDevOpsPipelineAddCommand extends BaseProjectCommand {
145147
fs.writeFileSync(path.resolve(pipelineFile), yaml.stringify(pipeline), 'utf-8');
146148
}
147149

148-
private updatePipeline(solutionName: string, pipeline: AzureDevOpsPipeline, options: GlobalOptions): void {
150+
private updatePipeline(sppkgPath: string | undefined, pipeline: AzureDevOpsPipeline, options: GlobalOptions): void {
149151
if (options.name) {
150152
pipeline.name = options.name;
151153
}
@@ -195,17 +197,18 @@ class SpfxProjectAzureDevOpsPipelineAddCommand extends BaseProjectCommand {
195197

196198
if (options.scope === 'sitecollection') {
197199
script.script = script.script.replace(`{{deploy}}`, `m365 spo app deploy --name '$(PackageName)' --appCatalogScope sitecollection --appCatalogUrl '$(SiteAppCatalogUrl)'`);
198-
script.script = script.script.replace(`{{addApp}}`, `m365 spo app add --filePath '$(Build.SourcesDirectory)/sharepoint/solution/$(PackageName)' --appCatalogScope sitecollection --appCatalogUrl '$(SiteAppCatalogUrl)' --overwrite`);
200+
script.script = script.script.replace(`{{addApp}}`, `m365 spo app add --filePath '$(Build.SourcesDirectory)/sharepoint/$(SppkgPath)' --appCatalogScope sitecollection --appCatalogUrl '$(SiteAppCatalogUrl)' --overwrite`);
199201
this.assignPipelineVariables(pipeline, 'SiteAppCatalogUrl', options.siteUrl);
200202
}
201203
else {
202204
script.script = script.script.replace(`{{deploy}}`, `m365 spo app deploy --name '$(PackageName)' --appCatalogScope 'tenant'`);
203-
script.script = script.script.replace(`{{addApp}}`, `m365 spo app add --filePath '$(Build.SourcesDirectory)/sharepoint/solution/$(PackageName)' --overwrite`);
205+
script.script = script.script.replace(`{{addApp}}`, `m365 spo app add --filePath '$(Build.SourcesDirectory)/sharepoint/$(SppkgPath)' --overwrite`);
204206
pipeline.variables = pipeline.variables.filter(v => v.name !== 'SiteAppCatalogUrl');
205207
}
206208

207-
if (solutionName) {
208-
this.assignPipelineVariables(pipeline, 'PackageName', `${solutionName}.sppkg`);
209+
if (sppkgPath) {
210+
this.assignPipelineVariables(pipeline, 'SppkgPath', sppkgPath);
211+
this.assignPipelineVariables(pipeline, 'PackageName', path.basename(sppkgPath));
209212
}
210213

211214
if (options.skipFeatureDeployment) {

src/m365/spfx/commands/project/project-github-workflow-add.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
107107
else if (fakePath.toString() === path.join(projectPath, '.github', 'workflows')) {
108108
return true;
109109
}
110+
else if (fakePath.toString() === path.join(projectPath, 'package.json')) {
111+
return true;
112+
}
113+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
114+
return true;
115+
}
110116

111117
throw `Invalid path: ${fakePath}`;
112118
});
@@ -115,6 +121,9 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
115121
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
116122
return '{"name": "test"}';
117123
}
124+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
125+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
126+
}
118127

119128
throw `Invalid path: ${filePath}`;
120129
});
@@ -134,6 +143,12 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
134143
if (fakePath.toString() === path.join(projectPath, '.github', 'workflows')) {
135144
return true;
136145
}
146+
else if (fakePath.toString() === path.join(projectPath, 'package.json')) {
147+
return true;
148+
}
149+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
150+
return true;
151+
}
137152

138153
return false;
139154
});
@@ -142,6 +157,9 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
142157
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
143158
return '{"name": "test"}';
144159
}
160+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
161+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
162+
}
145163

146164
throw `Invalid path: ${filePath}`;
147165
});
@@ -169,6 +187,9 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
169187
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
170188
return '{"name": "test"}';
171189
}
190+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
191+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
192+
}
172193

173194
throw `Invalid path: ${filePath}`;
174195
});
@@ -180,6 +201,12 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
180201
else if (fakePath.toString() === path.join(projectPath, '.github', 'workflows')) {
181202
return true;
182203
}
204+
else if (fakePath.toString() === path.join(projectPath, 'package.json')) {
205+
return true;
206+
}
207+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
208+
return true;
209+
}
183210

184211
throw `Invalid path: ${fakePath}`;
185212
});
@@ -199,6 +226,9 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
199226
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
200227
return '{"name": "test"}';
201228
}
229+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
230+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
231+
}
202232

203233
throw `Invalid path: ${filePath}`;
204234
});
@@ -210,6 +240,12 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
210240
else if (fakePath.toString() === path.join(projectPath, '.github', 'workflows')) {
211241
return true;
212242
}
243+
else if (fakePath.toString() === path.join(projectPath, 'package.json')) {
244+
return true;
245+
}
246+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
247+
return true;
248+
}
213249

214250
throw `Invalid path: ${fakePath}`;
215251
});
@@ -228,6 +264,9 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
228264
if (filePath.toString() === path.join(projectPath, 'package.json') && options === 'utf-8') {
229265
return '{"name": "test"}';
230266
}
267+
else if (filePath.toString() === path.join(projectPath, 'config', 'package-solution.json') && options === 'utf-8') {
268+
return '{"paths": {"zippedPackage": "solution/test.sppkg"}}';
269+
}
231270

232271
throw `Invalid path: ${filePath}`;
233272
});
@@ -239,6 +278,12 @@ describe(commands.PROJECT_GITHUB_WORKFLOW_ADD, () => {
239278
else if (fakePath.toString() === path.join(projectPath, '.github', 'workflows')) {
240279
return true;
241280
}
281+
else if (fakePath.toString() === path.join(projectPath, 'package.json')) {
282+
return true;
283+
}
284+
else if (fakePath.toString() === path.join(projectPath, 'config', 'package-solution.json')) {
285+
return true;
286+
}
242287

243288
throw `Invalid path: ${fakePath}`;
244289
});

0 commit comments

Comments
 (0)