@microsoft/managed-apps-cli 0.5.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.
Files changed (331) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +280 -0
  3. package/dist/ArgumentProvider.d.ts +102 -0
  4. package/dist/ArgumentProvider.d.ts.map +1 -0
  5. package/dist/ArgumentProvider.js +376 -0
  6. package/dist/ArgumentProvider.js.map +1 -0
  7. package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts +18 -0
  8. package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts.map +1 -0
  9. package/dist/Authentication/NodeMsalAuthenticationProvider.js +99 -0
  10. package/dist/Authentication/NodeMsalAuthenticationProvider.js.map +1 -0
  11. package/dist/Authentication/OrchardAuthenticationProvider.d.ts +13 -0
  12. package/dist/Authentication/OrchardAuthenticationProvider.d.ts.map +1 -0
  13. package/dist/Authentication/OrchardAuthenticationProvider.js +30 -0
  14. package/dist/Authentication/OrchardAuthenticationProvider.js.map +1 -0
  15. package/dist/Authentication/ServicePrincipalAuthenticationProvider.d.ts +23 -0
  16. package/dist/Authentication/ServicePrincipalAuthenticationProvider.d.ts.map +1 -0
  17. package/dist/Authentication/ServicePrincipalAuthenticationProvider.js +60 -0
  18. package/dist/Authentication/ServicePrincipalAuthenticationProvider.js.map +1 -0
  19. package/dist/Bin.d.ts +6 -0
  20. package/dist/Bin.d.ts.map +1 -0
  21. package/dist/Bin.js +7 -0
  22. package/dist/Bin.js.map +1 -0
  23. package/dist/Cli.d.ts +2 -0
  24. package/dist/Cli.d.ts.map +1 -0
  25. package/dist/Cli.js +91 -0
  26. package/dist/Cli.js.map +1 -0
  27. package/dist/CliSettings.d.ts +14 -0
  28. package/dist/CliSettings.d.ts.map +1 -0
  29. package/dist/CliSettings.js +78 -0
  30. package/dist/CliSettings.js.map +1 -0
  31. package/dist/CliUtils.d.ts +92 -0
  32. package/dist/CliUtils.d.ts.map +1 -0
  33. package/dist/CliUtils.js +218 -0
  34. package/dist/CliUtils.js.map +1 -0
  35. package/dist/Constants/CliConstants.d.ts +6 -0
  36. package/dist/Constants/CliConstants.d.ts.map +1 -0
  37. package/dist/Constants/CliConstants.js +6 -0
  38. package/dist/Constants/CliConstants.js.map +1 -0
  39. package/dist/Constants/CommandRegistry.d.ts +23 -0
  40. package/dist/Constants/CommandRegistry.d.ts.map +1 -0
  41. package/dist/Constants/CommandRegistry.js +193 -0
  42. package/dist/Constants/CommandRegistry.js.map +1 -0
  43. package/dist/Constants/HelpStrings.d.ts +321 -0
  44. package/dist/Constants/HelpStrings.d.ts.map +1 -0
  45. package/dist/Constants/HelpStrings.js +438 -0
  46. package/dist/Constants/HelpStrings.js.map +1 -0
  47. package/dist/Constants.d.ts +10 -0
  48. package/dist/Constants.d.ts.map +1 -0
  49. package/dist/Constants.js +18 -0
  50. package/dist/Constants.js.map +1 -0
  51. package/dist/Errors/CliError.d.ts +5 -0
  52. package/dist/Errors/CliError.d.ts.map +1 -0
  53. package/dist/Errors/CliError.js +7 -0
  54. package/dist/Errors/CliError.js.map +1 -0
  55. package/dist/FS/CliFs.d.ts +41 -0
  56. package/dist/FS/CliFs.d.ts.map +1 -0
  57. package/dist/FS/CliFs.js +136 -0
  58. package/dist/FS/CliFs.js.map +1 -0
  59. package/dist/HttpClient/CliHttpClient.d.ts +24 -0
  60. package/dist/HttpClient/CliHttpClient.d.ts.map +1 -0
  61. package/dist/HttpClient/CliHttpClient.js +122 -0
  62. package/dist/HttpClient/CliHttpClient.js.map +1 -0
  63. package/dist/HttpClient/OrchardProxy.d.ts +11 -0
  64. package/dist/HttpClient/OrchardProxy.d.ts.map +1 -0
  65. package/dist/HttpClient/OrchardProxy.js +25 -0
  66. package/dist/HttpClient/OrchardProxy.js.map +1 -0
  67. package/dist/HttpClient/Proxy.d.ts +7 -0
  68. package/dist/HttpClient/Proxy.d.ts.map +1 -0
  69. package/dist/HttpClient/Proxy.js +5 -0
  70. package/dist/HttpClient/Proxy.js.map +1 -0
  71. package/dist/Logger/CliLogger.d.ts +36 -0
  72. package/dist/Logger/CliLogger.d.ts.map +1 -0
  73. package/dist/Logger/CliLogger.js +235 -0
  74. package/dist/Logger/CliLogger.js.map +1 -0
  75. package/dist/Logger/ConsoleWriter.d.ts +15 -0
  76. package/dist/Logger/ConsoleWriter.d.ts.map +1 -0
  77. package/dist/Logger/ConsoleWriter.js +45 -0
  78. package/dist/Logger/ConsoleWriter.js.map +1 -0
  79. package/dist/Logger/Constants.d.ts +6 -0
  80. package/dist/Logger/Constants.d.ts.map +1 -0
  81. package/dist/Logger/Constants.js +15 -0
  82. package/dist/Logger/Constants.js.map +1 -0
  83. package/dist/Logger/LoggerSettings.d.ts +19 -0
  84. package/dist/Logger/LoggerSettings.d.ts.map +1 -0
  85. package/dist/Logger/LoggerSettings.js +114 -0
  86. package/dist/Logger/LoggerSettings.js.map +1 -0
  87. package/dist/Logger/OneDSWriter.d.ts +22 -0
  88. package/dist/Logger/OneDSWriter.d.ts.map +1 -0
  89. package/dist/Logger/OneDSWriter.js +129 -0
  90. package/dist/Logger/OneDSWriter.js.map +1 -0
  91. package/dist/Logger/Scrubber/ScrubCustomDimensions.d.ts +5 -0
  92. package/dist/Logger/Scrubber/ScrubCustomDimensions.d.ts.map +1 -0
  93. package/dist/Logger/Scrubber/ScrubCustomDimensions.js +46 -0
  94. package/dist/Logger/Scrubber/ScrubCustomDimensions.js.map +1 -0
  95. package/dist/Logger/Scrubber/ScrubCustomDimensionsConfig.d.ts +27 -0
  96. package/dist/Logger/Scrubber/ScrubCustomDimensionsConfig.d.ts.map +1 -0
  97. package/dist/Logger/Scrubber/ScrubCustomDimensionsConfig.js +168 -0
  98. package/dist/Logger/Scrubber/ScrubCustomDimensionsConfig.js.map +1 -0
  99. package/dist/Logger/Scrubber/ScrubUtils.d.ts +8 -0
  100. package/dist/Logger/Scrubber/ScrubUtils.d.ts.map +1 -0
  101. package/dist/Logger/Scrubber/ScrubUtils.js +34 -0
  102. package/dist/Logger/Scrubber/ScrubUtils.js.map +1 -0
  103. package/dist/Types/Argument.types.d.ts +27 -0
  104. package/dist/Types/Argument.types.d.ts.map +1 -0
  105. package/dist/Types/Argument.types.js +5 -0
  106. package/dist/Types/Argument.types.js.map +1 -0
  107. package/dist/Types/Cli.types.d.ts +32 -0
  108. package/dist/Types/Cli.types.d.ts.map +1 -0
  109. package/dist/Types/Cli.types.js +5 -0
  110. package/dist/Types/Cli.types.js.map +1 -0
  111. package/dist/Types/Command.types.d.ts +52 -0
  112. package/dist/Types/Command.types.d.ts.map +1 -0
  113. package/dist/Types/Command.types.js +8 -0
  114. package/dist/Types/Command.types.js.map +1 -0
  115. package/dist/Types/Datasource.types.d.ts +43 -0
  116. package/dist/Types/Datasource.types.d.ts.map +1 -0
  117. package/dist/Types/Datasource.types.js +5 -0
  118. package/dist/Types/Datasource.types.js.map +1 -0
  119. package/dist/Types/LoggerTypes.types.d.ts +17 -0
  120. package/dist/Types/LoggerTypes.types.d.ts.map +1 -0
  121. package/dist/Types/LoggerTypes.types.js +5 -0
  122. package/dist/Types/LoggerTypes.types.js.map +1 -0
  123. package/dist/Utils/BuildVerbDescriptionTable.d.ts +19 -0
  124. package/dist/Utils/BuildVerbDescriptionTable.d.ts.map +1 -0
  125. package/dist/Utils/BuildVerbDescriptionTable.js +87 -0
  126. package/dist/Utils/BuildVerbDescriptionTable.js.map +1 -0
  127. package/dist/Utils/ConsoleOutput.d.ts +80 -0
  128. package/dist/Utils/ConsoleOutput.d.ts.map +1 -0
  129. package/dist/Utils/ConsoleOutput.js +151 -0
  130. package/dist/Utils/ConsoleOutput.js.map +1 -0
  131. package/dist/Utils/DetectPackageManager.d.ts +9 -0
  132. package/dist/Utils/DetectPackageManager.d.ts.map +1 -0
  133. package/dist/Utils/DetectPackageManager.js +24 -0
  134. package/dist/Utils/DetectPackageManager.js.map +1 -0
  135. package/dist/Utils/EnhanceNetworkError.d.ts +14 -0
  136. package/dist/Utils/EnhanceNetworkError.d.ts.map +1 -0
  137. package/dist/Utils/EnhanceNetworkError.js +59 -0
  138. package/dist/Utils/EnhanceNetworkError.js.map +1 -0
  139. package/dist/Utils/EnvVars.d.ts +17 -0
  140. package/dist/Utils/EnvVars.d.ts.map +1 -0
  141. package/dist/Utils/EnvVars.js +38 -0
  142. package/dist/Utils/EnvVars.js.map +1 -0
  143. package/dist/Utils/GitCredentialConfig.d.ts +12 -0
  144. package/dist/Utils/GitCredentialConfig.d.ts.map +1 -0
  145. package/dist/Utils/GitCredentialConfig.js +32 -0
  146. package/dist/Utils/GitCredentialConfig.js.map +1 -0
  147. package/dist/Utils/GitUtils.d.ts +22 -0
  148. package/dist/Utils/GitUtils.d.ts.map +1 -0
  149. package/dist/Utils/GitUtils.js +35 -0
  150. package/dist/Utils/GitUtils.js.map +1 -0
  151. package/dist/Utils/Paging.d.ts +23 -0
  152. package/dist/Utils/Paging.d.ts.map +1 -0
  153. package/dist/Utils/Paging.js +67 -0
  154. package/dist/Utils/Paging.js.map +1 -0
  155. package/dist/Utils/ReadPackageJson.d.ts +9 -0
  156. package/dist/Utils/ReadPackageJson.d.ts.map +1 -0
  157. package/dist/Utils/ReadPackageJson.js +13 -0
  158. package/dist/Utils/ReadPackageJson.js.map +1 -0
  159. package/dist/Utils/ResolveCommand.d.ts +26 -0
  160. package/dist/Utils/ResolveCommand.d.ts.map +1 -0
  161. package/dist/Utils/ResolveCommand.js +62 -0
  162. package/dist/Utils/ResolveCommand.js.map +1 -0
  163. package/dist/Utils/RunCommand.d.ts +33 -0
  164. package/dist/Utils/RunCommand.d.ts.map +1 -0
  165. package/dist/Utils/RunCommand.js +83 -0
  166. package/dist/Utils/RunCommand.js.map +1 -0
  167. package/dist/Utils/Spinner.d.ts +15 -0
  168. package/dist/Utils/Spinner.d.ts.map +1 -0
  169. package/dist/Utils/Spinner.js +71 -0
  170. package/dist/Utils/Spinner.js.map +1 -0
  171. package/dist/Utils/zipBuildArtifact.d.ts +35 -0
  172. package/dist/Utils/zipBuildArtifact.d.ts.map +1 -0
  173. package/dist/Utils/zipBuildArtifact.js +100 -0
  174. package/dist/Utils/zipBuildArtifact.js.map +1 -0
  175. package/dist/Verbs/AddAction.d.ts +6 -0
  176. package/dist/Verbs/AddAction.d.ts.map +1 -0
  177. package/dist/Verbs/AddAction.js +87 -0
  178. package/dist/Verbs/AddAction.js.map +1 -0
  179. package/dist/Verbs/AddDataSource.d.ts +19 -0
  180. package/dist/Verbs/AddDataSource.d.ts.map +1 -0
  181. package/dist/Verbs/AddDataSource.js +173 -0
  182. package/dist/Verbs/AddDataSource.js.map +1 -0
  183. package/dist/Verbs/AddFlow.d.ts +12 -0
  184. package/dist/Verbs/AddFlow.d.ts.map +1 -0
  185. package/dist/Verbs/AddFlow.js +67 -0
  186. package/dist/Verbs/AddFlow.js.map +1 -0
  187. package/dist/Verbs/AddTable.d.ts +6 -0
  188. package/dist/Verbs/AddTable.d.ts.map +1 -0
  189. package/dist/Verbs/AddTable.js +163 -0
  190. package/dist/Verbs/AddTable.js.map +1 -0
  191. package/dist/Verbs/AuthStatus.d.ts +6 -0
  192. package/dist/Verbs/AuthStatus.d.ts.map +1 -0
  193. package/dist/Verbs/AuthStatus.js +18 -0
  194. package/dist/Verbs/AuthStatus.js.map +1 -0
  195. package/dist/Verbs/BuildMaafApp.d.ts +6 -0
  196. package/dist/Verbs/BuildMaafApp.d.ts.map +1 -0
  197. package/dist/Verbs/BuildMaafApp.js +96 -0
  198. package/dist/Verbs/BuildMaafApp.js.map +1 -0
  199. package/dist/Verbs/ConnectorHelpers.d.ts +37 -0
  200. package/dist/Verbs/ConnectorHelpers.d.ts.map +1 -0
  201. package/dist/Verbs/ConnectorHelpers.js +116 -0
  202. package/dist/Verbs/ConnectorHelpers.js.map +1 -0
  203. package/dist/Verbs/CreateMaafApp.d.ts +58 -0
  204. package/dist/Verbs/CreateMaafApp.d.ts.map +1 -0
  205. package/dist/Verbs/CreateMaafApp.js +712 -0
  206. package/dist/Verbs/CreateMaafApp.js.map +1 -0
  207. package/dist/Verbs/DeleteDataSource.d.ts +6 -0
  208. package/dist/Verbs/DeleteDataSource.d.ts.map +1 -0
  209. package/dist/Verbs/DeleteDataSource.js +105 -0
  210. package/dist/Verbs/DeleteDataSource.js.map +1 -0
  211. package/dist/Verbs/DeleteMaafApp.d.ts +23 -0
  212. package/dist/Verbs/DeleteMaafApp.d.ts.map +1 -0
  213. package/dist/Verbs/DeleteMaafApp.js +98 -0
  214. package/dist/Verbs/DeleteMaafApp.js.map +1 -0
  215. package/dist/Verbs/DeployMaafApp.d.ts +6 -0
  216. package/dist/Verbs/DeployMaafApp.d.ts.map +1 -0
  217. package/dist/Verbs/DeployMaafApp.js +501 -0
  218. package/dist/Verbs/DeployMaafApp.js.map +1 -0
  219. package/dist/Verbs/Dev.d.ts +6 -0
  220. package/dist/Verbs/Dev.d.ts.map +1 -0
  221. package/dist/Verbs/Dev.js +212 -0
  222. package/dist/Verbs/Dev.js.map +1 -0
  223. package/dist/Verbs/FindDataverseApi.d.ts +12 -0
  224. package/dist/Verbs/FindDataverseApi.d.ts.map +1 -0
  225. package/dist/Verbs/FindDataverseApi.js +101 -0
  226. package/dist/Verbs/FindDataverseApi.js.map +1 -0
  227. package/dist/Verbs/GetMaafApp.d.ts +6 -0
  228. package/dist/Verbs/GetMaafApp.d.ts.map +1 -0
  229. package/dist/Verbs/GetMaafApp.js +59 -0
  230. package/dist/Verbs/GetMaafApp.js.map +1 -0
  231. package/dist/Verbs/GetMaafBuildStatus.d.ts +6 -0
  232. package/dist/Verbs/GetMaafBuildStatus.d.ts.map +1 -0
  233. package/dist/Verbs/GetMaafBuildStatus.js +67 -0
  234. package/dist/Verbs/GetMaafBuildStatus.js.map +1 -0
  235. package/dist/Verbs/GetSettings.d.ts +12 -0
  236. package/dist/Verbs/GetSettings.d.ts.map +1 -0
  237. package/dist/Verbs/GetSettings.js +48 -0
  238. package/dist/Verbs/GetSettings.js.map +1 -0
  239. package/dist/Verbs/Init.d.ts +6 -0
  240. package/dist/Verbs/Init.d.ts.map +1 -0
  241. package/dist/Verbs/Init.js +171 -0
  242. package/dist/Verbs/Init.js.map +1 -0
  243. package/dist/Verbs/ListConnectionReferences.d.ts +6 -0
  244. package/dist/Verbs/ListConnectionReferences.d.ts.map +1 -0
  245. package/dist/Verbs/ListConnectionReferences.js +70 -0
  246. package/dist/Verbs/ListConnectionReferences.js.map +1 -0
  247. package/dist/Verbs/ListConnectorActions.d.ts +6 -0
  248. package/dist/Verbs/ListConnectorActions.d.ts.map +1 -0
  249. package/dist/Verbs/ListConnectorActions.js +73 -0
  250. package/dist/Verbs/ListConnectorActions.js.map +1 -0
  251. package/dist/Verbs/ListConnectors.d.ts +6 -0
  252. package/dist/Verbs/ListConnectors.d.ts.map +1 -0
  253. package/dist/Verbs/ListConnectors.js +66 -0
  254. package/dist/Verbs/ListConnectors.js.map +1 -0
  255. package/dist/Verbs/ListDatasets.d.ts +6 -0
  256. package/dist/Verbs/ListDatasets.d.ts.map +1 -0
  257. package/dist/Verbs/ListDatasets.js +82 -0
  258. package/dist/Verbs/ListDatasets.js.map +1 -0
  259. package/dist/Verbs/ListEnvironmentVariables.d.ts +14 -0
  260. package/dist/Verbs/ListEnvironmentVariables.d.ts.map +1 -0
  261. package/dist/Verbs/ListEnvironmentVariables.js +66 -0
  262. package/dist/Verbs/ListEnvironmentVariables.js.map +1 -0
  263. package/dist/Verbs/ListFlows.d.ts +13 -0
  264. package/dist/Verbs/ListFlows.d.ts.map +1 -0
  265. package/dist/Verbs/ListFlows.js +88 -0
  266. package/dist/Verbs/ListFlows.js.map +1 -0
  267. package/dist/Verbs/ListMaafApps.d.ts +71 -0
  268. package/dist/Verbs/ListMaafApps.d.ts.map +1 -0
  269. package/dist/Verbs/ListMaafApps.js +168 -0
  270. package/dist/Verbs/ListMaafApps.js.map +1 -0
  271. package/dist/Verbs/ListSqlStoredProcedures.d.ts +6 -0
  272. package/dist/Verbs/ListSqlStoredProcedures.d.ts.map +1 -0
  273. package/dist/Verbs/ListSqlStoredProcedures.js +84 -0
  274. package/dist/Verbs/ListSqlStoredProcedures.js.map +1 -0
  275. package/dist/Verbs/ListTables.d.ts +6 -0
  276. package/dist/Verbs/ListTables.d.ts.map +1 -0
  277. package/dist/Verbs/ListTables.js +94 -0
  278. package/dist/Verbs/ListTables.js.map +1 -0
  279. package/dist/Verbs/Login.d.ts +6 -0
  280. package/dist/Verbs/Login.d.ts.map +1 -0
  281. package/dist/Verbs/Login.js +28 -0
  282. package/dist/Verbs/Login.js.map +1 -0
  283. package/dist/Verbs/Logout.d.ts +6 -0
  284. package/dist/Verbs/Logout.d.ts.map +1 -0
  285. package/dist/Verbs/Logout.js +28 -0
  286. package/dist/Verbs/Logout.js.map +1 -0
  287. package/dist/Verbs/MaafAppPermissions.d.ts +23 -0
  288. package/dist/Verbs/MaafAppPermissions.d.ts.map +1 -0
  289. package/dist/Verbs/MaafAppPermissions.js +278 -0
  290. package/dist/Verbs/MaafAppPermissions.js.map +1 -0
  291. package/dist/Verbs/Pack.d.ts +42 -0
  292. package/dist/Verbs/Pack.d.ts.map +1 -0
  293. package/dist/Verbs/Pack.js +114 -0
  294. package/dist/Verbs/Pack.js.map +1 -0
  295. package/dist/Verbs/RefreshDataSource.d.ts +6 -0
  296. package/dist/Verbs/RefreshDataSource.d.ts.map +1 -0
  297. package/dist/Verbs/RefreshDataSource.js +72 -0
  298. package/dist/Verbs/RefreshDataSource.js.map +1 -0
  299. package/dist/Verbs/RemoveFlow.d.ts +12 -0
  300. package/dist/Verbs/RemoveFlow.d.ts.map +1 -0
  301. package/dist/Verbs/RemoveFlow.js +99 -0
  302. package/dist/Verbs/RemoveFlow.js.map +1 -0
  303. package/dist/Verbs/SetSetting.d.ts +12 -0
  304. package/dist/Verbs/SetSetting.d.ts.map +1 -0
  305. package/dist/Verbs/SetSetting.js +141 -0
  306. package/dist/Verbs/SetSetting.js.map +1 -0
  307. package/dist/Verbs/ShareMaafApp.d.ts +12 -0
  308. package/dist/Verbs/ShareMaafApp.d.ts.map +1 -0
  309. package/dist/Verbs/ShareMaafApp.js +14 -0
  310. package/dist/Verbs/ShareMaafApp.js.map +1 -0
  311. package/dist/Verbs/ShowMaafApp.d.ts +94 -0
  312. package/dist/Verbs/ShowMaafApp.d.ts.map +1 -0
  313. package/dist/Verbs/ShowMaafApp.js +216 -0
  314. package/dist/Verbs/ShowMaafApp.js.map +1 -0
  315. package/dist/Verbs/TelemetrySettings.d.ts +8 -0
  316. package/dist/Verbs/TelemetrySettings.d.ts.map +1 -0
  317. package/dist/Verbs/TelemetrySettings.js +104 -0
  318. package/dist/Verbs/TelemetrySettings.js.map +1 -0
  319. package/dist/Verbs/UnshareMaafApp.d.ts +12 -0
  320. package/dist/Verbs/UnshareMaafApp.d.ts.map +1 -0
  321. package/dist/Verbs/UnshareMaafApp.js +14 -0
  322. package/dist/Verbs/UnshareMaafApp.js.map +1 -0
  323. package/dist/Verbs/VerbConstants.d.ts +415 -0
  324. package/dist/Verbs/VerbConstants.d.ts.map +1 -0
  325. package/dist/Verbs/VerbConstants.js +382 -0
  326. package/dist/Verbs/VerbConstants.js.map +1 -0
  327. package/dist/Verbs/index.d.ts +39 -0
  328. package/dist/Verbs/index.d.ts.map +1 -0
  329. package/dist/Verbs/index.js +39 -0
  330. package/dist/Verbs/index.js.map +1 -0
  331. package/package.json +52 -0
