kasy-cli 1.31.6 → 1.31.8

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/bin/kasy.js CHANGED
@@ -602,7 +602,22 @@ function buildProgram(language) {
602
602
  ],
603
603
  });
604
604
  if (sub === 'configure') await runCodemagicConfigure('.', { language });
605
- else if (sub === 'release') await runCodemagicRelease('.', { language });
605
+ else if (sub === 'release') {
606
+ const platform = await ui.select({
607
+ message: t('cli.command.codemagic.release.platformPick'),
608
+ options: [
609
+ { value: 'both', label: t('cli.command.codemagic.release.platform.both') },
610
+ { value: 'ios', label: t('cli.command.codemagic.release.platform.ios') },
611
+ { value: 'android', label: t('cli.command.codemagic.release.platform.android') },
612
+ ],
613
+ });
614
+ if (typeof platform === 'string') {
615
+ const relOpts = { language };
616
+ if (platform === 'ios') relOpts.ios = true;
617
+ else if (platform === 'android') relOpts.android = true;
618
+ await runCodemagicRelease('.', relOpts);
619
+ }
620
+ }
606
621
  else if (sub === 'status') {
607
622
  const buildId = await ui.text({
608
623
  message: 'Build ID',
@@ -625,9 +640,11 @@ function buildProgram(language) {
625
640
  codemagicCmd
626
641
  .command('release')
627
642
  .argument('[directory]', 'Project folder (default: current directory)', '.')
643
+ .option('--ios', t('cli.command.codemagic.release.iosOption'))
644
+ .option('--android', t('cli.command.codemagic.release.androidOption'))
628
645
  .description(t('cli.command.codemagic.release.description'))
629
- .action(async (directory) => {
630
- await runCodemagicRelease(directory, { language });
646
+ .action(async (directory, opts) => {
647
+ await runCodemagicRelease(directory, { language, ios: opts.ios, android: opts.android });
631
648
  }),
632
649
  t
633
650
  );
@@ -121,15 +121,17 @@ Atalho: `make release-ios`
121
121
 
122
122
  ---
123
123
 
124
- ### `kasy codemagic` — Release iOS (nuvem)
124
+ ### `kasy codemagic` — Release iOS/Android (nuvem)
125
125
 
126
- Dispara build iOS no Codemagic (ideal sem Mac).
126
+ Dispara build **iOS e Android** no Codemagic (ideal sem Mac), levando as chaves do `.env` no disparo.
127
127
 
128
128
  ```bash
129
- kasy add ci # cria codemagic.yaml (se necessário)
130
- kasy codemagic configure # token API + app/workflow ID
131
- kasy codemagic release # inicia build na nuvem
132
- kasy codemagic status <id> # status do build
129
+ kasy add ci # entrega o codemagic.yaml (se necessário)
130
+ kasy codemagic configure # token API valida e lista seus apps → branch
131
+ kasy codemagic release # iOS + Android na nuvem
132
+ kasy codemagic release --ios # iOS
133
+ kasy codemagic release --android # só Android
134
+ kasy codemagic status <id> # status do build
133
135
  ```
134
136
 
135
137
  Documentação: `docs/codemagic-release.md` (idioma da CLI)
@@ -11,18 +11,39 @@ const {
11
11
  codemagicReleaseDocPath,
12
12
  writeCodemagicEnv,
13
13
  validateCodemagicSetup,
14
+ loadProjectEnv,
15
+ listApps,
16
+ workflowIdFor,
17
+ resolveReleaseVars,
14
18
  triggerBuild,
15
19
  getBuildStatus,
16
20
  URL_CODEMAGIC_APPS,
17
21
  URL_CODEMAGIC_API,
18
22
  } = require('../utils/codemagic-release');
19
23
 
24
+ // Human label per platform (proper nouns — same across locales).
25
+ const PLATFORM_LABEL = { ios: 'iOS', android: 'Android' };
26
+
20
27
  async function assertProject(projectDir, t) {
21
28
  if (!(await isKasyFlutterProject(projectDir))) {
22
29
  throw new Error(t('codemagic.error.notProject'));
23
30
  }
24
31
  }
25
32
 
33
+ /**
34
+ * Decide which platforms to release. Explicit flags win; with no flag we build
35
+ * both. Returns an ordered list like ['ios', 'android'].
36
+ */
37
+ function platformsFromOptions(options) {
38
+ const wantIos = !!options.ios;
39
+ const wantAndroid = !!options.android;
40
+ if (!wantIos && !wantAndroid) return ['ios', 'android'];
41
+ const platforms = [];
42
+ if (wantIos) platforms.push('ios');
43
+ if (wantAndroid) platforms.push('android');
44
+ return platforms;
45
+ }
46
+
26
47
  async function runConfigure(directory, options = {}) {
27
48
  const lang = options.language || detectDefaultLanguage();
28
49
  const t = createTranslator(lang);
@@ -35,39 +56,62 @@ async function runConfigure(directory, options = {}) {
35
56
  ui.log.message(kleur.dim(`${t('codemagic.configure.doc')}: ${codemagicReleaseDocPath(lang)}`));
36
57
  ui.log.warn(t('codemagic.configure.checklist'));
37
58
 
38
- ui.log.info(t('codemagic.configure.openingLinks'));
39
- openUrl(URL_CODEMAGIC_API);
40
- openUrl(URL_CODEMAGIC_APPS);
41
-
42
59
  const cancel = () => { ui.cancel(t('codemagic.configure.cancelled')); process.exit(0); };
43
60
  const required = (v) => (v && String(v).trim().length > 0 ? undefined : t('codemagic.configure.q.required'));
44
61
 
45
- const token = await ui.password({
62
+ // 1. API token open the settings page so the user can copy it.
63
+ ui.log.info(t('codemagic.configure.openingToken'));
64
+ openUrl(URL_CODEMAGIC_API);
65
+ const tokenRaw = await ui.password({
46
66
  message: t('codemagic.configure.q.token'),
47
67
  validate: required,
48
68
  onCancel: cancel,
49
69
  });
50
- const appId = await ui.text({
51
- message: t('codemagic.configure.q.appId'),
52
- validate: required,
53
- onCancel: cancel,
54
- });
55
- const workflowId = await ui.text({
56
- message: t('codemagic.configure.q.workflowId'),
57
- initialValue: 'ios-workflow',
58
- onCancel: cancel,
59
- });
60
- const branch = await ui.text({
70
+ const token = String(tokenRaw).trim();
71
+
72
+ // 2. Validate the token by listing the user's apps.
73
+ const spinner = ui.spinner();
74
+ spinner.start(t('codemagic.configure.validating'));
75
+ let apps;
76
+ try {
77
+ apps = await listApps(token);
78
+ spinner.stop(t('codemagic.configure.validated'));
79
+ } catch (err) {
80
+ spinner.stop(`${t('codemagic.configure.tokenInvalid')} (${err.message})`, 2);
81
+ process.exitCode = 1;
82
+ return;
83
+ }
84
+
85
+ // 3. Pick the app (or guide the user to connect a repo first).
86
+ let appId;
87
+ if (!apps.length) {
88
+ ui.log.warn(t('codemagic.configure.noApps'));
89
+ openUrl(URL_CODEMAGIC_APPS);
90
+ const manual = await ui.text({
91
+ message: t('codemagic.configure.q.appId'),
92
+ validate: required,
93
+ onCancel: cancel,
94
+ });
95
+ appId = String(manual).trim();
96
+ } else {
97
+ appId = await ui.select({
98
+ message: t('codemagic.configure.pickApp'),
99
+ options: apps.map((a) => ({ value: a.id, label: a.name, hint: a.id })),
100
+ });
101
+ if (typeof appId === 'symbol') cancel();
102
+ }
103
+
104
+ // 4. Branch that triggers the build.
105
+ const branchRaw = await ui.text({
61
106
  message: t('codemagic.configure.q.branch'),
62
107
  initialValue: 'main',
63
108
  onCancel: cancel,
64
109
  });
65
110
 
66
111
  await writeCodemagicEnv(projectDir, {
67
- token: String(token).trim(),
68
- appId: String(appId).trim(),
69
- workflowId: String(workflowId).trim() || 'ios-workflow',
70
- branch: String(branch).trim() || 'main',
112
+ token,
113
+ appId,
114
+ branch: String(branchRaw).trim() || 'main',
71
115
  });
72
116
 
73
117
  ui.log.success(t('codemagic.configure.success'));
@@ -87,44 +131,64 @@ async function runRelease(directory, options = {}) {
87
131
  if (validation.issues.includes('missing_yaml')) {
88
132
  ui.log.message(kleur.dim(t('codemagic.error.addCi')));
89
133
  }
90
- if (validation.issues.includes('missing_env')) {
134
+ if (validation.issues.includes('missing_env') || validation.issues.includes('missing_token') || validation.issues.includes('missing_app_id')) {
91
135
  ui.log.message(kleur.dim(t('codemagic.error.runConfigure')));
92
136
  }
93
137
  process.exitCode = 1;
94
138
  return;
95
139
  }
96
140
 
97
- const googleScheme = await validateGoogleIosUrlScheme(projectDir);
98
- if (!googleScheme.ok) {
99
- printCompactHeader(t);
100
- ui.log.error(t('codemagic.error.googleUrlScheme'));
101
- ui.log.message(kleur.dim(googleScheme.error));
102
- ui.log.info(t('ios.error.googleUrlSchemeHint'));
103
- process.exitCode = 1;
104
- return;
141
+ const platforms = platformsFromOptions(options);
142
+
143
+ // iOS builds need the Google Sign-In URL scheme to be in sync.
144
+ if (platforms.includes('ios')) {
145
+ const googleScheme = await validateGoogleIosUrlScheme(projectDir);
146
+ if (!googleScheme.ok) {
147
+ printCompactHeader(t);
148
+ ui.log.error(t('codemagic.error.googleUrlScheme'));
149
+ ui.log.message(kleur.dim(googleScheme.error));
150
+ ui.log.info(t('ios.error.googleUrlSchemeHint'));
151
+ process.exitCode = 1;
152
+ return;
153
+ }
105
154
  }
106
155
 
107
156
  printCompactHeader(t);
108
157
  ui.intro(t('codemagic.release.title'));
109
158
 
110
- const spinner = ui.spinner();
111
- spinner.start(t('codemagic.release.spin'));
112
- try {
113
- const result = await triggerBuild(projectDir, validation.env);
114
- spinner.stop(t('codemagic.release.spinDone'));
115
- const buildId = result.buildId || result._id;
116
- if (buildId) {
117
- ui.log.success(t('codemagic.release.triggered'));
118
- ui.log.info(`Build ID: ${kleur.cyan(buildId)}`);
119
- ui.log.message(kleur.dim(`https://codemagic.io/builds/${buildId}`));
120
- ui.outro('');
121
- } else {
122
- ui.outro(t('codemagic.release.triggered'));
159
+ // Read the project's keys and carry them with the build.
160
+ const projectEnv = await loadProjectEnv(projectDir);
161
+ const variables = resolveReleaseVars(projectEnv);
162
+
163
+ if (platforms.includes('ios') && !variables.RC_IOS_PROD_KEY) {
164
+ ui.log.warn(t('codemagic.release.noIosKey'));
165
+ }
166
+ if (platforms.includes('android') && !variables.RC_ANDROID_PROD_KEY) {
167
+ ui.log.warn(t('codemagic.release.noAndroidKey'));
168
+ }
169
+
170
+ let anyFailed = false;
171
+ for (const platform of platforms) {
172
+ const label = PLATFORM_LABEL[platform];
173
+ const workflowId = workflowIdFor(validation.env, platform);
174
+ const spinner = ui.spinner();
175
+ spinner.start(t('codemagic.release.spinPlatform', { platform: label }));
176
+ try {
177
+ const result = await triggerBuild(validation.env, { workflowId, variables });
178
+ const buildId = result.buildId || result._id;
179
+ spinner.stop(t('codemagic.release.triggeredPlatform', { platform: label }));
180
+ if (buildId) {
181
+ ui.log.info(`Build ID: ${kleur.cyan(buildId)}`);
182
+ ui.log.message(kleur.dim(`https://codemagic.io/app/${validation.env.CODEMAGIC_APP_ID}/build/${buildId}`));
183
+ }
184
+ } catch (err) {
185
+ anyFailed = true;
186
+ spinner.stop(`${label}: ${err.message}`, 2);
123
187
  }
124
- } catch (err) {
125
- spinner.stop(err.message, 2);
126
- process.exitCode = 1;
127
188
  }
189
+
190
+ if (anyFailed) process.exitCode = 1;
191
+ ui.outro('');
128
192
  }
129
193
 
130
194
  async function runStatus(buildId, directory, options = {}) {
@@ -168,4 +232,5 @@ module.exports = {
168
232
  runConfigure,
169
233
  runRelease,
170
234
  runStatus,
235
+ platformsFromOptions,
171
236
  };
@@ -58,6 +58,13 @@ dependencies:
58
58
  open_filex: ^4.7.0
59
59
  package_info_plus: ^8.3.0
60
60
  path_provider: ^2.1.5
61
+ # Pin the Apple impl of path_provider below 2.6.0/2.5.0. Those versions pull in
62
+ # objective_c via "native assets", whose build hook is unquoted and breaks every
63
+ # build on Windows when the username has a space (C:\Users\John Silva) — even a
64
+ # Chrome/web run, where the Apple-only hook is dead weight. 2.5.1 is the last
65
+ # release without objective_c (added in 2.5.0, reverted in 2.5.1, re-added in
66
+ # 2.6.0). Drop this pin once the upstream web-target regression is fixed.
67
+ path_provider_foundation: 2.5.1
61
68
  permission_handler: ^12.0.1
62
69
  provider: ^6.1.0
63
70
  pub_semver: ^2.2.0
@@ -59,6 +59,13 @@ dependencies:
59
59
  open_filex: ^4.7.0
60
60
  package_info_plus: ^8.3.0
61
61
  path_provider: ^2.1.5
62
+ # Pin the Apple impl of path_provider below 2.6.0/2.5.0. Those versions pull in
63
+ # objective_c via "native assets", whose build hook is unquoted and breaks every
64
+ # build on Windows when the username has a space (C:\Users\John Silva) — even a
65
+ # Chrome/web run, where the Apple-only hook is dead weight. 2.5.1 is the last
66
+ # release without objective_c (added in 2.5.0, reverted in 2.5.1, re-added in
67
+ # 2.6.0). Drop this pin once the upstream web-target regression is fixed.
68
+ path_provider_foundation: 2.5.1
62
69
  permission_handler: ^12.0.1
63
70
  provider: ^6.1.0
64
71
  pub_semver: ^2.2.0
@@ -27,12 +27,14 @@ const ALWAYS_EXCLUDE = new Set([
27
27
  '.cursor',
28
28
  '.idea',
29
29
  '.claude', // AI assistant instructions — not for the client
30
- // CI/CD configs delivered only when the user selects the 'ci' module
31
- // (these files live in features/ci/ and are applied as a feature patch)
30
+ // GitHub/GitLab CI pipelines are not shipped to generated projects yet.
32
31
  '.github',
33
32
  '.gitlab',
34
33
  '.gitlab-ci.yml',
35
- 'codemagic.yaml',
34
+ // NOTE: codemagic.yaml is intentionally NOT excluded here. It ships in the
35
+ // base template and generate.js removes it unless the 'ci' module is
36
+ // selected (see the "Phase B" gating). This is what lets `kasy codemagic`
37
+ // work in a generated project.
36
38
  ]);
37
39
 
38
40
  // File basenames that should NEVER be copied, regardless of directory depth
@@ -236,6 +236,13 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
236
236
  // Remove directories of modules that were NOT selected
237
237
  await removeModuleDirs(targetDir, modules);
238
238
 
239
+ // codemagic.yaml ships in the base template (so `kasy codemagic` works out
240
+ // of the box). It belongs to the 'ci' module — strip it when ci was not
241
+ // selected, mirroring how the other optional modules are gated above.
242
+ if (!modules.includes('ci')) {
243
+ await fs.remove(path.join(targetDir, 'codemagic.yaml'));
244
+ }
245
+
239
246
  // Write no-op analytics_api.dart if analytics not selected
240
247
  if (!modules.includes('analytics')) {
241
248
  await writeNoOpAnalyticsApi(targetDir, packageName);
@@ -8,18 +8,22 @@ const CODEMAGIC_ENV_REL = path.join('.kasy', 'codemagic.env');
8
8
  const CODEMAGIC_API = 'https://api.codemagic.io';
9
9
 
10
10
  const URL_CODEMAGIC_APPS = 'https://codemagic.io/apps';
11
- const URL_CODEMAGIC_API = 'https://codemagic.io/settings/integrations';
11
+ // Codemagic API token lives in the account settings → Integrations page.
12
+ const URL_CODEMAGIC_API = 'https://codemagic.io/settings';
13
+
14
+ // Default workflow ids defined in the codemagic.yaml shipped by the `ci` feature.
15
+ const DEFAULT_IOS_WORKFLOW = 'ios-workflow';
16
+ const DEFAULT_ANDROID_WORKFLOW = 'android-workflow';
12
17
 
13
18
  function codemagicReleaseDocPath() {
14
19
  return 'docs/codemagic-release.md';
15
20
  }
16
21
 
17
- async function loadCodemagicEnv(projectDir) {
18
- const envPath = path.join(projectDir, CODEMAGIC_ENV_REL);
19
- if (!(await fs.pathExists(envPath))) {
20
- return null;
21
- }
22
- const content = await fs.readFile(envPath, 'utf8');
22
+ /**
23
+ * Parse a simple KEY=VALUE env file into an object. Ignores blanks/comments.
24
+ * Values keep everything after the first '=' (so URLs with ':' survive).
25
+ */
26
+ function parseEnvContent(content) {
23
27
  const env = {};
24
28
  for (const line of content.split('\n')) {
25
29
  const trimmed = line.trim();
@@ -31,12 +35,32 @@ async function loadCodemagicEnv(projectDir) {
31
35
  return env;
32
36
  }
33
37
 
34
- async function writeCodemagicEnv(projectDir, { token, appId, workflowId, branch }) {
38
+ async function loadCodemagicEnv(projectDir) {
39
+ const envPath = path.join(projectDir, CODEMAGIC_ENV_REL);
40
+ if (!(await fs.pathExists(envPath))) {
41
+ return null;
42
+ }
43
+ return parseEnvContent(await fs.readFile(envPath, 'utf8'));
44
+ }
45
+
46
+ /**
47
+ * Read the project's runtime `.env` (the app's config). Returns {} when absent.
48
+ */
49
+ async function loadProjectEnv(projectDir) {
50
+ const envPath = path.join(projectDir, '.env');
51
+ if (!(await fs.pathExists(envPath))) {
52
+ return {};
53
+ }
54
+ return parseEnvContent(await fs.readFile(envPath, 'utf8'));
55
+ }
56
+
57
+ async function writeCodemagicEnv(projectDir, { token, appId, iosWorkflowId, androidWorkflowId, branch }) {
35
58
  const lines = [
36
59
  '# Generated by kasy codemagic configure — do not commit',
37
60
  `CODEMAGIC_API_TOKEN=${token}`,
38
61
  `CODEMAGIC_APP_ID=${appId}`,
39
- `CODEMAGIC_WORKFLOW_ID=${workflowId || 'ios-workflow'}`,
62
+ `CODEMAGIC_IOS_WORKFLOW_ID=${iosWorkflowId || DEFAULT_IOS_WORKFLOW}`,
63
+ `CODEMAGIC_ANDROID_WORKFLOW_ID=${androidWorkflowId || DEFAULT_ANDROID_WORKFLOW}`,
40
64
  `CODEMAGIC_BRANCH=${branch || 'main'}`,
41
65
  '',
42
66
  ];
@@ -83,12 +107,92 @@ function httpsJson(method, urlPath, token, body) {
83
107
  });
84
108
  }
85
109
 
86
- async function triggerBuild(projectDir, env) {
110
+ /**
111
+ * List the Codemagic applications the token can access.
112
+ * Returns [{ id, name }] so the configure wizard can let the user pick one
113
+ * instead of pasting a raw Mongo id.
114
+ */
115
+ async function listApps(token) {
116
+ const result = await httpsJson('GET', '/apps', token);
117
+ const apps = Array.isArray(result) ? result : (result.applications || []);
118
+ return apps
119
+ .map((a) => ({ id: a._id || a.id, name: a.appName || a.name || a.repository?.name || '(unnamed app)' }))
120
+ .filter((a) => a.id);
121
+ }
122
+
123
+ /**
124
+ * Resolve which workflow id to trigger for a platform, honoring custom ids
125
+ * saved at configure time and falling back to the shipped defaults. The legacy
126
+ * single CODEMAGIC_WORKFLOW_ID (older configs) is treated as the iOS workflow.
127
+ */
128
+ function workflowIdFor(env, platform) {
129
+ if (platform === 'android') {
130
+ return env.CODEMAGIC_ANDROID_WORKFLOW_ID || DEFAULT_ANDROID_WORKFLOW;
131
+ }
132
+ return env.CODEMAGIC_IOS_WORKFLOW_ID || env.CODEMAGIC_WORKFLOW_ID || DEFAULT_IOS_WORKFLOW;
133
+ }
134
+
135
+ /**
136
+ * Build the environment variables to inject at trigger time, read from the
137
+ * project's `.env`. These travel with the build so the cloud has the SAME
138
+ * production keys you use locally — the user does not have to fill the
139
+ * Codemagic variable groups by hand.
140
+ *
141
+ * RevenueCat SDK keys (appl_/goog_/test_) are public keys, safe to send this
142
+ * way. Real secrets (keystore, App Store key, service account) stay in the
143
+ * dashboard and are NOT sent here.
144
+ */
145
+ function resolveReleaseVars(projectEnv) {
146
+ const vars = { ENV: 'prod' };
147
+
148
+ const passthrough = [
149
+ 'BACKEND_URL',
150
+ 'RC_TEST_KEY',
151
+ 'RC_IOS_PROD_KEY',
152
+ 'RC_ANDROID_PROD_KEY',
153
+ 'MIXPANEL_TOKEN',
154
+ 'SENTRY_DSN',
155
+ ];
156
+ for (const key of passthrough) {
157
+ const value = projectEnv[key];
158
+ if (value && value.trim()) vars[key] = value.trim();
159
+ }
160
+
161
+ // LLM chat endpoint may be named either way across template versions; the app
162
+ // reads AI_CHAT_ENDPOINT.
163
+ const endpoint = projectEnv.AI_CHAT_ENDPOINT || projectEnv.LLM_CHAT_ENDPOINT;
164
+ if (endpoint && endpoint.trim()) vars.AI_CHAT_ENDPOINT = endpoint.trim();
165
+
166
+ // Legacy single-key projects (pre test/prod split): promote a non-test key
167
+ // into the prod slot so release builds still get a real key.
168
+ if (!vars.RC_IOS_PROD_KEY && projectEnv.RC_IOS_API_KEY && !projectEnv.RC_IOS_API_KEY.startsWith('test_')) {
169
+ vars.RC_IOS_PROD_KEY = projectEnv.RC_IOS_API_KEY.trim();
170
+ }
171
+ if (!vars.RC_ANDROID_PROD_KEY && projectEnv.RC_ANDROID_API_KEY && !projectEnv.RC_ANDROID_API_KEY.startsWith('test_')) {
172
+ vars.RC_ANDROID_PROD_KEY = projectEnv.RC_ANDROID_API_KEY.trim();
173
+ }
174
+
175
+ // Numeric App Store id → APP_ID, used by the iOS workflow for build numbering.
176
+ if (projectEnv.APP_STORE_ID && projectEnv.APP_STORE_ID.trim()) {
177
+ vars.APP_ID = projectEnv.APP_STORE_ID.trim();
178
+ }
179
+
180
+ return vars;
181
+ }
182
+
183
+ /**
184
+ * Trigger one build. `variables` is injected via the build's environment so the
185
+ * cloud build carries the project's production keys.
186
+ */
187
+ async function triggerBuild(env, { workflowId, branch, variables }) {
87
188
  const body = {
88
189
  appId: env.CODEMAGIC_APP_ID,
89
- workflowId: env.CODEMAGIC_WORKFLOW_ID,
90
- branch: env.CODEMAGIC_BRANCH || 'main',
190
+ workflowId,
191
+ branch: branch || env.CODEMAGIC_BRANCH || 'main',
91
192
  };
193
+ if (variables && Object.keys(variables).length > 0) {
194
+ body.environment = { variables };
195
+ }
92
196
  return httpsJson('POST', '/builds', env.CODEMAGIC_API_TOKEN, body);
93
197
  }
94
198
 
@@ -109,7 +213,6 @@ async function validateCodemagicSetup(projectDir) {
109
213
  }
110
214
  if (!env.CODEMAGIC_API_TOKEN) issues.push('missing_token');
111
215
  if (!env.CODEMAGIC_APP_ID) issues.push('missing_app_id');
112
- if (!env.CODEMAGIC_WORKFLOW_ID) issues.push('missing_workflow');
113
216
  return { ok: issues.length === 0, issues, env };
114
217
  }
115
218
 
@@ -118,9 +221,15 @@ module.exports = {
118
221
  CODEMAGIC_API,
119
222
  URL_CODEMAGIC_APPS,
120
223
  URL_CODEMAGIC_API,
224
+ DEFAULT_IOS_WORKFLOW,
225
+ DEFAULT_ANDROID_WORKFLOW,
121
226
  codemagicReleaseDocPath,
122
227
  loadCodemagicEnv,
228
+ loadProjectEnv,
123
229
  writeCodemagicEnv,
230
+ listApps,
231
+ workflowIdFor,
232
+ resolveReleaseVars,
124
233
  triggerBuild,
125
234
  getBuildStatus,
126
235
  validateCodemagicSetup,
@@ -389,12 +389,18 @@ module.exports = {
389
389
  'cli.command.ios.help.before': 'How to publish to the App Store:\n 1) kasy ios configure → do this once (saves Apple credentials)\n 2) kasy ios release → run for each new version (build + upload)\n\nUse "build" to only generate the IPA, "clean" if a build fails.\n',
390
390
  'cli.command.codemagic.description': 'Build the app in the cloud (no Mac needed)',
391
391
  'cli.command.codemagic.configure.description': 'Configure Codemagic API credentials',
392
- 'cli.command.codemagic.release.description': 'Start a Codemagic iOS workflow build',
392
+ 'cli.command.codemagic.release.description': 'Start a cloud build (iOS, Android, or both)',
393
+ 'cli.command.codemagic.release.iosOption': 'Build only iOS',
394
+ 'cli.command.codemagic.release.androidOption': 'Build only Android',
395
+ 'cli.command.codemagic.release.platformPick': 'Which platform do you want to build?',
396
+ 'cli.command.codemagic.release.platform.both': 'iOS + Android',
397
+ 'cli.command.codemagic.release.platform.ios': 'iOS only',
398
+ 'cli.command.codemagic.release.platform.android': 'Android only',
393
399
  'cli.command.codemagic.status.description': 'Show Codemagic build status by ID',
394
400
  'cli.command.codemagic.picker.intro': 'Build in the cloud with Codemagic',
395
401
  'cli.command.codemagic.picker.message': 'What do you want to do?',
396
402
  'cli.command.codemagic.picker.statusHint': 'Asks for the build ID',
397
- 'cli.command.codemagic.help.before': 'How to build with Codemagic (no Mac needed):\n 1) kasy codemagic configure → do this once (saves API token)\n 2) kasy codemagic release → starts a cloud build for each version\n\nUse "status <buildId>" to check the progress of a running build.\n',
403
+ 'cli.command.codemagic.help.before': 'How to build with Codemagic (no Mac needed):\n 1) kasy codemagic configure → do this once (saves API token + app)\n 2) kasy codemagic release → cloud build for each version\n kasy codemagic release --ios (iOS only)\n kasy codemagic release --android (Android only)\n\nUse "status <buildId>" to check the progress of a running build.\n',
398
404
 
399
405
  'ios.configure.title': 'iOS App Store — setup',
400
406
  'ios.configure.bundleId': 'Bundle ID',
@@ -456,19 +462,24 @@ module.exports = {
456
462
  'codemagic.configure.title': 'Codemagic — setup',
457
463
  'codemagic.configure.doc': 'Guide',
458
464
  'codemagic.configure.checklist': 'Complete signing + App Store Connect in the Codemagic dashboard (see guide).',
459
- 'codemagic.configure.openingLinks': 'Opening Codemagic in your browser…',
465
+ 'codemagic.configure.openingToken': 'Opening Codemagic settings — copy your API token…',
460
466
  'codemagic.configure.q.token': 'Codemagic API token',
461
467
  'codemagic.configure.q.appId': 'Codemagic App ID',
462
- 'codemagic.configure.q.workflowId': 'Workflow ID (from codemagic.yaml)',
463
468
  'codemagic.configure.q.branch': 'Git branch to build',
464
469
  'codemagic.configure.q.required': 'Required',
470
+ 'codemagic.configure.validating': 'Checking token and loading your apps…',
471
+ 'codemagic.configure.validated': 'Token OK',
472
+ 'codemagic.configure.tokenInvalid': 'Could not validate the token',
473
+ 'codemagic.configure.noApps': 'No apps found on this account. Connect your repository in the Codemagic dashboard first.',
474
+ 'codemagic.configure.pickApp': 'Which app do you want to build?',
465
475
  'codemagic.configure.cancelled': 'Setup cancelled',
466
476
  'codemagic.configure.success': 'Codemagic credentials saved',
467
477
  'codemagic.configure.next': 'Next',
468
478
  'codemagic.release.title': 'Triggering Codemagic build…',
469
- 'codemagic.release.triggered': 'Build started',
470
- 'codemagic.release.spin': 'Starting build on Codemagic',
471
- 'codemagic.release.spinDone': 'Build queued on Codemagic',
479
+ 'codemagic.release.spinPlatform': 'Starting {platform} build on Codemagic…',
480
+ 'codemagic.release.triggeredPlatform': '{platform} build queued on Codemagic',
481
+ 'codemagic.release.noIosKey': 'No iOS production RevenueCat key (RC_IOS_PROD_KEY) in .env — subscriptions may not load in the build.',
482
+ 'codemagic.release.noAndroidKey': 'No Android production RevenueCat key (RC_ANDROID_PROD_KEY) in .env — subscriptions may not load in the build.',
472
483
  'codemagic.status.title': 'Build status',
473
484
  'codemagic.status.usage': 'Usage: kasy codemagic status <buildId>',
474
485
  'codemagic.status.spin': 'Fetching build status…',
@@ -391,12 +391,18 @@ module.exports = {
391
391
  'cli.command.ios.help.before': 'Cómo publicar en la App Store:\n 1) kasy ios configure → hazlo una vez (guarda las credenciales Apple)\n 2) kasy ios release → ejecuta en cada nueva versión (build + subida)\n\nUsa "build" si solo quieres generar el IPA, "clean" si un build falló.\n',
392
392
  'cli.command.codemagic.description': 'Compila la app en la nube (sin necesitar Mac)',
393
393
  'cli.command.codemagic.configure.description': 'Configurar credenciales API de Codemagic',
394
- 'cli.command.codemagic.release.description': 'Iniciar build del workflow iOS en Codemagic',
394
+ 'cli.command.codemagic.release.description': 'Iniciar build en la nube (iOS, Android o ambos)',
395
+ 'cli.command.codemagic.release.iosOption': 'Compilar solo iOS',
396
+ 'cli.command.codemagic.release.androidOption': 'Compilar solo Android',
397
+ 'cli.command.codemagic.release.platformPick': '¿Qué plataforma quieres compilar?',
398
+ 'cli.command.codemagic.release.platform.both': 'iOS + Android',
399
+ 'cli.command.codemagic.release.platform.ios': 'Solo iOS',
400
+ 'cli.command.codemagic.release.platform.android': 'Solo Android',
395
401
  'cli.command.codemagic.status.description': 'Estado del build Codemagic por ID',
396
402
  'cli.command.codemagic.picker.intro': 'Compilar en la nube con Codemagic',
397
403
  'cli.command.codemagic.picker.message': '¿Qué quieres hacer?',
398
404
  'cli.command.codemagic.picker.statusHint': 'Pregunta el ID del build',
399
- 'cli.command.codemagic.help.before': 'Cómo compilar con Codemagic (sin necesitar Mac):\n 1) kasy codemagic configure → hazlo una vez (guarda el token de la API)\n 2) kasy codemagic release → inicia un build en la nube por versión\n\nUsa "status <buildId>" para seguir el progreso de un build.\n',
405
+ 'cli.command.codemagic.help.before': 'Cómo compilar con Codemagic (sin necesitar Mac):\n 1) kasy codemagic configure → hazlo una vez (guarda el token de la API + app)\n 2) kasy codemagic release → build en la nube por versión\n kasy codemagic release --ios (solo iOS)\n kasy codemagic release --android (solo Android)\n\nUsa "status <buildId>" para seguir el progreso de un build.\n',
400
406
 
401
407
  'ios.configure.title': 'App Store iOS — configuración',
402
408
  'ios.configure.bundleId': 'Bundle ID',
@@ -458,19 +464,24 @@ module.exports = {
458
464
  'codemagic.configure.title': 'Codemagic — configuración',
459
465
  'codemagic.configure.doc': 'Guía',
460
466
  'codemagic.configure.checklist': 'Complete firma + App Store Connect en el panel Codemagic (vea la guía).',
461
- 'codemagic.configure.openingLinks': 'Abriendo Codemagic en el navegador…',
467
+ 'codemagic.configure.openingToken': 'Abriendo la configuración de Codemagic copia tu token de la API…',
462
468
  'codemagic.configure.q.token': 'Token API de Codemagic',
463
469
  'codemagic.configure.q.appId': 'App ID en Codemagic',
464
- 'codemagic.configure.q.workflowId': 'Workflow ID (de codemagic.yaml)',
465
470
  'codemagic.configure.q.branch': 'Rama Git para build',
466
471
  'codemagic.configure.q.required': 'Obligatorio',
472
+ 'codemagic.configure.validating': 'Verificando el token y cargando tus apps…',
473
+ 'codemagic.configure.validated': 'Token válido',
474
+ 'codemagic.configure.tokenInvalid': 'No se pudo validar el token',
475
+ 'codemagic.configure.noApps': 'No se encontraron apps en esta cuenta. Conecta tu repositorio en el panel de Codemagic primero.',
476
+ 'codemagic.configure.pickApp': '¿Qué app quieres compilar?',
467
477
  'codemagic.configure.cancelled': 'Configuración cancelada',
468
478
  'codemagic.configure.success': 'Credenciales Codemagic guardadas',
469
479
  'codemagic.configure.next': 'Siguiente paso',
470
480
  'codemagic.release.title': 'Disparando build en Codemagic…',
471
- 'codemagic.release.triggered': 'Build iniciado',
472
- 'codemagic.release.spin': 'Iniciando build en Codemagic',
473
- 'codemagic.release.spinDone': 'Build encolado en Codemagic',
481
+ 'codemagic.release.spinPlatform': 'Iniciando build de {platform} en Codemagic…',
482
+ 'codemagic.release.triggeredPlatform': 'Build de {platform} encolado en Codemagic',
483
+ 'codemagic.release.noIosKey': 'Sin clave de producción iOS de RevenueCat (RC_IOS_PROD_KEY) en .env — las suscripciones pueden no cargar en el build.',
484
+ 'codemagic.release.noAndroidKey': 'Sin clave de producción Android de RevenueCat (RC_ANDROID_PROD_KEY) en .env — las suscripciones pueden no cargar en el build.',
474
485
  'codemagic.status.title': 'Estado del build',
475
486
  'codemagic.status.usage': 'Uso: kasy codemagic status <buildId>',
476
487
  'codemagic.status.spin': 'Consultando estado del build…',
@@ -389,12 +389,18 @@ module.exports = {
389
389
  'cli.command.ios.help.before': 'Como publicar na App Store:\n 1) kasy ios configure → faça isso uma vez (salva as credenciais Apple)\n 2) kasy ios release → rode em cada nova versão (build + envio)\n\nUse "build" se quiser só gerar o IPA, "clean" se um build falhou.\n',
390
390
  'cli.command.codemagic.description': 'Compila o app na nuvem (sem precisar de Mac)',
391
391
  'cli.command.codemagic.configure.description': 'Configurar credenciais da API Codemagic',
392
- 'cli.command.codemagic.release.description': 'Iniciar build do workflow iOS no Codemagic',
392
+ 'cli.command.codemagic.release.description': 'Iniciar build na nuvem (iOS, Android ou os dois)',
393
+ 'cli.command.codemagic.release.iosOption': 'Compilar só o iOS',
394
+ 'cli.command.codemagic.release.androidOption': 'Compilar só o Android',
395
+ 'cli.command.codemagic.release.platformPick': 'Qual plataforma você quer compilar?',
396
+ 'cli.command.codemagic.release.platform.both': 'iOS + Android',
397
+ 'cli.command.codemagic.release.platform.ios': 'Só iOS',
398
+ 'cli.command.codemagic.release.platform.android': 'Só Android',
393
399
  'cli.command.codemagic.status.description': 'Status do build Codemagic por ID',
394
400
  'cli.command.codemagic.picker.intro': 'Compilar na nuvem com Codemagic',
395
401
  'cli.command.codemagic.picker.message': 'O que você quer fazer?',
396
402
  'cli.command.codemagic.picker.statusHint': 'Pergunta o ID do build',
397
- 'cli.command.codemagic.help.before': 'Como compilar com Codemagic (sem precisar de Mac):\n 1) kasy codemagic configure → faça isso uma vez (salva o token da API)\n 2) kasy codemagic release → inicia um build na nuvem a cada versão\n\nUse "status <buildId>" para acompanhar o progresso de um build.\n',
403
+ 'cli.command.codemagic.help.before': 'Como compilar com Codemagic (sem precisar de Mac):\n 1) kasy codemagic configure → faça isso uma vez (salva o token da API + app)\n 2) kasy codemagic release → build na nuvem a cada versão\n kasy codemagic release --ios (só iOS)\n kasy codemagic release --android (só Android)\n\nUse "status <buildId>" para acompanhar o progresso de um build.\n',
398
404
 
399
405
  'ios.configure.title': 'App Store iOS — configuração',
400
406
  'ios.configure.bundleId': 'Bundle ID',
@@ -456,19 +462,24 @@ module.exports = {
456
462
  'codemagic.configure.title': 'Codemagic — configuração',
457
463
  'codemagic.configure.doc': 'Guia',
458
464
  'codemagic.configure.checklist': 'Conclua assinatura + App Store Connect no painel Codemagic (veja o guia).',
459
- 'codemagic.configure.openingLinks': 'Abrindo Codemagic no navegador…',
465
+ 'codemagic.configure.openingToken': 'Abrindo as configurações do Codemagic copie o token da API…',
460
466
  'codemagic.configure.q.token': 'Token da API Codemagic',
461
467
  'codemagic.configure.q.appId': 'App ID no Codemagic',
462
- 'codemagic.configure.q.workflowId': 'Workflow ID (do codemagic.yaml)',
463
468
  'codemagic.configure.q.branch': 'Branch Git para build',
464
469
  'codemagic.configure.q.required': 'Obrigatório',
470
+ 'codemagic.configure.validating': 'Verificando o token e carregando seus apps…',
471
+ 'codemagic.configure.validated': 'Token válido',
472
+ 'codemagic.configure.tokenInvalid': 'Não foi possível validar o token',
473
+ 'codemagic.configure.noApps': 'Nenhum app encontrado nesta conta. Conecte seu repositório no painel do Codemagic primeiro.',
474
+ 'codemagic.configure.pickApp': 'Qual app você quer compilar?',
465
475
  'codemagic.configure.cancelled': 'Configuração cancelada',
466
476
  'codemagic.configure.success': 'Credenciais Codemagic salvas',
467
477
  'codemagic.configure.next': 'Próximo passo',
468
478
  'codemagic.release.title': 'Disparando build no Codemagic…',
469
- 'codemagic.release.triggered': 'Build iniciado',
470
- 'codemagic.release.spin': 'Iniciando build no Codemagic',
471
- 'codemagic.release.spinDone': 'Build enfileirado no Codemagic',
479
+ 'codemagic.release.spinPlatform': 'Iniciando build de {platform} no Codemagic…',
480
+ 'codemagic.release.triggeredPlatform': 'Build de {platform} enfileirado no Codemagic',
481
+ 'codemagic.release.noIosKey': 'Sem chave de produção iOS do RevenueCat (RC_IOS_PROD_KEY) no .env — as assinaturas podem não carregar no build.',
482
+ 'codemagic.release.noAndroidKey': 'Sem chave de produção Android do RevenueCat (RC_ANDROID_PROD_KEY) no .env — as assinaturas podem não carregar no build.',
472
483
  'codemagic.status.title': 'Status do build',
473
484
  'codemagic.status.usage': 'Uso: kasy codemagic status <buildId>',
474
485
  'codemagic.status.spin': 'Consultando status do build…',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.31.6",
3
+ "version": "1.31.8",
4
4
  "description": "CLI for scaffolding production-ready Flutter SaaS apps with Firebase, Supabase, or API REST backends.",
5
5
  "bin": {
6
6
  "kasy": "./bin/kasy.js"
@@ -0,0 +1,211 @@
1
+ # Codemagic CI/CD — shipped by the Kasy `ci` feature.
2
+ #
3
+ # iOS and Android run as SEPARATE workflows (ios-workflow / android-workflow),
4
+ # so you can build and publish each platform on its own. `kasy codemagic release`
5
+ # triggers them: `--ios`, `--android`, or both.
6
+ #
7
+ # App keys (RevenueCat, backend URL, Sentry, Mixpanel, App Store id) arrive as
8
+ # ENVIRONMENT VARIABLES, two ways:
9
+ # 1. `kasy codemagic release` injects them automatically from your local .env, or
10
+ # 2. you store them in the Codemagic UI as variable groups.
11
+ # The "Generate .env" step below rebuilds the app's .env asset from those vars,
12
+ # so the build always has the production RevenueCat keys (appl_/goog_) — never
13
+ # the test_ key. ENV=prod is also passed as a dart-define (compile-time const).
14
+ #
15
+ # What you still set up ONCE in the Codemagic dashboard (UI-only, by design):
16
+ # - Connect your Git repository (Add application)
17
+ # - iOS: App Store Connect integration (the Apple API key)
18
+ # - Android: upload the keystore + the Google Play service account JSON
19
+
20
+ workflows:
21
+ android-workflow:
22
+ name: Android Workflow
23
+ instance_type: mac_mini_m1
24
+ max_build_duration: 120
25
+ environment:
26
+ android_signing:
27
+ - keystore_reference # <-- Reference name of the keystore you upload in the Codemagic UI
28
+ groups:
29
+ - google_play # <-- Group holding GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS (and any app keys)
30
+ vars:
31
+ PACKAGE_NAME: com.aicrus.firebase.kit # <-- Your Android applicationId
32
+ GOOGLE_PLAY_TRACK: "internal" # <-- internal | alpha | beta | production
33
+ flutter: stable
34
+ triggering:
35
+ cancel_previous_builds: true
36
+ events:
37
+ - push
38
+ branch_patterns:
39
+ - pattern: '*'
40
+ include: false
41
+ source: false
42
+ - pattern: 'main'
43
+ include: true
44
+ source: true
45
+ scripts:
46
+ - name: Set up local.properties
47
+ script: |
48
+ echo "flutter.sdk=$HOME/programs/flutter" > "$CM_BUILD_DIR/android/local.properties"
49
+ - name: Get Flutter packages
50
+ script: |
51
+ flutter packages pub get
52
+ - name: Generate .env from build environment
53
+ script: |
54
+ # The app reads its config from a bundled .env asset at runtime. That
55
+ # file is gitignored (never in the repo), so recreate it here from the
56
+ # environment variables provided by `kasy codemagic release` or by a
57
+ # Codemagic variable group. In a release build the app uses the
58
+ # production RevenueCat key (appl_/goog_) from RC_*_PROD_KEY.
59
+ cat > "$CM_BUILD_DIR/.env" <<EOF
60
+ ENV=prod
61
+ BACKEND_URL=${BACKEND_URL:-}
62
+ AI_CHAT_ENDPOINT=${AI_CHAT_ENDPOINT:-}
63
+ RC_TEST_KEY=${RC_TEST_KEY:-}
64
+ RC_IOS_PROD_KEY=${RC_IOS_PROD_KEY:-}
65
+ RC_ANDROID_PROD_KEY=${RC_ANDROID_PROD_KEY:-}
66
+ MIXPANEL_TOKEN=${MIXPANEL_TOKEN:-}
67
+ SENTRY_DSN=${SENTRY_DSN:-}
68
+ APP_STORE_ID=${APP_STORE_ID:-}
69
+ EOF
70
+ - name: Unit tests
71
+ script: |
72
+ mkdir -p test-results
73
+ flutter test --machine > test-results/flutter.json
74
+ test_report: test-results/flutter.json
75
+ - name: Build AAB with Flutter
76
+ script: |
77
+ BUILD_NUMBER=$(($(google-play get-latest-build-number --package-name "$PACKAGE_NAME" --tracks="$GOOGLE_PLAY_TRACK") + 1))
78
+ flutter build appbundle --release \
79
+ --dart-define=ENV=prod \
80
+ --build-name=1.0.$BUILD_NUMBER \
81
+ --build-number=$BUILD_NUMBER
82
+ artifacts:
83
+ - build/**/outputs/**/*.aab
84
+ - build/**/outputs/**/mapping.txt
85
+ - flutter_drive.log
86
+ publishing:
87
+ email:
88
+ recipients:
89
+ - # <-- Put your email here or add others recipients
90
+ notify:
91
+ success: true
92
+ failure: true
93
+ google_play:
94
+ credentials: $GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS
95
+ track: $GOOGLE_PLAY_TRACK
96
+ submit_as_draft: true
97
+ ios-workflow:
98
+ name: iOS Workflow
99
+ instance_type: mac_mini_m1
100
+ max_build_duration: 120
101
+ integrations:
102
+ app_store_connect: codemagic # <-- Name of your App Store Connect integration (Codemagic UI)
103
+ environment:
104
+ ios_signing:
105
+ distribution_type: app_store
106
+ bundle_identifier: com.aicrus.firebase.kit # <-- Your iOS bundle identifier
107
+ # APP_ID (numeric App Store Connect id) and the app keys arrive as
108
+ # environment variables: `kasy codemagic release` sends them from your .env.
109
+ # No variable group is required for iOS (App Store auth uses the integration
110
+ # above). For dashboard/push-triggered builds, add the keys as environment
111
+ # variables in the Codemagic app settings and a `groups:` entry here.
112
+ flutter: stable
113
+ xcode: latest # <-- set to specific version e.g. 15.0 to avoid unexpected updates.
114
+ cocoapods: default
115
+ triggering:
116
+ cancel_previous_builds: true
117
+ events:
118
+ - push
119
+ branch_patterns:
120
+ - pattern: '*'
121
+ include: false
122
+ source: false
123
+ - pattern: 'main'
124
+ include: true
125
+ source: true
126
+ scripts:
127
+ - name: Set up code signing settings on Xcode project
128
+ script: |
129
+ xcode-project use-profiles
130
+ - name: Get Flutter packages
131
+ script: |
132
+ flutter packages pub get
133
+ - name: Install pods
134
+ script: |
135
+ find . -name "Podfile" -execdir pod install \;
136
+ - name: Validate Google Sign-In iOS URL scheme
137
+ script: |
138
+ GOOGLE_PLIST="ios/Runner/GoogleService-Info.plist"
139
+ INFO_PLIST="ios/Runner/Info.plist"
140
+ if [ -f "$GOOGLE_PLIST" ]; then
141
+ REVERSED_CLIENT_ID=$(/usr/libexec/PlistBuddy -c "Print :REVERSED_CLIENT_ID" "$GOOGLE_PLIST" 2>/dev/null || true)
142
+ if [ -z "$REVERSED_CLIENT_ID" ]; then
143
+ echo "REVERSED_CLIENT_ID not found in $GOOGLE_PLIST"
144
+ exit 1
145
+ fi
146
+ if ! /usr/libexec/PlistBuddy -c "Print :CFBundleURLTypes" "$INFO_PLIST" 2>/dev/null | grep -Fq "$REVERSED_CLIENT_ID"; then
147
+ echo "Google Sign-In iOS URL scheme mismatch."
148
+ echo "Expected CFBundleURLSchemes to include: $REVERSED_CLIENT_ID"
149
+ echo "Run flutterfire configure/project setup before building."
150
+ exit 1
151
+ fi
152
+ fi
153
+ - name: Generate .env from build environment
154
+ script: |
155
+ # See the Android workflow note above — recreate the bundled .env asset
156
+ # from environment variables so the release build gets the production
157
+ # RevenueCat key (appl_) instead of the test_ key.
158
+ cat > "$CM_BUILD_DIR/.env" <<EOF
159
+ ENV=prod
160
+ BACKEND_URL=${BACKEND_URL:-}
161
+ AI_CHAT_ENDPOINT=${AI_CHAT_ENDPOINT:-}
162
+ RC_TEST_KEY=${RC_TEST_KEY:-}
163
+ RC_IOS_PROD_KEY=${RC_IOS_PROD_KEY:-}
164
+ RC_ANDROID_PROD_KEY=${RC_ANDROID_PROD_KEY:-}
165
+ MIXPANEL_TOKEN=${MIXPANEL_TOKEN:-}
166
+ SENTRY_DSN=${SENTRY_DSN:-}
167
+ APP_STORE_ID=${APP_STORE_ID:-}
168
+ EOF
169
+ - name: Flutter analyze # <-- remove if you don't like flutter analyze
170
+ script: |
171
+ flutter analyze
172
+ - name: Flutter unit tests
173
+ script: |
174
+ flutter test
175
+ ignore_failure: false # You should never build an app that has failing tests.
176
+ - name: Flutter build ipa and automatic versioning
177
+ script: |
178
+ flutter build ipa --release \
179
+ --dart-define=ENV=prod \
180
+ --build-name=1.0.0 \
181
+ --build-number=$(($(app-store-connect get-latest-app-store-build-number "$APP_ID") + 1)) \
182
+ --export-options-plist=/Users/builder/export_options.plist
183
+ artifacts:
184
+ - build/ios/ipa/*.ipa
185
+ - /tmp/xcodebuild_logs/*.log
186
+ - flutter_drive.log
187
+ publishing:
188
+ # See the following link for details about email publishing - https://docs.codemagic.io/publishing-yaml/distribution/#email
189
+ email:
190
+ recipients:
191
+ - # <-- Put your email here or add others recipients
192
+ notify:
193
+ success: true
194
+ failure: true
195
+ app_store_connect:
196
+ # Use codemagic integration (easier)
197
+ auth: integration
198
+ # ====================================
199
+ ## Or push all keys manually here
200
+ ## ====================================
201
+ #api_key: $APP_STORE_CONNECT_PRIVATE_KEY
202
+ #key_id: $APP_STORE_CONNECT_KEY_IDENTIFIER
203
+ #issuer_id: $APP_STORE_CONNECT_ISSUER_ID
204
+ ## ====================================
205
+ submit_to_app_store: false # Set true to send to App Store review automatically
206
+ release_type: MANUAL
207
+ # Configuration related to TestFlight (optional)
208
+ # Note: This action is performed during post-processing.
209
+ submit_to_testflight: true
210
+ beta_groups: # Specify the names of beta tester groups that will get access to the build once it has passed beta review.
211
+ - kasy # <-- Put your beta group name here
@@ -1,43 +1,64 @@
1
- # Publish iOS with Codemagic (no Mac)
1
+ # Publish to the cloud with Codemagic (no Mac)
2
+
3
+ Publishes **iOS** and **Android** through the Codemagic cloud. Each platform has
4
+ its own workflow, so you can ship them separately or both together.
2
5
 
3
6
  ## Prerequisites
4
7
 
5
- - [Codemagic](https://codemagic.io) account
6
- - Git repository connected to Codemagic
7
- - Apple Developer account + app in App Store Connect
8
+ - A [Codemagic](https://codemagic.io) account
9
+ - A Git repository connected to Codemagic
10
+ - iOS: Apple Developer account + app in App Store Connect
11
+ - Android: app in Google Play Console + a signing keystore
8
12
 
9
- ## 1. Add CI to the project (if missing)
13
+ ## 1. Add CI to the project (if you don't have it yet)
10
14
 
11
15
  ```bash
12
16
  kasy add ci
13
17
  ```
14
18
 
15
- Creates `codemagic.yaml` at the project root.
19
+ This creates `codemagic.yaml` at the project root (`ios-workflow` and
20
+ `android-workflow`).
21
+
22
+ ## 2. Set up in the Codemagic dashboard (once)
23
+
24
+ These are secrets and can only be done in the dashboard — one time:
16
25
 
17
- ## 2. Configure in the Codemagic dashboard
26
+ 1. Open [codemagic.io/apps](https://codemagic.io/apps) and **connect your repository**.
27
+ 2. **iOS** — *Integrations → App Store Connect*: connect the Apple API key. Its
28
+ name goes in `integrations: app_store_connect:` in `codemagic.yaml`.
29
+ 3. **Android** — *Code signing identities → Android keystores*: upload the
30
+ keystore with **Reference name** `keystore_reference` (matching `codemagic.yaml`).
31
+ 4. **Android** — upload the **Google Play service account** JSON as the secret
32
+ variable `GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS` (group `google_play`).
18
33
 
19
- 1. Open [codemagic.io/apps](https://codemagic.io/apps) and add your repository.
20
- 2. **Code signing (iOS):** set up App Store distribution certificate and profile.
21
- 3. **Integrations App Store Connect:** link your Apple account.
22
- 4. **Environment variables:** add to your variable group (e.g. `appstore_credentials`):
23
- - `BACKEND_URL`, `SENTRY_DSN`, `RC_IOS_API_KEY`, `RC_ANDROID_API_KEY`, `MIXPANEL_TOKEN`
24
- 5. In `codemagic.yaml`, set `APP_ID` (numeric App Store Connect app ID from the app URL).
34
+ > The **app keys** (RevenueCat, backend, etc.) do **not** need to be filled in
35
+ > the dashboard: `kasy codemagic release` sends them automatically from your
36
+ > `.env`. If you'd rather trigger from the dashboard/push, add them to the
37
+ > variable groups instead.
25
38
 
26
- ## 3. Configure from the terminal
39
+ ## 3. Set up in the terminal (once)
27
40
 
28
41
  ```bash
29
42
  kasy codemagic configure
30
43
  ```
31
44
 
32
- Enter **API token** (Settings → Codemagic API), **App ID**, and **Workflow ID** (default `ios-workflow`).
45
+ The wizard opens the **API token** page (Settings → Codemagic API), validates the
46
+ token and **lists your apps** to pick from — no IDs to type. It saves everything
47
+ to `.kasy/codemagic.env` (gitignored).
33
48
 
34
49
  ## 4. Trigger a build
35
50
 
36
51
  ```bash
37
- kasy codemagic release
52
+ kasy codemagic release # iOS + Android
53
+ kasy codemagic release --ios # iOS only
54
+ kasy codemagic release --android # Android only
38
55
  ```
39
56
 
40
- The cloud build runs and may upload to TestFlight per `codemagic.yaml`.
57
+ The command reads your `.env` and carries the production keys (including the
58
+ RevenueCat production key, `appl_`/`goog_`) with the trigger. A step in
59
+ `codemagic.yaml` recreates the `.env` in the cloud before building, so the build
60
+ ships with the right keys. Per `codemagic.yaml`, iOS goes to TestFlight and
61
+ Android to the configured track (`internal` by default).
41
62
 
42
63
  ## Build status
43
64
 
@@ -45,6 +66,12 @@ The cloud build runs and may upload to TestFlight per `codemagic.yaml`.
45
66
  kasy codemagic status <buildId>
46
67
  ```
47
68
 
69
+ ## Can I use the Mac and Codemagic at the same time?
70
+
71
+ Yes. They are two paths to the same store; they don't conflict. The build number
72
+ is computed by the cloud (latest in the store + 1), so it rarely collides. For
73
+ day-to-day, pick one main path.
74
+
48
75
  ## Local Mac
49
76
 
50
- If you have a Mac: [ios-release.md](./ios-release.md) and `kasy ios release`.
77
+ If you have a Mac: see [ios-release.md](./ios-release.md) and `kasy ios release`.
@@ -1,10 +1,14 @@
1
- # Publicar iOS con Codemagic (sin Mac)
1
+ # Publicar en la nube con Codemagic (sin Mac)
2
+
3
+ Publica **iOS** y **Android** por la nube de Codemagic. Cada plataforma tiene su
4
+ propio workflow, así que puedes enviarlas por separado o las dos juntas.
2
5
 
3
6
  ## Requisitos
4
7
 
5
8
  - Cuenta [Codemagic](https://codemagic.io)
6
9
  - Repositorio Git conectado a Codemagic
7
- - Cuenta Apple Developer + app en App Store Connect
10
+ - iOS: cuenta Apple Developer + app en App Store Connect
11
+ - Android: app en Google Play Console + un keystore de firma
8
12
 
9
13
  ## 1. Agregar CI al proyecto (si falta)
10
14
 
@@ -12,32 +16,48 @@
12
16
  kasy add ci
13
17
  ```
14
18
 
15
- Crea `codemagic.yaml` en la raíz del proyecto.
19
+ Crea `codemagic.yaml` en la raíz del proyecto (workflows `ios-workflow` y
20
+ `android-workflow`).
21
+
22
+ ## 2. Configurar en el panel Codemagic (una vez)
23
+
24
+ Estos elementos son secretos y solo se hacen en el panel — una vez:
16
25
 
17
- ## 2. Configurar en el panel Codemagic
26
+ 1. Abre [codemagic.io/apps](https://codemagic.io/apps) y **conecta el repositorio**.
27
+ 2. **iOS** — *Integrations → App Store Connect*: conecta la clave de Apple. Su
28
+ nombre va en `integrations: app_store_connect:` en `codemagic.yaml`.
29
+ 3. **Android** — *Code signing identities → Android keystores*: sube el keystore
30
+ con el **Reference name** `keystore_reference` (igual que en `codemagic.yaml`).
31
+ 4. **Android** — sube el JSON de la **service account de Google Play** como
32
+ variable secreta `GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS` (grupo `google_play`).
18
33
 
19
- 1. Abre [codemagic.io/apps](https://codemagic.io/apps) y agrega el repositorio.
20
- 2. **Code signing (iOS):** certificado y perfil de distribución App Store.
21
- 3. **Integrations App Store Connect:** vincula la cuenta Apple.
22
- 4. **Environment variables:** en el grupo del app (ej. `appstore_credentials`):
23
- - `BACKEND_URL`, `SENTRY_DSN`, `RC_IOS_API_KEY`, `RC_ANDROID_API_KEY`, `MIXPANEL_TOKEN`
24
- 5. En `codemagic.yaml`, completa `APP_ID` (ID numérico del app en App Store Connect).
34
+ > Las **claves del app** (RevenueCat, backend, etc.) **no** hace falta cargarlas
35
+ > en el panel: `kasy codemagic release` las envía automáticamente desde tu `.env`.
36
+ > Si prefieres disparar desde el panel/push, agrégalas a los grupos de variables.
25
37
 
26
- ## 3. Configurar en la terminal
38
+ ## 3. Configurar en la terminal (una vez)
27
39
 
28
40
  ```bash
29
41
  kasy codemagic configure
30
42
  ```
31
43
 
32
- Indica **API token** (Settings → Codemagic API), **App ID** y **Workflow ID** (`ios-workflow` por defecto).
44
+ El asistente abre la página del **API token** (Settings → Codemagic API), valida
45
+ el token y **lista tus apps** para elegir — sin escribir IDs. Guarda todo en
46
+ `.kasy/codemagic.env` (no versionado).
33
47
 
34
48
  ## 4. Disparar build
35
49
 
36
50
  ```bash
37
- kasy codemagic release
51
+ kasy codemagic release # iOS + Android
52
+ kasy codemagic release --ios # solo iOS
53
+ kasy codemagic release --android # solo Android
38
54
  ```
39
55
 
40
- El build en la nube puede subir a TestFlight según `codemagic.yaml`.
56
+ El comando lee tu `.env` y lleva las claves de producción (incluida la clave de
57
+ producción de RevenueCat, `appl_`/`goog_`) junto al disparo. Un paso del
58
+ `codemagic.yaml` recrea el `.env` en la nube antes de compilar, así el build sale
59
+ con las claves correctas. Según `codemagic.yaml`, iOS va a TestFlight y Android al
60
+ track configurado (`internal` por defecto).
41
61
 
42
62
  ## Estado del build
43
63
 
@@ -45,6 +65,12 @@ El build en la nube puede subir a TestFlight según `codemagic.yaml`.
45
65
  kasy codemagic status <buildId>
46
66
  ```
47
67
 
68
+ ## ¿Puedo usar el Mac y Codemagic a la vez?
69
+
70
+ Sí. Son dos caminos hacia la misma tienda; no entran en conflicto. El número de
71
+ build lo calcula la nube (último en la tienda + 1), por lo que rara vez choca.
72
+ Para el día a día, elige un camino principal.
73
+
48
74
  ## Mac local
49
75
 
50
76
  Si tienes Mac: [ios-release.md](./ios-release.md) y `kasy ios release`.
@@ -1,10 +1,14 @@
1
- # Publicar iOS com Codemagic (sem Mac)
1
+ # Publicar na nuvem com Codemagic (sem Mac)
2
+
3
+ Publica **iOS** e **Android** pela nuvem do Codemagic. Cada plataforma tem seu
4
+ próprio workflow, então você envia separado ou os dois juntos.
2
5
 
3
6
  ## Pré-requisitos
4
7
 
5
8
  - Conta [Codemagic](https://codemagic.io)
6
9
  - Repositório Git conectado ao Codemagic
7
- - Conta Apple Developer + app no App Store Connect
10
+ - iOS: conta Apple Developer + app no App Store Connect
11
+ - Android: app no Google Play Console + keystore de assinatura
8
12
 
9
13
  ## 1. Adicionar CI ao projeto (se ainda não tiver)
10
14
 
@@ -12,32 +16,49 @@
12
16
  kasy add ci
13
17
  ```
14
18
 
15
- Isso cria `codemagic.yaml` na raiz do projeto.
19
+ Isso cria o `codemagic.yaml` na raiz do projeto (workflows `ios-workflow` e
20
+ `android-workflow`).
21
+
22
+ ## 2. Configurar no painel Codemagic (uma vez)
23
+
24
+ Estes itens são secretos e só podem ser feitos no painel — uma vez:
16
25
 
17
- ## 2. Configurar no painel Codemagic
26
+ 1. Abra [codemagic.io/apps](https://codemagic.io/apps) e **conecte o repositório**.
27
+ 2. **iOS** — *Integrations → App Store Connect*: conecte a chave da Apple. No
28
+ `codemagic.yaml`, o nome dela vai em `integrations: app_store_connect:`.
29
+ 3. **Android** — *Code signing identities → Android keystores*: suba o keystore
30
+ com o **Reference name** `keystore_reference` (o mesmo usado no `codemagic.yaml`).
31
+ 4. **Android** — suba o JSON da **service account do Google Play** como variável
32
+ secreta `GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS` (grupo `google_play`).
18
33
 
19
- 1. Abra [codemagic.io/apps](https://codemagic.io/apps) e adicione o repositório.
20
- 2. **Code signing (iOS):** configure certificado e perfil de distribuição App Store.
21
- 3. **Integrations App Store Connect:** conecte a conta Apple (recomendado).
22
- 4. **Environment variables:** adicione no grupo do app (ex.: `appstore_credentials`):
23
- - `BACKEND_URL`, `SENTRY_DSN`, `RC_IOS_API_KEY`, `RC_ANDROID_API_KEY`, `MIXPANEL_TOKEN`
24
- 5. No `codemagic.yaml`, preencha `APP_ID` (ID numérico do app no App Store Connect — aparece na URL do app).
34
+ > As **chaves do app** (RevenueCat, backend, etc.) **não** precisam ser
35
+ > preenchidas no painel: o `kasy codemagic release` as envia automaticamente a
36
+ > partir do seu `.env`. Se preferir disparar pelo painel/push, aí sim cadastre-as
37
+ > nos grupos de variáveis.
25
38
 
26
- ## 3. Configurar no terminal
39
+ ## 3. Configurar no terminal (uma vez)
27
40
 
28
41
  ```bash
29
42
  kasy codemagic configure
30
43
  ```
31
44
 
32
- Informe o **API token** (Settings → Codemagic API), **App ID** e **Workflow ID** (`ios-workflow` por padrão).
45
+ O assistente abre a página do **token da API** (Settings → Codemagic API), valida
46
+ o token e **lista os seus apps** para você escolher — sem digitar IDs. Salva tudo
47
+ em `.kasy/codemagic.env` (não versionado).
33
48
 
34
49
  ## 4. Disparar build
35
50
 
36
51
  ```bash
37
- kasy codemagic release
52
+ kasy codemagic release # iOS + Android
53
+ kasy codemagic release --ios # só iOS
54
+ kasy codemagic release --android # só Android
38
55
  ```
39
56
 
40
- O build roda na nuvem e, conforme o `codemagic.yaml`, pode enviar para TestFlight automaticamente.
57
+ O comando o seu `.env` e leva as chaves de produção (incluindo a chave de
58
+ produção do RevenueCat, `appl_`/`goog_`) junto no disparo. Um passo do
59
+ `codemagic.yaml` recria o `.env` na nuvem antes de compilar, então o build sai
60
+ com as chaves certas. Conforme o `codemagic.yaml`, o iOS vai para o TestFlight e
61
+ o Android para o track configurado (`internal` por padrão).
41
62
 
42
63
  ## Status do build
43
64
 
@@ -45,6 +66,12 @@ O build roda na nuvem e, conforme o `codemagic.yaml`, pode enviar para TestFligh
45
66
  kasy codemagic status <buildId>
46
67
  ```
47
68
 
69
+ ## Posso usar o Mac e o Codemagic ao mesmo tempo?
70
+
71
+ Pode. São dois caminhos para a mesma loja, não conflitam. O número do build é
72
+ calculado pela própria nuvem (último na loja + 1), então dificilmente colide.
73
+ No dia a dia, escolha um caminho principal.
74
+
48
75
  ## Mac local
49
76
 
50
- Se tiver Mac: [ios-release.md](./ios-release.md) e `kasy ios release`.
77
+ Se tiver Mac: veja [ios-release.md](./ios-release.md) e `kasy ios release`.
@@ -79,6 +79,13 @@ dependencies:
79
79
  open_filex: ^4.7.0
80
80
  package_info_plus: ^8.3.0
81
81
  path_provider: ^2.1.5
82
+ # Pin the Apple impl of path_provider below 2.6.0/2.5.0. Those versions pull in
83
+ # objective_c via "native assets", whose build hook is unquoted and breaks every
84
+ # build on Windows when the username has a space (C:\Users\John Silva) — even a
85
+ # Chrome/web run, where the Apple-only hook is dead weight. 2.5.1 is the last
86
+ # release without objective_c (added in 2.5.0, reverted in 2.5.1, re-added in
87
+ # 2.6.0). Drop this pin once the upstream web-target regression is fixed.
88
+ path_provider_foundation: 2.5.1
82
89
  permission_handler: ^12.0.1
83
90
  provider: ^6.1.0
84
91
  pub_semver: ^2.2.0