Replace manifest upload with zip import, add webApplicationInfo flags#2752
Replace manifest upload with zip import, add webApplicationInfo flags#2752heyitsaamir wants to merge 2 commits intomainfrom
Conversation
…, auto version bump The CLI's manifestToAppDetails() was silently dropping fields (most critically webApplicationInfo.resource, breaking SSO). Instead of patching the mapping, this eliminates it entirely by using TDP's zip import endpoint — the backend handles all manifest-to-definition mapping, so no fields are lost. Also adds --web-app-info-id / --web-app-info-resource flags to `teams app update` and automatic patch version bumping on manifest upload when content changes. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Reworks Teams app manifest upload to use TDP zip import (so manifest fields aren’t dropped), adds teams app update flags for webApplicationInfo, and introduces auto patch-version bumping when uploading changed manifests with unchanged versions.
Changes:
- Switch manifest upload to download existing package → replace
manifest.json→ import zip with overwrite. - Add
--web-app-info-id/--web-app-info-resourceoptions toteams app update. - Add version utilities + tests and implement optional auto patch bumping on manifest upload (
--no-bump-versionto disable).
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| teams.md/docs/cli/commands/app/update.md | Documents new app update flags (version, icons, webApplicationInfo, json). |
| teams.md/docs/cli/commands/app/manifest-upload.md | Documents zip import behavior + --no-bump-version and version bump rules. |
| packages/cli/tests/web-app-info-update.test.ts | Adds offline validation tests for new webApplicationInfo flags. |
| packages/cli/tests/version.test.ts | Adds unit tests for version compare/bump helpers. |
| packages/cli/tests/manifest-upload-zip.test.ts | Adds tests verifying zip import + icon preservation behavior. |
| packages/cli/src/utils/version.ts | Introduces bumpPatchVersion and compareVersions. |
| packages/cli/src/commands/app/update.ts | Implements new webApplicationInfo flags + validation + update payload mapping. |
| packages/cli/src/commands/app/manifest/upload.ts | Adds --no-bump-version flag and returns bumped version info in JSON output. |
| packages/cli/src/commands/app/manifest/actions.ts | Implements zip-import upload call path + optional auto version bump logic. |
| packages/cli/src/apps/tdp.ts | Extends importAppPackage to support overwrite query parameter. |
| packages/cli/src/apps/api.ts | Replaces manifest upload implementation with zip download/modify/import flow. |
| } | ||
|
|
||
| return { version: manifest.version, versionBumped }; |
There was a problem hiding this comment.
[HIGH] UploadResult requires version: string, but this function explicitly allows uploading a manifest missing version (user can confirm). That path returns { version: manifest.version } which can be undefined at runtime while still typed as string. Make UploadResult.version optional/nullable (and adjust JSON output accordingly) or require version when proceeding.
There was a problem hiding this comment.
Fixed — made UploadResult.version optional. Downstream consumer (upload.ts) already had ManifestUploadOutput.version as optional.
| if (options.webAppInfoResource !== undefined && options.webAppInfoResource.length > 100) { | ||
| throw new CliError( | ||
| 'VALIDATION_FORMAT', | ||
| 'webApplicationInfo resource URI must be 100 characters or less.' | ||
| ); | ||
| } |
There was a problem hiding this comment.
[HIGH] --web-app-info-resource validation only checks length. Elsewhere the CLI expects api://botid-${botId} format (see app doctor). If this flag is meant to configure SSO correctly, add format validation (at least api://botid- prefix; ideally validate against the app’s botId when available) to prevent creating configs that doctor will immediately warn/fail on.
There was a problem hiding this comment.
Fixed — added api:// prefix validation with a helpful error message. Also added a test case for it.
| @@ -0,0 +1,30 @@ | |||
| /** | |||
| * Bump the patch (last) segment of a dotted version string. | |||
There was a problem hiding this comment.
[NIT] Docstring says “Bump the patch segment”, but the function bumps the last segment (so 1.0 → 1.1, i.e., minor). Consider rewording to avoid implying strict SemVer patch behavior.
| * Bump the patch (last) segment of a dotted version string. | |
| * Increment the last numeric segment of a dotted version string. |
There was a problem hiding this comment.
Fixed — updated to "Increment the last numeric segment of a dotted version string."
| export function compareVersions(a: string, b: string): number | null { | ||
| const pa = a.split('.').map(Number); | ||
| const pb = b.split('.').map(Number); | ||
| if (pa.some(isNaN) || pb.some(isNaN)) return null; | ||
| const maxLen = Math.max(pa.length, pb.length); |
There was a problem hiding this comment.
[HIGH] compareVersions uses split('.').map(Number), which treats empty segments as 0 (e.g., "1." or "1..0" parses instead of returning null). Add a stricter numeric-only validation (regex per segment) before converting so malformed versions don’t compare as valid.
There was a problem hiding this comment.
Fixed — added strict per-segment regex validation (/^\d+$/) so empty segments like "1..0" or "1." now return null. Added tests for both cases.
| function runJson<T = Record<string, unknown>>(command: string): { data: T; exitCode: number } { | ||
| const { stdout, exitCode } = run(command); | ||
| return { data: JSON.parse(stdout) as T, exitCode }; | ||
| } |
There was a problem hiding this comment.
[HIGH] runJson<T = Record<string, unknown>> + downstream as Record<string, ...> casts make these tests loosely typed and easy to break when the JSON error shape changes. Prefer a small explicit interface for the JSON error response (e.g., { ok: false; error: { code: string; message?: string } }) and type runJson to return that.
There was a problem hiding this comment.
Fixed — added a JsonErrorResponse interface and typed runJson to return it instead of Record<string, unknown>. Removed all inline casts.
| try { | ||
| // Download existing package to preserve icons | ||
| zipBuffer = await downloadAppPackage(token, teamsAppId); | ||
| } catch { | ||
| // No existing package — create fresh zip with default icons | ||
| await importAppPackage(token, createDefaultZip(manifestJson), true); | ||
| return; |
There was a problem hiding this comment.
[CRITICAL] uploadManifest falls back to creating a default zip on any download failure. A transient/500 error would silently overwrite existing icons with defaults. Only treat “no existing package” as fallback (e.g., 404) and rethrow other errors (consider throwing a typed CliError from downloadAppPackage that preserves status).
There was a problem hiding this comment.
Fixed — downloadAppPackage now throws a typed CliError with statusCode, and uploadManifest only falls back to default zip on 404. All other errors are rethrown.
…safety - Only fall back to default zip on 404 in uploadManifest (not any error) - Use stable sorted-key stringify for manifest comparison - Make UploadResult.version optional to match runtime behavior - Add api:// prefix validation for --web-app-info-resource - Fix compareVersions to reject malformed versions (empty segments) - Add JsonErrorResponse interface and remove Record casts in tests - Update bumpPatchVersion docstring for accuracy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
manifestToAppDetails()— replaced manifest upload with TDP's zip import endpoint (/appdefinitions/v2/import?overwriteIfAppAlreadyExists=true). The backend handles all manifest-to-definition mapping, fixing the bug wherewebApplicationInfo.resource(and many other fields) were silently dropped.--web-app-info-id/--web-app-info-resourceflags toteams app updatefor directly editing webApplicationInfo without a full manifest upload.teams app manifest upload— when manifest version matches server but content changed, patch version is auto-bumped (opt out with--no-bump-version).Test plan
npm run buildcompiles cleanlynpm run test— 86 tests pass (24 new: version utilities, zip import, webApplicationInfo validation)teams app update <id> --web-app-info-id <id> --web-app-info-resource api://botid-<id>→ doctor confirmswebApplicationInfo configured => passteams app manifest upload manifest.json <id>→webApplicationInfo.resourcepersists on server (verified via download)1.0.0→1.0.1--no-bump-version→ version stays the same1.0.1→1.0.2)🤖 Generated with Claude Code