Skip to content

feat: SI CLI ↔ Console integration (auth, SaaS state, unified endpoints) - #211

Open
Blankll wants to merge 50 commits into
masterfrom
feat/si-console-integration
Open

feat: SI CLI ↔ Console integration (auth, SaaS state, unified endpoints)#211
Blankll wants to merge 50 commits into
masterfrom
feat/si-console-integration

Conversation

@Blankll

@Blankll Blankll commented Jul 28, 2026

Copy link
Copy Markdown
Member

Implements SI CLI Console integration as designed in #210.

Summary

  • Auth: si login (browser + API key), si logout, si whoami
  • State mgmt: SaasStateBackend with unified POST /api/v1/deployments/ endpoint, auto-provisioning, phase lifecycle
  • YAML: default backend changed to SAAS
  • Credentials: ~/.serverlessinsight/credentials.json, SI_API_KEY env var, --si-api-key flag
  • Lock: deployment-level locking via server-side phase check (409 on conflict)
  • API client: replaced ky dependency with a thin native fetch wrapper (zero deps, fixes jest ESM parsing of ky in tests)

Merged from master

  • feat: add triggers.http and domain support for Tencent SCF and Aliyun FC3 (#213) — merged in, no conflicts outstanding

Test fixes

  • saasStateBackend.test.ts: rewritten to match current unified-endpoint API (credentials injected via loadCredentials() mock, POST /api/v1/deployments/ provisioning, phase lifecycle, forceUnlock/readLock)
  • show.test.ts: fallback-to-local test now drives the real createStateBackend failure path

Commits

  1. feat: add SAAS backend type and default parse
  2. feat: add credential store and API client
  3. feat: add SaaS state backend with unified deployment lifecycle
  4. feat: add CLI auth commands and SaaS state integration
  5. feat: add i18n keys, tests, and ky dependency
  6. chore: replace ky with native fetch wrapper (apiClient + tests, remove ky dep)
  7. test: fix SaaS state backend and show tests to match unified endpoints

Blankll and others added 11 commits July 28, 2026 22:28
Add StateBackendType.SAAS enum, SaasBackendConfig, console metadata fields to StateFile, default parseBackend to SAAS.
Add credentialStore for ~/.serverlessinsight/credentials.json and API client with ky-based HTTP client for Console communication.
Add SaasStateBackend implementing StateBackend interface via Console API. Uses unified POST /api/v1/deployments/ endpoint with auto-provisioning and phase-based lifecycle (init→start→complete/fail).
Add si login/logout/whoami commands with browser-based auth and API key input. Update deploy and show commands for SaaS state backend support.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add SAAS_BACKEND_*, LOGIN_*, API_ERROR_* i18n keys for en/zh-CN. Add unit tests for credentialStore, apiClient, and saasStateBackend. Add ky HTTP client dependency.
- Unwrap { code, messages, data } envelope so callers consume payload directly

- Convert snake_case payload keys to camelCase (skip opaque JSONB fields like state_json/spec)

- validateApiKey reads org_id/org_name and nested user.email from the real validate response

Fixes: si login saved credentials without orgId (org/name/userEmail all undefined)

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… lock read

- Prefix all API paths with leading slash (baseUrl + path was malformed)

- saveState sends full StateFile + SHA-256 contentHash (backend requires it)

- readLock handles data:null when no deployment is active

Fixes: si deploy could not provision/create deployments or sync state against the console

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…sole

CLI cannot force-unlock SaaS-managed deployments (the API requires an admin JWT session, which the CLI never holds). Instead of attempting the request and failing with 'No lock found' / 4002, print a clear hint pointing to the console URL.

- saasStateBackend.forceUnlock: throw SAAS_FORCE_UNLOCK_NOT_SUPPORTED instead of calling the API

- forceUnlockCommand: detect SaaS backend (no backend config or type=saas) and print the hint before touching any lock file

- i18n: en + zh-CN keys

- tests: updated saas backend forceUnlock test; added SaaS-mode command test

Verified: 2555 unit tests pass, lint clean, tsc clean.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ckend creation in force-unlock

Two CI failures fixed:

1. PR #211 made the default state backend SaaS (parseBackend: no state_manager -> SAAS), breaking 20 service tests that expected local state files. Their fixtures now explicitly declare backend.state_manager.type=LOCAL, so they don't depend on the default.

2. forceUnlockCommand created the backend BEFORE checking for SaaS mode. createSaasStateBackend throws SAAS_BACKEND_NO_CREDENTIALS when no API key exists, so the web-console hint was unreachable in CI (no credentials). The SaaS check now runs before createStateBackend.

Verified: 136 suites / 2577 tests pass, branches 86.04% (threshold 85%), lint clean.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.37700% with 192 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.43%. Comparing base (30bee16) to head (0253b4c).

Files with missing lines Patch % Lines
src/common/volcengineClient/tlsOperations.ts 59.72% 9 Missing and 20 partials ⚠️
src/commands/login.ts 83.87% 9 Missing and 6 partials ⚠️
src/common/tencentClient/tdsqlcOperations.ts 56.66% 9 Missing and 4 partials ⚠️
src/stack/aliyunStack/apigwResource.ts 84.33% 3 Missing and 10 partials ⚠️
src/stack/scfStack/scfResource.ts 85.22% 6 Missing and 7 partials ⚠️
src/common/aliyunClient/rdsOperations.ts 55.55% 8 Missing and 4 partials ⚠️
src/common/tencentClient/esOperations.ts 53.84% 8 Missing and 4 partials ⚠️
src/stack/aliyunStack/fc3Resource.ts 87.09% 5 Missing and 3 partials ⚠️
src/common/apiClient.ts 89.09% 1 Missing and 5 partials ⚠️
src/common/stateBackend/saasStateBackend.ts 90.90% 3 Missing and 3 partials ⚠️
... and 31 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #211      +/-   ##
==========================================
- Coverage   88.58%   88.43%   -0.15%     
==========================================
  Files         148      155       +7     
  Lines        8540     9305     +765     
  Branches     2299     2499     +200     
==========================================
+ Hits         7565     8229     +664     
- Misses        421      442      +21     
- Partials      554      634      +80     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Blankll and others added 18 commits August 3, 2026 11:31
…ows CI)