@@ -0,0 +1,712 @@
1
+ /*!
2
+ * Copyright (C) Microsoft Corporation. All rights reserved.
3
+ */
4
+ import { createAsync, getPlayerServiceConfig, updateEnvironmentName, writeRepoConfig, } from '@microsoft/managed-apps-actions';
5
+ import { AppConfigFileName } from '@microsoft/managed-apps-common/config';
6
+ import { getUserRoutingEnvironmentAsync, ManagedGovernanceError, ManagedGovernanceTimeoutError, pollRoutingEnvironmentAsync, } from '@microsoft/managed-apps-common/services';
7
+ import { execFileSync } from 'child_process';
8
+ import * as fs from 'fs';
9
+ import { downloadTemplate } from 'giget';
10
+ import * as path from 'path';
11
+ import { getCliLogger } from '../CliSettings.js';
12
+ import { createDefaultStringOption } from '../CliUtils.js';
13
+ import { CLI_CLIENT_ID } from '../Constants.js';
14
+ import { CLI_BINARY_NAME } from '../Constants/CliConstants.js';
15
+ import { HelpStrings } from '../Constants/HelpStrings.js';
16
+ import { AppError, ConfigurationError, NotFoundError, UsageError } from '../Errors/CliError.js';
17
+ import { printInfo, printJsonSuccess, printSuccess, printWarning } from '../Utils/ConsoleOutput.js';
18
+ import { buildGcmConfig } from '../Utils/GitCredentialConfig.js';
19
+ import { CreateMaafAppArguments } from './VerbConstants.js';
20
+ const DEFAULT_TEMPLATE = 'github:microsoft/Managed-Apps/templates/vite8';
21
+ /**
22
+ * Sentinel value for `--repo` that selects the no-source-control flow: just create the
23
+ * server-side app record, write `ms.config.json` with `repoType: 'none'`, and skip every
24
+ * local-setup step (git init/clone, template overlay, GCM config) plus the server-side
25
+ * GRS auto-provisioning. Comparison is case-insensitive — `none`, `None`, `NONE` all
26
+ * select this flow. Any other non-empty `--repo` value is treated as a remote URL.
27
+ */
28
+ const REPO_NONE_SENTINEL = 'none';
29
+ /**
30
+ * Maps the raw `--repo` flag value to a `(repoType, remoteUrl)` pair.
31
+ * - omitted / empty → `native` (default GRS-backed flow)
32
+ * - `none` (case-insensitive) → `none` (no GRS, no local setup)
33
+ * - any other non-empty value → `github` (external GHE binding, value flows through as
34
+ * `remoteUrl` to the action and the eventual git clone)
35
+ */
36
+ export function parseRepoOption(rawRepoValue) {
37
+ if (rawRepoValue === undefined || rawRepoValue === '') {
38
+ return { repoType: 'native', remoteUrl: undefined };
39
+ }
40
+ if (rawRepoValue.trim().toLowerCase() === REPO_NONE_SENTINEL) {
41
+ return { repoType: 'none', remoteUrl: undefined };
42
+ }
43
+ return { repoType: 'github', remoteUrl: rawRepoValue };
44
+ }
45
+ export async function createMaafAppAsync(argumentProvider) {
46
+ createOptionSchema(argumentProvider);
47
+ await argumentProvider.runVerb();
48
+ const tenantId = await argumentProvider.getTenantIdAsync();
49
+ if (!tenantId) {
50
+ throw new UsageError('Unable to determine tenant ID. Ensure you are logged in.');
51
+ }
52
+ const logger = getCliLogger();
53
+ const httpClient = argumentProvider.getHttpClient();
54
+ const { region } = getPlayerServiceConfig();
55
+ const displayName = await argumentProvider.getOption('displayName');
56
+ if (!displayName) {
57
+ throw new UsageError('Argument displayName is required. Use --display-name or -n to specify it.');
58
+ }
59
+ // The positional arg (or cwd) is both the git repo root and the app dir.
60
+ const appDir = argumentProvider.getPositionalArg(0) ?? process.cwd();
61
+ const template = await argumentProvider.getOption('template', /* ignorePrompt */ true);
62
+ // CLI surface is --repo. Three flows, decided up front so the rest of the verb can
63
+ // branch on `repoType` instead of inspecting `remoteUrl` everywhere:
64
+ // - undefined → native (GRS-backed; current default)
65
+ // - 'none' → no-source-control (skip GRS + skip every local setup step)
66
+ // - any other value → github (external GHE binding; clone the URL locally)
67
+ // The local variable name `remoteUrl` is preserved on purpose: the wire contract to
68
+ // RP and the apps-actions param are still `remoteUrl` / `externalRepo.remoteUrl`, so
69
+ // renaming end-to-end is deferred until those callers move.
70
+ const rawRepoValue = await argumentProvider.getOption('repo',
71
+ /* ignorePrompt */ true);
72
+ const { repoType, remoteUrl } = parseRepoOption(rawRepoValue);
73
+ // Read --repository-id up front (instead of after preflight, like the other prompts)
74
+ // so the `--repo none` conflict check below has it. It is `optionalInNonInteractive`
75
+ // and registered with `ignorePrompt: true`, so reading early never triggers an
76
+ // interactive prompt.
77
+ const repositoryId = await argumentProvider.getOption('repositoryId',
78
+ /* ignorePrompt */ true);
79
+ // --json emits a single machine-parseable envelope on stdout, so every
80
+ // informational `printInfo` below is gated behind `!jsonMode` to keep
81
+ // stdout free of prose ahead of the envelope. `printError` writes to
82
+ // stderr and is unaffected.
83
+ const jsonMode = argumentProvider.isJsonMode();
84
+ // Identity confirmation: print the tenant + region we're targeting before
85
+ // anything else — preflight, routing, createAsync — so a user with multiple
86
+ // accounts signed in has the longest possible window to CTRL-C before any
87
+ // local check runs or server call fires. Environment id isn't known yet
88
+ // (routing is what resolves it); included in the success line at the end.
89
+ // Wording is intentionally tentative ("About to create…") so a preflight
90
+ // failure on the next line doesn't read as contradicting a "Creating…" line.
91
+ if (!jsonMode) {
92
+ printInfo(`About to create app '${displayName}' in tenant ${tenantId} (region ${region}).`);
93
+ }
94
+ // ─── Preflight ────────────────────────────────────────────────────────────
95
+ // Run all local-only checks before any server call. Once createAsync
96
+ // succeeds the user has a MAAF app + GRS repo on the server; a later
97
+ // local-setup failure leaves them orphaned with no clean recovery path.
98
+ // Anything we can validate without hitting the server belongs here.
99
+ //
100
+ // --repo path: still requires git installed, writable target, and an empty
101
+ // target (since `git clone` refuses non-empty destinations and we don't
102
+ // destructively wipe local files). Template-source validation is skipped —
103
+ // the template is overlaid onto the cloned working tree, but the same
104
+ // giget call validates the source. To bind a non-empty directory to a
105
+ // MAAF app, use `ms app init` instead.
106
+ //
107
+ // --repo none path: no git, no clone, no template — only ms.config.json
108
+ // gets written locally. The preflight reduces to (1) target dir must be
109
+ // writable so the config write later won't fail post-create, and (2) the
110
+ // dir must NOT already contain an ms.config.json (silently rebinding an
111
+ // existing config to a new app would be a footgun). Empty-dir is NOT
112
+ // required: the user opted out of scaffolding, so dropping an
113
+ // ms.config.json next to existing files is the intent.
114
+ // ──────────────────────────────────────────────────────────────────────────
115
+ if (!jsonMode) {
116
+ printInfo('Running preflight checks...');
117
+ }
118
+ // --repo none is incompatible with --repository-id: the former opts out of any
119
+ // platform-managed git binding, the latter forces one. Fail fast before any
120
+ // server call so we don't half-bind the app.
121
+ if (repoType === 'none' && repositoryId) {
122
+ throw new UsageError("'--repo none' is incompatible with '--repository-id'. " +
123
+ "Use '--repo none' for an app with no git binding, or omit '--repo' (or pass a repo URL) to use a backing repository.");
124
+ }
125
+ // --repo none does not scaffold a template, so a user-supplied --template would be
126
+ // silently dropped. Reject up front rather than appear to honor it.
127
+ if (repoType === 'none' && template !== DEFAULT_TEMPLATE) {
128
+ throw new UsageError("'--template' is not supported with '--repo none' because no files are scaffolded. " +
129
+ "Remove '--template' or drop '--repo none' if you want template scaffolding.");
130
+ }
131
+ if (repoType !== 'none') {
132
+ ensureGitInstalled();
133
+ }
134
+ if (repoType === 'native') {
135
+ ensureTemplateSourceValid(template);
136
+ }
137
+ ensureTargetDirectoryWritable(appDir);
138
+ if (repoType === 'github') {
139
+ ensureRepoTargetDirectoryEmpty(appDir);
140
+ }
141
+ else if (repoType === 'native') {
142
+ ensureTargetDirectoryEmpty(appDir);
143
+ }
144
+ else {
145
+ // --repo none: empty-dir is intentionally not required. We still guard against
146
+ // overwriting an existing ms.config.json so the user can't silently rebind a
147
+ // pre-existing app folder to a brand-new server app.
148
+ ensureNoExistingConfig(appDir);
149
+ }
150
+ // ─── End preflight ────────────────────────────────────────────────────────
151
+ // The debug env-var override (MS_CLI_MAAF_DEBUG_ENVIRONMENT_ID) is honored by ArgumentProvider
152
+ // and surfaced via getEnvironmentId(). When set, skip the routing API call entirely.
153
+ // For the supported user flow, getEnvironmentId() returns undefined here and routing fires.
154
+ const preResolvedEnvironmentId = argumentProvider.getEnvironmentId();
155
+ // Fire off PDE routing in parallel with the remaining option prompts. The result is awaited
156
+ // immediately before createAsync, so prompt latency is hidden behind the network call.
157
+ // Skipped entirely when the debug override pre-resolved the env id.
158
+ const routingEnvPromise = preResolvedEnvironmentId
159
+ ? undefined
160
+ : getUserRoutingEnvironmentAsync(httpClient, tenantId, CLI_CLIENT_ID, { provisionDataverse: false }, region, logger);
161
+ // If a later prompt throws before we await the promise, the rejection would otherwise be
162
+ // unhandled. Log and swallow here; `await routingEnvPromise` below still surfaces the error.
163
+ routingEnvPromise?.catch((error) => {
164
+ logger.trackErrorEvent('CreateMaafApp.RoutingEnvironmentBackgroundFailure', {
165
+ error: error instanceof Error ? error.message : String(error),
166
+ });
167
+ });
168
+ const description = await argumentProvider.getOption('description',
169
+ /* ignorePrompt */ true);
170
+ const buildPath = await argumentProvider.getOption('buildPath', /* ignorePrompt */ true);
171
+ const buildCommand = await argumentProvider.getOption('buildCommand',
172
+ /* ignorePrompt */ true);
173
+ // Explicit branch keeps the non-null read of routingEnvPromise reachable only when
174
+ // preResolvedEnvironmentId is undefined — i.e. exactly when routingEnvPromise is defined.
175
+ const environmentId = preResolvedEnvironmentId !== undefined
176
+ ? preResolvedEnvironmentId
177
+ : await resolveRoutingEnvironmentIdAsync(routingEnvPromise, httpClient, logger);
178
+ updateEnvironmentName(environmentId);
179
+ // Re-check the directory invariant right before the server call. The same
180
+ // check ran in preflight, but the routing-env resolution between then and
181
+ // now is async and may take seconds — long enough for the user (or another
182
+ // tool) to drop a file (or ms.config.json) into appDir. Repeating the check
183
+ // here keeps the TOCTOU window between "directory looks fine" and
184
+ // "createAsync succeeded" as small as possible without ever orphaning a
185
+ // server-side app.
186
+ if (repoType === 'github') {
187
+ ensureRepoTargetDirectoryEmpty(appDir);
188
+ }
189
+ else if (repoType === 'native') {
190
+ ensureTargetDirectoryEmpty(appDir);
191
+ }
192
+ else {
193
+ ensureNoExistingConfig(appDir);
194
+ }
195
+ const { app, cloneUrl } = await createAsync({
196
+ tenantId,
197
+ environmentId,
198
+ logger,
199
+ actionsParams: { displayName, description, repositoryId, remoteUrl, repoType },
200
+ });
201
+ // `--repo none` is the only path where the server intentionally returns no GRS clone
202
+ // URL (we asked for "no platform-managed repo"). The native and github flows both
203
+ // expect one: native uses it as origin for the first push; github uses it as the
204
+ // platform-managed build-artifact store backing the external GHE binding. Treat its
205
+ // absence on those flows as a server bug — the hoisted check below guards both the
206
+ // pretty-output `(clone URL: undefined)` and JSON-envelope `gitRemoteUrl: undefined`
207
+ // failure modes that would otherwise leak through.
208
+ if (repoType !== 'none' && !cloneUrl) {
209
+ const recoveryHint = buildOrphanedAppRecoveryHint(app.id, app.environmentId);
210
+ if (repoType === 'github') {
211
+ throw new AppError(`App '${app.displayName}' was created and bound to ${remoteUrl}, but the ` +
212
+ `platform-managed git repository for build artifact storage was not provisioned. ` +
213
+ `This is a platform-side issue; please report it.\n` +
214
+ recoveryHint);
215
+ }
216
+ throw new AppError(`App '${app.displayName}' was created on the server, but creating a git repository failed. ` +
217
+ `This is a platform-side issue; please report it. ` +
218
+ `In the meantime, the partial app needs to be cleaned up.\n` +
219
+ recoveryHint);
220
+ }
221
+ // Set up local files. Runs after the API calls succeed; if any step here
222
+ // fails the server app is already created and the recovery hint below tells
223
+ // the user how to clean it up. The branch per `repoType`:
224
+ // - native: init local git repo against the auto-provisioned GRS clone URL,
225
+ // then overlay the template, then write ms.config.json.
226
+ // - github: clone the user's external repo, sanity-check it's empty,
227
+ // overlay the template, then write ms.config.json.
228
+ // - none: no git, no template — just mkdir -p and write ms.config.json.
229
+ try {
230
+ if (repoType === 'github') {
231
+ // External GitHub Enterprise path: clone the developer's repo. Auth flows through
232
+ // the user's existing git credential setup (gh, GCM browser OAuth, SSH key, PAT,
233
+ // x509 client cert, etc.); the CLI does not inject any tokens at this layer (the
234
+ // Microsoft GitHub App is server-side only per the MAAF GitHub Integration design
235
+ // doc Requirement 1).
236
+ cloneRepo({ directory: appDir, repoUrl: remoteUrl });
237
+ // Post-clone sanity: --repo at P1 expects an empty GHE repo we can scaffold the
238
+ // template into. If the cloned working tree already has content (existing project,
239
+ // README, LICENSE, etc.), `ms app init` is the path that handles binding an existing
240
+ // project to a new MAAF app — bail with that pointer instead of overwriting files.
241
+ const workingTreeEntries = fs.readdirSync(appDir).filter((entry) => entry !== '.git');
242
+ if (workingTreeEntries.length > 0) {
243
+ 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.`);
244
+ }
245
+ }
246
+ else if (repoType === 'native') {
247
+ initLocalRepo({
248
+ directory: appDir,
249
+ gitUrl: cloneUrl,
250
+ loginAuthority: argumentProvider.getLoginAuthority(tenantId),
251
+ authResource: argumentProvider.getAuthResourceUrl(),
252
+ jsonMode,
253
+ });
254
+ }
255
+ else {
256
+ // --repo none: the user opted out of all scaffolding. Just ensure the
257
+ // target dir exists so the writeRepoConfig call below can land
258
+ // ms.config.json next to whatever the user already has (or in a fresh
259
+ // empty dir). `mkdirSync(..., { recursive: true })` is a no-op if the
260
+ // directory already exists.
261
+ fs.mkdirSync(appDir, { recursive: true });
262
+ }
263
+ // Overlay the template for the flows that scaffold one. `--repo none` opts out of
264
+ // scaffolding entirely, so this block is skipped and the user's existing files
265
+ // (if any) are left alone.
266
+ if (repoType !== 'none') {
267
+ if (!jsonMode) {
268
+ printInfo(`Fetching template '${template}' into ${appDir}...`);
269
+ }
270
+ await downloadTemplate(template, { dir: appDir, force: true });
271
+ }
272
+ const config = {
273
+ appId: app.id,
274
+ appDisplayName: app.displayName,
275
+ description: app.description,
276
+ environmentId: app.environmentId,
277
+ // `--repo none` apps have no platform-managed git binding; omit `repositoryId`
278
+ // from the persisted config so downstream verbs that key off it see the
279
+ // intentional absence rather than an empty/placeholder value.
280
+ ...(repoType !== 'none' && { repositoryId: app.repositoryId }),
281
+ repoType,
282
+ cloud: argumentProvider.getCloud(),
283
+ buildPath: buildPath ?? './dist',
284
+ buildCommand: buildCommand ?? 'npm run build',
285
+ };
286
+ await writeRepoConfig(path.join(appDir, AppConfigFileName), config);
287
+ }
288
+ catch (setupError) {
289
+ // UsageError carries already-actionable messages (e.g. "repo already contains an
290
+ // app — try ms app init"). Let those through unwrapped so the user sees the
291
+ // intended phrasing instead of the generic "local setup failed" prefix.
292
+ if (setupError instanceof UsageError) {
293
+ throw setupError;
294
+ }
295
+ throw new AppError(`App '${app.displayName}' was created, but local setup failed: ${setupError instanceof Error ? setupError.message : String(setupError)}\n` + buildOrphanedAppRecoveryHint(app.id, app.environmentId));
296
+ }
297
+ if (jsonMode) {
298
+ // `appId` is the bare GUID returned by the server (`MaafAppDefinition.id`) and
299
+ // also written to ms.config.json — used by every subsequent CLI command
300
+ // (build, deploy, info, etc.). `gitRemoteUrl` is the GRS clone URL (auto-provisioned
301
+ // alongside the external binding for build artifact storage) and is omitted for
302
+ // `--repo none` since no platform-managed git repo exists. `externalRepoUrl` is the
303
+ // user-supplied GHE URL and only appears in the `github` flow. `repoType` is always
304
+ // emitted so automation can detect the new no-source-control path.
305
+ printJsonSuccess({
306
+ appId: app.id,
307
+ repoType,
308
+ ...(repoType === 'github' && remoteUrl && { externalRepoUrl: remoteUrl }),
309
+ ...(repoType !== 'none' && cloneUrl && { gitRemoteUrl: cloneUrl }),
310
+ appDir,
311
+ });
312
+ }
313
+ else {
314
+ printSuccess(`Created app '${app.displayName}' in ${appDir}.`);
315
+ if (repoType === 'github') {
316
+ printInfo(`A platform-managed git repository was also provisioned for build artifact storage (clone URL: ${cloneUrl}).`);
317
+ }
318
+ else if (repoType === 'none') {
319
+ printInfo('Your app has been created. Run `git init` (or your VCS of choice) when ready; ' +
320
+ 'subsequent `ms app deploy` calls do not require a git remote.');
321
+ }
322
+ }
323
+ }
324
+ async function resolveRoutingEnvironmentIdAsync(resultPromise, httpClient, logger) {
325
+ // Translate routing-service errors into a UsageError so the user sees an actionable message
326
+ // rather than the raw "ManagedGovernance request failed with status 403" implementation detail.
327
+ // NotFoundError throws inside the try also flow through `toRoutingUsageError`, which leaves
328
+ // non-ManagedGovernance errors unchanged.
329
+ try {
330
+ const result = await resultPromise;
331
+ if (result.status === 200) {
332
+ const name = result.data.environment?.name;
333
+ if (!name) {
334
+ throw new NotFoundError('Routing service returned 200 with no environment name.');
335
+ }
336
+ return name;
337
+ }
338
+ // 202: provisioning is in progress — poll until it completes.
339
+ // Non-2xx responses are already thrown as ManagedGovernanceError inside
340
+ // getUserRoutingEnvironmentAsync, so only 200/202 reach this code path.
341
+ const routingResponse = await pollRoutingEnvironmentAsync(httpClient, result.pollingUrl, result.authResource, logger);
342
+ if (!routingResponse.environment?.name) {
343
+ throw new NotFoundError('Routing service finished provisioning but returned no environment name.');
344
+ }
345
+ return routingResponse.environment.name;
346
+ }
347
+ catch (err) {
348
+ throw toRoutingUsageError(err);
349
+ }
350
+ }
351
+ function toRoutingUsageError(err) {
352
+ if (err instanceof ManagedGovernanceError) {
353
+ return new UsageError(`Could not provision a Developer environment for your tenant (status ${err.status}). ` +
354
+ 'Ensure your account has the required permissions to create a Developer environment.');
355
+ }
356
+ if (err instanceof ManagedGovernanceTimeoutError) {
357
+ return new UsageError('Timed out waiting for a Developer environment to provision. Try again.');
358
+ }
359
+ return err instanceof Error ? err : new Error(String(err));
360
+ }
361
+ /**
362
+ * Clones the developer's external repo (GitHub Enterprise Cloud in v1) into `directory`.
363
+ * Auth flows through the user's existing git credential setup — the CLI does not inject
364
+ * tokens here. On failure, surfaces a CLI-friendly error pointing the user at the
365
+ * GitHub App install prerequisite; git's own stderr is preserved (stdio: inherit) so
366
+ * the underlying message appears inline above the wrapper.
367
+ *
368
+ * Targeting rules (mirrors `git clone` semantics):
369
+ * - directory non-existent → clone creates it
370
+ * - directory exists and is empty → clone fills it
371
+ * - directory exists and is non-empty → clone fails (caller's pre-network guard
372
+ * should reject this case earlier with a clearer message)
373
+ */
374
+ export function cloneRepo({ directory, repoUrl }) {
375
+ try {
376
+ execFileSync('git', ['clone', repoUrl, directory], {
377
+ stdio: ['ignore', 'inherit', 'inherit'],
378
+ });
379
+ }
380
+ catch (err) {
381
+ throw new ConfigurationError(`Failed to clone '${repoUrl}' into '${directory}'. Ensure the Microsoft GitHub App is installed and authorized for this repository, and that your git credentials can access it (gh auth login, GCM, or your configured SSH key/PAT). Underlying error: ${err instanceof Error ? err.message : String(err)}`);
382
+ }
383
+ }
384
+ export function initLocalRepo({ directory, gitUrl, loginAuthority, authResource, jsonMode = false, }) {
385
+ let gcmConfig;
386
+ try {
387
+ gcmConfig = buildGcmConfig({ gitUrl, loginAuthority, authResource });
388
+ }
389
+ catch (err) {
390
+ throw new ConfigurationError(`Git credential helper setup failed: ${err instanceof Error ? err.message : String(err)}`);
391
+ }
392
+ fs.mkdirSync(directory, { recursive: true });
393
+ execFileSync('git', ['init'], { cwd: directory, stdio: ['ignore', 'ignore', 'inherit'] });
394
+ // Write GCM config before fetching so the first network call is already authenticated.
395
+ for (const [key, value] of gcmConfig) {
396
+ execFileSync('git', ['config', '--local', key, value], {
397
+ cwd: directory,
398
+ stdio: ['ignore', 'ignore', 'inherit'],
399
+ });
400
+ }
401
+ // `git remote add` fails if origin already exists. Pipe stderr so we can inspect the
402
+ // message and only fall back to `set-url` for that specific case — other failures
403
+ // (permissions, corrupt repo) must propagate rather than produce a misleading set-url error.
404
+ try {
405
+ execFileSync('git', ['remote', 'add', 'origin', gitUrl], {
406
+ cwd: directory,
407
+ stdio: ['ignore', 'ignore', 'pipe'],
408
+ });
409
+ }
410
+ catch (err) {
411
+ const stderr = err.stderr?.toString() ?? '';
412
+ if (!stderr.includes('already exists')) {
413
+ process.stderr.write(stderr);
414
+ throw err;
415
+ }
416
+ execFileSync('git', ['remote', 'set-url', 'origin', gitUrl], {
417
+ cwd: directory,
418
+ stdio: ['ignore', 'ignore', 'inherit'],
419
+ });
420
+ }
421
+ // The first `git fetch` triggers GCM's OAuth flow if the user has no cached
422
+ // credentials, which can pop a browser without warning. Tell the user up
423
+ // front so they don't see the browser as a surprise (and know which host /
424
+ // account to authenticate against). Skipped under `--json` so the prose
425
+ // doesn't end up interleaved with the JSON envelope on stdout.
426
+ if (!jsonMode) {
427
+ printInfo(`Authenticating to ${new URL(gitUrl).host} for the initial git fetch. ` +
428
+ 'A browser window may open. Sign in with the account associated with this tenant.');
429
+ }
430
+ execFileSync('git', ['fetch', 'origin'], {
431
+ cwd: directory,
432
+ stdio: ['ignore', 'ignore', 'inherit'],
433
+ });
434
+ // Probe for origin/main locally (no network needed after fetch).
435
+ // A broad catch here is safe: the only failure mode is "ref doesn't exist".
436
+ let hasMain = false;
437
+ try {
438
+ execFileSync('git', ['rev-parse', '--verify', 'origin/main'], {
439
+ cwd: directory,
440
+ stdio: ['ignore', 'ignore', 'ignore'],
441
+ });
442
+ hasMain = true;
443
+ }
444
+ catch {
445
+ // Empty GRS repo — origin/main not yet present.
446
+ }
447
+ if (hasMain) {
448
+ execFileSync('git', ['checkout', '-b', 'main', 'origin/main'], {
449
+ cwd: directory,
450
+ stdio: ['ignore', 'ignore', 'inherit'],
451
+ });
452
+ }
453
+ }
454
+ // ─── Recovery hint for post-create failures ─────────────────────────────────
455
+ // Once createAsync returns, the MAAF app + GRS repo exist on the server; any
456
+ // later failure (no gitUrl, initLocalRepo, downloadTemplate, writeRepoConfig)
457
+ // strands them. Append this hint to the error message so the user can copy a
458
+ // working `app delete --force` line straight from their terminal and retry.
459
+ // ────────────────────────────────────────────────────────────────────────────
460
+ export function buildOrphanedAppRecoveryHint(serverAppId, environmentId) {
461
+ return (`The server app is still in environment ${environmentId} (id: ${serverAppId}).\n` +
462
+ `If you want to clean it up before retrying, run the following command:\n` +
463
+ ` npx ${CLI_BINARY_NAME} app delete --app ${serverAppId} --environment-id ${environmentId} --force`);
464
+ }
465
+ // ─── Preflight helpers ──────────────────────────────────────────────────────
466
+ // All checks below run before any server call so a local misconfiguration
467
+ // (missing git, bad template source, unwritable directory) fails fast and
468
+ // never leaves an orphaned MAAF app + GRS repo on the server.
469
+ // ────────────────────────────────────────────────────────────────────────────
470
+ export function ensureGitInstalled() {
471
+ try {
472
+ execFileSync('git', ['--version'], { stdio: ['ignore', 'ignore', 'ignore'] });
473
+ }
474
+ catch {
475
+ throw new ConfigurationError("'git' is required but was not found on PATH. Install git from https://git-scm.com/ and retry.");
476
+ }
477
+ }
478
+ // Known giget provider prefixes. Used only for a *soft* "did you mean?" warning —
479
+ // not a hard reject, so adding a new giget provider doesn't silently fail
480
+ // preflight here. Sourced from giget's docs (github, gitlab, bitbucket, sourcehut,
481
+ // http, https) as of the version pinned in package.json. Update opportunistically
482
+ // when bumping giget; a stale entry only suppresses one warning, never blocks
483
+ // a valid template.
484
+ const KNOWN_GIGET_PROVIDERS = new Set([
485
+ 'github',
486
+ 'gitlab',
487
+ 'bitbucket',
488
+ 'sourcehut',
489
+ 'http',
490
+ 'https',
491
+ ]);
492
+ // Single-edit Damerau-Levenshtein distance, capped at 1: returns true iff `a`
493
+ // is one substitution, one insertion, one deletion, or one adjacent
494
+ // transposition away from `b`. Used to pick the closest known provider for a
495
+ // "did you mean?" hint without pulling in a dependency.
496
+ function isOneEditAway(a, b) {
497
+ if (a === b)
498
+ return false;
499
+ if (Math.abs(a.length - b.length) > 1)
500
+ return false;
501
+ if (a.length === b.length) {
502
+ let diff = 0;
503
+ let transposition = null;
504
+ for (let i = 0; i < a.length; i++) {
505
+ if (a[i] !== b[i]) {
506
+ diff++;
507
+ if (transposition === null)
508
+ transposition = i;
509
+ if (diff > 2)
510
+ return false;
511
+ }
512
+ }
513
+ if (diff <= 1)
514
+ return true;
515
+ if (diff === 2 && transposition !== null) {
516
+ // Adjacent swap: gihub vs github, swap positions transposition and transposition+1.
517
+ return a[transposition] === b[transposition + 1] && a[transposition + 1] === b[transposition];
518
+ }
519
+ return false;
520
+ }
521
+ // One insertion or deletion away. Walk both strings; allow one skip.
522
+ const [shorter, longer] = a.length < b.length ? [a, b] : [b, a];
523
+ let i = 0;
524
+ let j = 0;
525
+ let skipped = false;
526
+ while (i < shorter.length && j < longer.length) {
527
+ if (shorter[i] === longer[j]) {
528
+ i++;
529
+ j++;
530
+ continue;
531
+ }
532
+ if (skipped)
533
+ return false;
534
+ skipped = true;
535
+ j++;
536
+ }
537
+ return true;
538
+ }
539
+ export function ensureTemplateSourceValid(source) {
540
+ // Hard validation: only enforce non-empty. Any further structural validation
541
+ // (provider name, URL shape, registry lookup) is giget's responsibility —
542
+ // duplicating its provider list locally goes stale silently when giget adds
543
+ // support for a new provider, turning a valid template into a misleading
544
+ // "unknown provider" error at preflight.
545
+ if (!source) {
546
+ throw new UsageError('Template source is empty. Use --template to specify one.');
547
+ }
548
+ // Soft validation: if the source looks like `<prefix>:<rest>` but the prefix
549
+ // is one edit away from a known giget provider (e.g. `gihub:` → `github:`),
550
+ // warn but proceed. giget will produce the authoritative error if the
551
+ // prefix is genuinely invalid; for the common typo case we surface the fix
552
+ // before the network round-trip. No hard-fail keeps this forward-compatible
553
+ // with new giget providers — a stale `KNOWN_GIGET_PROVIDERS` set only
554
+ // suppresses a warning, it never blocks a valid template.
555
+ const prefixMatch = /^([a-z][a-z0-9+.-]*):/i.exec(source);
556
+ if (!prefixMatch)
557
+ return;
558
+ const prefix = prefixMatch[1].toLowerCase();
559
+ if (KNOWN_GIGET_PROVIDERS.has(prefix))
560
+ return;
561
+ const suggestion = [...KNOWN_GIGET_PROVIDERS].find((known) => isOneEditAway(prefix, known));
562
+ if (suggestion) {
563
+ printWarning(`Template source '${source}' uses an unrecognized provider prefix '${prefix}:'. Did you mean '${suggestion}:'?`);
564
+ }
565
+ }
566
+ export function ensureTargetDirectoryEmpty(appDir) {
567
+ // Scaffolding into a non-empty directory would mix the template with the
568
+ // user's files, and initLocalRepo's `git remote set-url` fallback would
569
+ // silently replace an existing `origin` remote. Called twice: once in
570
+ // preflight (fail fast before any network) and once right before
571
+ // createAsync (catch a TOCTOU race where the dir gets populated during
572
+ // routing/option resolution).
573
+ if (!fs.existsSync(appDir))
574
+ return;
575
+ const entries = fs.readdirSync(appDir);
576
+ if (entries.length > 0) {
577
+ throw new UsageError(`'${appDir}' is not empty. Specify an empty directory as the first argument, or run 'ms app init' to bind an existing project to a new app.`);
578
+ }
579
+ }
580
+ export function ensureRepoTargetDirectoryEmpty(appDir) {
581
+ // --repo variant: empty-dir is mandatory. `git clone` refuses non-empty
582
+ // destinations and we don't destructively wipe local files. To bind an
583
+ // existing local project to a new MAAF app, use `ms app init` instead.
584
+ // Called twice (preflight + just-before-createAsync) for the same TOCTOU
585
+ // reasons as ensureTargetDirectoryEmpty.
586
+ if (!fs.existsSync(appDir))
587
+ return;
588
+ const entries = fs.readdirSync(appDir);
589
+ if (entries.length > 0) {
590
+ 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.`);
591
+ }
592
+ }
593
+ export function ensureNoExistingConfig(appDir) {
594
+ // --repo none variant: empty-dir is not required, but we MUST refuse to overwrite an
595
+ // existing ms.config.json. Silently replacing it would rebind the folder (and any
596
+ // subsequent `ms app push/build/deploy` invocations) to a different server app — a
597
+ // footgun loud enough to gate fail-fast in preflight. Called twice (preflight +
598
+ // just-before-createAsync) for the same TOCTOU reasons as ensureTargetDirectoryEmpty.
599
+ const configPath = path.join(appDir, AppConfigFileName);
600
+ if (fs.existsSync(configPath)) {
601
+ throw new UsageError(`'${configPath}' already exists. --repo none refuses to overwrite an existing app config; ` +
602
+ `remove the file (or choose a different directory) before retrying, or run 'ms app init' ` +
603
+ `to bind this directory to a new app interactively.`);
604
+ }
605
+ }
606
+ export function ensureTargetDirectoryWritable(appDir) {
607
+ // Probe write permission on the target dir (or its nearest existing
608
+ // ancestor, where mkdirSync will need to write). Catches read-only mounts
609
+ // and permission-denied locations before any server-side resource is
610
+ // created. The empty-dir check is intentionally NOT here — it runs inline
611
+ // just before initLocalRepo so the TOCTOU window between check and write
612
+ // stays as small as possible (a directory can become non-empty while the
613
+ // routing/createAsync calls are in flight).
614
+ //
615
+ // Best-effort caveat: `accessSync(W_OK)` is a syscall-level approximation,
616
+ // not a real write. On Windows in particular, NTFS ACL inheritance can grant
617
+ // W_OK on the ancestor while the actual CreateDirectory / CreateFile fails
618
+ // later (UAC virtualization, parent ACE applies but a deny ACE on the leaf
619
+ // does not). We accept that: catching every Windows ACL edge case requires
620
+ // a real mkdir+write+delete probe, which leaves debris on success and has
621
+ // its own TOCTOU. The recovery hint on post-create failures is the
622
+ // backstop when the probe is too optimistic.
623
+ let probe = appDir;
624
+ while (!fs.existsSync(probe)) {
625
+ const parent = path.dirname(probe);
626
+ if (parent === probe)
627
+ break;
628
+ probe = parent;
629
+ }
630
+ try {
631
+ fs.accessSync(probe, fs.constants.W_OK);
632
+ }
633
+ catch {
634
+ throw new ConfigurationError(`'${appDir}' is not writable. Choose a different directory or fix the permissions and retry.`);
635
+ }
636
+ }
637
+ function createOptionSchema(argumentProvider) {
638
+ argumentProvider.setDescription(HelpStrings.app.create.description);
639
+ argumentProvider.addExamples(HelpStrings.app.create.examples);
640
+ argumentProvider.consumePositionalArg('[directory]');
641
+ argumentProvider.addOptions([
642
+ createDefaultStringOption({
643
+ flags: {
644
+ key: CreateMaafAppArguments.DisplayName.name,
645
+ alias: CreateMaafAppArguments.DisplayName.alias,
646
+ },
647
+ env: CreateMaafAppArguments.DisplayName.envVar,
648
+ message: CreateMaafAppArguments.DisplayName.promptMessage,
649
+ description: HelpStrings.app.create.options.displayName,
650
+ }),
651
+ {
652
+ flags: {
653
+ key: CreateMaafAppArguments.Description.name,
654
+ alias: CreateMaafAppArguments.Description.alias,
655
+ },
656
+ env: CreateMaafAppArguments.Description.envVar,
657
+ description: HelpStrings.app.create.options.description,
658
+ optionalInNonInteractive: true,
659
+ },
660
+ {
661
+ flags: {
662
+ key: CreateMaafAppArguments.RepositoryId.name,
663
+ alias: CreateMaafAppArguments.RepositoryId.alias,
664
+ },
665
+ env: CreateMaafAppArguments.RepositoryId.envVar,
666
+ description: HelpStrings.app.create.options.repositoryId,
667
+ optionalInNonInteractive: true,
668
+ },
669
+ {
670
+ flags: {
671
+ key: CreateMaafAppArguments.Template.name,
672
+ alias: CreateMaafAppArguments.Template.alias,
673
+ },
674
+ env: CreateMaafAppArguments.Template.envVar,
675
+ default: DEFAULT_TEMPLATE,
676
+ description: HelpStrings.app.create.options.template,
677
+ optionalInNonInteractive: true,
678
+ },
679
+ {
680
+ // No short alias for --repo yet; see VerbConstants.CreateMaafAppArguments.Repo.
681
+ flags: {
682
+ key: CreateMaafAppArguments.Repo.name,
683
+ },
684
+ env: CreateMaafAppArguments.Repo.envVar,
685
+ description: HelpStrings.app.create.options.repo,
686
+ optionalInNonInteractive: true,
687
+ },
688
+ createDefaultStringOption({
689
+ flags: {
690
+ key: CreateMaafAppArguments.BuildPath.name,
691
+ alias: CreateMaafAppArguments.BuildPath.alias,
692
+ },
693
+ env: CreateMaafAppArguments.BuildPath.envVar,
694
+ message: CreateMaafAppArguments.BuildPath.promptMessage,
695
+ initialValue: CreateMaafAppArguments.BuildPath.initialValue,
696
+ default: './dist',
697
+ description: HelpStrings.app.create.options.buildPath,
698
+ }),
699
+ createDefaultStringOption({
700
+ flags: {
701
+ key: CreateMaafAppArguments.BuildCommand.name,
702
+ alias: CreateMaafAppArguments.BuildCommand.alias,
703
+ },
704
+ env: CreateMaafAppArguments.BuildCommand.envVar,
705
+ message: CreateMaafAppArguments.BuildCommand.promptMessage,
706
+ initialValue: CreateMaafAppArguments.BuildCommand.initialValue,
707
+ default: 'npm run build',
708
+ description: HelpStrings.app.create.options.buildCommand,
709
+ }),
710
+ ]);
711
+ }
712
+ //# sourceMappingURL=CreateMaafApp.js.map