@pnp/cli-microsoft365 7.5.0 → 7.6.0-beta.443bfd8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +3 -0
- package/allCommands.json +1 -1
- package/allCommandsFull.json +1 -1
- package/dist/Auth.js +10 -8
- package/dist/cli/cli.js +1 -1
- package/dist/m365/purview/commands/threatassessment/threatassessment-add.js +123 -0
- package/dist/m365/purview/commands/threatassessment/threatassessment-list.js +104 -0
- package/dist/m365/purview/commands.js +3 -1
- package/dist/m365/spfx/commands/project/DeployWorkflow.js +111 -0
- package/dist/m365/spfx/commands/project/project-azuredevops-pipeline-add.js +183 -0
- package/dist/m365/spfx/commands/project/project-azuredevops-pipeline-model.js +2 -0
- package/dist/m365/spfx/commands/project/project-github-workflow-add.js +3 -4
- package/dist/m365/spfx/commands.js +1 -0
- package/dist/m365/spo/commands/user/user-remove.js +93 -16
- package/docs/docs/cmd/purview/threatassessment/threatassessment-add.mdx +131 -0
- package/docs/docs/cmd/purview/threatassessment/threatassessment-list.mdx +110 -0
- package/docs/docs/cmd/spfx/project/project-azuredevops-pipeline-add.mdx +87 -0
- package/docs/docs/cmd/spo/user/user-remove.mdx +40 -9
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
package/dist/Auth.js
CHANGED
|
@@ -144,7 +144,7 @@ export class Auth {
|
|
|
144
144
|
else {
|
|
145
145
|
if (debug) {
|
|
146
146
|
if (!accessToken) {
|
|
147
|
-
await logger.logToStderr(`No token found for resource ${resource}
|
|
147
|
+
await logger.logToStderr(`No token found for resource ${resource}.`);
|
|
148
148
|
}
|
|
149
149
|
else {
|
|
150
150
|
await logger.logToStderr(`Access token expired. Token: ${accessToken.accessToken}, ExpiresAt: ${accessToken.expiresOn}`);
|
|
@@ -193,9 +193,9 @@ export class Auth {
|
|
|
193
193
|
const response = await getTokenPromise(resource, logger, debug, fetchNew);
|
|
194
194
|
if (!response) {
|
|
195
195
|
if (debug) {
|
|
196
|
-
await logger.logToStderr(
|
|
196
|
+
await logger.logToStderr('getTokenPromise authentication result is null.');
|
|
197
197
|
}
|
|
198
|
-
throw
|
|
198
|
+
throw 'Failed to retrieve an access token. Please try again.';
|
|
199
199
|
}
|
|
200
200
|
else {
|
|
201
201
|
if (debug) {
|
|
@@ -387,7 +387,7 @@ export class Auth {
|
|
|
387
387
|
scopes: [`${resource}/.default`]
|
|
388
388
|
});
|
|
389
389
|
}
|
|
390
|
-
async ensureAccessTokenWithCertificate(resource, logger, debug) {
|
|
390
|
+
async ensureAccessTokenWithCertificate(resource, logger, debug, fetchNew) {
|
|
391
391
|
const nodeForge = (await import('node-forge')).default;
|
|
392
392
|
const { pem, pki, asn1, pkcs12 } = nodeForge;
|
|
393
393
|
if (debug) {
|
|
@@ -442,7 +442,8 @@ export class Auth {
|
|
|
442
442
|
}
|
|
443
443
|
this.clientApplication = await this.getConfidentialClient(logger, debug, this.connection.thumbprint, cert);
|
|
444
444
|
return this.clientApplication.acquireTokenByClientCredential({
|
|
445
|
-
scopes: [`${resource}/.default`]
|
|
445
|
+
scopes: [`${resource}/.default`],
|
|
446
|
+
skipCache: fetchNew
|
|
446
447
|
});
|
|
447
448
|
}
|
|
448
449
|
async ensureAccessTokenWithIdentity(resource, logger, debug) {
|
|
@@ -562,10 +563,11 @@ export class Auth {
|
|
|
562
563
|
}
|
|
563
564
|
}
|
|
564
565
|
}
|
|
565
|
-
async ensureAccessTokenWithSecret(resource, logger, debug) {
|
|
566
|
+
async ensureAccessTokenWithSecret(resource, logger, debug, fetchNew) {
|
|
566
567
|
this.clientApplication = await this.getConfidentialClient(logger, debug, undefined, undefined, this.connection.secret);
|
|
567
568
|
return this.clientApplication.acquireTokenByClientCredential({
|
|
568
|
-
scopes: [`${resource}/.default`]
|
|
569
|
+
scopes: [`${resource}/.default`],
|
|
570
|
+
skipCache: fetchNew
|
|
569
571
|
});
|
|
570
572
|
}
|
|
571
573
|
async calculateThumbprint(certificate) {
|
|
@@ -579,7 +581,7 @@ export class Auth {
|
|
|
579
581
|
let resource = url;
|
|
580
582
|
const pos = resource.indexOf('/', 8);
|
|
581
583
|
if (pos > -1) {
|
|
582
|
-
resource = resource.
|
|
584
|
+
resource = resource.substring(0, pos);
|
|
583
585
|
}
|
|
584
586
|
if (resource === 'https://api.bap.microsoft.com' ||
|
|
585
587
|
resource === 'https://api.powerapps.com' ||
|
package/dist/cli/cli.js
CHANGED
|
@@ -98,7 +98,7 @@ async function execute(rawArgs) {
|
|
|
98
98
|
if (parsedArgs.output !== 'none') {
|
|
99
99
|
printHelp(await getHelpMode(parsedArgs));
|
|
100
100
|
}
|
|
101
|
-
return
|
|
101
|
+
return;
|
|
102
102
|
}
|
|
103
103
|
delete cli.optionsFromArgs.options._;
|
|
104
104
|
delete cli.optionsFromArgs.options['--'];
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _PurviewThreatAssessmentAddCommand_instances, _PurviewThreatAssessmentAddCommand_initTelemetry, _PurviewThreatAssessmentAddCommand_initOptions, _PurviewThreatAssessmentAddCommand_initValidators, _PurviewThreatAssessmentAddCommand_initOptionSets;
|
|
7
|
+
import request from '../../../../request.js';
|
|
8
|
+
import { accessToken } from '../../../../utils/accessToken.js';
|
|
9
|
+
import GraphCommand from '../../../base/GraphCommand.js';
|
|
10
|
+
import commands from '../../commands.js';
|
|
11
|
+
import auth from '../../../../Auth.js';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
class PurviewThreatAssessmentAddCommand extends GraphCommand {
|
|
15
|
+
get name() {
|
|
16
|
+
return commands.THREATASSESSMENT_ADD;
|
|
17
|
+
}
|
|
18
|
+
get description() {
|
|
19
|
+
return 'Create a threat assessment';
|
|
20
|
+
}
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
_PurviewThreatAssessmentAddCommand_instances.add(this);
|
|
24
|
+
this.allowedTypes = ['file', 'url'];
|
|
25
|
+
this.allowedExpectedAssessments = ['block', 'unblock'];
|
|
26
|
+
this.allowedCategories = ['spam', 'phishing', 'malware'];
|
|
27
|
+
__classPrivateFieldGet(this, _PurviewThreatAssessmentAddCommand_instances, "m", _PurviewThreatAssessmentAddCommand_initTelemetry).call(this);
|
|
28
|
+
__classPrivateFieldGet(this, _PurviewThreatAssessmentAddCommand_instances, "m", _PurviewThreatAssessmentAddCommand_initOptions).call(this);
|
|
29
|
+
__classPrivateFieldGet(this, _PurviewThreatAssessmentAddCommand_instances, "m", _PurviewThreatAssessmentAddCommand_initValidators).call(this);
|
|
30
|
+
__classPrivateFieldGet(this, _PurviewThreatAssessmentAddCommand_instances, "m", _PurviewThreatAssessmentAddCommand_initOptionSets).call(this);
|
|
31
|
+
}
|
|
32
|
+
async commandAction(logger, args) {
|
|
33
|
+
try {
|
|
34
|
+
if (accessToken.isAppOnlyAccessToken(auth.connection.accessTokens[this.resource].accessToken)) {
|
|
35
|
+
throw 'This command currently does not support app only permissions.';
|
|
36
|
+
}
|
|
37
|
+
if (this.verbose) {
|
|
38
|
+
await logger.logToStderr(`Adding threat assessment of type ${args.options.type} with expected assessment ${args.options.expectedAssessment} and category ${args.options.category}`);
|
|
39
|
+
}
|
|
40
|
+
const requestBody = {
|
|
41
|
+
expectedAssessment: args.options.expectedAssessment,
|
|
42
|
+
category: args.options.category,
|
|
43
|
+
url: args.options.url,
|
|
44
|
+
contentData: args.options.path && fs.readFileSync(args.options.path).toString('base64'),
|
|
45
|
+
fileName: args.options.path && path.basename(args.options.path)
|
|
46
|
+
};
|
|
47
|
+
switch (args.options.type) {
|
|
48
|
+
case 'file':
|
|
49
|
+
requestBody['@odata.type'] = '#microsoft.graph.fileAssessmentRequest';
|
|
50
|
+
break;
|
|
51
|
+
case 'url':
|
|
52
|
+
requestBody['@odata.type'] = '#microsoft.graph.urlAssessmentRequest';
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
const requestOptions = {
|
|
56
|
+
url: `${this.resource}/v1.0/informationProtection/threatAssessmentRequests`,
|
|
57
|
+
headers: {
|
|
58
|
+
accept: 'application/json;odata.metadata=none'
|
|
59
|
+
},
|
|
60
|
+
data: requestBody,
|
|
61
|
+
responseType: 'json'
|
|
62
|
+
};
|
|
63
|
+
const response = await request.post(requestOptions);
|
|
64
|
+
await logger.log(response);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
this.handleRejectedODataPromise(err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
_PurviewThreatAssessmentAddCommand_instances = new WeakSet(), _PurviewThreatAssessmentAddCommand_initTelemetry = function _PurviewThreatAssessmentAddCommand_initTelemetry() {
|
|
72
|
+
this.telemetry.push((args) => {
|
|
73
|
+
Object.assign(this.telemetryProperties, {
|
|
74
|
+
path: typeof args.options.path !== 'undefined',
|
|
75
|
+
url: typeof args.options.url !== 'undefined'
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}, _PurviewThreatAssessmentAddCommand_initOptions = function _PurviewThreatAssessmentAddCommand_initOptions() {
|
|
79
|
+
this.options.unshift({
|
|
80
|
+
option: '-t, --type <type>',
|
|
81
|
+
autocomplete: this.allowedTypes
|
|
82
|
+
}, {
|
|
83
|
+
option: '-e, --expectedAssessment <expectedAssessment>',
|
|
84
|
+
autocomplete: this.allowedExpectedAssessments
|
|
85
|
+
}, {
|
|
86
|
+
option: '-c, --category <category>',
|
|
87
|
+
autocomplete: this.allowedCategories
|
|
88
|
+
}, {
|
|
89
|
+
option: '-p, --path [path]'
|
|
90
|
+
}, {
|
|
91
|
+
option: '-u, --url [url]'
|
|
92
|
+
});
|
|
93
|
+
}, _PurviewThreatAssessmentAddCommand_initValidators = function _PurviewThreatAssessmentAddCommand_initValidators() {
|
|
94
|
+
this.validators.push(async (args) => {
|
|
95
|
+
if (!this.allowedTypes.some(type => type === args.options.type)) {
|
|
96
|
+
return `${args.options.type} is not an allowed type. Allowed types are ${this.allowedTypes.join('|')}`;
|
|
97
|
+
}
|
|
98
|
+
if (!this.allowedExpectedAssessments.some(expectedAssessment => expectedAssessment === args.options.expectedAssessment)) {
|
|
99
|
+
return `${args.options.expectedAssessment} is not an allowed expected assessment. Allowed expected assessments are ${this.allowedExpectedAssessments.join('|')}`;
|
|
100
|
+
}
|
|
101
|
+
if (!this.allowedCategories.some(category => category === args.options.category)) {
|
|
102
|
+
return `${args.options.category} is not an allowed category. Allowed categories are ${this.allowedCategories.join('|')}`;
|
|
103
|
+
}
|
|
104
|
+
if (args.options.path && !fs.existsSync(args.options.path)) {
|
|
105
|
+
return `File '${args.options.path}' not found. Please provide a valid path to the file.`;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
}, _PurviewThreatAssessmentAddCommand_initOptionSets = function _PurviewThreatAssessmentAddCommand_initOptionSets() {
|
|
110
|
+
this.optionSets.push({
|
|
111
|
+
options: ['path'],
|
|
112
|
+
runsWhen: (args) => {
|
|
113
|
+
return args.options.type === 'file';
|
|
114
|
+
}
|
|
115
|
+
}, {
|
|
116
|
+
options: ['url'],
|
|
117
|
+
runsWhen: (args) => {
|
|
118
|
+
return args.options.type === 'url';
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
export default new PurviewThreatAssessmentAddCommand();
|
|
123
|
+
//# sourceMappingURL=threatassessment-add.js.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _PurviewThreatAssessmentListCommand_instances, _a, _PurviewThreatAssessmentListCommand_initTelemetry, _PurviewThreatAssessmentListCommand_initOptions, _PurviewThreatAssessmentListCommand_initValidators;
|
|
7
|
+
import { odata } from '../../../../utils/odata.js';
|
|
8
|
+
import GraphCommand from '../../../base/GraphCommand.js';
|
|
9
|
+
import commands from '../../commands.js';
|
|
10
|
+
class PurviewThreatAssessmentListCommand extends GraphCommand {
|
|
11
|
+
get name() {
|
|
12
|
+
return commands.THREATASSESSMENT_LIST;
|
|
13
|
+
}
|
|
14
|
+
get description() {
|
|
15
|
+
return 'Get a list of threat assessments';
|
|
16
|
+
}
|
|
17
|
+
defaultProperties() {
|
|
18
|
+
return ['id', 'type', 'category'];
|
|
19
|
+
}
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
_PurviewThreatAssessmentListCommand_instances.add(this);
|
|
23
|
+
__classPrivateFieldGet(this, _PurviewThreatAssessmentListCommand_instances, "m", _PurviewThreatAssessmentListCommand_initTelemetry).call(this);
|
|
24
|
+
__classPrivateFieldGet(this, _PurviewThreatAssessmentListCommand_instances, "m", _PurviewThreatAssessmentListCommand_initOptions).call(this);
|
|
25
|
+
__classPrivateFieldGet(this, _PurviewThreatAssessmentListCommand_instances, "m", _PurviewThreatAssessmentListCommand_initValidators).call(this);
|
|
26
|
+
}
|
|
27
|
+
async commandAction(logger, args) {
|
|
28
|
+
if (this.verbose) {
|
|
29
|
+
logger.logToStderr('Retrieving a list of threat assessments');
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const filter = this.getFilterQuery(args.options);
|
|
33
|
+
const items = await odata.getAllItems(`${this.resource}/v1.0/informationProtection/threatAssessmentRequests${filter}`, 'minimal');
|
|
34
|
+
let itemsToReturn = [];
|
|
35
|
+
switch (args.options.type) {
|
|
36
|
+
case 'mail':
|
|
37
|
+
itemsToReturn = items.filter(item => item['@odata.type'] === '#microsoft.graph.mailAssessmentRequest');
|
|
38
|
+
break;
|
|
39
|
+
case 'emailFile':
|
|
40
|
+
itemsToReturn = items.filter(item => item['@odata.type'] === '#microsoft.graph.emailFileAssessmentRequest');
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
itemsToReturn = items;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
for (const item of itemsToReturn) {
|
|
47
|
+
item['type'] = this.getConvertedType(item['@odata.type']);
|
|
48
|
+
delete item['@odata.type'];
|
|
49
|
+
}
|
|
50
|
+
await logger.log(itemsToReturn);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
this.handleRejectedODataJsonPromise(err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Content type is not equal to type.
|
|
57
|
+
// Threat assessments of type emailFile have contentType mail as well.
|
|
58
|
+
// This function gets the correct filter URL to be able to query the least amount of data
|
|
59
|
+
getFilterQuery(options) {
|
|
60
|
+
if (options.type === undefined) {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
if (options.type === 'emailFile') {
|
|
64
|
+
return `?$filter=contentType eq 'mail'`;
|
|
65
|
+
}
|
|
66
|
+
return `?$filter=contentType eq '${options.type}'`;
|
|
67
|
+
}
|
|
68
|
+
getConvertedType(type) {
|
|
69
|
+
switch (type) {
|
|
70
|
+
case '#microsoft.graph.mailAssessmentRequest':
|
|
71
|
+
return 'mail';
|
|
72
|
+
case '#microsoft.graph.fileAssessmentRequest':
|
|
73
|
+
return 'file';
|
|
74
|
+
case '#microsoft.graph.emailFileAssessmentRequest':
|
|
75
|
+
return 'emailFile';
|
|
76
|
+
case '#microsoft.graph.urlAssessmentRequest':
|
|
77
|
+
return 'url';
|
|
78
|
+
default:
|
|
79
|
+
return 'Unknown';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
_a = PurviewThreatAssessmentListCommand, _PurviewThreatAssessmentListCommand_instances = new WeakSet(), _PurviewThreatAssessmentListCommand_initTelemetry = function _PurviewThreatAssessmentListCommand_initTelemetry() {
|
|
84
|
+
this.telemetry.push((args) => {
|
|
85
|
+
Object.assign(this.telemetryProperties, {
|
|
86
|
+
type: typeof args.options.type !== 'undefined'
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}, _PurviewThreatAssessmentListCommand_initOptions = function _PurviewThreatAssessmentListCommand_initOptions() {
|
|
90
|
+
this.options.unshift({
|
|
91
|
+
option: '-t, --type [type]',
|
|
92
|
+
autocomplete: _a.allowedTypes
|
|
93
|
+
});
|
|
94
|
+
}, _PurviewThreatAssessmentListCommand_initValidators = function _PurviewThreatAssessmentListCommand_initValidators() {
|
|
95
|
+
this.validators.push(async (args) => {
|
|
96
|
+
if (args.options.type && _a.allowedTypes.indexOf(args.options.type) < 0) {
|
|
97
|
+
return `${args.options.type} is not a valid type. Allowed values are ${_a.allowedTypes.join(', ')}`;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
PurviewThreatAssessmentListCommand.allowedTypes = ['mail', 'file', 'emailFile', 'url'];
|
|
103
|
+
export default new PurviewThreatAssessmentListCommand();
|
|
104
|
+
//# sourceMappingURL=threatassessment-list.js.map
|
|
@@ -18,6 +18,8 @@ export default {
|
|
|
18
18
|
SENSITIVITYLABEL_GET: `${prefix} sensitivitylabel get`,
|
|
19
19
|
SENSITIVITYLABEL_LIST: `${prefix} sensitivitylabel list`,
|
|
20
20
|
SENSITIVITYLABEL_POLICYSETTINGS_LIST: `${prefix} sensitivitylabel policysettings list`,
|
|
21
|
-
|
|
21
|
+
THREATASSESSMENT_ADD: `${prefix} threatassessment add`,
|
|
22
|
+
THREATASSESSMENT_GET: `${prefix} threatassessment get`,
|
|
23
|
+
THREATASSESSMENT_LIST: `${prefix} threatassessment list`
|
|
22
24
|
};
|
|
23
25
|
//# sourceMappingURL=commands.js.map
|
|
@@ -53,4 +53,115 @@ export const workflow = {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
|
+
export const pipeline = {
|
|
57
|
+
name: "Deploy Solution",
|
|
58
|
+
trigger: {
|
|
59
|
+
branches: {
|
|
60
|
+
include: [
|
|
61
|
+
"main"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
pool: {
|
|
66
|
+
vmImage: "ubuntu-latest"
|
|
67
|
+
},
|
|
68
|
+
variables: [
|
|
69
|
+
{
|
|
70
|
+
name: "CertificateBase64Encoded",
|
|
71
|
+
value: ""
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "CertificateSecureFileId",
|
|
75
|
+
value: ""
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "CertificatePassword",
|
|
79
|
+
value: ""
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "EntraAppId",
|
|
83
|
+
value: ""
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "UserName",
|
|
87
|
+
value: ""
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "Password",
|
|
91
|
+
value: ""
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "TenantId",
|
|
95
|
+
value: ""
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "SharePointBaseUrl",
|
|
99
|
+
value: ""
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "PackageName",
|
|
103
|
+
value: ""
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "SiteAppCatalogUrl",
|
|
107
|
+
value: ""
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
stages: [
|
|
111
|
+
{
|
|
112
|
+
stage: "Build_and_Deploy",
|
|
113
|
+
jobs: [
|
|
114
|
+
{
|
|
115
|
+
job: "Build_and_Deploy",
|
|
116
|
+
steps: [
|
|
117
|
+
{
|
|
118
|
+
task: "NodeTool@0",
|
|
119
|
+
displayName: "Use Node.js",
|
|
120
|
+
inputs: {
|
|
121
|
+
versionSpec: "18.x"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
task: "Npm@1",
|
|
126
|
+
displayName: "Run npm install",
|
|
127
|
+
inputs: {
|
|
128
|
+
command: "install"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
task: "Gulp@0",
|
|
133
|
+
displayName: "Gulp bundle",
|
|
134
|
+
inputs: {
|
|
135
|
+
gulpFile: "./gulpfile.js",
|
|
136
|
+
targets: "bundle",
|
|
137
|
+
arguments: "--ship"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
task: "Gulp@0",
|
|
142
|
+
displayName: "Gulp package",
|
|
143
|
+
inputs: {
|
|
144
|
+
targets: "package-solution",
|
|
145
|
+
arguments: "--ship"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
task: "Npm@1",
|
|
150
|
+
displayName: "Install CLI for Microsoft 365",
|
|
151
|
+
inputs: {
|
|
152
|
+
command: "custom",
|
|
153
|
+
verbose: false,
|
|
154
|
+
customCommand: "install -g @pnp/cli-microsoft365"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
script: "\n{{login}} \nm365 spo set --url '$(SharePointBaseUrl)' \n{{addApp}} \n{{deploy}}\n",
|
|
159
|
+
displayName: "CLI for Microsoft 365 Deploy App"
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
};
|
|
56
167
|
//# sourceMappingURL=DeployWorkflow.js.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _SpfxProjectAzureDevOpsPipelineAddCommand_instances, _a, _SpfxProjectAzureDevOpsPipelineAddCommand_initTelemetry, _SpfxProjectAzureDevOpsPipelineAddCommand_initOptions, _SpfxProjectAzureDevOpsPipelineAddCommand_initValidators;
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import yaml from 'yaml';
|
|
10
|
+
import { CommandError } from '../../../../Command.js';
|
|
11
|
+
import commands from '../../commands.js';
|
|
12
|
+
import { BaseProjectCommand } from './base-project-command.js';
|
|
13
|
+
import { validation } from '../../../../utils/validation.js';
|
|
14
|
+
import { pipeline } from './DeployWorkflow.js';
|
|
15
|
+
import { fsUtil } from '../../../../utils/fsUtil.js';
|
|
16
|
+
import { parse } from 'semver';
|
|
17
|
+
class SpfxProjectAzureDevOpsPipelineAddCommand extends BaseProjectCommand {
|
|
18
|
+
get name() {
|
|
19
|
+
return commands.PROJECT_AZUREDEVOPS_PIPELINE_ADD;
|
|
20
|
+
}
|
|
21
|
+
get description() {
|
|
22
|
+
return 'Adds a Azure DevOps Pipeline for a SharePoint Framework project.';
|
|
23
|
+
}
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
_SpfxProjectAzureDevOpsPipelineAddCommand_instances.add(this);
|
|
27
|
+
__classPrivateFieldGet(this, _SpfxProjectAzureDevOpsPipelineAddCommand_instances, "m", _SpfxProjectAzureDevOpsPipelineAddCommand_initTelemetry).call(this);
|
|
28
|
+
__classPrivateFieldGet(this, _SpfxProjectAzureDevOpsPipelineAddCommand_instances, "m", _SpfxProjectAzureDevOpsPipelineAddCommand_initOptions).call(this);
|
|
29
|
+
__classPrivateFieldGet(this, _SpfxProjectAzureDevOpsPipelineAddCommand_instances, "m", _SpfxProjectAzureDevOpsPipelineAddCommand_initValidators).call(this);
|
|
30
|
+
}
|
|
31
|
+
async commandAction(logger, args) {
|
|
32
|
+
this.projectRootPath = this.getProjectRoot(process.cwd());
|
|
33
|
+
if (this.projectRootPath === null) {
|
|
34
|
+
throw new CommandError(`Couldn't find project root folder`, _a.ERROR_NO_PROJECT_ROOT_FOLDER);
|
|
35
|
+
}
|
|
36
|
+
const solutionPackageJsonFile = path.join(this.projectRootPath, 'package.json');
|
|
37
|
+
const packageJson = fs.readFileSync(solutionPackageJsonFile, 'utf-8');
|
|
38
|
+
const solutionName = JSON.parse(packageJson).name;
|
|
39
|
+
if (this.debug) {
|
|
40
|
+
logger.logToStderr(`Adding Azure DevOps pipeline in the current SPFx project`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
this.updatePipeline(solutionName, pipeline, args.options);
|
|
44
|
+
this.savePipeline(pipeline);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new CommandError(error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
savePipeline(pipeline) {
|
|
51
|
+
const azureDevOpsPath = path.join(this.projectRootPath, '.azuredevops');
|
|
52
|
+
fsUtil.ensureDirectory(azureDevOpsPath);
|
|
53
|
+
const pipelinesPath = path.join(azureDevOpsPath, 'pipelines');
|
|
54
|
+
fsUtil.ensureDirectory(pipelinesPath);
|
|
55
|
+
const pipelineFile = path.join(pipelinesPath, 'deploy-spfx-solution.yml');
|
|
56
|
+
fs.writeFileSync(path.resolve(pipelineFile), yaml.stringify(pipeline), 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
updatePipeline(solutionName, pipeline, options) {
|
|
59
|
+
if (options.name) {
|
|
60
|
+
pipeline.name = options.name;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
delete pipeline.name;
|
|
64
|
+
}
|
|
65
|
+
if (options.branchName) {
|
|
66
|
+
pipeline.trigger.branches.include[0] = options.branchName;
|
|
67
|
+
}
|
|
68
|
+
const version = this.getProjectVersion();
|
|
69
|
+
if (!version) {
|
|
70
|
+
throw `Unable to determine the version of the current SharePoint Framework project`;
|
|
71
|
+
}
|
|
72
|
+
const minorVersion = parse(version)?.minor;
|
|
73
|
+
if (minorVersion === undefined) {
|
|
74
|
+
throw `Unable to determine the minor version of the current SharePoint Framework project`;
|
|
75
|
+
}
|
|
76
|
+
if (minorVersion < 18) {
|
|
77
|
+
const node = this.getNodeAction(pipeline);
|
|
78
|
+
if (node.inputs) {
|
|
79
|
+
node.inputs.versionSpec = '16.x';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const script = this.getScriptAction(pipeline);
|
|
83
|
+
if (script.script) {
|
|
84
|
+
if (options.loginMethod === 'user') {
|
|
85
|
+
script.script = script.script.replace(`{{login}}`, `m365 login --authType password --userName '$(UserName)' --password '$(Password)'`);
|
|
86
|
+
pipeline.variables = pipeline.variables.filter(v => v.name !== 'CertificateBase64Encoded' &&
|
|
87
|
+
v.name !== 'CertificateSecureFileId' &&
|
|
88
|
+
v.name !== 'CertificatePassword' &&
|
|
89
|
+
v.name !== 'EntraAppId' &&
|
|
90
|
+
v.name !== 'TenantId');
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
script.script = script.script.replace(`{{login}}`, `m365 login --authType certificate --certificateBase64Encoded '$(CertificateBase64Encoded)' --password '$(CertificatePassword)' --appId '$(EntraAppId)' --tenant '$(TenantId)'`);
|
|
94
|
+
pipeline.variables = pipeline.variables.filter(v => v.name !== 'UserName' &&
|
|
95
|
+
v.name !== 'Password');
|
|
96
|
+
}
|
|
97
|
+
if (options.scope === 'sitecollection') {
|
|
98
|
+
script.script = script.script.replace(`{{deploy}}`, `m365 spo app deploy --name '$(PackageName)' --appCatalogScope sitecollection --appCatalogUrl '$(SiteAppCatalogUrl)'`);
|
|
99
|
+
script.script = script.script.replace(`{{addApp}}`, `m365 spo app add --filePath '$(Build.SourcesDirectory)/sharepoint/solution/$(PackageName)' --appCatalogScope sitecollection --appCatalogUrl '$(SiteAppCatalogUrl)' --overwrite`);
|
|
100
|
+
this.assignPipelineVariables(pipeline, 'SiteAppCatalogUrl', options.siteUrl);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
script.script = script.script.replace(`{{deploy}}`, `m365 spo app deploy --name '$(PackageName)' --appCatalogScope 'tenant'`);
|
|
104
|
+
script.script = script.script.replace(`{{addApp}}`, `m365 spo app add --filePath '$(Build.SourcesDirectory)/sharepoint/solution/$(PackageName)' --overwrite`);
|
|
105
|
+
pipeline.variables = pipeline.variables.filter(v => v.name !== 'SiteAppCatalogUrl');
|
|
106
|
+
}
|
|
107
|
+
if (solutionName) {
|
|
108
|
+
this.assignPipelineVariables(pipeline, 'PackageName', `${solutionName}.sppkg`);
|
|
109
|
+
}
|
|
110
|
+
if (options.skipFeatureDeployment) {
|
|
111
|
+
script.script = script.script.replace(`m365 spo app deploy `, `m365 spo app deploy --skipFeatureDeployment `);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
assignPipelineVariables(pipeline, variableName, newVariableValue) {
|
|
116
|
+
const variable = pipeline.variables.find(v => v.name === variableName);
|
|
117
|
+
if (variable) {
|
|
118
|
+
variable.value = newVariableValue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
getScriptAction(pipeline) {
|
|
122
|
+
const steps = this.getPipelineSteps(pipeline);
|
|
123
|
+
return steps.find(step => step.script);
|
|
124
|
+
}
|
|
125
|
+
getNodeAction(pipeline) {
|
|
126
|
+
const steps = this.getPipelineSteps(pipeline);
|
|
127
|
+
return steps.find(step => step.task && step.task.indexOf('NodeTool') >= 0);
|
|
128
|
+
}
|
|
129
|
+
getPipelineSteps(pipeline) {
|
|
130
|
+
return pipeline.stages[0].jobs[0].steps;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
_a = SpfxProjectAzureDevOpsPipelineAddCommand, _SpfxProjectAzureDevOpsPipelineAddCommand_instances = new WeakSet(), _SpfxProjectAzureDevOpsPipelineAddCommand_initTelemetry = function _SpfxProjectAzureDevOpsPipelineAddCommand_initTelemetry() {
|
|
134
|
+
this.telemetry.push((args) => {
|
|
135
|
+
Object.assign(this.telemetryProperties, {
|
|
136
|
+
name: typeof args.options.name !== 'undefined',
|
|
137
|
+
branchName: typeof args.options.branchName !== 'undefined',
|
|
138
|
+
loginMethod: typeof args.options.loginMethod !== 'undefined',
|
|
139
|
+
scope: typeof args.options.scope !== 'undefined',
|
|
140
|
+
skipFeatureDeployment: !!args.options.skipFeatureDeployment
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}, _SpfxProjectAzureDevOpsPipelineAddCommand_initOptions = function _SpfxProjectAzureDevOpsPipelineAddCommand_initOptions() {
|
|
144
|
+
this.options.unshift({
|
|
145
|
+
option: '-n, --name [name]'
|
|
146
|
+
}, {
|
|
147
|
+
option: '-b, --branchName [branchName]'
|
|
148
|
+
}, {
|
|
149
|
+
option: '-l, --loginMethod [loginMethod]',
|
|
150
|
+
autocomplete: _a.loginMethod
|
|
151
|
+
}, {
|
|
152
|
+
option: '-s, --scope [scope]',
|
|
153
|
+
autocomplete: _a.scope
|
|
154
|
+
}, {
|
|
155
|
+
option: '-u, --siteUrl [siteUrl]'
|
|
156
|
+
}, {
|
|
157
|
+
option: '--skipFeatureDeployment'
|
|
158
|
+
});
|
|
159
|
+
}, _SpfxProjectAzureDevOpsPipelineAddCommand_initValidators = function _SpfxProjectAzureDevOpsPipelineAddCommand_initValidators() {
|
|
160
|
+
this.validators.push(async (args) => {
|
|
161
|
+
if (args.options.scope && args.options.scope === 'sitecollection') {
|
|
162
|
+
if (!args.options.siteUrl) {
|
|
163
|
+
return `siteUrl option has to be defined when scope set to ${args.options.scope}`;
|
|
164
|
+
}
|
|
165
|
+
const isValidSharePointUrl = validation.isValidSharePointUrl(args.options.siteUrl);
|
|
166
|
+
if (isValidSharePointUrl !== true) {
|
|
167
|
+
return isValidSharePointUrl;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (args.options.loginMethod && _a.loginMethod.indexOf(args.options.loginMethod) < 0) {
|
|
171
|
+
return `${args.options.loginMethod} is not a valid login method. Allowed values are ${_a.loginMethod.join(', ')}`;
|
|
172
|
+
}
|
|
173
|
+
if (args.options.scope && _a.scope.indexOf(args.options.scope) < 0) {
|
|
174
|
+
return `${args.options.scope} is not a valid scope. Allowed values are ${_a.scope.join(', ')}`;
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
SpfxProjectAzureDevOpsPipelineAddCommand.loginMethod = ['application', 'user'];
|
|
180
|
+
SpfxProjectAzureDevOpsPipelineAddCommand.scope = ['tenant', 'sitecollection'];
|
|
181
|
+
SpfxProjectAzureDevOpsPipelineAddCommand.ERROR_NO_PROJECT_ROOT_FOLDER = 1;
|
|
182
|
+
export default new SpfxProjectAzureDevOpsPipelineAddCommand();
|
|
183
|
+
//# sourceMappingURL=project-azuredevops-pipeline-add.js.map
|
|
@@ -13,6 +13,7 @@ import { validation } from '../../../../utils/validation.js';
|
|
|
13
13
|
import commands from '../../commands.js';
|
|
14
14
|
import { workflow } from './DeployWorkflow.js';
|
|
15
15
|
import { BaseProjectCommand } from './base-project-command.js';
|
|
16
|
+
import { parse } from 'semver';
|
|
16
17
|
class SpfxProjectGithubWorkflowAddCommand extends BaseProjectCommand {
|
|
17
18
|
get name() {
|
|
18
19
|
return commands.PROJECT_GITHUB_WORKFLOW_ADD;
|
|
@@ -67,10 +68,8 @@ class SpfxProjectGithubWorkflowAddCommand extends BaseProjectCommand {
|
|
|
67
68
|
if (!version) {
|
|
68
69
|
throw `Unable to determine the version of the current SharePoint Framework project`;
|
|
69
70
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const minorVersion = minorVersionString ? Number(minorVersionString) : null;
|
|
73
|
-
if (minorVersion === null || isNaN(minorVersion)) {
|
|
71
|
+
const minorVersion = parse(version)?.minor;
|
|
72
|
+
if (minorVersion === undefined) {
|
|
74
73
|
throw `Unable to determine the minor version of the current SharePoint Framework project`;
|
|
75
74
|
}
|
|
76
75
|
if (minorVersion < 18) {
|
|
@@ -2,6 +2,7 @@ const prefix = 'spfx';
|
|
|
2
2
|
export default {
|
|
3
3
|
DOCTOR: `${prefix} doctor`,
|
|
4
4
|
PACKAGE_GENERATE: `${prefix} package generate`,
|
|
5
|
+
PROJECT_AZUREDEVOPS_PIPELINE_ADD: `${prefix} project azuredevops pipeline add`,
|
|
5
6
|
PROJECT_DOCTOR: `${prefix} project doctor`,
|
|
6
7
|
PROJECT_EXTERNALIZE: `${prefix} project externalize`,
|
|
7
8
|
PROJECT_GITHUB_WORKFLOW_ADD: `${prefix} project github workflow add`,
|