credentialStore.test.ts hardcoded POSIX paths ('/home/testuser/...'), which broke on windows-latest (backslash separators). Build expected paths with path.join so the assertions match on all platforms.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…s patch coverage)

Codecov reported 54.8% patch coverage — login.ts (87 lines), whoami.ts (14), logout.ts (11) had zero tests. Added:

- login.test.ts (8 tests): API-key flow, env key, invalid format, validate failure, re-auth confirmation, decline, interactive prompt, and the full browser callback flow (real local HTTP server round-trip)

- whoami.test.ts (3 tests): logged out, logged in, missing-field fallbacks

- logout.test.ts (2 tests): logged out, credential deletion

Also fixed a real bug in loginWithBrowser: the 5-minute timeout timer was never cleared after a successful callback, keeping the process alive for 5 minutes after login. clearTimeout now runs on callback.

Verified: 2590 tests pass, branches 86.66%, login.ts coverage 0% -> 89.8%.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The real-HTTP version hung under coverage (open socket + 300s timeout timer). Mock node:http so the callback handler runs in-memory and the timeout timer is cleared on success, letting jest exit cleanly.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SEO: explicit Alibaba/Tencent/Huawei/Volcengine serverless CLI keywords in the README header, matching the repo topics.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…gger

Tencent SCF CreateFunction is async — the function stays in 'Creating' status until the platform finishes provisioning. The CLI immediately called CreateTrigger, which Tencent rejects with '当前函数状态无法进行此操作' (Status is Creating, unsupport operate), failing deploys of functions with http triggers (e.g. event-function-url probe).

- scfResource: poll GetFunction until Status=Active (3s interval, 60s max) after createFunction, before trigger/domain creation

- constants: SCF_STATUS_POLL_INTERVAL_MS / SCF_STATUS_POLL_MAX_ATTEMPTS

