claude-toolkit 0.1.20 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +61 -26
- package/bin/cli.ts +57 -12
- package/docs/README.md +1 -0
- package/docs/best-practices/capacitor/README.md +64 -0
- package/docs/best-practices/capacitor/capacitor-8.md +60 -0
- package/docs/best-practices/capacitor/capgo-setup.md +151 -0
- package/docs/best-practices/capacitor/channels-and-rollouts.md +101 -0
- package/docs/best-practices/capacitor/live-updates-ota.md +75 -0
- package/docs/best-practices/capacitor/security-encryption.md +68 -0
- package/docs/stacks/capacitor-ota.md +145 -0
- package/package.json +1 -1
- package/src/detect.ts +152 -0
- package/src/index.ts +2 -0
- package/src/types.ts +5 -1
- package/stacks/capacitor/skills/ct-capacitor-ota/SKILL.md +178 -0
- package/stacks/capacitor/stack.json +64 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.24 (2026-06-01)
|
|
4
|
+
|
|
5
|
+
- feat: add capacitor stack with Capgo OTA live updates, channels, and encryption
|
|
6
|
+
|
|
7
|
+
## 0.1.22 (2026-04-11)
|
|
8
|
+
|
|
9
|
+
- feat: add stack auto-detection with drift reporting
|
|
10
|
+
|
|
11
|
+
## 0.1.21 (2026-04-11)
|
|
12
|
+
|
|
13
|
+
- docs: improve README intro and standardize formatting
|
|
14
|
+
|
|
3
15
|
## 0.1.20 (2026-04-06)
|
|
4
16
|
|
|
5
17
|
- docs: update storybook docs with Vitest 4 addon-vitest config
|
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# claude-toolkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**claude-toolkit is a toolbox that Claude Code picks up and uses on its own — no prompting required, saving tokens and ensuring consistent behavior across your team.**
|
|
4
|
+
|
|
5
|
+
The toolkit auto-detects your tech stacks (SolidJS, Vite, Playwright, Cloudflare, etc.) and generates a `.claude/` directory that Claude Code automatically loads. Instead of every team member re-explaining project conventions, tooling, and patterns each session (burning tokens every time), Claude already knows — the knowledge is baked into the generated config. As your project evolves, the toolkit detects stack drift and suggests config updates to stay in sync.
|
|
6
|
+
|
|
7
|
+
This means any developer on your team gets the same Claude behavior for a given project, regardless of how they prompt. The toolkit is project-specific but team-consistent: Claude follows the same debugging methodology, the same testing patterns, and the same code quality standards for everyone.
|
|
8
|
+
|
|
9
|
+
Whether you're starting a new project from scratch or consolidating an existing one, the toolkit gives Claude immediate context about your stack and conventions — so it produces code that fits from day one, or aligns with what's already in place.
|
|
4
10
|
|
|
5
11
|
## Quick Start
|
|
6
12
|
|
|
@@ -19,45 +25,74 @@ bunx claude-toolkit init
|
|
|
19
25
|
3. Claude Code picks up the generated config automatically
|
|
20
26
|
|
|
21
27
|
```ts
|
|
22
|
-
import { defineConfig } from
|
|
28
|
+
import { defineConfig } from "claude-toolkit";
|
|
23
29
|
|
|
24
30
|
export default defineConfig({
|
|
25
|
-
stacks: [
|
|
26
|
-
packageManager:
|
|
31
|
+
stacks: ["solidjs", "rust-wasm", "cloudflare", "protobuf"],
|
|
32
|
+
packageManager: "bun",
|
|
27
33
|
hooks: {
|
|
28
|
-
formatter:
|
|
29
|
-
testRunner:
|
|
30
|
-
typeCheck:
|
|
31
|
-
extraChecks: [
|
|
34
|
+
formatter: "bun run prettier --write",
|
|
35
|
+
testRunner: "bun run vitest run",
|
|
36
|
+
typeCheck: "bun run tsc --noEmit",
|
|
37
|
+
extraChecks: ["cargo check --target wasm32-unknown-unknown"],
|
|
32
38
|
},
|
|
33
39
|
git: {
|
|
34
|
-
branchPrefix:
|
|
35
|
-
protectedBranches: [
|
|
40
|
+
branchPrefix: "feat",
|
|
41
|
+
protectedBranches: ["main"],
|
|
36
42
|
},
|
|
37
|
-
})
|
|
43
|
+
});
|
|
38
44
|
```
|
|
39
45
|
|
|
40
46
|
## CLI Commands
|
|
41
47
|
|
|
42
|
-
| Command
|
|
43
|
-
|
|
44
|
-
| `bunx claude-toolkit init` | Scaffold config file and generate `.claude/`
|
|
48
|
+
| Command | Description |
|
|
49
|
+
| -------------------------- | -------------------------------------------------------- |
|
|
50
|
+
| `bunx claude-toolkit init` | Scaffold config file and generate `.claude/` |
|
|
45
51
|
| `bunx claude-toolkit sync` | Regenerate `.claude/` from config (after toolkit update) |
|
|
46
|
-
| `bunx claude-toolkit help` | Show available commands
|
|
52
|
+
| `bunx claude-toolkit help` | Show available commands |
|
|
53
|
+
|
|
54
|
+
## Stack Auto-Detection
|
|
55
|
+
|
|
56
|
+
The toolkit automatically detects which stacks your project uses by scanning `package.json` dependencies, config files, and project structure.
|
|
57
|
+
|
|
58
|
+
**On `init`** (new project), detected stacks are pre-filled in the generated config:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
Detected stacks:
|
|
62
|
+
solidjs — found solid-js in dependencies
|
|
63
|
+
vite — found vite.config.ts
|
|
64
|
+
cloudflare — found wrangler.toml
|
|
65
|
+
|
|
66
|
+
Created claude-toolkit.config.ts
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**On `sync`** (existing config), the toolkit compares your configured stacks against what it detects and reports any drift:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
Stack drift detected:
|
|
73
|
+
+ playwright — found @playwright/test in dependencies (not in config)
|
|
74
|
+
- rust-wasm — in config but not detected in project
|
|
75
|
+
|
|
76
|
+
Suggested update in claude-toolkit.config.ts:
|
|
77
|
+
stacks: ["solidjs", "vite", "cloudflare", "playwright"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This keeps your config aligned as your project evolves — stacks you add or remove are surfaced automatically. The suggestion is informational; your config is not modified unless you update it yourself.
|
|
47
81
|
|
|
48
82
|
## Available Stacks
|
|
49
83
|
|
|
50
|
-
| Stack
|
|
51
|
-
|
|
52
|
-
| `solidjs`
|
|
53
|
-
| `vite`
|
|
54
|
-
| `vanilla-extract` | Type-safe CSS, sprinkles, recipes, themes
|
|
55
|
-
| `rust-wasm`
|
|
56
|
-
| `protobuf`
|
|
57
|
-
| `cloudflare`
|
|
58
|
-
| `i18n-typesafe`
|
|
59
|
-
| `playwright`
|
|
60
|
-
| `storybook`
|
|
84
|
+
| Stack | Skills Added |
|
|
85
|
+
| ----------------- | ------------------------------------------------------------------ |
|
|
86
|
+
| `solidjs` | SolidJS reactivity, signals, components |
|
|
87
|
+
| `vite` | Vite build config, plugins, Vitest testing, coverage, browser mode |
|
|
88
|
+
| `vanilla-extract` | Type-safe CSS, sprinkles, recipes, themes |
|
|
89
|
+
| `rust-wasm` | Rust WASM for Cloudflare Workers |
|
|
90
|
+
| `protobuf` | Protocol Buffers, code generation, contracts |
|
|
91
|
+
| `cloudflare` | D1 database, KV cache, Wrangler |
|
|
92
|
+
| `i18n-typesafe` | typesafe-i18n internationalization |
|
|
93
|
+
| `playwright` | Playwright E2E testing, Page Objects, fixtures, CI/CD |
|
|
94
|
+
| `storybook` | Storybook interaction testing, CSF 3, visual regression |
|
|
95
|
+
| `capacitor` | Capacitor 8 native runtime, Capgo OTA live updates, channels |
|
|
61
96
|
|
|
62
97
|
## Core Features (always included)
|
|
63
98
|
|
package/bin/cli.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
12
|
import { readFile, writeFile } from "node:fs/promises";
|
|
13
13
|
import { join, resolve } from "node:path";
|
|
14
|
+
import { detectStacks } from "../src/detect.js";
|
|
14
15
|
import { generate } from "../src/generator.js";
|
|
15
16
|
import type { ClaudeToolkitConfig } from "../src/types.js";
|
|
16
17
|
|
|
@@ -19,9 +20,7 @@ const CONFIG_FILENAME = "claude-toolkit.config.ts";
|
|
|
19
20
|
async function loadConfig(projectDir: string): Promise<ClaudeToolkitConfig> {
|
|
20
21
|
const configPath = join(projectDir, CONFIG_FILENAME);
|
|
21
22
|
if (!existsSync(configPath)) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`Config not found: ${configPath}\nRun "claude-toolkit init" first.`,
|
|
24
|
-
);
|
|
23
|
+
throw new Error(`Config not found: ${configPath}\nRun "claude-toolkit init" first.`);
|
|
25
24
|
}
|
|
26
25
|
const module = await import(configPath);
|
|
27
26
|
return module.default as ClaudeToolkitConfig;
|
|
@@ -36,23 +35,37 @@ async function init(projectDir: string): Promise<void> {
|
|
|
36
35
|
return sync(projectDir);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
// Detect stacks
|
|
39
|
+
const detected = detectStacks(projectDir);
|
|
40
|
+
if (detected.length > 0) {
|
|
41
|
+
console.log("Detected stacks:");
|
|
42
|
+
const maxLen = Math.max(...detected.map((d) => d.name.length));
|
|
43
|
+
for (const d of detected) {
|
|
44
|
+
console.log(` ${d.name.padEnd(maxLen)} — ${d.reason}`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
console.log(`No stacks detected. You can add them manually in ${CONFIG_FILENAME}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build stacks literal for config injection
|
|
51
|
+
const stacksLiteral =
|
|
52
|
+
detected.length > 0
|
|
53
|
+
? `stacks: [${detected.map((d) => `"${d.name}"`).join(", ")}]`
|
|
54
|
+
: "stacks: []";
|
|
55
|
+
|
|
56
|
+
// Copy starter config with detected stacks injected
|
|
57
|
+
const templatePath = join(import.meta.dirname, "..", "templates", "claude-toolkit.config.ts");
|
|
46
58
|
if (existsSync(templatePath)) {
|
|
47
59
|
const template = await readFile(templatePath, "utf-8");
|
|
48
|
-
|
|
60
|
+
const configContent = template.replace("stacks: []", stacksLiteral);
|
|
61
|
+
await writeFile(configPath, configContent, "utf-8");
|
|
49
62
|
console.log(`Created ${CONFIG_FILENAME}`);
|
|
50
63
|
} else {
|
|
51
64
|
// Inline fallback
|
|
52
65
|
const defaultConfig = `import { defineConfig } from 'claude-toolkit'
|
|
53
66
|
|
|
54
67
|
export default defineConfig({
|
|
55
|
-
|
|
68
|
+
${stacksLiteral},
|
|
56
69
|
packageManager: 'bun',
|
|
57
70
|
hooks: {
|
|
58
71
|
formatter: 'bun run prettier --write',
|
|
@@ -75,6 +88,38 @@ export default defineConfig({
|
|
|
75
88
|
|
|
76
89
|
async function sync(projectDir: string): Promise<void> {
|
|
77
90
|
const config = await loadConfig(projectDir);
|
|
91
|
+
|
|
92
|
+
// Compare detected stacks against config
|
|
93
|
+
const detected = detectStacks(projectDir);
|
|
94
|
+
const configuredNames = new Set(config.stacks);
|
|
95
|
+
const detectedNames = new Set(detected.map((d) => d.name));
|
|
96
|
+
|
|
97
|
+
const missing = detected.filter((d) => !configuredNames.has(d.name));
|
|
98
|
+
const stale = config.stacks.filter((s) => !detectedNames.has(s));
|
|
99
|
+
|
|
100
|
+
if (missing.length > 0 || stale.length > 0) {
|
|
101
|
+
console.log("\nStack drift detected:");
|
|
102
|
+
if (missing.length > 0) {
|
|
103
|
+
const maxLen = Math.max(...missing.map((d) => d.name.length));
|
|
104
|
+
for (const d of missing) {
|
|
105
|
+
console.log(` + ${d.name.padEnd(maxLen)} — ${d.reason} (not in config)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (stale.length > 0) {
|
|
109
|
+
for (const s of stale) {
|
|
110
|
+
console.log(` - ${s} — in config but not detected in project`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const suggested = [
|
|
114
|
+
...new Set([
|
|
115
|
+
...config.stacks.filter((s) => !stale.includes(s)),
|
|
116
|
+
...missing.map((d) => d.name),
|
|
117
|
+
]),
|
|
118
|
+
];
|
|
119
|
+
console.log(`\nSuggested update in ${CONFIG_FILENAME}:`);
|
|
120
|
+
console.log(` stacks: [${suggested.map((s) => `"${s}"`).join(", ")}]\n`);
|
|
121
|
+
}
|
|
122
|
+
|
|
78
123
|
await generate(projectDir, config);
|
|
79
124
|
console.log("Sync complete.");
|
|
80
125
|
}
|
package/docs/README.md
CHANGED
|
@@ -50,3 +50,4 @@ Stack-specific skills activated based on your `claude-toolkit.config.ts` configu
|
|
|
50
50
|
| [ct-i18n-typesafe](stacks/i18n-typesafe.md) | `i18n-typesafe` | Type-safe internationalization with compile-time key checking |
|
|
51
51
|
| [ct-playwright-patterns](stacks/playwright-patterns.md) | `playwright` | E2E testing with Page Objects, fixtures, auth, network mocking, CI/CD |
|
|
52
52
|
| [ct-storybook-patterns](stacks/storybook-patterns.md) | `storybook` | Interaction testing, CSF 3, play functions, a11y, visual regression |
|
|
53
|
+
| [ct-capacitor-ota](stacks/capacitor-ota.md) | `capacitor` | Capacitor 8 native runtime and Capgo OTA live updates, channels, encryption |
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Capacitor & OTA Best Practices
|
|
2
|
+
|
|
3
|
+
> A curated collection of best practices for Capacitor 8 native apps and Capgo over-the-air (OTA) live updates, sourced from official Capacitor documentation, the Capgo docs and blog, and the plugin maintainers as of June 2026.
|
|
4
|
+
|
|
5
|
+
Capacitor wraps a web app (your `webDir` bundle) in a native iOS/Android shell. **OTA** lets you replace that web bundle on installed devices in minutes — without an app store review cycle. Capgo (`@capgo/capacitor-updater`) is the live-update layer that delivers, verifies, and rolls back those bundles.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────┐
|
|
9
|
+
│ Native shell (store) │ ← Capacitor core + plugins; ships via App Store / Play
|
|
10
|
+
│ ┌───────────────────────┐ │ Changes here REQUIRE a new binary.
|
|
11
|
+
│ │ Web bundle (OTA) │ │ ← JS / HTML / CSS; swapped by Capgo in minutes.
|
|
12
|
+
│ │ notifyAppReady() ✓ │ │ Changes here ship over the air.
|
|
13
|
+
│ └───────────────────────┘ │
|
|
14
|
+
└─────────────────────────────┘
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The Two Layers — and the Golden Rule
|
|
18
|
+
|
|
19
|
+
| Layer | What it is | How it ships | Review latency |
|
|
20
|
+
| -------------- | ------------------------------------------- | --------------------------- | ------------------- |
|
|
21
|
+
| **Native** | Capacitor core, plugins, native deps | App Store / Google Play | Hours to days |
|
|
22
|
+
| **Web (OTA)** | Your compiled `webDir` (JS/HTML/CSS/assets) | Capgo channel swap | Minutes |
|
|
23
|
+
|
|
24
|
+
**Golden rule:** OTA the web layer only. If a change needs a native API the installed shell doesn't have — a new plugin, a native dependency, a Capacitor major bump — it **must** go through the store first. OTA-ing a native contract mismatch crashes the app. Every other rule in this guide follows from this one.
|
|
25
|
+
|
|
26
|
+
## When to Use Which Channel
|
|
27
|
+
|
|
28
|
+
| Question | Answer |
|
|
29
|
+
| ----------------------------------------------------------------- | --------------------------------------- |
|
|
30
|
+
| Did I change native code, add a plugin, or bump Capacitor major? | **Store binary** (then OTA on top) |
|
|
31
|
+
| Bug fix / copy change / styling / feature in JS only? | **OTA** to `production` |
|
|
32
|
+
| Needs QA sign-off before users see it? | **OTA** to `staging`, promote later |
|
|
33
|
+
| Testing on dev/emulator builds? | **OTA** to `development` |
|
|
34
|
+
| Need to undo a bad release right now? | **Channel rollback** (crown icon) |
|
|
35
|
+
|
|
36
|
+
## Technology Versions (as of June 2026)
|
|
37
|
+
|
|
38
|
+
| Tool | Version | Notes |
|
|
39
|
+
| -------------------------- | ------- | ---------------------------------------------------------------- |
|
|
40
|
+
| `@capacitor/core` & CLI | ^8.3.x | 8.3.1 (2026-04-16); Node 22+ required |
|
|
41
|
+
| `@capacitor/ios` | ^8.3.x | iOS 15.0 target, Xcode 26+, SPM default for new projects |
|
|
42
|
+
| `@capacitor/android` | ^8.3.x | minSdk 24, compileSdk/targetSdk 36, Gradle 8.14.3, Kotlin 2.2.20 |
|
|
43
|
+
| `@capgo/capacitor-updater` | ^8.x | Major tracks Capacitor major; v8 stores channel locally |
|
|
44
|
+
| `@capgo/cli` | latest | `npx @capgo/cli@latest …` — pin in CI |
|
|
45
|
+
|
|
46
|
+
## Shared Principles
|
|
47
|
+
|
|
48
|
+
1. **`notifyAppReady()` is non-negotiable.** Call it at the top of bootstrap. Without it, every OTA bundle auto-rolls back after `appReadyTimeout`.
|
|
49
|
+
2. **Match the bundle to the shell.** A web bundle is only valid against the native version that shipped it. Use channel version gates to prevent mismatches.
|
|
50
|
+
3. **Stage every rollout.** Canary to ~10%, widen over 24h. Instant rollback beats instant deploy.
|
|
51
|
+
4. **Sign your bundles.** End-to-end encryption (RSA-2048 + AES-256) means only your users can read an update — keep the private key out of git.
|
|
52
|
+
5. **Keep the cloud in control of production.** Omit `defaultChannel` from production binaries so the dashboard default governs rollout and rollback.
|
|
53
|
+
6. **Monitor before you widen.** Watch the `appReady` / `downloadFailed` ratio; a spike is your signal to roll back.
|
|
54
|
+
7. **Pin tooling.** Unpinned `@capgo/cli@latest` in CI can change upload behavior between runs.
|
|
55
|
+
|
|
56
|
+
## Guides
|
|
57
|
+
|
|
58
|
+
| Guide | Summary |
|
|
59
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
60
|
+
| [Capacitor 8 Platform](capacitor-8.md) | Version floor, breaking changes, SPM-by-default, SystemBars, upgrade checklist. |
|
|
61
|
+
| [Live Updates & OTA Strategy](live-updates-ota.md) | What to OTA vs ship native, performance, delta updates, scheduling. |
|
|
62
|
+
| [Capgo Setup & API](capgo-setup.md) | Plugin install, config, `notifyAppReady`, autoUpdate modes, manual flow, CLI. |
|
|
63
|
+
| [Channels & Staged Rollouts](channels-and-rollouts.md) | Channel ladder, precedence, canary rollouts, rollback, monitoring. |
|
|
64
|
+
| [Security & Encryption](security-encryption.md) | E2E encryption (v2), key management, code signing, store compliance. |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Capacitor 8 Platform
|
|
2
|
+
|
|
3
|
+
> Capacitor 8 raised the platform floor across the board. Get the toolchain right before touching OTA — a mismatched native baseline is the first thing that breaks live updates.
|
|
4
|
+
|
|
5
|
+
Latest at time of writing: **8.3.1** (2026-04-16). 8.3.0 (2026-03-25) and 8.2.0 (2026-03-06) precede it.
|
|
6
|
+
|
|
7
|
+
## Minimum Requirements
|
|
8
|
+
|
|
9
|
+
| Area | Requirement |
|
|
10
|
+
| ------- | --------------------------------------------------------------------------- |
|
|
11
|
+
| Node | **22+** (latest LTS recommended) |
|
|
12
|
+
| iOS | Xcode **26.0+**, deployment target **15.0** |
|
|
13
|
+
| Android | Android Studio Otter (2025.2.1)+, `minSdk 24`, `compileSdk 36`, `targetSdk 36` |
|
|
14
|
+
| Gradle | Android Gradle plugin **8.13.0**, wrapper **8.14.3**, Kotlin **2.2.20** |
|
|
15
|
+
|
|
16
|
+
## Headline Changes
|
|
17
|
+
|
|
18
|
+
### Swift Package Manager is the default (iOS)
|
|
19
|
+
|
|
20
|
+
New iOS projects scaffold with **SPM instead of CocoaPods**. Existing CocoaPods projects keep working — the ecosystem is migrating, not forcing a cutover. 8.3.0 added experimental config for `swift-tools-version` and support for SPM package traits in the generated `Package.swift`.
|
|
21
|
+
|
|
22
|
+
### Edge-to-edge via the SystemBars plugin (Android)
|
|
23
|
+
|
|
24
|
+
An internal `SystemBars` plugin now handles status/navigation bar appearance for immersive layouts automatically, with a public API for fine control. It version-gates behavior and coexists with `@capacitor/status-bar`. Consequently the old config flag **`android.adjustMarginsForEdgeToEdge` was removed** — use SystemBars instead.
|
|
25
|
+
|
|
26
|
+
## Breaking Changes Checklist
|
|
27
|
+
|
|
28
|
+
When upgrading to Capacitor 8:
|
|
29
|
+
|
|
30
|
+
- [ ] **Node 22+** on every dev machine and CI runner.
|
|
31
|
+
- [ ] **Android config syntax:** Gradle properties now require `=` (e.g. `compileSdk = 36`, not `compileSdk 36`).
|
|
32
|
+
- [ ] **Android layout rename:** `bridge_layout_main.xml` → `capacitor_bridge_layout_main.xml`.
|
|
33
|
+
- [ ] **Android resize:** add `density` to `configChanges` in `AndroidManifest.xml` to stop the WebView reloading on resize.
|
|
34
|
+
- [ ] **Remove `android.adjustMarginsForEdgeToEdge`** from `capacitor.config` — replaced by SystemBars.
|
|
35
|
+
- [ ] **iOS `appendUserAgent`:** the fixed concatenation may need a leading space to preserve your intended user-agent string.
|
|
36
|
+
- [ ] **iOS lifecycle:** the framework now emits `viewDidAppear` / `viewWillTransition` — delete any custom extensions that duplicated this.
|
|
37
|
+
|
|
38
|
+
## 8.2–8.3 Notable Fixes
|
|
39
|
+
|
|
40
|
+
These land automatically on patch upgrades but are worth knowing because they touch OTA-adjacent behavior:
|
|
41
|
+
|
|
42
|
+
- **Android `server.url` with paths** is now parsed correctly (8.3.0) — relevant if you point the WebView at a custom/self-hosted update host.
|
|
43
|
+
- **Android `isNewBinary()`** handles a null `versionName` (8.3.1) — the check that decides whether a native update invalidates OTA bundles.
|
|
44
|
+
- **HTTP `fetch`** handles `URL` objects and form-data boundary extraction (8.3.0/8.3.1).
|
|
45
|
+
- **CLI** inlines CSS sourcemaps alongside JS (8.3.0) and respects `CAPACITOR_COCOAPODS_PATH` (8.3.1).
|
|
46
|
+
|
|
47
|
+
## Keeping Native and OTA in Lockstep
|
|
48
|
+
|
|
49
|
+
The single most important interaction: **a native upgrade invalidates incompatible OTA bundles.** `resetWhenUpdate: true` (the Capgo default) wipes downloaded bundles when the native app updates, so users fall back to the bundle baked into the new binary rather than running a stale OTA bundle against new native code. Keep it on unless you have a specific reason not to.
|
|
50
|
+
|
|
51
|
+
When you bump Capacitor or add a plugin:
|
|
52
|
+
|
|
53
|
+
1. Ship a new store binary with the updated native layer.
|
|
54
|
+
2. Bump your bundle's version so old shells don't pull a bundle meant for the new native contract.
|
|
55
|
+
3. Use channel version gates (`--disable-auto-update major|minor`) to enforce the boundary.
|
|
56
|
+
|
|
57
|
+
## See Also
|
|
58
|
+
|
|
59
|
+
- [Live Updates & OTA Strategy](live-updates-ota.md) — what's safe to OTA on top of a given binary
|
|
60
|
+
- [Channels & Staged Rollouts](channels-and-rollouts.md) — version gates that enforce the native/web contract
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Capgo Setup & API
|
|
2
|
+
|
|
3
|
+
> `@capgo/capacitor-updater` ships in three modes: fully managed (Capgo Cloud), self-hosted (your own update server), or manual (you drive every download/apply in JS). This guide covers the managed path and the manual escape hatch.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @capgo/capacitor-updater && npx cap sync
|
|
9
|
+
npx @capgo/cli@latest init <API_KEY>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`init` is an interactive onboarding: it registers the app in Capgo Cloud, injects the `notifyAppReady()` call, builds, uploads the first bundle, and verifies the update round-trips. Run it once per app.
|
|
13
|
+
|
|
14
|
+
## notifyAppReady() — Read This First
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { CapacitorUpdater } from "@capgo/capacitor-updater";
|
|
18
|
+
|
|
19
|
+
// As early as possible in bootstrap, before heavy async work.
|
|
20
|
+
CapacitorUpdater.notifyAppReady();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The plugin arms a timer (`appReadyTimeout`, default **10000ms**) the moment a bundle loads. Call `notifyAppReady()` before it fires and the bundle is confirmed; miss it and the plugin **auto-rolls back** to the previous bundle. This is the mechanism that makes a broken release self-heal — never remove it, and don't bury it behind slow startup code.
|
|
24
|
+
|
|
25
|
+
## Configuration Reference
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// capacitor.config.ts
|
|
29
|
+
import type { CapacitorConfig } from "@capacitor/cli";
|
|
30
|
+
|
|
31
|
+
const config: CapacitorConfig = {
|
|
32
|
+
appId: "com.example.app",
|
|
33
|
+
appName: "Example",
|
|
34
|
+
webDir: "dist",
|
|
35
|
+
plugins: {
|
|
36
|
+
CapacitorUpdater: {
|
|
37
|
+
autoUpdate: "atBackground",
|
|
38
|
+
appReadyTimeout: 10000,
|
|
39
|
+
responseTimeout: 20,
|
|
40
|
+
autoDeleteFailed: true,
|
|
41
|
+
autoDeletePrevious: true,
|
|
42
|
+
resetWhenUpdate: true,
|
|
43
|
+
// defaultChannel: "Development", // TEST/QA BUILDS ONLY
|
|
44
|
+
// publicKey: "...", // injected by `key create` for E2E encryption
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default config;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Setting | Default | Purpose |
|
|
53
|
+
| -------------------- | -------------------------------------- | ------------------------------------------------------------ |
|
|
54
|
+
| `autoUpdate` | `"atBackground"` | When to apply downloaded updates (see table below) |
|
|
55
|
+
| `appReadyTimeout` | `10000` (ms) | Grace period for `notifyAppReady()` before rollback |
|
|
56
|
+
| `responseTimeout` | `20` (s) | Update-server API call timeout |
|
|
57
|
+
| `periodCheckDelay` | `600` (s) | Poll interval; **cannot be < 600** |
|
|
58
|
+
| `autoDeleteFailed` | `true` | Remove bundles that failed to apply |
|
|
59
|
+
| `autoDeletePrevious` | `true` | Remove the prior bundle after a successful update |
|
|
60
|
+
| `resetWhenUpdate` | `true` | Wipe OTA bundles when the native app updates |
|
|
61
|
+
| `defaultChannel` | _(unset)_ | Pin a channel — **test builds only** |
|
|
62
|
+
| `allowModifyUrl` | `false` | Let JS change update/stats/channel URLs at runtime |
|
|
63
|
+
| `publicKey` | _(unset)_ | RSA public key for end-to-end encryption (v2) |
|
|
64
|
+
| `updateUrl` | `https://plugin.capgo.app/updates` | Update-check endpoint (override for self-hosted) |
|
|
65
|
+
| `statsUrl` | `https://plugin.capgo.app/stats` | Stats endpoint |
|
|
66
|
+
| `channelUrl` | `https://plugin.capgo.app/channel_self`| Channel self-assignment endpoint |
|
|
67
|
+
|
|
68
|
+
> `directUpdate` is deprecated. Use the `autoUpdate` string modes instead.
|
|
69
|
+
|
|
70
|
+
## autoUpdate Modes
|
|
71
|
+
|
|
72
|
+
| Value | Behavior |
|
|
73
|
+
| ---------------------------- | -------------------------------------------------------------- |
|
|
74
|
+
| `false` / `"off"` | No automatic updates — you drive the lifecycle from JS |
|
|
75
|
+
| `"atBackground"` *(default)* | Download silently; apply when the app moves to the background |
|
|
76
|
+
| `"atInstall"` | Apply immediately after a fresh install / native update |
|
|
77
|
+
| `"onLaunch"` | Apply immediately on next launch, then revert to background |
|
|
78
|
+
| `"always"` | Apply immediately whenever an update check runs |
|
|
79
|
+
| `"onlyDownload"` | Download but never auto-apply; emits `updateAvailable` |
|
|
80
|
+
|
|
81
|
+
**Default lifecycle:** on launch Capgo checks the channel → downloads any new bundle silently → waits for background/close → user gets the update on next launch. Seamless, no spinner.
|
|
82
|
+
|
|
83
|
+
## Manual Mode
|
|
84
|
+
|
|
85
|
+
Set `autoUpdate: false` to control everything yourself — useful for custom UX (e.g. "Update available" prompts) or applying only on explicit user action.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { CapacitorUpdater } from "@capgo/capacitor-updater";
|
|
89
|
+
import { App } from "@capacitor/app";
|
|
90
|
+
import { SplashScreen } from "@capacitor/splash-screen";
|
|
91
|
+
|
|
92
|
+
CapacitorUpdater.addListener("download", ({ percent }) => {
|
|
93
|
+
console.log(`Downloading: ${percent}%`);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
App.addListener("appStateChange", async ({ isActive }) => {
|
|
97
|
+
if (isActive) {
|
|
98
|
+
const latest = await CapacitorUpdater.getLatest();
|
|
99
|
+
if (latest.url) {
|
|
100
|
+
const bundle = await CapacitorUpdater.download({
|
|
101
|
+
version: latest.version,
|
|
102
|
+
url: latest.url,
|
|
103
|
+
});
|
|
104
|
+
await CapacitorUpdater.next({ id: bundle.id }); // queue for next background
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Core methods
|
|
111
|
+
|
|
112
|
+
| Method | Purpose |
|
|
113
|
+
| ----------------- | -------------------------------------------------------------- |
|
|
114
|
+
| `notifyAppReady()`| Confirm the current bundle booted (always required) |
|
|
115
|
+
| `getLatest()` | Query the server for the latest bundle metadata |
|
|
116
|
+
| `download()` | Download a bundle by `{ version, url }` |
|
|
117
|
+
| `next({ id })` | Queue a bundle to apply on next background |
|
|
118
|
+
| `set({ id })` | Apply a bundle now — **destroys the JS context** (hard reload) |
|
|
119
|
+
| `current()` | Inspect the active bundle + native version |
|
|
120
|
+
| `reload()` | Apply a pending update without backgrounding |
|
|
121
|
+
| `reset()` | Revert to the bundle baked into the native binary |
|
|
122
|
+
| `setChannel()` | Switch the device's channel at runtime (v8+, self-assign) |
|
|
123
|
+
|
|
124
|
+
### Events
|
|
125
|
+
|
|
126
|
+
`download` · `downloadComplete` · `updateAvailable` · `downloadFailed` · `appReady` · `majorAvailable`
|
|
127
|
+
|
|
128
|
+
Subscribe to `downloadFailed` and `appReady` for monitoring; subscribe to `majorAvailable` to detect a bundle that requires a native update you can't OTA.
|
|
129
|
+
|
|
130
|
+
## CLI Cheat Sheet
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx @capgo/cli@latest login <API_KEY> # store key (--local to scope to repo)
|
|
134
|
+
npx @capgo/cli@latest app add com.example.app # register an app
|
|
135
|
+
npx @capgo/cli@latest bundle upload --channel=production # build → upload → assign
|
|
136
|
+
npx @capgo/cli@latest bundle upload --key-v2 --channel=production # encrypted upload
|
|
137
|
+
npx @capgo/cli@latest bundle upload --delta-only # require delta-capable clients
|
|
138
|
+
npx @capgo/cli@latest bundle compatibility -c production # check native compatibility first
|
|
139
|
+
npx @capgo/cli@latest channel set production -s default # set the cloud default channel
|
|
140
|
+
npx @capgo/cli@latest key create # generate the E2E key pair
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Useful `bundle upload` flags: `--channel`, `--key-v2` / `--no-key`, `--delta` / `--delta-only`, `--external` (link your own storage), `--tus` (resumable upload), `--encrypted-checksum`.
|
|
144
|
+
|
|
145
|
+
**Pin the CLI version in CI** (`@capgo/cli@8.x` rather than `@latest`) so upload behavior doesn't change between pipeline runs.
|
|
146
|
+
|
|
147
|
+
## See Also
|
|
148
|
+
|
|
149
|
+
- [Channels & Staged Rollouts](channels-and-rollouts.md) — what `--channel` and `setChannel` actually do
|
|
150
|
+
- [Security & Encryption](security-encryption.md) — the `--key-v2` flow in full
|
|
151
|
+
- [Live Updates & OTA Strategy](live-updates-ota.md) — when to use each `autoUpdate` mode
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Channels & Staged Rollouts
|
|
2
|
+
|
|
3
|
+
> A channel is a pointer to one JS bundle. Devices subscribe to a channel; you change which bundle the channel points to. That indirection is what makes rollout and rollback instant and rebuild-free.
|
|
4
|
+
|
|
5
|
+
## The Channel Ladder
|
|
6
|
+
|
|
7
|
+
Mirror your release pipeline with channels, and version bundles with semver pre-release tags so relationships are obvious:
|
|
8
|
+
|
|
9
|
+
| Channel | Bundle version | Audience |
|
|
10
|
+
| ------------- | -------------- | ------------------------- |
|
|
11
|
+
| `development` | `1.2.3-dev.1` | dev / emulator builds |
|
|
12
|
+
| `qa` | `1.2.3-qa.1` | QA verification |
|
|
13
|
+
| `staging` | `1.2.3-rc.1` | production-like sign-off |
|
|
14
|
+
| `production` | `1.2.3` | end users (store version) |
|
|
15
|
+
|
|
16
|
+
Promote a build up the ladder by uploading (or re-pointing) it to the next channel — no native rebuild between stages.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @capgo/cli@latest bundle upload --channel=staging # ship to staging
|
|
20
|
+
# ...QA signs off...
|
|
21
|
+
npx @capgo/cli@latest bundle upload --channel=production # promote to production
|
|
22
|
+
npx @capgo/cli@latest channel set production -s default # ensure it's the cloud default
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Channel Precedence
|
|
26
|
+
|
|
27
|
+
When a device checks for updates, Capgo resolves the channel in this order (highest wins):
|
|
28
|
+
|
|
29
|
+
1. **Forced device mapping** — pin specific device IDs (testing/exception layer).
|
|
30
|
+
2. **Cloud per-device override** — dashboard/API change for one device (up to ~2 min to propagate).
|
|
31
|
+
3. **Config `defaultChannel`** — set in `capacitor.config` (baked into the binary).
|
|
32
|
+
4. **Cloud Default Channel** — the fallback ~99% of users follow.
|
|
33
|
+
|
|
34
|
+
**Production rule:** ship production binaries **without** `defaultChannel`. If you bake a channel into the binary, those users stop following the cloud default — which is exactly the lever you use for instant rollout and rollback. Reserve `defaultChannel` for builds you hand directly to testers.
|
|
35
|
+
|
|
36
|
+
## Runtime Switching with setChannel()
|
|
37
|
+
|
|
38
|
+
Let users opt into a beta from inside the app:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { CapacitorUpdater } from "@capgo/capacitor-updater";
|
|
42
|
+
|
|
43
|
+
await CapacitorUpdater.setChannel({ channel: "beta", triggerAutoUpdate: true });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- Requires **"Allow device self-assignment"** enabled on the target channel; otherwise the call fails.
|
|
47
|
+
- In **plugin v8+**, `setChannel` stores the choice **locally on the device** and takes effect on the next update check — no 2-minute backend replication lag. The backend still validates the self-assignment permission at check time.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx @capgo/cli@latest channel set beta --self-assign # enable opt-in
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Version Gates: Enforce the Native/Web Contract
|
|
54
|
+
|
|
55
|
+
Channels can refuse updates that cross a version boundary the native shell can't honor:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Block cross-major OTA on production (e.g. don't push a 2.x bundle to 1.x shells)
|
|
59
|
+
npx @capgo/cli@latest channel set production --disable-auto-update major
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| `--disable-auto-update` | Allows |
|
|
63
|
+
| ----------------------- | --------------------------------------------------- |
|
|
64
|
+
| `major` | minor + patch within the same major |
|
|
65
|
+
| `minor` | patch only within the same major.minor |
|
|
66
|
+
| `patch` | patch increments only |
|
|
67
|
+
| `metadata` | requires minimum version metadata on bundles |
|
|
68
|
+
| `none` | all semver-compatible updates |
|
|
69
|
+
|
|
70
|
+
Also available: **disable auto-downgrade** (don't send a bundle older than the device's native version) and per-platform targeting (`--ios`, `--android`). These gates are how you keep a web bundle from landing on a shell that can't run it.
|
|
71
|
+
|
|
72
|
+
## Staged Rollout Playbook
|
|
73
|
+
|
|
74
|
+
1. **Canary** — point the channel at the new bundle for ~10% of users (or a dedicated `canary` channel).
|
|
75
|
+
2. **Observe** — watch the success signal for 1–2 hours (next section).
|
|
76
|
+
3. **Widen** — ramp to 100% over ~24h if healthy.
|
|
77
|
+
4. **Schedule** — time heavy rollouts for **1–4 AM local**, Wi-Fi-preferred, so downloads don't compete with active use.
|
|
78
|
+
|
|
79
|
+
**Benchmarks:** aim for **~95% adoption within 24h** and **~82% global success rate**. Falling short is a signal to pause and investigate, not to widen.
|
|
80
|
+
|
|
81
|
+
## Rollback
|
|
82
|
+
|
|
83
|
+
Instant and rebuild-free — the core OTA payoff:
|
|
84
|
+
|
|
85
|
+
1. Open the channel in the Capgo dashboard.
|
|
86
|
+
2. Find the previous good build and click the **crown icon**.
|
|
87
|
+
3. Confirm. The channel now points at the old bundle; devices pick it up on next check-in.
|
|
88
|
+
|
|
89
|
+
Because rollback is this cheap, bias toward shipping small and reverting fast over holding releases for exhaustive pre-launch testing.
|
|
90
|
+
|
|
91
|
+
## Monitoring
|
|
92
|
+
|
|
93
|
+
- **`appReady` vs `downloadFailed`** — the health ratio. A `downloadFailed` spike, or `appReady` events not keeping pace with downloads, means bundles are failing to boot → roll back.
|
|
94
|
+
- **Capgo stats dashboard** — adoption curve and per-version success rate. Compare against the 95% / 82% benchmarks.
|
|
95
|
+
- **Alert, then act** — wire a threshold alert so a bad canary pages you before you widen.
|
|
96
|
+
|
|
97
|
+
## See Also
|
|
98
|
+
|
|
99
|
+
- [Live Updates & OTA Strategy](live-updates-ota.md) — why staged rollout is the whole point
|
|
100
|
+
- [Capacitor 8 Platform](capacitor-8.md) — what makes a bundle native-incompatible
|
|
101
|
+
- [Security & Encryption](security-encryption.md) — signing the bundles a channel serves
|