@microsoft/managed-apps-cli 0.8.0 → 0.9.0
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/README.md +13 -7
- package/dist/ArgumentProvider.d.ts.map +1 -1
- package/dist/ArgumentProvider.js +26 -19
- package/dist/ArgumentProvider.js.map +1 -1
- package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts +16 -0
- package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts.map +1 -1
- package/dist/Authentication/NodeMsalAuthenticationProvider.js +58 -6
- package/dist/Authentication/NodeMsalAuthenticationProvider.js.map +1 -1
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +22 -7
- package/dist/Cli.js.map +1 -1
- package/dist/CliSettings.d.ts +2 -2
- package/dist/CliSettings.d.ts.map +1 -1
- package/dist/CliSettings.js +2 -2
- package/dist/CliUtils.d.ts +16 -6
- package/dist/CliUtils.d.ts.map +1 -1
- package/dist/CliUtils.js +15 -23
- package/dist/CliUtils.js.map +1 -1
- package/dist/Constants/HelpStrings.d.ts +1 -1
- package/dist/Constants/HelpStrings.d.ts.map +1 -1
- package/dist/Constants/HelpStrings.js +12 -12
- package/dist/Constants/HelpStrings.js.map +1 -1
- package/dist/Constants.d.ts +1 -1
- package/dist/Constants.d.ts.map +1 -1
- package/dist/Constants.js +5 -2
- package/dist/Constants.js.map +1 -1
- package/dist/Errors/GitHubErrorMap.d.ts.map +1 -1
- package/dist/Errors/GitHubErrorMap.js +17 -10
- package/dist/Errors/GitHubErrorMap.js.map +1 -1
- package/dist/HttpClient/CliHttpClient.d.ts +13 -0
- package/dist/HttpClient/CliHttpClient.d.ts.map +1 -1
- package/dist/HttpClient/CliHttpClient.js +24 -0
- package/dist/HttpClient/CliHttpClient.js.map +1 -1
- package/dist/Logger/CliLogger.d.ts +50 -8
- package/dist/Logger/CliLogger.d.ts.map +1 -1
- package/dist/Logger/CliLogger.js +92 -37
- package/dist/Logger/CliLogger.js.map +1 -1
- package/dist/Logger/ConsoleWriter.d.ts +5 -0
- package/dist/Logger/ConsoleWriter.d.ts.map +1 -1
- package/dist/Logger/ConsoleWriter.js +22 -14
- package/dist/Logger/ConsoleWriter.js.map +1 -1
- package/dist/Middletier/FunctionsBundler.js +5 -1
- package/dist/Middletier/FunctionsBundler.js.map +1 -1
- package/dist/Middletier/PackFunctions.d.ts +5 -4
- package/dist/Middletier/PackFunctions.d.ts.map +1 -1
- package/dist/Middletier/PackFunctions.js +8 -7
- package/dist/Middletier/PackFunctions.js.map +1 -1
- package/dist/Types/Cli.types.d.ts +1 -1
- package/dist/Types/Cli.types.d.ts.map +1 -1
- package/dist/Utils/DcfRecovery.d.ts.map +1 -1
- package/dist/Utils/DcfRecovery.js +12 -4
- package/dist/Utils/DcfRecovery.js.map +1 -1
- package/dist/Utils/EnvVars.d.ts +1 -1
- package/dist/Utils/EnvVars.d.ts.map +1 -1
- package/dist/Utils/EnvVars.js.map +1 -1
- package/dist/Utils/RepoUrl.d.ts +11 -6
- package/dist/Utils/RepoUrl.d.ts.map +1 -1
- package/dist/Utils/RepoUrl.js +1 -1
- package/dist/Utils/RepoUrl.js.map +1 -1
- package/dist/Utils/ResolveCommand.d.ts +32 -2
- package/dist/Utils/ResolveCommand.d.ts.map +1 -1
- package/dist/Utils/ResolveCommand.js +70 -11
- package/dist/Utils/ResolveCommand.js.map +1 -1
- package/dist/Verbs/CreateMaafApp.d.ts +5 -5
- package/dist/Verbs/CreateMaafApp.d.ts.map +1 -1
- package/dist/Verbs/CreateMaafApp.js +43 -25
- package/dist/Verbs/CreateMaafApp.js.map +1 -1
- package/dist/Verbs/GetSettings.d.ts.map +1 -1
- package/dist/Verbs/GetSettings.js +9 -18
- package/dist/Verbs/GetSettings.js.map +1 -1
- package/dist/Verbs/Init.d.ts.map +1 -1
- package/dist/Verbs/Init.js +27 -52
- package/dist/Verbs/Init.js.map +1 -1
- package/dist/Verbs/MaafAppPermissions.d.ts +12 -6
- package/dist/Verbs/MaafAppPermissions.d.ts.map +1 -1
- package/dist/Verbs/MaafAppPermissions.js +208 -66
- package/dist/Verbs/MaafAppPermissions.js.map +1 -1
- package/dist/Verbs/RefreshGitAuth.d.ts.map +1 -1
- package/dist/Verbs/RefreshGitAuth.js +30 -15
- package/dist/Verbs/RefreshGitAuth.js.map +1 -1
- package/dist/Verbs/SetSetting.d.ts.map +1 -1
- package/dist/Verbs/SetSetting.js +39 -49
- package/dist/Verbs/SetSetting.js.map +1 -1
- package/dist/Verbs/VerbConstants.d.ts +13 -12
- package/dist/Verbs/VerbConstants.d.ts.map +1 -1
- package/dist/Verbs/VerbConstants.js +10 -15
- package/dist/Verbs/VerbConstants.js.map +1 -1
- package/package.json +5 -5
package/dist/Verbs/Init.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createAsync, getEnvironmentByName, getPlayerServiceConfig, resolveAppSe
|
|
|
5
5
|
import { AppConfigFileName, VERSION } from '@microsoft/managed-apps-common/config';
|
|
6
6
|
import { getUserRoutingEnvironmentAsync } from '@microsoft/managed-apps-common/services';
|
|
7
7
|
import { getCliLogger, getSettings } from '../CliSettings.js';
|
|
8
|
-
import { createDefaultStringOption } from '../CliUtils.js';
|
|
8
|
+
import { createDefaultStringOption, resolveEnvironmentId } from '../CliUtils.js';
|
|
9
9
|
import { CLI_CLIENT_ID } from '../Constants.js';
|
|
10
10
|
import { CLI_BINARY_NAME } from '../Constants/CliConstants.js';
|
|
11
11
|
import { HelpStrings } from '../Constants/HelpStrings.js';
|
|
@@ -14,7 +14,6 @@ import { printInfo, printSuccess } from '../Utils/ConsoleOutput.js';
|
|
|
14
14
|
import { withGitHubDcfRecoveryAsync } from '../Utils/DcfRecovery.js';
|
|
15
15
|
import { resolveRoutingEnvironmentIdAsync } from '../Utils/EnvironmentRoutingUtils.js';
|
|
16
16
|
import { getCurrentGitRemoteUrl } from '../Utils/GitUtils.js';
|
|
17
|
-
import { parseGitHubRepoUrl } from '../Utils/RepoUrl.js';
|
|
18
17
|
import { InitArguments } from './VerbConstants.js';
|
|
19
18
|
export async function initAsync(argumentProvider) {
|
|
20
19
|
const settings = getSettings();
|
|
@@ -31,8 +30,9 @@ export async function initAsync(argumentProvider) {
|
|
|
31
30
|
throw new UsageError('Unable to determine tenant ID. Ensure you are logged in.');
|
|
32
31
|
}
|
|
33
32
|
// Validate required options.
|
|
34
|
-
const preResolvedEnvironmentId =
|
|
35
|
-
|
|
33
|
+
const preResolvedEnvironmentId = await resolveEnvironmentId(argumentProvider, {
|
|
34
|
+
failIfMissing: false,
|
|
35
|
+
});
|
|
36
36
|
if (preResolvedEnvironmentId && !(await validateEnvironmentExists(preResolvedEnvironmentId))) {
|
|
37
37
|
throw new NotFoundError(`Environment '${preResolvedEnvironmentId}' not found. Please verify the environment ID and ensure you have access to it.`);
|
|
38
38
|
}
|
|
@@ -47,41 +47,24 @@ export async function initAsync(argumentProvider) {
|
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
// determine the repoType to use for this app.
|
|
50
|
-
// if there is a .git in the current project, read the remote URL.
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
50
|
+
// if there is a .git in the current project, read the remote URL. If
|
|
51
|
+
// `--use-existing-git-repo` is set, link it to the app. Otherwise (or if
|
|
52
|
+
// there is no .git), a platform-managed repo will be provisioned and linked
|
|
53
|
+
// to the app instead. URL shape is no longer validated here — the server is
|
|
54
|
+
// the authority and will reject anything it can't bind.
|
|
54
55
|
const remoteUrl = getCurrentGitRemoteUrl();
|
|
55
56
|
let selectedRemoteUrl;
|
|
56
57
|
let repoType = 'native';
|
|
57
58
|
if (remoteUrl) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
repoType = 'github';
|
|
65
|
-
selectedRemoteUrl = remoteUrl;
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
printInfo(`Will not use the existing git repo. A new platform-managed git repo will be provisioned and linked to the app instead.`);
|
|
69
|
-
}
|
|
59
|
+
printInfo(`Detected git remote URL: ${remoteUrl}`);
|
|
60
|
+
const useExistingGitRepo = await argumentProvider.getOption('useExistingGitRepo', false);
|
|
61
|
+
if (useExistingGitRepo) {
|
|
62
|
+
printInfo(`Will use the existing git repo and link it to the app.`);
|
|
63
|
+
repoType = 'github';
|
|
64
|
+
selectedRemoteUrl = remoteUrl;
|
|
70
65
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
error: error instanceof Error ? error.message : String(error),
|
|
74
|
-
gitRemoteUrl: remoteUrl,
|
|
75
|
-
});
|
|
76
|
-
printInfo(`Detected git remote URL '${remoteUrl}' is not a valid GitHub Enterprise URL.`);
|
|
77
|
-
const provisionGitRepo = await argumentProvider.getOption('provisionGitRepo', false);
|
|
78
|
-
if (provisionGitRepo) {
|
|
79
|
-
printInfo(`A new platform-managed git repo will be provisioned and linked to the app. The existing git repo will not be used.`);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
printInfo(`No git repo will be linked to the app. You will be able to upload build artifacts manually when you want to publish the app.`);
|
|
83
|
-
repoType = 'none';
|
|
84
|
-
}
|
|
66
|
+
else {
|
|
67
|
+
printInfo(`Will not use the existing git repo. A new platform-managed git repo will be provisioned and linked to the app instead.`);
|
|
85
68
|
}
|
|
86
69
|
}
|
|
87
70
|
// not ignoring prompts for these options
|
|
@@ -109,6 +92,7 @@ export async function initAsync(argumentProvider) {
|
|
|
109
92
|
repositoryId: undefined,
|
|
110
93
|
remoteUrl: selectedRemoteUrl,
|
|
111
94
|
repoType,
|
|
95
|
+
origin: settings.origin,
|
|
112
96
|
};
|
|
113
97
|
// On `repoType: 'github'`, wrap createAsync with the DCF catch-and-retry:
|
|
114
98
|
// a missing/expired GitHub identity mapping is recovered in-band by running
|
|
@@ -152,14 +136,15 @@ export async function initAsync(argumentProvider) {
|
|
|
152
136
|
await writeRepoConfig(settings.fileConfig.appConfigPath, appConfig);
|
|
153
137
|
printSuccess(`Created ${AppConfigFileName} for ${displayName}.`);
|
|
154
138
|
printInfo(`Hint: Run 'npx ${CLI_BINARY_NAME} app add connection' to connect data, or 'npx ${CLI_BINARY_NAME} dev' to start the app locally.`);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
139
|
+
// Use `git push -u origin HEAD` (not bare `git push`) so the hint works
|
|
140
|
+
// even when no upstream is configured for the current branch — common in
|
|
141
|
+
// freshly-cloned or freshly-init'd projects. Mirrors what `ms app create`
|
|
142
|
+
// runs automatically. Always printed: `repoType` here is `'native' | 'github'`
|
|
143
|
+
// (Init's local `'none'` branch was removed when CLI-side URL validation was
|
|
144
|
+
// dropped — `repoType: 'none'` still exists for `ms app create --repo none`.)
|
|
145
|
+
// and both flows benefit from the commit-and-push reminder.
|
|
146
|
+
printInfo(`Hint: Commit and push to wire your app to the remote repository: ` +
|
|
147
|
+
`git add . && git commit -m '<message>' && git push -u origin HEAD.`);
|
|
163
148
|
}
|
|
164
149
|
function createOptionSchema(argumentProvider) {
|
|
165
150
|
argumentProvider.setDescription(HelpStrings.app.init.description);
|
|
@@ -244,12 +229,6 @@ function createOptionSchema(argumentProvider) {
|
|
|
244
229
|
},
|
|
245
230
|
description: HelpStrings.app.init.options.useExistingGitRepo,
|
|
246
231
|
},
|
|
247
|
-
{
|
|
248
|
-
flags: {
|
|
249
|
-
key: InitArguments.ProvisionGitRepo.name,
|
|
250
|
-
},
|
|
251
|
-
description: HelpStrings.app.init.options.provisionGitRepo,
|
|
252
|
-
},
|
|
253
232
|
]);
|
|
254
233
|
argumentProvider.addSwitch({
|
|
255
234
|
flags: { key: InitArguments.OpenBrowser.name },
|
|
@@ -274,8 +253,4 @@ async function validateEnvironmentExists(environmentId) {
|
|
|
274
253
|
return false;
|
|
275
254
|
}
|
|
276
255
|
}
|
|
277
|
-
function normalizeOptionalString(value) {
|
|
278
|
-
const normalized = value?.trim();
|
|
279
|
-
return normalized ? normalized : undefined;
|
|
280
|
-
}
|
|
281
256
|
//# sourceMappingURL=Init.js.map
|
package/dist/Verbs/Init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Init.js","sourceRoot":"","sources":["../../src/Verbs/Init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,GAChB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AAGzF,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"Init.js","sourceRoot":"","sources":["../../src/Verbs/Init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,GAChB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AAGzF,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,gCAAgC,EAAE,MAAM,kCAAkC,CAAC;AACpF,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,gBAAkC;IAChE,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAE/B,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,UAAU,CAClB,GAAG,iBAAiB,uFAAuF,CAC5G,CAAC;IACJ,CAAC;IAED,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IACrC,MAAM,gBAAgB,CAAC,OAAO,EAAE,CAAC;IAEjC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAG,gBAAgB,CAAC,aAAa,EAAE,CAAC;IACpD,MAAM,EAAE,MAAM,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAE5C,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,gBAAgB,EAAE,CAAC;IAC3D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,UAAU,CAAC,0DAA0D,CAAC,CAAC;IACnF,CAAC;IAED,6BAA6B;IAC7B,MAAM,wBAAwB,GAAG,MAAM,oBAAoB,CAAC,gBAAgB,EAAE;QAC5E,aAAa,EAAE,KAAK;KACrB,CAAC,CAAC;IAEH,IAAI,wBAAwB,IAAI,CAAC,CAAC,MAAM,yBAAyB,CAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC;QAC7F,MAAM,IAAI,aAAa,CACrB,gBAAgB,wBAAwB,iFAAiF,CAC1H,CAAC;IACJ,CAAC;IAED,MAAM,iBAAiB,GACrB,wBAAwB,KAAK,SAAS;QACpC,CAAC,CAAC,8BAA8B,CAC5B,UAAU,EACV,QAAQ,EACR,aAAa,EACb,EAAE,kBAAkB,EAAE,KAAK,EAAE,EAC7B,MAAM,EACN,MAAM,CACP;QACH,CAAC,CAAC,SAAS,CAAC;IAChB,yFAAyF;IACzF,6FAA6F;IAC7F,iBAAiB,EAAE,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC1C,MAAM,CAAC,eAAe,CAAC,mDAAmD,EAAE;YAC1E,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,8CAA8C;IAC9C,qEAAqE;IACrE,yEAAyE;IACzE,4EAA4E;IAC5E,4EAA4E;IAC5E,wDAAwD;IACxD,MAAM,SAAS,GAAG,sBAAsB,EAAE,CAAC;IAC3C,IAAI,iBAAqC,CAAC;IAC1C,IAAI,QAAQ,GAAa,QAAQ,CAAC;IAClC,IAAI,SAAS,EAAE,CAAC;QACd,SAAS,CAAC,4BAA4B,SAAS,EAAE,CAAC,CAAC;QACnD,MAAM,kBAAkB,GAAG,MAAM,gBAAgB,CAAC,SAAS,CACzD,oBAAoB,EACpB,KAAK,CACN,CAAC;QACF,IAAI,kBAAkB,EAAE,CAAC;YACvB,SAAS,CAAC,wDAAwD,CAAC,CAAC;YACpE,QAAQ,GAAG,QAAQ,CAAC;YACpB,iBAAiB,GAAG,SAAS,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,SAAS,CACP,wHAAwH,CACzH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,MAAM,WAAW,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAS,aAAa,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAS,aAAa,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAS,WAAW,CAAC,CAAC;IACxE,MAAM,YAAY,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAS,cAAc,CAAC,CAAC;IAC9E,MAAM,eAAe,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAS,iBAAiB,CAAC,CAAC;IACpF,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAS,UAAU,CAAC,CAAC;IAEtE,4EAA4E;IAC5E,yEAAyE;IACzE,+CAA+C;IAC/C,MAAM,WAAW,GACf,CAAC,MAAM,gBAAgB,CAAC,SAAS,CAC/B,aAAa,CAAC,WAAW,CAAC,SAAS;IACnC,kBAAkB,CAAC,IAAI,CACxB,CAAC,IAAI,KAAK,CAAC;IACd,iEAAiE;IACjE,MAAM,WAAW,GACf,CAAC,MAAM,gBAAgB,CAAC,SAAS,CAC/B,aAAa,CAAC,WAAW,CAAC,SAAS;IACnC,kBAAkB,CAAC,IAAI,CACxB,CAAC,IAAI,KAAK,CAAC;IAEd,MAAM,aAAa,GACjB,wBAAwB;QACxB,CAAC,MAAM,gCAAgC,CAAC,iBAAkB,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAEnF,qBAAqB,CAAC,aAAa,CAAC,CAAC;IAErC,6FAA6F;IAC7F,MAAM,aAAa,GAAG;QACpB,WAAW;QACX,WAAW;QACX,YAAY,EAAE,SAAS;QACvB,SAAS,EAAE,iBAAiB;QAC5B,QAAQ;QACR,MAAM,EAAE,QAAQ,CAAC,MAAM;KACxB,CAAC;IAEF,0EAA0E;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,oDAAoD;IACpD,yEAAyE;IACzE,+DAA+D;IAC/D,MAAM,SAAS,GAAG,CAAC,EAAE,OAAO,EAAwB,EAAkC,EAAE,CACtF,WAAW,CAAC;QACV,QAAQ;QACR,aAAa;QACb,MAAM;QACN,aAAa,EAAE;YACb,GAAG,aAAa;YAChB,GAAG,CAAC,WAAW,IAAI,CAAC,OAAO,IAAI,QAAQ,KAAK,QAAQ,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;SAC/E;KACF,CAAC,CAAC;IAEL,MAAM,YAAY,GAAG,MAAM,0BAA0B,CAAC,SAAS,EAAE;QAC/D,QAAQ;QACR,eAAe,EAAE,iBAAiB;QAClC,QAAQ;QACR,QAAQ,EAAE,gBAAgB,CAAC,UAAU,EAAE;QACvC,WAAW;QACX,IAAI,EAAE,UAAU;KACjB,CAAC,CAAC;IAEH,MAAM,SAAS,GAAc;QAC3B,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE;QAC1B,cAAc,EAAE,YAAY,CAAC,GAAG,CAAC,WAAW;QAC5C,aAAa;QACb,WAAW,EAAE,YAAY,CAAC,GAAG,CAAC,WAAW;QACzC,QAAQ;QACR,KAAK,EAAE,gBAAgB,CAAC,QAAQ,EAAE;QAClC,SAAS;QACT,YAAY;QACZ,eAAe;QACf,QAAQ;QACR,oBAAoB,EAAE,EAAE;QACxB,kBAAkB,EAAE,EAAE;QACtB,WAAW,EAAE,kBAAkB,CAAC,SAAS,CAAC;KAC3C,CAAC;IACF,MAAM,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IACpE,YAAY,CAAC,WAAW,iBAAiB,QAAQ,WAAW,GAAG,CAAC,CAAC;IACjE,SAAS,CACP,kBAAkB,eAAe,iDAAiD,eAAe,iCAAiC,CACnI,CAAC;IACF,wEAAwE;IACxE,yEAAyE;IACzE,0EAA0E;IAC1E,+EAA+E;IAC/E,6EAA6E;IAC7E,8EAA8E;IAC9E,4DAA4D;IAC5D,SAAS,CACP,mEAAmE;QACjE,oEAAoE,CACvE,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,gBAAkC;IAC5D,gBAAgB,CAAC,cAAc,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAClE,gBAAgB,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5D,gBAAgB,CAAC,UAAU,CAAC;QAC1B,yBAAyB,CAAC;YACxB,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,aAAa,CAAC,IAAI;gBACrC,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,KAAK;aACzC;YACD,GAAG,EAAE,aAAa,CAAC,aAAa,CAAC,MAAM;YACvC,OAAO,EAAE,aAAa,CAAC,aAAa,CAAC,aAAa;YAClD,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa;SACxD,CAAC;QACF,yBAAyB,CAAC;YACxB,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,WAAW,CAAC,IAAI;gBACnC,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,KAAK;aACvC;YACD,GAAG,EAAE,aAAa,CAAC,WAAW,CAAC,MAAM;YACrC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,aAAa;YAChD,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;SACtD,CAAC;QACF,yBAAyB,CAAC;YACxB,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,WAAW,CAAC,IAAI;gBACnC,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,KAAK;aACvC;YACD,GAAG,EAAE,aAAa,CAAC,WAAW,CAAC,MAAM;YACrC,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,aAAa;YAChD,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;SACtD,CAAC;QACF,yBAAyB,CAAC;YACxB,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,IAAI;gBACjC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,KAAK;aACrC;YACD,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM;YACnC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,aAAa;YAC9C,YAAY,EAAE,aAAa,CAAC,SAAS,CAAC,YAAY;YAClD,OAAO,EAAE,QAAQ;YACjB,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS;SACpD,CAAC;QACF,yBAAyB,CAAC;YACxB,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,YAAY,CAAC,IAAI;gBACpC,KAAK,EAAE,aAAa,CAAC,YAAY,CAAC,KAAK;aACxC;YACD,GAAG,EAAE,aAAa,CAAC,YAAY,CAAC,MAAM;YACtC,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,aAAa;YACjD,YAAY,EAAE,aAAa,CAAC,YAAY,CAAC,YAAY;YACrD,OAAO,EAAE,eAAe;YACxB,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY;SACvD,CAAC;QACF,yBAAyB,CAAC;YACxB,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,eAAe,CAAC,IAAI;aACxC;YACD,GAAG,EAAE,aAAa,CAAC,eAAe,CAAC,MAAM;YACzC,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,aAAa;YACpD,YAAY,EAAE,aAAa,CAAC,eAAe,CAAC,YAAY;YACxD,OAAO,EAAE,YAAY;YACrB,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe;SAC1D,CAAC;QACF,yBAAyB,CAAC;YACxB,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI;gBAChC,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK;aACpC;YACD,GAAG,EAAE,aAAa,CAAC,QAAQ,CAAC,MAAM;YAClC,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,aAAa;YAC7C,YAAY,EAAE,aAAa,CAAC,QAAQ,CAAC,YAAY;YACjD,OAAO,EAAE,SAAS;YAClB,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ;SACnD,CAAC;KACH,CAAC,CAAC;IAEH,gBAAgB,CAAC,WAAW,CAAC;QAC3B;YACE,KAAK,EAAE;gBACL,GAAG,EAAE,aAAa,CAAC,kBAAkB,CAAC,IAAI;aAC3C;YACD,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB;SAC7D;KACF,CAAC,CAAC;IACH,gBAAgB,CAAC,SAAS,CAAC;QACzB,KAAK,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC,WAAW,CAAC,IAAI,EAAE;QAC9C,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;KACtD,CAAC,CAAC;IACH,gBAAgB,CAAC,SAAS,CAAC;QACzB,KAAK,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC,WAAW,CAAC,IAAI,EAAE;QAC9C,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW;KACtD,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,yBAAyB,CAAC,aAAqB;IAC5D,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAC9D,OAAO,CAAC,CAAC,WAAW,EAAE,UAAU,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -7,10 +7,13 @@ export type MaafAppPermissionsMode = 'share' | 'unshare';
|
|
|
7
7
|
/**
|
|
8
8
|
* Selects which access level the caller wants to grant/revoke:
|
|
9
9
|
*
|
|
10
|
-
* - `play` — read-only access to the MAAF app itself
|
|
11
|
-
*
|
|
12
|
-
* - `edit` — read + write access to the
|
|
13
|
-
* `
|
|
10
|
+
* - `play` — read-only access to the MAAF app itself, granted at the app scope
|
|
11
|
+
* via PowerApps RP (`MicrosoftAppReader` role).
|
|
12
|
+
* - `edit` — read + write access to the app's code. The concrete scope depends
|
|
13
|
+
* on `repoType`: for `native` repositories the `RepositoryContributor` role
|
|
14
|
+
* is granted at the GRS repo scope; for `none` / `github` repositories the
|
|
15
|
+
* `MicrosoftAppContributor` role is granted at the MAAF app scope instead
|
|
16
|
+
* (since there is no platform-managed repository to attach a role to).
|
|
14
17
|
*
|
|
15
18
|
* Each invocation targets exactly one access level; the two scopes are
|
|
16
19
|
* independent.
|
|
@@ -32,8 +35,11 @@ export type MaafAppAccessLevel = 'play' | 'edit';
|
|
|
32
35
|
* to AAD object ids via Graph, pass GUID-shaped inputs through unchanged.
|
|
33
36
|
* 5. Dispatch to the matching share/unshare path:
|
|
34
37
|
* - `play` → MAAF `modifyRoleAssignments` (`MicrosoftAppReader` at app scope).
|
|
35
|
-
* - `edit` → fetch the app's `repositoryId` then
|
|
36
|
-
* (`RepositoryContributor` at repo scope).
|
|
38
|
+
* - `edit` + `repoType === 'native'` → fetch the app's `repositoryId` then
|
|
39
|
+
* GRS `modifyRoleAssignments` (`RepositoryContributor` at repo scope).
|
|
40
|
+
* - `edit` + `repoType === 'none' | 'github'` → MAAF `modifyRoleAssignments`
|
|
41
|
+
* (`MicrosoftAppContributor` at app scope); GRS is skipped because the
|
|
42
|
+
* platform does not manage the repository.
|
|
37
43
|
* For unshare, paginate the matching list endpoint and pass through the
|
|
38
44
|
* full `RoleAssignment[]` records on `delete` (the strict wire contract
|
|
39
45
|
* requires `roleAssignmentId`, which only the list endpoint provides).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MaafAppPermissions.d.ts","sourceRoot":"","sources":["../../src/Verbs/MaafAppPermissions.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"MaafAppPermissions.d.ts","sourceRoot":"","sources":["../../src/Verbs/MaafAppPermissions.ts"],"names":[],"mappings":"AAAA;;GAEG;AA2BH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS5D,4EAA4E;AAC5E,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAC;AAuBjD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,8BAA8B,CAClD,gBAAgB,EAAE,gBAAgB,EAClC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA+If"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import { GrsRepositoryContributorRoleName, infoAsync, MaafAppReaderRoleName, modifyGrsRoleAssignmentsAsync, modifyMaafRoleAssignmentsAsync, resolvePrincipalByEmailAsync, unshareGrsRepoByPrincipalsAsync, unshareMaafAppByPrincipalsAsync, } from '@microsoft/managed-apps-actions';
|
|
4
|
+
import { GrsRepositoryContributorRoleName, infoAsync, MaafAppContributorRoleName, MaafAppReaderRoleName, modifyGrsRoleAssignmentsAsync, modifyMaafRoleAssignmentsAsync, resolvePrincipalByEmailAsync, unshareGrsRepoByPrincipalsAsync, unshareMaafAppByPrincipalsAsync, } from '@microsoft/managed-apps-actions';
|
|
5
5
|
import { AppConfigFileName } from '@microsoft/managed-apps-common/config';
|
|
6
6
|
import { HttpClientError } from '@microsoft/managed-apps-common/services';
|
|
7
7
|
import { getCliLogger, getSettings } from '../CliSettings.js';
|
|
@@ -27,8 +27,11 @@ import { ShareMaafAppArguments, UnshareMaafAppArguments } from './VerbConstants.
|
|
|
27
27
|
* to AAD object ids via Graph, pass GUID-shaped inputs through unchanged.
|
|
28
28
|
* 5. Dispatch to the matching share/unshare path:
|
|
29
29
|
* - `play` → MAAF `modifyRoleAssignments` (`MicrosoftAppReader` at app scope).
|
|
30
|
-
* - `edit` → fetch the app's `repositoryId` then
|
|
31
|
-
* (`RepositoryContributor` at repo scope).
|
|
30
|
+
* - `edit` + `repoType === 'native'` → fetch the app's `repositoryId` then
|
|
31
|
+
* GRS `modifyRoleAssignments` (`RepositoryContributor` at repo scope).
|
|
32
|
+
* - `edit` + `repoType === 'none' | 'github'` → MAAF `modifyRoleAssignments`
|
|
33
|
+
* (`MicrosoftAppContributor` at app scope); GRS is skipped because the
|
|
34
|
+
* platform does not manage the repository.
|
|
32
35
|
* For unshare, paginate the matching list endpoint and pass through the
|
|
33
36
|
* full `RoleAssignment[]` records on `delete` (the strict wire contract
|
|
34
37
|
* requires `roleAssignmentId`, which only the list endpoint provides).
|
|
@@ -50,72 +53,144 @@ export async function executeMaafAppPermissionsAsync(argumentProvider, mode) {
|
|
|
50
53
|
}
|
|
51
54
|
const appName = await resolveAppNameAsync(argumentProvider);
|
|
52
55
|
const access = parseAccessLevel(await argumentProvider.getOption('access'));
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// `repoType` only affects edit routing. For play we should never fail on the
|
|
57
|
+
// strict migration check for legacy config shape.
|
|
58
|
+
const repoType = access === 'edit'
|
|
59
|
+
? resolveRepoType(cliSettings.appConfig)
|
|
60
|
+
: (cliSettings.appConfig.repoType ?? 'none');
|
|
57
61
|
const inputs = parseInputs(argumentProvider.getPositionalArg(0));
|
|
58
62
|
if (inputs.length === 0) {
|
|
59
63
|
throw new UsageError('Provide at least one principal as a comma-separated list (AAD object IDs or email addresses).');
|
|
60
64
|
}
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
65
|
+
// Top-level scenario that ties together every server call the verb makes:
|
|
66
|
+
// Graph principal resolution (`ResolvePrincipalByEmail` per email), optional
|
|
67
|
+
// app-info fetch (`GetMaafApp`) when the edit path needs the `repositoryId`,
|
|
68
|
+
// and the final modify call (one of `ModifyMaafRoleAssignments`,
|
|
69
|
+
// `ModifyGrsRepositoryRoleAssignments`, `UnshareMaafAppByPrincipals`, or
|
|
70
|
+
// `UnshareGrsRepoByPrincipals`). Each of those actions already emits its own
|
|
71
|
+
// wire-scoped scenario; this scenario captures the verb-level outcome plus
|
|
72
|
+
// the routing decision (scope/role) so we can correlate. All properties are
|
|
73
|
+
// PII-safe — counts and routing flags only, no emails / GUIDs / app names.
|
|
74
|
+
const logger = getCliLogger();
|
|
75
|
+
const scenario = logger.trackScenario('MaafApp.SharePermissions', {
|
|
76
|
+
mode,
|
|
77
|
+
access,
|
|
78
|
+
repoType,
|
|
79
|
+
inputCount: inputs.length,
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
// Failures (bad shape, no Graph match, transport error) are collected here so
|
|
83
|
+
// we can warn the user but still proceed with the entries that resolved.
|
|
84
|
+
const failures = [];
|
|
85
|
+
const refs = await resolveAllAsync(inputs, failures);
|
|
86
|
+
// Verb-level summary of the Graph fan-out. Each underlying lookup also
|
|
87
|
+
// emits a `ResolvePrincipalByEmail` scenario; this event makes the
|
|
88
|
+
// aggregate result queryable per share/unshare invocation.
|
|
89
|
+
logger.trackActivityEvent('MaafApp.SharePermissions.PrincipalResolution', {
|
|
90
|
+
mode,
|
|
91
|
+
inputCount: inputs.length,
|
|
92
|
+
resolvedCount: refs.length,
|
|
93
|
+
failedCount: failures.length,
|
|
94
|
+
});
|
|
95
|
+
// If nothing resolved, the operation has no inputs to apply — abort with a
|
|
96
|
+
// single error listing every failure rather than calling the RP with an empty
|
|
97
|
+
// array.
|
|
98
|
+
if (refs.length === 0) {
|
|
99
|
+
throw new UsageError(formatFailures(failures));
|
|
100
|
+
}
|
|
101
|
+
// Otherwise warn about the unresolved entries up front (so they're visible
|
|
102
|
+
// before the success line), then proceed with the resolved subset.
|
|
103
|
+
if (failures.length > 0) {
|
|
104
|
+
printWarning(formatFailures(failures));
|
|
105
|
+
}
|
|
106
|
+
const dispatch = mode === 'share' ? shareDispatchAsync : unshareDispatchAsync;
|
|
107
|
+
const outcome = await dispatch({
|
|
108
|
+
tenantId,
|
|
109
|
+
environmentId,
|
|
110
|
+
appName,
|
|
92
111
|
access,
|
|
93
|
-
|
|
94
|
-
|
|
112
|
+
refs,
|
|
113
|
+
repoType,
|
|
114
|
+
jsonMode: argumentProvider.isJsonMode(),
|
|
115
|
+
});
|
|
116
|
+
scenario.complete({
|
|
117
|
+
resolvedCount: refs.length,
|
|
118
|
+
failedResolutionCount: failures.length,
|
|
119
|
+
scope: outcome.scope,
|
|
120
|
+
role: outcome.role,
|
|
121
|
+
// App-scope contributor fallback for `edit` is the noteworthy routing
|
|
122
|
+
// case introduced for non-native `repoType`. Tagged here so it's a
|
|
123
|
+
// simple boolean filter on the success event.
|
|
124
|
+
appScopeContributorFallback: access === 'edit' && outcome.scope === 'app',
|
|
95
125
|
...(mode === 'unshare' && {
|
|
96
126
|
revokedCount: outcome.revokedCount ?? 0,
|
|
97
127
|
revokedPrincipalCount: outcome.revokedPrincipalCount ?? 0,
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
notFoundCount: outcome.notFound?.length ?? 0,
|
|
129
|
+
skippedCount: outcome.skipped?.length ?? 0,
|
|
100
130
|
}),
|
|
101
|
-
warnings: failures,
|
|
102
131
|
});
|
|
132
|
+
if (argumentProvider.isJsonMode()) {
|
|
133
|
+
// Envelope semantics for this verb: `success: true` means the modify call
|
|
134
|
+
// reached the RP and returned. When some inputs failed to resolve before
|
|
135
|
+
// the modify, `partialFailure` is `true` and `warnings` lists the
|
|
136
|
+
// unresolved entries. Consumers requiring full success must check both
|
|
137
|
+
// `success === true && partialFailure === false`. `warnings` is always
|
|
138
|
+
// present (empty array on full success) so consumers can treat its
|
|
139
|
+
// length uniformly. The server response is spread flat into the envelope
|
|
140
|
+
// (matching every other verb that emits JSON) instead of nested under a
|
|
141
|
+
// `result` key. For unshare we also include the action-layer bookkeeping
|
|
142
|
+
// (`revokedCount`, `revokedPrincipalCount`, `notFound`, `skipped`) that
|
|
143
|
+
// would otherwise only appear in the human summary — so automation gets
|
|
144
|
+
// the same fidelity as text mode.
|
|
145
|
+
printJsonSuccess({
|
|
146
|
+
access,
|
|
147
|
+
partialFailure: failures.length > 0,
|
|
148
|
+
...outcome.result,
|
|
149
|
+
...(mode === 'unshare' && {
|
|
150
|
+
revokedCount: outcome.revokedCount ?? 0,
|
|
151
|
+
revokedPrincipalCount: outcome.revokedPrincipalCount ?? 0,
|
|
152
|
+
notFound: (outcome.notFound ?? []).map(toJsonRef),
|
|
153
|
+
skipped: (outcome.skipped ?? []).map(toJsonRef),
|
|
154
|
+
}),
|
|
155
|
+
warnings: failures,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
printSummary({ appName, mode, access, inputCount: refs.length, outcome });
|
|
160
|
+
}
|
|
103
161
|
}
|
|
104
|
-
|
|
105
|
-
|
|
162
|
+
catch (error) {
|
|
163
|
+
scenario.failure(error);
|
|
164
|
+
throw error;
|
|
106
165
|
}
|
|
107
166
|
}
|
|
108
167
|
async function shareDispatchAsync(inputs) {
|
|
109
|
-
const { tenantId, environmentId, appName, access, refs } = inputs;
|
|
168
|
+
const { tenantId, environmentId, appName, access, refs, repoType, jsonMode } = inputs;
|
|
110
169
|
if (access === 'play') {
|
|
111
|
-
const entries = refs.map(toMaafPermissionEntry);
|
|
170
|
+
const entries = refs.map((r) => toMaafPermissionEntry(r, MaafAppReaderRoleName));
|
|
171
|
+
const result = await modifyMaafRoleAssignmentsAsync({
|
|
172
|
+
tenantId,
|
|
173
|
+
environmentId,
|
|
174
|
+
logger: getCliLogger(),
|
|
175
|
+
actionsParams: { appName, put: entries, delete: [] },
|
|
176
|
+
});
|
|
177
|
+
return { result, scope: 'app', role: MaafAppReaderRoleName };
|
|
178
|
+
}
|
|
179
|
+
// access === 'edit': route by repoType.
|
|
180
|
+
if (repoType !== 'native') {
|
|
181
|
+
// The app has no platform-managed repository, so grant contributor access
|
|
182
|
+
// at the app scope instead of going through GRS.
|
|
183
|
+
if (!jsonMode) {
|
|
184
|
+
printInfo(`App '${appName}' has no platform-managed repository, so granting contributor access at the app scope instead of repository scope.`);
|
|
185
|
+
}
|
|
186
|
+
const entries = refs.map((r) => toMaafPermissionEntry(r, MaafAppContributorRoleName));
|
|
112
187
|
const result = await modifyMaafRoleAssignmentsAsync({
|
|
113
188
|
tenantId,
|
|
114
189
|
environmentId,
|
|
115
190
|
logger: getCliLogger(),
|
|
116
191
|
actionsParams: { appName, put: entries, delete: [] },
|
|
117
192
|
});
|
|
118
|
-
return { result };
|
|
193
|
+
return { result, scope: 'app', role: MaafAppContributorRoleName };
|
|
119
194
|
}
|
|
120
195
|
const repoId = await resolveRepositoryIdAsync({ tenantId, environmentId, appName });
|
|
121
196
|
const entries = refs.map(toGrsPermissionEntry);
|
|
@@ -125,30 +200,49 @@ async function shareDispatchAsync(inputs) {
|
|
|
125
200
|
logger: getCliLogger(),
|
|
126
201
|
actionsParams: { repoId, put: entries, delete: [] },
|
|
127
202
|
});
|
|
128
|
-
return { result };
|
|
203
|
+
return { result, scope: 'repo', role: GrsRepositoryContributorRoleName };
|
|
129
204
|
}
|
|
130
205
|
async function unshareDispatchAsync(inputs) {
|
|
131
|
-
const { tenantId, environmentId, appName, access, refs } = inputs;
|
|
206
|
+
const { tenantId, environmentId, appName, access, refs, repoType, jsonMode } = inputs;
|
|
132
207
|
const principalIds = refs.map((r) => r.id);
|
|
133
208
|
const refsByPrincipalId = new Map(refs.map((r) => [r.id, r]));
|
|
134
|
-
const
|
|
135
|
-
|
|
209
|
+
const useAppScope = access === 'play' || repoType !== 'native';
|
|
210
|
+
const role = useAppScope
|
|
211
|
+
? access === 'play'
|
|
212
|
+
? MaafAppReaderRoleName
|
|
213
|
+
: MaafAppContributorRoleName
|
|
214
|
+
: GrsRepositoryContributorRoleName;
|
|
215
|
+
let actionResult;
|
|
216
|
+
if (useAppScope) {
|
|
217
|
+
if (access === 'edit') {
|
|
218
|
+
if (!jsonMode) {
|
|
219
|
+
printInfo(`App '${appName}' has no platform-managed repository, so revoking contributor access at the app scope instead of repository scope.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
actionResult = await unshareMaafAppByPrincipalsAsync({
|
|
136
223
|
tenantId,
|
|
137
224
|
environmentId,
|
|
138
225
|
logger: getCliLogger(),
|
|
139
|
-
actionsParams: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
226
|
+
actionsParams: {
|
|
227
|
+
appName,
|
|
228
|
+
principalIds,
|
|
229
|
+
roleDefinitionName: role,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const repoId = await resolveRepositoryIdAsync({ tenantId, environmentId, appName });
|
|
235
|
+
actionResult = await unshareGrsRepoByPrincipalsAsync({
|
|
236
|
+
tenantId,
|
|
237
|
+
environmentId,
|
|
238
|
+
logger: getCliLogger(),
|
|
239
|
+
actionsParams: { repoId, principalIds },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
150
242
|
return {
|
|
151
243
|
result: actionResult.result,
|
|
244
|
+
scope: useAppScope ? 'app' : 'repo',
|
|
245
|
+
role,
|
|
152
246
|
revokedCount: actionResult.revokedCount,
|
|
153
247
|
revokedPrincipalCount: actionResult.revokedPrincipalCount,
|
|
154
248
|
notFound: actionResult.notFoundPrincipalIds.map((id) => refsByPrincipalId.get(id) ?? { id, input: id }),
|
|
@@ -156,6 +250,27 @@ async function unshareDispatchAsync(inputs) {
|
|
|
156
250
|
{ id: s.principalObjectId, input: s.principalObjectId }),
|
|
157
251
|
};
|
|
158
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Resolves the effective {@link RepoType} from an `AppConfig`.
|
|
255
|
+
*
|
|
256
|
+
* Hard rule: if `repoType` is missing AND the config carries a (non-empty,
|
|
257
|
+
* non-zero-GUID) `repositoryId`, refuse to guess - the user must re-run
|
|
258
|
+
* `app init` to migrate the config. When both are missing we treat it as
|
|
259
|
+
* `'none'`: there is no repo-scope to act on, and the app-scope fallback in
|
|
260
|
+
* the share/unshare dispatchers handles edit correctly.
|
|
261
|
+
*/
|
|
262
|
+
function resolveRepoType(appConfig) {
|
|
263
|
+
if (appConfig.repoType) {
|
|
264
|
+
return appConfig.repoType;
|
|
265
|
+
}
|
|
266
|
+
const hasRepoId = !!appConfig.repositoryId && appConfig.repositoryId !== '00000000-0000-0000-0000-000000000000';
|
|
267
|
+
if (hasRepoId) {
|
|
268
|
+
throw new UsageError(`${AppConfigFileName} is missing the 'repoType' field but has a 'repositoryId' set. ` +
|
|
269
|
+
`This usually means the file was written by an older CLI. ` +
|
|
270
|
+
`Re-run "npx ${CLI_BINARY_NAME} app init" to migrate the config, then retry.`);
|
|
271
|
+
}
|
|
272
|
+
return 'none';
|
|
273
|
+
}
|
|
159
274
|
/**
|
|
160
275
|
* Fetches the app's `repositoryId` via the info action. Used by the `edit`
|
|
161
276
|
* access paths to drive the GRS repo-scope share/unshare endpoints.
|
|
@@ -177,13 +292,16 @@ async function resolveRepositoryIdAsync(args) {
|
|
|
177
292
|
* verb keeps its pre-flag behaviour for callers that haven't migrated. Throws
|
|
178
293
|
* a `UsageError` on any other value — Commander.js doesn't enforce enum
|
|
179
294
|
* membership by default and we want a precise error message.
|
|
295
|
+
*
|
|
296
|
+
* `play` and `edit` are the only externally visible levels. The role and scope
|
|
297
|
+
* the level resolves to are decided downstream (see {@link MaafAppAccessLevel}).
|
|
180
298
|
*/
|
|
181
299
|
function parseAccessLevel(raw) {
|
|
182
300
|
const value = (raw ?? 'play').trim().toLowerCase();
|
|
183
301
|
if (value === 'play' || value === 'edit') {
|
|
184
302
|
return value;
|
|
185
303
|
}
|
|
186
|
-
throw new UsageError(`Unknown --access value '${raw}'. Use 'play' (default — app access) or 'edit' (
|
|
304
|
+
throw new UsageError(`Unknown --access value '${raw}'. Use 'play' (default — app access) or 'edit' (edit access).`);
|
|
187
305
|
}
|
|
188
306
|
/**
|
|
189
307
|
* Splits a comma-separated string into a deduplicated, trimmed list of inputs.
|
|
@@ -289,6 +407,15 @@ function formatFailures(failures) {
|
|
|
289
407
|
*/
|
|
290
408
|
function extractGraphErrorMessage(error) {
|
|
291
409
|
if (error instanceof HttpClientError) {
|
|
410
|
+
// A 403 from Graph is a consent/permission problem with the caller's
|
|
411
|
+
// sign-in, not a bad principal. Echoing Graph's opaque "Insufficient
|
|
412
|
+
// privileges to complete the operation" under the "Could not resolve
|
|
413
|
+
// <email>" heading misleads users into thinking the email is wrong, so we
|
|
414
|
+
// replace it with an actionable message naming the real cause and the
|
|
415
|
+
// ways forward. See {@link formatGraphPermissionError}.
|
|
416
|
+
if (error.status === 403) {
|
|
417
|
+
return formatGraphPermissionError(error.status);
|
|
418
|
+
}
|
|
292
419
|
const message = extractGraphMessageFromBody(error.body);
|
|
293
420
|
if (message !== undefined) {
|
|
294
421
|
return `Graph ${error.status}: ${message}`;
|
|
@@ -297,6 +424,21 @@ function extractGraphErrorMessage(error) {
|
|
|
297
424
|
}
|
|
298
425
|
return error instanceof Error ? error.message : String(error);
|
|
299
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Builds the actionable reason for a Graph 403 hit during principal resolution.
|
|
429
|
+
* The email→object-id lookup needs the `User.ReadBasic.All` and
|
|
430
|
+
* `Group.Read.All` delegated Graph permissions; a 403 means the active
|
|
431
|
+
* account's token doesn't carry them in this tenant (consent was never
|
|
432
|
+
* granted). Rather than surface Graph's opaque "Insufficient privileges", we
|
|
433
|
+
* name the cause and the two ways forward: get those permissions consented, or
|
|
434
|
+
* skip the lookup entirely by passing the AAD object ID (GUID) instead of an
|
|
435
|
+
* email.
|
|
436
|
+
*/
|
|
437
|
+
function formatGraphPermissionError(status) {
|
|
438
|
+
return (`Graph ${status}: your sign-in lacks permission to look up accounts by email. ` +
|
|
439
|
+
`Pass the AAD object ID (GUID) instead, or have an admin grant the ` +
|
|
440
|
+
`User.ReadBasic.All and Group.Read.All Graph permissions.`);
|
|
441
|
+
}
|
|
300
442
|
/**
|
|
301
443
|
* Parses a Graph error body string and returns the inner `error.message`. Returns
|
|
302
444
|
* `undefined` if the body is missing, not valid JSON, or doesn't carry the
|
|
@@ -331,11 +473,11 @@ function looksLikeEmail(input) {
|
|
|
331
473
|
* (when the user supplied one) keeps the wire payload self-describing for server
|
|
332
474
|
* logs even though the response strips it.
|
|
333
475
|
*/
|
|
334
|
-
function toMaafPermissionEntry(ref) {
|
|
476
|
+
function toMaafPermissionEntry(ref, role) {
|
|
335
477
|
return {
|
|
336
478
|
name: ref.displayName ?? ref.input,
|
|
337
479
|
properties: {
|
|
338
|
-
roleName:
|
|
480
|
+
roleName: role,
|
|
339
481
|
principal: ref.email ? { id: ref.id, email: ref.email } : { id: ref.id },
|
|
340
482
|
},
|
|
341
483
|
};
|