- tests: +wait-for-active (Creating->Active sequence, trigger created only after), +timeout error when never Active; adapted existing mocks to include Status: 'Active'

Verified: 481 scf/tencent tests pass, lint clean, tsc clean.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Single generic wait implementation for async cloud resource creation (mirrors Terraform StateChangeConf / Tencent waitScfFunctionReady).

- pollUntil<T>: fetch/isDone/intervalMs/maxAttempts/onProgress; fetch errors propagate; PollingTimeoutError carries last value

- requiredConsecutiveHits: done state must be observed N consecutive times (anti stale-read for eventually-consistent APIs)

- Tests: 12 cases (immediate, multi-poll, timeout, fetch-throw, null, onProgress, maxAttempts=1, consecutive hits)

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…update/delete waits)

Architecture consistency: waits belong in the client operations layer (the established RDS/TDSQL/ES pattern), not the resource layer.

- scfOperations: createFunction polls Status==='Active' before returning (pollUntil); waitForFunctionActive before update (update-before-active race); waitForFunctionDeleted after delete (delete-then-recreate race)

- scfResource: removed waitForFunctionActive + local delay + SCF constants import; createResource now relies on createFunction's internal wait

- tests: relocated 2 polling tests from scfResource.test to scfOperations.test (behavior moved); default GetFunction mock so waits complete; delete tests mock null for waitForFunctionDeleted

Verified: 100 scf tests pass, tsc clean.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…eserve i18n error messages)

Migrate the two bespoke polling loops to the shared pollUntil primitive. Error states (DELETED/FAILED) throw from the fetch wrapper; PollingTimeoutError is translated back to i18n messages (ES_APP_TIMEOUT_READY/DELETE) with { cause } so existing test assertions stay green and the original error is preserved.

Also bumps tsconfig target ES2020 -> ES2022: the eslint preserve-caught-error rule requires Error options ({ cause }), which needs lib ES2022. Node 24 fully supports it; 2581 unit tests pass.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Migrate bespoke polling loops to the shared pollUntil primitive. Error states (DELETED/DELETE_FAILED) throw from the fetch wrapper; PollingTimeoutError translated to i18n RDS_INSTANCE_TIMEOUT_READY/DELETE with { cause }.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…throwing timeout fallback)

SLS waits never throw on timeout — they return a degraded object. PollingTimeoutError is caught and returns the fallback instead of rethrowing.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…helper)

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
FC3 createFunction now polls GetFunction state==='Active' before returning (official readiness value, not lastUpdateStatus which is 'Successful' right after create). Add waitForFunctionActive before updates, waitForFunctionDeleted after delete. Terminates on state==='Failed'.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…aits

veFaaS GetFunction response carries Status (CLI already reads it); ready value is 'Active'. createFunction now polls until Active, update waits for Active first (update-before-active race), delete waits for the function to be gone (delete-then-recreate race). Mirrors SCF/FC3 pattern.

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
SCF state-persistence hardening:
- create failures now propagate PartialResourceError with tainted state
  so a re-run reconciles instead of losing state (partial-failure loop fix)
- tainted pre-flight: adopt our own half-created function, skip re-create
- planner routes tainted state back to create
- D1: already-exists on the NON-tainted path is deliberately not
  recoverable (removed ResourceInUse/已存在/already-exist from
  isRecoverableCreateError) — the cloud resource may not be owned by
  this stack, so it throws PartialResourceError for manual resolution.
  Only the tainted pre-flight path may adopt a pre-existing function.
- trigger duplication guard on adoption + already-exists backstop
…(P1)

- saveState: atomic write via tmp + fsync + rename; .backup of previous state
  before replace; failure leaves original intact (no corrupt file)
- loadState: on JSON parse error try .backup, else throw StateCorruptError
  (no more silent empty-state -> full redeploy against existing cloud resources)
- StateFile: serial (monotonic) + lineage (UUID) for drift detection
- SaveStateFn -> Promise<void>; all 5 producers await backend.saveState
- all 16 executor onStateChange call sites awaited — tainted state written
  on failure is now guaranteed persisted BEFORE the deployer throws, closing
  the fire-and-forget state-loss window for remote (COS/OSS/SaaS) backends
