@microsoft/managed-apps-cli 0.7.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 +42 -6
- package/dist/ArgumentProvider.d.ts +11 -0
- package/dist/ArgumentProvider.d.ts.map +1 -1
- package/dist/ArgumentProvider.js +61 -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 +23 -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 +3 -4
- package/dist/CliSettings.js.map +1 -1
- package/dist/CliUtils.d.ts +25 -8
- package/dist/CliUtils.d.ts.map +1 -1
- package/dist/CliUtils.js +19 -23
- package/dist/CliUtils.js.map +1 -1
- package/dist/Constants/CommandRegistry.d.ts.map +1 -1
- package/dist/Constants/CommandRegistry.js +28 -1
- package/dist/Constants/CommandRegistry.js.map +1 -1
- package/dist/Constants/HelpStrings.d.ts +55 -5
- package/dist/Constants/HelpStrings.d.ts.map +1 -1
- package/dist/Constants/HelpStrings.js +113 -42
- 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 +18 -11
- package/dist/Errors/GitHubErrorMap.js.map +1 -1
- package/dist/FS/CliFs.d.ts.map +1 -1
- package/dist/FS/CliFs.js.map +1 -1
- package/dist/HttpClient/CliHttpClient.d.ts +30 -0
- package/dist/HttpClient/CliHttpClient.d.ts.map +1 -1
- package/dist/HttpClient/CliHttpClient.js +136 -1
- 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/Types/Command.types.d.ts +14 -3
- package/dist/Types/Command.types.d.ts.map +1 -1
- package/dist/Types/Command.types.js +4 -0
- package/dist/Types/Command.types.js.map +1 -1
- package/dist/Utils/BuildPolling.d.ts +18 -0
- package/dist/Utils/BuildPolling.d.ts.map +1 -0
- package/dist/Utils/BuildPolling.js +63 -0
- package/dist/Utils/BuildPolling.js.map +1 -0
- package/dist/Utils/BuildVerbDescriptionTable.d.ts +11 -1
- package/dist/Utils/BuildVerbDescriptionTable.d.ts.map +1 -1
- package/dist/Utils/BuildVerbDescriptionTable.js +73 -17
- package/dist/Utils/BuildVerbDescriptionTable.js.map +1 -1
- package/dist/Utils/DcfRecovery.d.ts +84 -0
- package/dist/Utils/DcfRecovery.d.ts.map +1 -0
- package/dist/Utils/DcfRecovery.js +266 -0
- package/dist/Utils/DcfRecovery.js.map +1 -0
- package/dist/Utils/EnhanceNetworkError.d.ts.map +1 -1
- package/dist/Utils/EnhanceNetworkError.js +2 -0
- package/dist/Utils/EnhanceNetworkError.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 +37 -1
- package/dist/Utils/ResolveCommand.d.ts.map +1 -1
- package/dist/Utils/ResolveCommand.js +97 -20
- package/dist/Utils/ResolveCommand.js.map +1 -1
- package/dist/Verbs/AddConnector.d.ts.map +1 -1
- package/dist/Verbs/AddConnector.js.map +1 -1
- package/dist/Verbs/AddConnectorFlows.d.ts.map +1 -1
- package/dist/Verbs/AddConnectorFlows.js +2 -2
- package/dist/Verbs/AddConnectorFlows.js.map +1 -1
- package/dist/Verbs/BuildMaafApp.d.ts.map +1 -1
- package/dist/Verbs/BuildMaafApp.js +98 -22
- package/dist/Verbs/BuildMaafApp.js.map +1 -1
- package/dist/Verbs/ConnectorHelpers.d.ts.map +1 -1
- package/dist/Verbs/ConnectorHelpers.js.map +1 -1
- package/dist/Verbs/CreateMaafApp.d.ts +54 -5
- package/dist/Verbs/CreateMaafApp.d.ts.map +1 -1
- package/dist/Verbs/CreateMaafApp.js +238 -238
- package/dist/Verbs/CreateMaafApp.js.map +1 -1
- package/dist/Verbs/DeployMaafApp.d.ts.map +1 -1
- package/dist/Verbs/DeployMaafApp.js +48 -20
- package/dist/Verbs/DeployMaafApp.js.map +1 -1
- package/dist/Verbs/GetMaafBuildStatus.d.ts.map +1 -1
- package/dist/Verbs/GetMaafBuildStatus.js +66 -17
- package/dist/Verbs/GetMaafBuildStatus.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 +63 -46
- package/dist/Verbs/Init.js.map +1 -1
- package/dist/Verbs/ListFlows.d.ts.map +1 -1
- package/dist/Verbs/ListFlows.js +1 -2
- package/dist/Verbs/ListFlows.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/PlayMaafApp.d.ts +22 -0
- package/dist/Verbs/PlayMaafApp.d.ts.map +1 -0
- package/dist/Verbs/PlayMaafApp.js +147 -0
- package/dist/Verbs/PlayMaafApp.js.map +1 -0
- package/dist/Verbs/RefreshDataSource.d.ts.map +1 -1
- package/dist/Verbs/RefreshDataSource.js.map +1 -1
- package/dist/Verbs/RefreshGitAuth.d.ts.map +1 -1
- package/dist/Verbs/RefreshGitAuth.js +31 -16
- 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/ShareLinkCreate.d.ts +23 -0
- package/dist/Verbs/ShareLinkCreate.d.ts.map +1 -0
- package/dist/Verbs/ShareLinkCreate.js +73 -0
- package/dist/Verbs/ShareLinkCreate.js.map +1 -0
- package/dist/Verbs/ShareLinkList.d.ts +16 -0
- package/dist/Verbs/ShareLinkList.d.ts.map +1 -0
- package/dist/Verbs/ShareLinkList.js +84 -0
- package/dist/Verbs/ShareLinkList.js.map +1 -0
- package/dist/Verbs/ShareLinkRevoke.d.ts +22 -0
- package/dist/Verbs/ShareLinkRevoke.d.ts.map +1 -0
- package/dist/Verbs/ShareLinkRevoke.js +137 -0
- package/dist/Verbs/ShareLinkRevoke.js.map +1 -0
- package/dist/Verbs/VerbConstants.d.ts +113 -16
- package/dist/Verbs/VerbConstants.d.ts.map +1 -1
- package/dist/Verbs/VerbConstants.js +102 -24
- package/dist/Verbs/VerbConstants.js.map +1 -1
- package/dist/Verbs/index.d.ts +4 -0
- package/dist/Verbs/index.d.ts.map +1 -1
- package/dist/Verbs/index.js +4 -0
- package/dist/Verbs/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { createAsync, getPlayerServiceConfig, updateEnvironmentName, writeRepoConfig, } from '@microsoft/managed-apps-actions';
|
|
5
5
|
import { AppConfigFileName } from '@microsoft/managed-apps-common/config';
|
|
6
|
-
import { getUserRoutingEnvironmentAsync } from '@microsoft/managed-apps-common/services';
|
|
6
|
+
import { getUserRoutingEnvironmentAsync, regionToCloudSafe, } from '@microsoft/managed-apps-common/services';
|
|
7
7
|
import { execFileSync } from 'child_process';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import { downloadTemplate } from 'giget';
|
|
10
|
+
import * as os from 'os';
|
|
10
11
|
import * as path from 'path';
|
|
11
|
-
import { getCliLogger } from '../CliSettings.js';
|
|
12
|
-
import { createDefaultStringOption } from '../CliUtils.js';
|
|
12
|
+
import { getCliLogger, getSettings } from '../CliSettings.js';
|
|
13
|
+
import { createDefaultStringOption, resolveEnvironmentId } from '../CliUtils.js';
|
|
13
14
|
import { CLI_CLIENT_ID } from '../Constants.js';
|
|
14
15
|
import { CLI_BINARY_NAME } from '../Constants/CliConstants.js';
|
|
15
16
|
import { HelpStrings } from '../Constants/HelpStrings.js';
|
|
16
17
|
import { AppError, ConfigurationError, UsageError } from '../Errors/CliError.js';
|
|
17
|
-
import { mapGitHubServerErrorToCliError, matchGitHubAuthError } from '../Errors/GitHubErrorMap.js';
|
|
18
18
|
import { printInfo, printJsonLine, printJsonSuccess, printSuccess, printWarning, } from '../Utils/ConsoleOutput.js';
|
|
19
|
+
import { withGitHubDcfRecoveryAsync } from '../Utils/DcfRecovery.js';
|
|
19
20
|
import { resolveRoutingEnvironmentIdAsync } from '../Utils/EnvironmentRoutingUtils.js';
|
|
20
21
|
import { buildGcmConfig } from '../Utils/GitCredentialConfig.js';
|
|
21
|
-
import { ConsoleGitHubDcfDisplay, JsonGitHubDcfDisplay } from '../Utils/GitHubDcfFlow.js';
|
|
22
|
-
import { computeRepoHash, parseGitHubRepoUrl } from '../Utils/RepoUrl.js';
|
|
23
22
|
import { CreateMaafAppArguments } from './VerbConstants.js';
|
|
24
23
|
const DEFAULT_TEMPLATE = 'github:microsoft/Managed-Apps/templates/vite8';
|
|
25
24
|
/**
|
|
@@ -34,8 +33,8 @@ const REPO_NONE_SENTINEL = 'none';
|
|
|
34
33
|
* Maps the raw `--repo` flag value to a `(repoType, remoteUrl)` pair.
|
|
35
34
|
* - omitted / empty → `native` (default GRS-backed flow)
|
|
36
35
|
* - `none` (case-insensitive) → `none` (no GRS, no local setup)
|
|
37
|
-
* - any other non-empty value → `github` (external
|
|
38
|
-
* `remoteUrl` to the action and the eventual git clone)
|
|
36
|
+
* - any other non-empty value → `github` (external GitHub binding — `*.ghe.com` or `github.com`;
|
|
37
|
+
* value flows through as `remoteUrl` to the action and the eventual git clone)
|
|
39
38
|
*
|
|
40
39
|
* Runtime invariant: `remoteUrl` is set iff `repoType === 'github'`. Consumers
|
|
41
40
|
* branch on `repoType` and assert `remoteUrl!` at github use sites — see
|
|
@@ -55,7 +54,7 @@ export function parseRepoOption(rawRepoValue) {
|
|
|
55
54
|
*
|
|
56
55
|
* Three repo flows, selected by `--repo`:
|
|
57
56
|
* - `native` (default): server auto-provisions a GRS repo; local clone happens via GCM.
|
|
58
|
-
* - `github`: external
|
|
57
|
+
* - `github`: external GitHub binding (`*.ghe.com` or `github.com`). Clones the user's repo locally. On the first
|
|
59
58
|
* `createAsync` call, a missing/expired server-side GitHub identity mapping is
|
|
60
59
|
* recoverable in-band: a single inline DCF run + exactly one `createAsync` retry.
|
|
61
60
|
* Any other error — or a second mapping failure after DCF — is a hard fail.
|
|
@@ -96,15 +95,25 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
96
95
|
// stdout free of prose ahead of the envelope. `printError` writes to
|
|
97
96
|
// stderr and is unaffected.
|
|
98
97
|
const jsonMode = argumentProvider.isJsonMode();
|
|
99
|
-
// Identity confirmation: print the tenant +
|
|
98
|
+
// Identity confirmation: print the tenant + cloud we're targeting before
|
|
100
99
|
// anything else — preflight, routing, createAsync — so a user with multiple
|
|
101
100
|
// accounts signed in has the longest possible window to CTRL-C before any
|
|
102
101
|
// local check runs or server call fires. Environment id isn't known yet
|
|
103
102
|
// (routing is what resolves it); included in the success line at the end.
|
|
104
103
|
// Wording is intentionally tentative ("About to create…") so a preflight
|
|
105
104
|
// failure on the next line doesn't read as contradicting a "Creating…" line.
|
|
105
|
+
//
|
|
106
|
+
// Surface `cloud` (user-facing, e.g. `public` / `usgov`) instead of `region`
|
|
107
|
+
// (internal, e.g. `prod` / `gccmoderate`) so the value matches what the user
|
|
108
|
+
// typed on `--cloud`. Regions without a Cloud equivalent (`preview`, `ex`,
|
|
109
|
+
// `rx`, or developer modes `local` / `dev`) fall back to the raw region —
|
|
110
|
+
// these don't reach this print via the current `cloudToRegion(this._cloud)`
|
|
111
|
+
// chain in ArgumentProvider, so the fallback is defensive against future
|
|
112
|
+
// code paths (e.g. routing-resolved regions).
|
|
106
113
|
if (!jsonMode) {
|
|
107
|
-
|
|
114
|
+
const cloud = regionToCloudSafe(region);
|
|
115
|
+
const target = cloud ? `cloud ${cloud}` : `region ${region}`;
|
|
116
|
+
printInfo(`About to create app '${displayName}' in tenant ${tenantId} (${target}).`);
|
|
108
117
|
}
|
|
109
118
|
// ─── Preflight ────────────────────────────────────────────────────────────
|
|
110
119
|
// Run all local-only checks before any server call. Once createAsync
|
|
@@ -142,23 +151,31 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
142
151
|
// RepoSelection is a flat interface (not a discriminated union), so `remoteUrl`
|
|
143
152
|
// doesn't auto-narrow to `string` here even though the github branch guarantees it.
|
|
144
153
|
ensureRepoTargetDirectoryEmpty(appDir);
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
154
|
+
// Pre-network: cheap partial clone into a temp dir + ls-tree HEAD to detect
|
|
155
|
+
// a non-empty remote before createAsync runs. Catches the rejection case
|
|
156
|
+
// without leaving an orphaned server-side app or scaffolded files in
|
|
157
|
+
// appDir. Cheapest probe shape: --depth=1 --filter=blob:none --no-checkout
|
|
158
|
+
// pulls refs + the tip commit + its tree(s) only (no blob bytes, no working
|
|
159
|
+
// tree). On any probe error (network, auth, server doesn't support partial
|
|
160
|
+
// clone, etc.) we swallow and let the subsequent cloneRepo emit its
|
|
161
|
+
// friendlier error — see ensureExternalRepoIsEmpty for the rationale.
|
|
162
|
+
ensureExternalRepoIsEmpty(remoteUrl);
|
|
150
163
|
}
|
|
151
164
|
else {
|
|
152
165
|
ensureTargetDirectoryEmpty(appDir);
|
|
153
166
|
}
|
|
154
167
|
// ─── End preflight ────────────────────────────────────────────────────────
|
|
155
168
|
// The debug env-var override (MS_CLI_MAAF_DEBUG_ENVIRONMENT_ID) is honored by ArgumentProvider
|
|
156
|
-
// and surfaced via getEnvironmentId().
|
|
157
|
-
//
|
|
158
|
-
|
|
169
|
+
// and surfaced via getEnvironmentId(). The shared `resolveEnvironmentId` helper checks the
|
|
170
|
+
// `--environment-id` flag first, then falls back to that. When set, skip the automatic
|
|
171
|
+
// environment-selection API call entirely. Without an explicit id, getEnvironmentId() returns
|
|
172
|
+
// undefined and the automatic environment lookup runs.
|
|
173
|
+
const preResolvedEnvironmentId = await resolveEnvironmentId(argumentProvider, {
|
|
174
|
+
failIfMissing: false,
|
|
175
|
+
});
|
|
159
176
|
// Fire off PDE routing in parallel with the remaining option prompts. The result is awaited
|
|
160
177
|
// immediately before createAsync, so prompt latency is hidden behind the network call.
|
|
161
|
-
// Skipped entirely when the
|
|
178
|
+
// Skipped entirely when the env id is pre-resolved (--environment-id flag or debug override).
|
|
162
179
|
const routingEnvPromise = preResolvedEnvironmentId
|
|
163
180
|
? undefined
|
|
164
181
|
: getUserRoutingEnvironmentAsync(httpClient, tenantId, CLI_CLIENT_ID, { provisionDataverse: false }, region, logger);
|
|
@@ -195,11 +212,14 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
195
212
|
else {
|
|
196
213
|
ensureTargetDirectoryEmpty(appDir);
|
|
197
214
|
}
|
|
198
|
-
// Read --
|
|
215
|
+
// Read --open-browser alongside --json so the recovery DCF helper has both
|
|
199
216
|
// inputs ready without a second async option read inside the catch block.
|
|
200
217
|
// Commander converts kebab-case flag names to camelCase property keys;
|
|
201
|
-
// the camelName constant keeps this lookup in sync with
|
|
202
|
-
const
|
|
218
|
+
// the camelName constant keeps this lookup in sync with OpenBrowser.name.
|
|
219
|
+
const openBrowser = (await argumentProvider.getOption(CreateMaafAppArguments.OpenBrowser.camelName,
|
|
220
|
+
/* ignorePrompt */ true)) ?? false;
|
|
221
|
+
// Forces DCF re-authentication even when a valid mapping exists.
|
|
222
|
+
const forceReauth = (await argumentProvider.getOption(CreateMaafAppArguments.ForceReauth.camelName,
|
|
203
223
|
/* ignorePrompt */ true)) ?? false;
|
|
204
224
|
// JSON mode (github flow only): emit a structured `create-validation` event
|
|
205
225
|
// right before the server call so automation parsing NDJSON sees the
|
|
@@ -210,95 +230,44 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
210
230
|
if (jsonMode && repoType === 'github') {
|
|
211
231
|
printJsonLine({ phase: 'create-validation' });
|
|
212
232
|
}
|
|
213
|
-
// Hoisted so the
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
233
|
+
// Hoisted so the fixed-shape params are defined once and reused across the
|
|
234
|
+
// first attempt and the post-DCF retry. `forceReauth` is injected per-call
|
|
235
|
+
// via `isRetry` so the retry does not re-trigger the server-side mapping
|
|
236
|
+
// bypass against the just-refreshed mapping.
|
|
237
|
+
const baseActionsParams = {
|
|
217
238
|
displayName,
|
|
218
239
|
description,
|
|
219
240
|
repositoryId,
|
|
220
241
|
remoteUrl,
|
|
221
242
|
repoType,
|
|
243
|
+
origin: getSettings().origin,
|
|
222
244
|
};
|
|
223
|
-
const runCreate = () => createAsync({
|
|
245
|
+
const runCreate = ({ isRetry, }) => createAsync({
|
|
224
246
|
tenantId,
|
|
225
247
|
environmentId,
|
|
226
248
|
logger,
|
|
227
|
-
actionsParams
|
|
249
|
+
actionsParams: {
|
|
250
|
+
...baseActionsParams,
|
|
251
|
+
...(forceReauth && !isRetry && { forceReauth: true }),
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
// On `repoType: 'github'`, wrap createAsync with the DCF catch-and-retry:
|
|
255
|
+
// a missing/expired GitHub identity mapping is recovered in-band by running
|
|
256
|
+
// DCF and retrying once; any other recognized server error is mapped to a
|
|
257
|
+
// friendly CLI error. Non-github flows pass through untouched. See
|
|
258
|
+
// `withGitHubDcfRecoveryAsync` for the full contract.
|
|
259
|
+
const createResult = await withGitHubDcfRecoveryAsync(runCreate, {
|
|
260
|
+
repoType,
|
|
261
|
+
externalRepoUrl: remoteUrl,
|
|
262
|
+
tenantId,
|
|
263
|
+
jsonMode,
|
|
264
|
+
openBrowser,
|
|
265
|
+
verb: 'app create',
|
|
228
266
|
});
|
|
229
|
-
// Inline catch-and-retry for recoverable GitHub auth errors. On the github
|
|
230
|
-
// flow, a missing/expired GitHub identity mapping is recoverable in-band by
|
|
231
|
-
// running the DCF wrapper inline against
|
|
232
|
-
// the same GRS environment, then retrying createAsync exactly once. Any
|
|
233
|
-
// other recognized GrsServerError — on either the first attempt or the
|
|
234
|
-
// post-DCF retry — is mapped via mapGitHubServerErrorToCliError before
|
|
235
|
-
// being rethrown so the user sees a friendly CLI-typed error rather than
|
|
236
|
-
// the raw server payload. Any second mapping-missing/expired after DCF —
|
|
237
|
-
// and anything not recognized at all — escapes as a hard failure (single-retry
|
|
238
|
-
// contract; we never loop).
|
|
239
|
-
let createResult;
|
|
240
|
-
try {
|
|
241
|
-
createResult = await runCreate();
|
|
242
|
-
}
|
|
243
|
-
catch (err) {
|
|
244
|
-
if (repoType !== 'github')
|
|
245
|
-
throw err;
|
|
246
|
-
const matched = matchGitHubAuthError(err);
|
|
247
|
-
if (!matched) {
|
|
248
|
-
if (err instanceof GrsServerError) {
|
|
249
|
-
const mapped = mapGitHubServerErrorToCliError({
|
|
250
|
-
code: err.code,
|
|
251
|
-
message: err.message,
|
|
252
|
-
status: err.status,
|
|
253
|
-
details: err.details,
|
|
254
|
-
}, { repoUrl: remoteUrl, verb: 'app create' });
|
|
255
|
-
if (mapped)
|
|
256
|
-
throw mapped;
|
|
257
|
-
}
|
|
258
|
-
throw err;
|
|
259
|
-
}
|
|
260
|
-
await runDcfRecoveryAsync({
|
|
261
|
-
remoteUrl: remoteUrl,
|
|
262
|
-
tenantId,
|
|
263
|
-
jsonMode,
|
|
264
|
-
noBrowser,
|
|
265
|
-
verb: 'app create',
|
|
266
|
-
});
|
|
267
|
-
try {
|
|
268
|
-
createResult = await runCreate();
|
|
269
|
-
}
|
|
270
|
-
catch (retryErr) {
|
|
271
|
-
const remapped = matchGitHubAuthError(retryErr);
|
|
272
|
-
if (remapped) {
|
|
273
|
-
// Single-retry contract: a second mapping-missing/expired surfaces
|
|
274
|
-
// as a hard AuthenticationError with the standard remediation
|
|
275
|
-
// pointer. We deliberately do NOT loop DCF — repeated failures
|
|
276
|
-
// suggest the user can't complete the device-code flow (denied,
|
|
277
|
-
// wrong account, app uninstalled, etc.) and another loop would
|
|
278
|
-
// just hang.
|
|
279
|
-
const mapped = mapGitHubServerErrorToCliError(remapped.payload, {
|
|
280
|
-
repoUrl: remoteUrl,
|
|
281
|
-
verb: 'app create',
|
|
282
|
-
});
|
|
283
|
-
throw mapped ?? retryErr;
|
|
284
|
-
}
|
|
285
|
-
if (retryErr instanceof GrsServerError) {
|
|
286
|
-
const mapped = mapGitHubServerErrorToCliError({
|
|
287
|
-
code: retryErr.code,
|
|
288
|
-
message: retryErr.message,
|
|
289
|
-
status: retryErr.status,
|
|
290
|
-
details: retryErr.details,
|
|
291
|
-
}, { repoUrl: remoteUrl, verb: 'app create' });
|
|
292
|
-
if (mapped)
|
|
293
|
-
throw mapped;
|
|
294
|
-
}
|
|
295
|
-
throw retryErr;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
267
|
const { app, cloneUrl } = createResult;
|
|
299
268
|
// Only the `native` flow expects a GRS clone URL — it's the origin for the first
|
|
300
269
|
// local push to the auto-provisioned platform-managed repo. `github` binds an
|
|
301
|
-
// external
|
|
270
|
+
// external GitHub repo (no GRS) and `none` is by-design source-control-free, so neither
|
|
302
271
|
// produces a `cloneUrl`. Missing on `native` is a server bug — the hoisted check
|
|
303
272
|
// below guards both the pretty-output `(clone URL: undefined)` and JSON-envelope
|
|
304
273
|
// `gitRemoteUrl: undefined` failure modes that would otherwise leak through.
|
|
@@ -317,21 +286,30 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
317
286
|
// - github: clone the user's external repo, sanity-check it's empty,
|
|
318
287
|
// overlay the template, then write ms.config.json.
|
|
319
288
|
// - none: no git, no clone — mkdir -p, overlay the template, write ms.config.json.
|
|
289
|
+
let pushOutcome = null;
|
|
320
290
|
try {
|
|
321
291
|
if (repoType === 'github') {
|
|
322
|
-
// External GitHub
|
|
292
|
+
// External GitHub path: clone the developer's repo. Auth flows through
|
|
323
293
|
// the user's existing git credential setup (gh, GCM browser OAuth, SSH key, PAT,
|
|
324
294
|
// x509 client cert, etc.); the CLI does not inject any tokens at this layer (the
|
|
325
295
|
// Microsoft GitHub App is server-side only per the MAAF GitHub Integration design
|
|
326
|
-
// doc Requirement 1).
|
|
296
|
+
// doc Requirement 1). The post-clone empty-check that used to live here moved
|
|
297
|
+
// to the preflight (ensureExternalRepoIsEmpty), so by the time we get here the
|
|
298
|
+
// remote is known to be empty and the clone produces an empty working tree.
|
|
327
299
|
cloneRepo({ directory: appDir, repoUrl: remoteUrl });
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
300
|
+
// Safety net: the pre-network probe (ensureExternalRepoIsEmpty) is fail-OPEN
|
|
301
|
+
// on errors (e.g. os.tmpdir() inaccessible, server doesn't support partial
|
|
302
|
+
// clone). When that happens, the actual cloneRepo may have pulled content
|
|
303
|
+
// we missed. One last readdirSync of appDir guarantees we don't silently
|
|
304
|
+
// overlay the template onto the user's files.
|
|
305
|
+
// The repo must be _completely_ empty — including hidden / dotfiles like
|
|
306
|
+
// .gitignore, .gitattributes, README, LICENSE, .git-credentials, etc.
|
|
307
|
+
// We only filter `.git` itself (the internal directory created by clone);
|
|
308
|
+
// any other entry, hidden or not, means the user has prior content here.
|
|
332
309
|
const workingTreeEntries = fs.readdirSync(appDir).filter((entry) => entry !== '.git');
|
|
333
310
|
if (workingTreeEntries.length > 0) {
|
|
334
|
-
throw new UsageError(`Repository ${remoteUrl} is not empty. Choose a different repository to create an app or run 'ms app init' to update the existing app
|
|
311
|
+
throw new UsageError(`Repository ${remoteUrl} is not empty (must contain no files of any kind, including hidden files like .gitignore, .gitattributes, README, or LICENSE). Choose a different repository to create an app or run 'ms app init' to update the existing app.\n\n` +
|
|
312
|
+
buildOrphanedAppRecoveryHint(app.id, app.environmentId));
|
|
335
313
|
}
|
|
336
314
|
}
|
|
337
315
|
else if (repoType === 'native') {
|
|
@@ -364,12 +342,22 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
364
342
|
// key off `repoType` instead of a placeholder value.
|
|
365
343
|
...(repoType === 'native' && { repositoryId: app.repositoryId }),
|
|
366
344
|
repoType,
|
|
345
|
+
// externalRepoUrl is persisted only for github-bound apps so that post-create verbs
|
|
346
|
+
// (build, deploy) know which GitHub host to authenticate against during inline DCF
|
|
347
|
+
// recovery without re-prompting the user for the URL.
|
|
348
|
+
...(repoType === 'github' && { externalRepoUrl: remoteUrl }),
|
|
367
349
|
cloud: argumentProvider.getCloud(),
|
|
368
350
|
buildPath: buildPath ?? './dist',
|
|
369
351
|
buildCommand: buildCommand ?? 'npm run build',
|
|
370
352
|
buildEntryPoint: buildEntryPoint ?? 'index.html',
|
|
371
353
|
};
|
|
372
354
|
await writeRepoConfig(path.join(appDir, AppConfigFileName), config);
|
|
355
|
+
// Auto commit + push for git-backed flows (native + github). The `none` flow
|
|
356
|
+
// has no remote, so it's skipped. Push failure is non-fatal — see the warning
|
|
357
|
+
// path in commitAndPushScaffoldAsync.
|
|
358
|
+
if (repoType !== 'none') {
|
|
359
|
+
pushOutcome = await commitAndPushScaffoldAsync(appDir, jsonMode);
|
|
360
|
+
}
|
|
373
361
|
}
|
|
374
362
|
catch (setupError) {
|
|
375
363
|
// UsageError carries already-actionable messages (e.g. "repo already contains an
|
|
@@ -385,15 +373,16 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
385
373
|
// also written to ms.config.json — used by every subsequent CLI command
|
|
386
374
|
// (build, deploy, info, etc.). `gitRemoteUrl` is the GRS clone URL and is only
|
|
387
375
|
// present for the `native` flow (the only flow that auto-provisions a
|
|
388
|
-
// platform-managed git repo). `externalRepoUrl` is the user-supplied
|
|
389
|
-
// only appears in the `github` flow. `repoType` is always emitted so
|
|
390
|
-
// can detect which source-control mode the app was created in.
|
|
376
|
+
// platform-managed git repo). `externalRepoUrl` is the user-supplied external
|
|
377
|
+
// GitHub URL and only appears in the `github` flow. `repoType` is always emitted so
|
|
378
|
+
// automation can detect which source-control mode the app was created in.
|
|
391
379
|
printJsonSuccess({
|
|
392
380
|
appId: app.id,
|
|
393
381
|
repoType,
|
|
394
382
|
...(repoType === 'github' && remoteUrl && { externalRepoUrl: remoteUrl }),
|
|
395
383
|
...(repoType === 'native' && cloneUrl && { gitRemoteUrl: cloneUrl }),
|
|
396
384
|
appDir,
|
|
385
|
+
...(pushOutcome && { push: pushOutcome }),
|
|
397
386
|
});
|
|
398
387
|
}
|
|
399
388
|
else {
|
|
@@ -408,7 +397,63 @@ export async function createMaafAppAsync(argumentProvider) {
|
|
|
408
397
|
}
|
|
409
398
|
}
|
|
410
399
|
/**
|
|
411
|
-
*
|
|
400
|
+
* Commits the scaffolded files and pushes to the remote. Called by both `native`
|
|
401
|
+
* and `github` flows after writeRepoConfig.
|
|
402
|
+
*
|
|
403
|
+
* Failure is non-fatal: prints a warning with the retry command and returns
|
|
404
|
+
* `{ success: false, ... }`. The server-side app and local scaffold are unchanged.
|
|
405
|
+
*
|
|
406
|
+
* `stdio: ['ignore', 'ignore', 'inherit']`:
|
|
407
|
+
* - stdin ignored: child can't prompt for input (would block forever).
|
|
408
|
+
* - stdout ignored: discards git's progress prose so pretty output stays clean.
|
|
409
|
+
* - stderr inherited: git's actual error messages reach the user's terminal
|
|
410
|
+
* so they see git's own context alongside our warning.
|
|
411
|
+
*
|
|
412
|
+
* Commit identity (user.name / user.email) is read by git itself from
|
|
413
|
+
* local-then-global config — we pass nothing. Missing identity surfaces as
|
|
414
|
+
* git's own descriptive commit error through the inherited stderr.
|
|
415
|
+
*/
|
|
416
|
+
export async function commitAndPushScaffoldAsync(appDir, jsonMode) {
|
|
417
|
+
// Full add+commit+push sequence: any of the three commands may have been the
|
|
418
|
+
// one that failed (e.g. `git commit` errors when user.name/user.email aren't
|
|
419
|
+
// set), so re-running just `git push` wouldn't recover. The sequence is
|
|
420
|
+
// idempotent: rerunning `git add .` and `git commit` on an already-committed
|
|
421
|
+
// state either no-ops or errors with "nothing to commit", in which case
|
|
422
|
+
// `git push` still pushes the existing commit.
|
|
423
|
+
const retryCommand = `git add . && git commit -m "Initial scaffold from 'ms app create'" && git push -u origin HEAD`;
|
|
424
|
+
try {
|
|
425
|
+
execFileSync('git', ['add', '.'], {
|
|
426
|
+
cwd: appDir,
|
|
427
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
428
|
+
});
|
|
429
|
+
execFileSync('git', ['commit', '-m', "Initial scaffold from 'ms app create'"], {
|
|
430
|
+
cwd: appDir,
|
|
431
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
432
|
+
});
|
|
433
|
+
execFileSync('git', ['push', '-u', 'origin', 'HEAD'], {
|
|
434
|
+
cwd: appDir,
|
|
435
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
436
|
+
});
|
|
437
|
+
return { success: true };
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
// Capture the entire error structurally — name + message + stack for
|
|
441
|
+
// Error instances, spread of own properties otherwise. JSON.stringify
|
|
442
|
+
// would otherwise drop Error's non-enumerable fields and emit `{}`.
|
|
443
|
+
const errorPayload = err instanceof Error
|
|
444
|
+
? { name: err.name, message: err.message, stack: err.stack }
|
|
445
|
+
: typeof err === 'object' && err !== null
|
|
446
|
+
? { ...err }
|
|
447
|
+
: { message: String(err) };
|
|
448
|
+
if (!jsonMode) {
|
|
449
|
+
printWarning(`Could not commit and push the initial scaffold. Your app and local scaffold are ready. ` +
|
|
450
|
+
`To finish wiring it up, run:\n ${retryCommand}`);
|
|
451
|
+
}
|
|
452
|
+
return { success: false, error: errorPayload, retryCommand };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Clones the developer's external GitHub repo (`*.ghe.com` tenant or `github.com`) into `directory`.
|
|
412
457
|
* Auth flows through the user's existing git credential setup — the CLI does not inject
|
|
413
458
|
* tokens here. On failure, surfaces a CLI-friendly error pointing the user at the
|
|
414
459
|
* GitHub App install prerequisite; git's own stderr is preserved (stdio: inherit) so
|
|
@@ -639,6 +684,73 @@ export function ensureRepoTargetDirectoryEmpty(appDir) {
|
|
|
639
684
|
throw new UsageError(`'${appDir}' is not empty. --repo requires an empty target directory; specify an empty directory as the first argument or move to one before running.`);
|
|
640
685
|
}
|
|
641
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Cheap pre-network probe to detect a non-empty remote BEFORE createAsync runs.
|
|
689
|
+
*
|
|
690
|
+
* Implementation: partial clone (`--depth=1 --filter=blob:none --no-checkout`)
|
|
691
|
+
* into an OS temp dir, then `git ls-tree -r HEAD`. The clone pulls refs + the
|
|
692
|
+
* tip commit + its tree objects only — no blob bytes, no working tree — so the
|
|
693
|
+
* bandwidth on the rejection path is tens of KB even for a multi-GB repo. The
|
|
694
|
+
* temp dir is removed in `finally`.
|
|
695
|
+
*
|
|
696
|
+
* Failure semantics — fail-OPEN:
|
|
697
|
+
* - Non-empty tree at HEAD → throws `UsageError` with the "is not empty" /
|
|
698
|
+
* "ms app init" actionable lead, BEFORE createAsync. No orphan, no leak.
|
|
699
|
+
* - Empty tree (incl. the cleaned-up-repo edge case: commit1 add, commit2
|
|
700
|
+
* delete all) → ls-tree returns no lines → returns without throwing.
|
|
701
|
+
* - Any error from git itself (auth, network blip, server doesn't support
|
|
702
|
+
* --filter, repo doesn't exist, etc.) → swallow and return. The subsequent
|
|
703
|
+
* cloneRepo will surface a friendlier message for the user-facing failure;
|
|
704
|
+
* we don't want this probe to become a parallel error-message surface.
|
|
705
|
+
*/
|
|
706
|
+
export function ensureExternalRepoIsEmpty(repoUrl) {
|
|
707
|
+
let probeDir;
|
|
708
|
+
try {
|
|
709
|
+
probeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-app-create-probe-'));
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
// Can't make a temp dir → punt. Real cloneRepo will surface any actionable
|
|
713
|
+
// issue (writable-fs, etc.) on the subsequent attempt.
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
try {
|
|
718
|
+
execFileSync('git', ['clone', '--depth=1', '--filter=blob:none', '--no-checkout', repoUrl, probeDir], { stdio: ['ignore', 'ignore', 'ignore'] });
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
// Clone itself failed (auth, network, repo not found, server doesn't
|
|
722
|
+
// support partial clone, etc.). Fail open: the user-facing error will
|
|
723
|
+
// come from the later cloneRepo which has the friendlier messaging.
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
let lsTreeOutput;
|
|
727
|
+
try {
|
|
728
|
+
lsTreeOutput = execFileSync('git', ['-C', probeDir, 'ls-tree', '-r', 'HEAD'], {
|
|
729
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
730
|
+
}).toString();
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
// Truly empty repo (no commits, no HEAD): ls-tree errors. Treat as empty
|
|
734
|
+
// so create proceeds; cloneRepo of a no-commits remote produces an empty
|
|
735
|
+
// working tree, which is the happy path.
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (lsTreeOutput.trim().length > 0) {
|
|
739
|
+
// `ls-tree -r HEAD` enumerates every tracked path — including hidden
|
|
740
|
+
// ones (.gitignore, .gitattributes, etc.). Any tracked file at all
|
|
741
|
+
// means the repo isn't a valid binding target for a fresh app.
|
|
742
|
+
throw new UsageError(`Repository ${repoUrl} is not empty (must contain no tracked files of any kind, including hidden files like .gitignore, .gitattributes, README, or LICENSE). Choose a different repository to create an app or run 'ms app init' to update the existing app.`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
finally {
|
|
746
|
+
try {
|
|
747
|
+
fs.rmSync(probeDir, { recursive: true, force: true });
|
|
748
|
+
}
|
|
749
|
+
catch {
|
|
750
|
+
// Best-effort cleanup; a leftover temp dir is acceptable.
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
642
754
|
export function ensureTargetDirectoryWritable(appDir) {
|
|
643
755
|
// Probe write permission on the target dir (or its nearest existing
|
|
644
756
|
// ancestor, where mkdirSync will need to write). Catches read-only mounts
|
|
@@ -670,136 +782,20 @@ export function ensureTargetDirectoryWritable(appDir) {
|
|
|
670
782
|
throw new ConfigurationError(`'${appDir}' is not writable. Choose a different directory or fix the permissions and retry.`);
|
|
671
783
|
}
|
|
672
784
|
}
|
|
673
|
-
/**
|
|
674
|
-
* Inline DCF recovery branch for the github catch-and-retry: only the
|
|
675
|
-
* `github` flow ever calls this — `native` and `none` have no GitHub identity
|
|
676
|
-
* mapping to refresh. SIGINT handling is scoped to this helper (registered
|
|
677
|
-
* and removed inside the `try/finally`) so Ctrl+C during the device-code
|
|
678
|
-
* polling loop doesn't leak a listener back to the verb's outer body. Server
|
|
679
|
-
* errors thrown by `authenticateForRepoAsync` are mapped through
|
|
680
|
-
* {@link mapGitHubServerErrorToCliError} before being rethrown so the verb's
|
|
681
|
-
* outer catch (and the single-retry contract) sees CLI-typed errors.
|
|
682
|
-
*/
|
|
683
|
-
async function runDcfRecoveryAsync(options) {
|
|
684
|
-
const { remoteUrl, tenantId, jsonMode, noBrowser, verb } = options;
|
|
685
|
-
// Re-parse here rather than threading the parsed components through every
|
|
686
|
-
// catch-and-retry call: preflight already validated the URL shape, so this
|
|
687
|
-
// is a cheap string-split on a rare path.
|
|
688
|
-
const { host, owner, repo } = parseGitHubRepoUrl(remoteUrl);
|
|
689
|
-
const repoHash = computeRepoHash(owner, repo);
|
|
690
|
-
const logger = getCliLogger();
|
|
691
|
-
// Telemetry: PII-safe — host is the parsed string, repoHash is a truncated
|
|
692
|
-
// SHA256. Emit Start at the top so a recovery that never reaches the retry
|
|
693
|
-
// (e.g. user denied auth) is still observable.
|
|
694
|
-
logger.trackActivityEvent('Maaf.Create.GitHubDcfRecovery', {
|
|
695
|
-
repoHost: host,
|
|
696
|
-
repoHash,
|
|
697
|
-
});
|
|
698
|
-
if (!jsonMode) {
|
|
699
|
-
printInfo('GitHub authentication is required to bind this repository.');
|
|
700
|
-
}
|
|
701
|
-
const display = composeRecoveryDisplay({ isJson: jsonMode, noBrowser });
|
|
702
|
-
// SIGINT plumbing: a Ctrl+C during the (potentially 15-minute) polling
|
|
703
|
-
// loop drops us out cleanly rather than leaving a dangling promise.
|
|
704
|
-
// Scoped to the recovery block only — createAsync itself doesn't take a
|
|
705
|
-
// signal yet.
|
|
706
|
-
const controller = new AbortController();
|
|
707
|
-
const onSigint = () => {
|
|
708
|
-
controller.abort();
|
|
709
|
-
};
|
|
710
|
-
process.once('SIGINT', onSigint);
|
|
711
|
-
try {
|
|
712
|
-
// The resolved auth result (githubLogin, mappingExpiresAt, etc.) is
|
|
713
|
-
// intentionally discarded: the retry path below doesn't need it — it
|
|
714
|
-
// just needs the server-side identity mapping to now exist so the next
|
|
715
|
-
// `createAsync` call clears the 401.
|
|
716
|
-
await authenticateForRepoAsync({
|
|
717
|
-
remoteUrl: `https://${host}/${owner}/${repo}`,
|
|
718
|
-
tenantId,
|
|
719
|
-
logger,
|
|
720
|
-
display,
|
|
721
|
-
// Browser launch is gated by the `display` object selected above:
|
|
722
|
-
// `composeRecoveryDisplay` returns a display whose `openVerificationUri`
|
|
723
|
-
// is undefined when `noBrowser` is true. `skipBrowser` here is the
|
|
724
|
-
// legacy lower-level kill-switch the action exposes; we hardcode it
|
|
725
|
-
// to false so the display contract is the single source of truth.
|
|
726
|
-
skipBrowser: false,
|
|
727
|
-
signal: controller.signal,
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
catch (error) {
|
|
731
|
-
// Telemetry: pair the Start event with a Failed event on every error path
|
|
732
|
-
// so a recovery that throws (DCF declined, server error, abort) still
|
|
733
|
-
// shows up as a closed pair in the activity stream rather than an
|
|
734
|
-
// orphaned Start. Pattern A (emit Failed in the catch) — chosen over a
|
|
735
|
-
// success-flag-in-finally to keep the existing throw flow untouched and
|
|
736
|
-
// avoid restructuring the GrsServerError mapping branch below.
|
|
737
|
-
logger.trackActivityEvent('Maaf.Create.GitHubDcfRecovery.Failed', {
|
|
738
|
-
repoHost: host,
|
|
739
|
-
repoHash,
|
|
740
|
-
});
|
|
741
|
-
// Only GrsServerError carries a structured `code` we can translate to a
|
|
742
|
-
// CLI-typed AppError. Anything else (plain Error, network error, etc.)
|
|
743
|
-
// propagates as-is — the verb's outer flow treats it as a hard failure
|
|
744
|
-
// (no retry, no second "Resuming create...").
|
|
745
|
-
if (error instanceof GrsServerError) {
|
|
746
|
-
const mapped = mapGitHubServerErrorToCliError({
|
|
747
|
-
code: error.code,
|
|
748
|
-
message: error.message,
|
|
749
|
-
status: error.status,
|
|
750
|
-
details: error.details,
|
|
751
|
-
}, { repoUrl: remoteUrl, verb });
|
|
752
|
-
if (mapped)
|
|
753
|
-
throw mapped;
|
|
754
|
-
}
|
|
755
|
-
throw error;
|
|
756
|
-
}
|
|
757
|
-
finally {
|
|
758
|
-
// Safe in both branches: if SIGINT already fired, `process.once` has
|
|
759
|
-
// already auto-removed the listener and `removeListener` is a no-op.
|
|
760
|
-
process.removeListener('SIGINT', onSigint);
|
|
761
|
-
}
|
|
762
|
-
logger.trackActivityEvent('Maaf.Create.GitHubDcfRecovery.Complete', {
|
|
763
|
-
repoHost: host,
|
|
764
|
-
repoHash,
|
|
765
|
-
});
|
|
766
|
-
if (!jsonMode) {
|
|
767
|
-
// Printed here (before the helper returns) rather than at the retry
|
|
768
|
-
// call site so it's adjacent to the DCF-completed telemetry event. If
|
|
769
|
-
// the subsequent createAsync retry hard-fails, the user sees
|
|
770
|
-
// "Resuming create..." followed by the failure message — still accurate:
|
|
771
|
-
// we DID resume; the resumed call is what failed.
|
|
772
|
-
printInfo('Resuming create...');
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* Build the right {@link GitHubDcfDisplay} for the create recovery path:
|
|
777
|
-
* - JSON mode → NDJSON events, never opens a browser.
|
|
778
|
-
* - `--no-browser` → console output, browser launch suppressed.
|
|
779
|
-
* - Default → console output + best-effort browser launch (TTY-gated).
|
|
780
|
-
*
|
|
781
|
-
* Note the inverted default vs `git auth refresh`: in recovery the user has
|
|
782
|
-
* already started a destructive operation (create), so we lean toward
|
|
783
|
-
* launching a browser by default and require explicit opt-out, instead of
|
|
784
|
-
* the refresh verb's explicit opt-in.
|
|
785
|
-
*/
|
|
786
|
-
function composeRecoveryDisplay(opts) {
|
|
787
|
-
if (opts.isJson) {
|
|
788
|
-
return new JsonGitHubDcfDisplay();
|
|
789
|
-
}
|
|
790
|
-
if (opts.noBrowser) {
|
|
791
|
-
return {
|
|
792
|
-
...ConsoleGitHubDcfDisplay,
|
|
793
|
-
openVerificationUri: undefined,
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
return ConsoleGitHubDcfDisplay;
|
|
797
|
-
}
|
|
798
785
|
function createOptionSchema(argumentProvider) {
|
|
799
786
|
argumentProvider.setDescription(HelpStrings.app.create.description);
|
|
800
787
|
argumentProvider.addExamples(HelpStrings.app.create.examples);
|
|
801
788
|
argumentProvider.consumePositionalArg('[directory]');
|
|
802
789
|
argumentProvider.addOptions([
|
|
790
|
+
{
|
|
791
|
+
flags: {
|
|
792
|
+
key: CreateMaafAppArguments.EnvironmentId.name,
|
|
793
|
+
alias: CreateMaafAppArguments.EnvironmentId.alias,
|
|
794
|
+
},
|
|
795
|
+
env: CreateMaafAppArguments.EnvironmentId.envVar,
|
|
796
|
+
description: HelpStrings.app.create.options.environmentId,
|
|
797
|
+
optionalInNonInteractive: true,
|
|
798
|
+
},
|
|
803
799
|
createDefaultStringOption({
|
|
804
800
|
flags: {
|
|
805
801
|
key: CreateMaafAppArguments.DisplayName.name,
|
|
@@ -880,8 +876,12 @@ function createOptionSchema(argumentProvider) {
|
|
|
880
876
|
}),
|
|
881
877
|
]);
|
|
882
878
|
argumentProvider.addSwitch({
|
|
883
|
-
flags: { key: CreateMaafAppArguments.
|
|
884
|
-
description: HelpStrings.app.create.options.
|
|
879
|
+
flags: { key: CreateMaafAppArguments.OpenBrowser.name },
|
|
880
|
+
description: HelpStrings.app.create.options.openBrowser,
|
|
881
|
+
});
|
|
882
|
+
argumentProvider.addSwitch({
|
|
883
|
+
flags: { key: CreateMaafAppArguments.ForceReauth.name },
|
|
884
|
+
description: HelpStrings.app.create.options.forceReauth,
|
|
885
885
|
});
|
|
886
886
|
}
|
|
887
887
|
//# sourceMappingURL=CreateMaafApp.js.map
|