diff --git a/cli/skills/usage/SKILL.md b/cli/skills/usage/SKILL.md index ba80957eb4..1fa6944d7f 100644 --- a/cli/skills/usage/SKILL.md +++ b/cli/skills/usage/SKILL.md @@ -24,7 +24,7 @@ TanStack Intent skills should stay focused and under the validator line limit, s ### Project setup and diagnostics -- `init [apikey] [appId]`: guided first-time setup for Capgo in a Capacitor app. The interactive flow now runs as a real Ink-based fullscreen onboarding so it uses the same UI stack as `build init` (alias: `build onboarding`), with a persistent dashboard, phase roadmap, progress cards, shared log area, and resume support. When dependency auto-detection fails on macOS, the flow opens a native file picker for `package.json` before falling back to manual path entry. If the local bundle ID already exists in the selected Capgo account, onboarding offers to reuse that app, then offers to delete and recreate it, then falls back to alternate bundle ID suggestions. If the user reuses a pending app that was already created in the web onboarding flow, the CLI syncs that selected dashboard app ID back into `capacitor.config.*` before the remaining steps continue. Outside that reused pending-app path, the CLI keeps using the local Capacitor app ID. It can also offer a final `npx skills add https://github.com/Cap-go/capgo-skills -g -y` install step before the GitHub support prompt; if accepted, the support menu includes `Cap-go/capgo-skills` alongside the updater-only and all-Capgo choices. If native platforms are missing, the onboarding can offer to run `cap add` for you. The updater step now verifies that `@capgo/capacitor-updater` is both declared in the selected `package.json` and resolvable from `node_modules`; if automatic install or later build/sync fails, onboarding prints the manual command, waits for the user to type `ready`, re-checks, and only then continues. During the iOS run-on-device step, onboarding asks whether to use a physical iPhone/iPad or a simulator; for physical devices, it asks the user to connect and unlock the device, then offers a check-again loop before launching with the detected target. If iOS sync validation fails during onboarding, the CLI can offer to run a one-line native reset command, wait for you to type `ready` after a manual fix, surface `doctor`, and save a support bundle before you leave the flow. +- `init [apikey] [appId]`: guided first-time setup for Capgo in a Capacitor app. The interactive flow now runs as a real Ink-based fullscreen onboarding so it uses the same UI stack as `build init` (alias: `build onboarding`), with a persistent dashboard, phase roadmap, progress cards, shared log area, and resume support. When dependency auto-detection fails on macOS, the flow opens a native file picker for `package.json` before falling back to manual path entry. If the local bundle ID already exists in the selected Capgo account, onboarding offers to reuse that app, then offers to delete and recreate it, then falls back to alternate bundle ID suggestions. If the user reuses a pending app that was already created in the web onboarding flow, the CLI syncs that selected dashboard app ID back into `capacitor.config.*` before the remaining steps continue. Outside that reused pending-app path, the CLI keeps using the local Capacitor app ID. It writes the new `autoUpdate` policy modes into config: `"atBackground"` for the default flow and `"always"` for instant updates. It can also offer a final `npx skills add https://github.com/Cap-go/capgo-skills -g -y` install step before the GitHub support prompt; if accepted, the support menu includes `Cap-go/capgo-skills` alongside the updater-only and all-Capgo choices. If native platforms are missing, the onboarding can offer to run `cap add` for you. The updater step now verifies that `@capgo/capacitor-updater` is both declared in the selected `package.json` and resolvable from `node_modules`; if automatic install or later build/sync fails, onboarding prints the manual command, waits for the user to type `ready`, re-checks, and only then continues. During the iOS run-on-device step, onboarding asks whether to use a physical iPhone/iPad or a simulator; for physical devices, it asks the user to connect and unlock the device, then offers a check-again loop before launching with the detected target. If iOS sync validation fails during onboarding, the CLI can offer to run a one-line native reset command, wait for you to type `ready` after a manual fix, surface `doctor`, and save a support bundle before you leave the flow. - `run device [platform]`: run a Capacitor app on a connected device or simulator. In an interactive terminal, omitting `[platform]` asks whether to start on iOS or Android. The command lists available devices and simulators, includes a reload option, and resolves the `cap run` command. Use `npx @capgo/cli@latest run device ios --no-launch` to exercise iOS physical/simulator target selection and print the resolved command without launching the app. - `login [apikey]`: store an API key locally. - `doctor`: inspect installation health and gather troubleshooting details. diff --git a/cli/src/bundle/upload.ts b/cli/src/bundle/upload.ts index d939971ff0..03dff5f221 100644 --- a/cli/src/bundle/upload.ts +++ b/cli/src/bundle/upload.ts @@ -20,6 +20,7 @@ import { getChecksum } from '../checksum' import { getRepoStarStatus, isRepoStarredInSession, starRepository } from '../github' import { confirmWithRememberedChoice } from '../promptPreferences' import { showReplicationProgress } from '../replicationProgress' +import { usesAlwaysDirectUpdate } from '../updaterConfig' import { baseKeyV2, BROTLI_MIN_UPDATER_VERSION_V5, BROTLI_MIN_UPDATER_VERSION_V6, BROTLI_MIN_UPDATER_VERSION_V7, canPromptInteractively, checkChecksum, checkCompatibilityCloud, checkPlanValidUpload, checkRemoteCliMessages, createSupabaseClient, deletedFailedVersion, findRoot, findSavedKey, formatError, getAppId, getBundleVersion, getCompatibilityDetails, getConfig, getInstalledVersion, getLocalConfig, getLocalDependencies, getOrganizationId, getPMAndCommand, getRemoteFileConfig, hasCliPermission, hasOrganizationPerm, isCompatible, isDeprecatedPluginVersion, OrganizationPerm, regexSemver, resolveUserIdFromApiKey, sendEvent, updateConfigUpdater, updateOrCreateChannel, updateOrCreateVersion, UPLOAD_TIMEOUT, uploadTUS, uploadUrl, zipFile } from '../utils' import { getVersionSuggestions, interactiveVersionBump } from '../versionHelpers' import { checkIndexPosition, searchInDirectory } from './check' @@ -749,21 +750,21 @@ export async function uploadBundleInternal(preAppid: string, options: OptionsUpl if (options.verbose) log.info(`[Verbose] Capacitor config loaded successfully`) - // Check if directUpdate is enabled and auto-enable delta updates - const directUpdateEnabled = extConfig?.config?.plugins?.CapacitorUpdater?.directUpdate === 'always' + // Check if instant updates are enabled and auto-enable delta updates. + const instantUpdateEnabled = usesAlwaysDirectUpdate(extConfig?.config?.plugins?.CapacitorUpdater) const interactive = canPromptInteractively({ silent }) - if (directUpdateEnabled && options.delta === undefined) { + if (instantUpdateEnabled && options.delta === undefined) { if (interactive) { - log.info('💡 Direct Update (instant updates) is enabled in your config') + log.info('💡 Instant updates are enabled in your config') log.info(' Delta updates send only changed files instead of the full bundle') const enableDelta = await pConfirm({ - message: 'Enable delta updates for this upload? (Recommended with Direct Update)', + message: 'Enable delta updates for this upload? (Recommended with instant updates)', initialValue: true, }) if (!pIsCancel(enableDelta) && enableDelta) { options.delta = true if (options.verbose) - log.info(`[Verbose] Delta updates auto-enabled due to Direct Update configuration`) + log.info(`[Verbose] Delta updates auto-enabled due to instant update configuration`) } } else if (!silent) { @@ -771,7 +772,7 @@ export async function uploadBundleInternal(preAppid: string, options: OptionsUpl if (options.delta !== false) { options.delta = true if (options.verbose) - log.info(`[Verbose] Delta updates auto-enabled in CI/CD mode due to Direct Update configuration`) + log.info(`[Verbose] Delta updates auto-enabled in CI/CD mode due to instant update configuration`) } } } diff --git a/cli/src/index.ts b/cli/src/index.ts index a2295506bf..c04c2147be 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -201,7 +201,7 @@ Example: npx @capgo/cli@latest bundle upload com.example.app --path ./dist --cha .option('--partial-only', `[DEPRECATED] Use --delta-only instead. Upload only incremental updates, skip full bundle`) .option('--delta', `Upload delta updates (only changed files) for instant, super-fast updates instead of big zip downloads`) .option('--delta-only', `Upload only delta updates without full bundle for maximum speed (useful for large apps)`) - .option('--no-delta', `Disable delta updates even if Direct Update is enabled`) + .option('--no-delta', `Disable delta updates even if instant updates are enabled`) .option('--encrypted-checksum ', `An encrypted checksum (signature). Used only when uploading an external bundle.`) .option('--auto-set-bundle', `Set the bundle in capacitor.config.json`) .option('--dry-upload', `Dry upload the bundle process: add the row in database without uploading files or updating channels (Used by Capgo for internal testing)`) diff --git a/cli/src/init/command.ts b/cli/src/init/command.ts index 7f6026d46c..ee5eaf5951 100644 --- a/cli/src/init/command.ts +++ b/cli/src/init/command.ts @@ -209,10 +209,9 @@ export function getInitUpdaterPluginConfig(appId: string, directInstall: boolean return { version: initNativeBundleVersion, appId, - autoUpdate: true, + autoUpdate: directInstall ? 'always' : 'atBackground', ...(directInstall ? { - directUpdate: 'always', autoSplashscreen: true, } : {}), diff --git a/cli/src/updaterConfig.ts b/cli/src/updaterConfig.ts new file mode 100644 index 0000000000..d1fd99a638 --- /dev/null +++ b/cli/src/updaterConfig.ts @@ -0,0 +1,20 @@ +type AutoUpdatePolicy = boolean | 'off' | 'atBackground' | 'atInstall' | 'onLaunch' | 'always' | 'onlyDownload' +type DirectUpdatePolicy = boolean | 'atInstall' | 'always' | 'onLaunch' + +export interface CapacitorUpdaterPluginConfig { + autoUpdate?: AutoUpdatePolicy + directUpdate?: DirectUpdatePolicy +} + +export function usesAlwaysDirectUpdate(config: CapacitorUpdaterPluginConfig | undefined): boolean { + const autoUpdate = config?.autoUpdate + + if (autoUpdate === 'always') + return true + + if (typeof autoUpdate === 'string' || autoUpdate === false) + return false + + const directUpdate = config?.directUpdate + return directUpdate === true || directUpdate === 'always' +} diff --git a/cli/test/test-init-guardrails.mjs b/cli/test/test-init-guardrails.mjs index 698811c578..ffd54bfc1c 100644 --- a/cli/test/test-init-guardrails.mjs +++ b/cli/test/test-init-guardrails.mjs @@ -14,6 +14,7 @@ import { isOnlyAllowedInitAutoTestChange, revertInitAutoTestChangeContent, } from '../src/init/command.ts' +import { usesAlwaysDirectUpdate } from '../src/updaterConfig.ts' let failures = 0 @@ -83,18 +84,27 @@ t('init updater config always starts from native version 0.0.0', () => { assert.deepEqual(getInitUpdaterPluginConfig('com.example.app', false), { version: '0.0.0', appId: 'com.example.app', - autoUpdate: true, + autoUpdate: 'atBackground', }) assert.deepEqual(getInitUpdaterPluginConfig('com.example.app', true), { version: '0.0.0', appId: 'com.example.app', - autoUpdate: true, - directUpdate: 'always', + autoUpdate: 'always', autoSplashscreen: true, }) }) +t('instant update detection supports new autoUpdate modes and legacy directUpdate', () => { + assert.equal(usesAlwaysDirectUpdate({ autoUpdate: 'always' }), true) + assert.equal(usesAlwaysDirectUpdate({ autoUpdate: 'atBackground', directUpdate: 'always' }), false) + assert.equal(usesAlwaysDirectUpdate({ autoUpdate: 'onlyDownload', directUpdate: true }), false) + assert.equal(usesAlwaysDirectUpdate({ autoUpdate: false, directUpdate: true }), false) + assert.equal(usesAlwaysDirectUpdate({ autoUpdate: true, directUpdate: true }), true) + assert.equal(usesAlwaysDirectUpdate({ directUpdate: 'always' }), true) + assert.equal(usesAlwaysDirectUpdate({ directUpdate: 'onLaunch' }), false) +}) + t('guided ota version suggestions stay on major zero when native baseline is pinned', () => { assert.equal(getInitOtaVersionBase('1.0.0'), '0.0.0') assert.equal(getInitSuggestedOtaVersion('1.0.0'), '0.0.1') diff --git a/cli/webdocs/bundle.mdx b/cli/webdocs/bundle.mdx index 00a2ffaa8c..e8a1d13bb8 100644 --- a/cli/webdocs/bundle.mdx +++ b/cli/webdocs/bundle.mdx @@ -67,7 +67,7 @@ npx @capgo/cli@latest bundle upload com.example.app --path ./dist --channel prod | **--partial-only** | boolean | [DEPRECATED] Use --delta-only instead. Upload only incremental updates, skip full bundle | | **--delta** | boolean | Upload delta updates (only changed files) for instant, super-fast updates instead of big zip downloads | | **--delta-only** | boolean | Upload only delta updates without full bundle for maximum speed (useful for large apps) | -| **--no-delta** | boolean | Disable delta updates even if Direct Update is enabled | +| **--no-delta** | boolean | Disable delta updates even if instant updates are enabled | | **--encrypted-checksum** | string | An encrypted checksum (signature). Used only when uploading an external bundle. | | **--auto-set-bundle** | boolean | Set the bundle in capacitor.config.json | | **--dry-upload** | boolean | Dry upload the bundle process: add the row in database without uploading files or updating channels (Used by Capgo for internal testing) | @@ -285,4 +285,3 @@ npx @capgo/cli@latest bundle zip com.example.app --path ./dist | **--no-code-check** | boolean | Ignore checking if notifyAppReady() is called in source code and index present in root folder | | **--key-v2** | boolean | Use encryption v2 | | **--package-json** | string | Paths to package.json files for monorepos (comma-separated) | -