Blankll added 21 commits August 4, 2026 17:35
…Serverless (P2)

- planners: tainted state -> create (cosPlanner, tdsqlcPlanner, esServerlessPlanner)
- resources: create wrapped in try/catch, tainted state persisted before
  primary create, any post-create error throws PartialResourceError with
  the tainted state (no more silent blank-state on partial failure)
- executors: PartialResourceError branch awaits onStateChange(updatedState)
  and returns partialFailure with updatedState
- delete unify (Q3): cosResource.deleteBucketPolicy no longer swallows
  real errors — propagates, removeResource only on full success
  (NoSuchBucket/404 idempotent tolerance kept)
…se/tablestore/apigw (P2)

- planners: tainted state -> create (oss/database/tablestore/apigw); apigw
  remote drift-import (findApiGroupByName) preserved for !currentState only
- resources: tainted state persisted before primary create; post-create
  errors (transfer-acceleration, cdn, dns, domain bind, api create/deploy)
  throw PartialResourceError with tainted state; apigw domain-failure catch
  converted from return-state to throw
- executors: PartialResourceError branch awaits onStateChange(updatedState),
  returns partialFailure with updatedState
- delete unify (Q3): oss CDN/dns and apigw abolishApi/deleteApi/deleteApiGroup
  swallows removed — errors propagate, removeResource only on full success
  (existing NOT-FOUND tolerances kept)
- planners: tainted state -> create (tosPlanner, apigwPlanner); apigw
  drift-import (findGatewayByName) preserved for !currentState only
- resources: tos create wrapped with tainted state before createBucket,
  post-create errors throw PartialResourceError; apigw partial state marked
  tainted, api/deploy/domain errors throw PartialResourceError, intermediate
  per-API state writes removed (final ready state carries all instances)
- tosExecutor PartialResourceError branch (previously dead) now live
- delete unify (Q3): vefaas deleteDependentResources, tos deleteBucketPolicy,
  apigw unbindDomain/deleteApi/deleteGateway swallows removed — errors
  propagate, removeResource only on full success (NOT-FOUND tolerances kept)
- acquireLockInternal: atomic lock creation via fs.openSync('wx') (O_EXCL)
  eliminates the read->write->verify race where two processes could both
  believe they held the lock; EEXIST falls into existing stale/dead-PID/
  timeout handling unchanged; partial-file cleanup on write failure
- StateBackend.withLock: optional onLockAcquired(lockId) callback threaded
  through local/remote/saas backends
- deploy/destroy: SIGINT/SIGTERM handlers release the active lock best-effort
  (local is sync unlink; remote relies on stale-TTL recovery) then exit(130);
  handlers removed in finally; idempotent against double-signal
- docs/state-locking.md: O_EXCL + crash-safety section
- StateVersionError: distinct error for state written by a newer CLI
- registerStateMigration/clearStateMigrations: migration registry (empty
  today — CURRENT is 3.0); loadState applies registered migrations
- migrateState: version-aware guard — only versions NEWER than CURRENT
  with no migration path are rejected (upgrade CLI); older/legacy
  versions (e.g. 1.0.0/0.0.1) load as-is; a migration chain that runs
  but fails to reach CURRENT throws rather than persisting partial state
- loadState re-throws StateVersionError (not swallowed into
  StateCorruptError); migrations apply to backup-sourced states
- export migrateState from stateManager; remoteStateBackend.loadState and
  saasStateBackend.loadState now run migrateState before hydrating stages,
  so a newer-unknown state version in COS/OSS/SaaS throws StateVersionError
  instead of loading silently (was local-backend only)
- legacy older versions (e.g. 1.0.0) still load as-is; tests added
…rror; add tainted pre-flight (reviewer N1)

- oss/tos createBucketResource/createResource: upload failure no longer
  swallowed (was marked status:'ready' with code missing) — error propagates
  to the outer catch -> PartialResourceError with tainted state, so the next
  run retries the upload instead of silently reporting ready
- tainted pre-flight (mirrors SCF): when state status is 'tainted' and the
  provider already has the bucket, skip createBucket and reuse it — closes
  the already-exists dead-loop on retry; D1-compliant (non-tainted path still
  throws on already-exists)
- createBucket moved inside the try so create failures also persist tainted
  state; post-upload getBucket refresh retained
Aligns with AWS/gcloud credential file conventions — the API key in
~/.serverlessinsight/credentials.json is a long-lived secret and must
not be world-readable. saveCredentials now chmods the file to 0600 after
writing.
Root cause found via real deploy (deploy-fn-url.sh): HTTP trigger creation
failed after the function was created, but trigger/domain code sat OUTSIDE
the createFunction PartialResourceError try/catch — so the raw SDK error
propagated to the executor's generic branch, which persisted the PRE-tainted
state. On SaaS backend the tainted state never reached the console (deployment
events showed start->fail with no state_sync), so a re-run could not reconcile.

- scfResource: createTrigger non-already-exists errors and createCustomDomain
  errors now throw PartialResourceError(stateAfterDependents, error); the
  final provider refresh failure does too
- fc3Resource: trigger + custom-domain creation wrapped in the same
  PartialResourceError protection; refresh failure aligned
- tests: createTrigger failure -> PartialResourceError with tainted state
  (scf + fc3)
…refresh (reviewer B1)

- HTTP_TRIGGER_AUTH_TYPE_REQUIRED validation moved to the top of createResource
  (before any cloud operation or tainted-state write) so an invalid trigger
  config can never strand a partially-created resource
- final getFunction provider refresh wrapped: a rejection now throws
  PartialResourceError(stateAfterDependents, ...) instead of a raw error that
  lost the tainted marker (function already exists in cloud)
…unbind-drift + delete NOT-FOUND (reviewer)

- fc3 createResource: final getFunction refresh wrapped in PartialResourceError
  (rejection no longer loses the tainted marker)
- fc3 deleteDependentResources: removed the catch-all that swallowed sub-delete
  errors then removed state — SLS/RAM/SG/NAS orphans now propagate, removeResource
  only on full success
- apigw updateApigwResource: unbindCustomDomain swallows removed (real errors
  propagate -> old state kept -> self-heal) instead of writing success-state
  with the cloud domain still bound (permanent DRIFT)
- apigwOperations: isApigwNotFoundError helper; deleteApiGroup/deleteApi/
  abolishApi/unbindCustomDomain tolerate NOT-FOUND (idempotent deletes) while
  real errors rethrow — delete no longer permanently stuck after mid-sequence
  failure; getApiGroup/getApi refactored to reuse it
- apigw createApigwResource: tainted state written BEFORE group creation so a
  post-create failure persists the marker instead of orphaning the group
- tests: rewrote 6 swallow-behavior tests, added create-taint/delete-tolerance/
  mid-sequence tests
…oint (reviewer)

- vefaas updateResource: tainted state written before createDependentResources
  so a partial TLS-creation failure persists the marker (PartialResourceError)
  instead of orphaning cloud resources with no retry path
- tlsOperations createProject/createTopic/createIndex: tolerate
  *AlreadyExists/ResourceAlreadyExists/Conflict codes — log and adopt the
  existing resource, so a retry after partial failure no longer collides on
  the project name (previously permanently stuck)
- service mockCloudClient: added missing sls deleteIndex/deleteLogstore/
  deleteProject mocks (the fc3 delete-swallow fix exposed their absence)
…y SDK error messages

Real deploy failure (deploy-fn-url.sh): a leftover HTTP trigger on the cloud
function (from a previous partial run) made CreateTrigger fail with a BLANK
error — the tainted state was persisted correctly, but the failure line showed
nothing to diagnose, and retries kept hitting the same already-exists.

- before createTrigger, always probe getFunction Triggers (not only on tainted
  adoption) — an already-attached trigger is now skipped instead of colliding
- toErrorMessage helper: Tencent SDK errors carry code/message/requestId and may
  throw with an empty message — compose them so PartialResourceError always
  surfaces a diagnosable failure line
- test: fresh create with trigger already attached in provider -> createTrigger
  skipped, no error
Root cause of the persistent deploy failure: the HTTP trigger's TriggerDesc
was built as {"authType":...} which is NOT the Tencent API Gateway trigger
format — CreateTrigger rejected it with InvalidParameterValue (previously
hidden by the blank-error bug). This affected both createResource and the
updateResource trigger-recreation path.

- buildTencentTriggerDesc(): produces the documented shape
  { api: { authRequired, requestConfig: { method }, isIntegratedResponse },
    service: { serviceName: 'SCF_API_SERVICE' },
    release: { environmentName: 'release' } }
  authRequired: 'FALSE' when auth_type public (mapAuthType 'NONE'), else 'TRUE'
- create + update trigger paths both use it
- removed now-unused mapAccess import/access vars
- tests updated to assert the new format
Tencent shut down NEW API Gateway triggers on 2024-07-01 and the product
on 2025-06-30 (官方下线通知). The HTTP trigger previously used the API
Gateway TriggerDesc format { api/service/release }, which CreateTrigger now
rejects with InvalidParameterValue (the product no longer exists).

Per official docs (创建函数 URL), Type='http' with TriggerDesc:
{ AuthType, NetConfig: { EnableIntranet, EnableExtranet } } — AuthType is the
mapAuthType result ('NONE' public / 'CAM'), NetConfig maps from the trigger's
access (enableExtranet/enableIntranet).

- buildTencentTriggerDesc(authType, netConfig) now emits the Function URL shape
- create + update trigger paths pass access flags through
- tests updated to the new format
…tion

Tencent's UpdateFunctionConfiguration rejects Handler and Runtime with
InvalidParameterValue.Handler / .Runtime — they are set at creation time and
cannot be changed. The update path was passing both, so a code-only change
(update in-place) failed on every deploy once the function was ready.

- updateFunctionConfiguration no longer sends Handler/Runtime
- createFunction unchanged (still sends them — needed at creation)
- test updated to assert the reduced param set
… for immutable Handler/Runtime

The update path was ALWAYS calling UpdateFunctionConfiguration with the full
config (including immutable Handler/Runtime), so every update-in-place — even
a code-only change — was rejected by Tencent with InvalidParameterValue.Handler.

- updateResource now diffs the mutable config fields (memorySize/timeout/
  environment) between existing state and desired; UpdateFunctionConfiguration
  is only called when one actually changed (code-only updates skip it)
- Handler/Runtime changes are a HARD error with a clear message (delete &
  recreate), instead of silently omitting them or hitting the opaque API error
- tests: config-unchanged -> no re-push; mutable-field change -> re-push;
  Handler change -> clear immutable error
The browser flow opened `${consoleUrl.replace('api.', 'console.')}/cli/authorize`,
which only works for prod subdomains. With SI_CONSOLE_URL=http://localhost:3000
(dev API), the replace is a no-op, so si login opened the API server's
/cli/authorize → 404.

resolveConsoleUiUrl: SI_CONSOLE_UI_URL overrides; else prod (api.→console.);
else local (localhost:3000→localhost:5173); else pass through.
- tests: 4 new cases (env override / prod / local / passthrough)
server.close() only stops accepting new connections; the browser's
keep-alive callback connection stayed open, keeping the event loop alive
and hanging si login after 'Logged in' was printed. server.closeAllConnections()
(added in this fix, Node 18+) forcibly closes it so the process exits.
The /callback success page was a bare text line. Now it returns an HTML
page that auto-redirects (3s meta refresh + manual link) to the Console
API-keys panel (${uiUrl}/${appId}/membership/api-keys) when the authorize
flow passes app_id, falling back to the Console root. Values are HTML-
escaped (not URL-encoded) so the redirect target stays intact.
- tests: callback carries app_id -> panel URL; no app_id -> root URL.

CLI: 2711 tests (1 new), tsc clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant