kasy-cli 1.5.3 → 1.6.0
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 +5 -2
- package/lib/commands/add.js +101 -100
- package/lib/commands/check.js +55 -38
- package/lib/commands/codemagic.js +61 -58
- package/lib/commands/deploy.js +49 -45
- package/lib/commands/docs.js +19 -18
- package/lib/commands/doctor.js +46 -44
- package/lib/commands/features.js +20 -17
- package/lib/commands/ios.js +69 -69
- package/lib/commands/new.js +529 -771
- package/lib/commands/notifications.js +59 -59
- package/lib/commands/remove.js +28 -27
- package/lib/commands/run.js +3 -1
- package/lib/commands/update.js +104 -96
- package/lib/commands/validate.js +24 -19
- package/lib/utils/apple-release.js +23 -11
- package/lib/utils/brand.js +72 -0
- package/lib/utils/checks.js +20 -9
- package/lib/utils/prompts.js +82 -142
- package/lib/utils/ui.js +92 -0
- package/lib/utils/updates.js +9 -8
- package/package.json +2 -1
package/lib/commands/update.js
CHANGED
|
@@ -6,8 +6,8 @@ const path = require('node:path');
|
|
|
6
6
|
const fs = require('fs-extra');
|
|
7
7
|
const pkg = require('../../package.json');
|
|
8
8
|
const kleur = require('kleur');
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const ui = require('../utils/ui');
|
|
10
|
+
const { printCompactHeader } = require('../utils/brand');
|
|
11
11
|
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
12
12
|
const {
|
|
13
13
|
AVAILABLE_FEATURES,
|
|
@@ -22,7 +22,6 @@ const { buildTokens } = require('../scaffold/backends/firebase/tokens');
|
|
|
22
22
|
const { localizeReleaseDocs } = require('../scaffold/shared/generator-utils');
|
|
23
23
|
|
|
24
24
|
const execAsync = promisify(exec);
|
|
25
|
-
const ora = oraPackage.default || oraPackage;
|
|
26
25
|
|
|
27
26
|
const CHANGELOG_PATH = path.join(__dirname, '..', 'scaffold', 'CHANGELOG.json');
|
|
28
27
|
|
|
@@ -146,6 +145,7 @@ async function getActiveModules(kitSetup, projectDir) {
|
|
|
146
145
|
async function runUpdate(module, options = {}) {
|
|
147
146
|
const t = createTranslator(options.language || detectDefaultLanguage());
|
|
148
147
|
const projectDir = path.resolve(options.directory || '.');
|
|
148
|
+
const cancel = () => { ui.cancel(t('update.cancelled')); process.exit(0); };
|
|
149
149
|
|
|
150
150
|
// 1. Must be inside a kasy project
|
|
151
151
|
const kitSetupPath = path.join(projectDir, 'kit_setup.json');
|
|
@@ -170,93 +170,98 @@ async function runUpdate(module, options = {}) {
|
|
|
170
170
|
if (module) {
|
|
171
171
|
const normalizedTarget = normalizeUpdateTarget(module);
|
|
172
172
|
if (normalizedTarget === COMPONENTS_UPDATE_TARGET) {
|
|
173
|
+
printCompactHeader(t);
|
|
174
|
+
ui.intro(t('update.applyingComponents'));
|
|
173
175
|
if (!options.yes) {
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
initial: false,
|
|
181
|
-
},
|
|
182
|
-
{ onCancel: () => { throw new Error(t('update.cancelled')); } }
|
|
183
|
-
);
|
|
176
|
+
ui.log.warn(t('update.warn.commitComponents'));
|
|
177
|
+
const confirmed = await ui.confirm({
|
|
178
|
+
message: t('update.confirmComponents'),
|
|
179
|
+
initialValue: false,
|
|
180
|
+
onCancel: cancel,
|
|
181
|
+
});
|
|
184
182
|
if (!confirmed) {
|
|
185
|
-
|
|
183
|
+
ui.outro(t('update.cancelled'));
|
|
186
184
|
return;
|
|
187
185
|
}
|
|
188
186
|
}
|
|
189
187
|
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
const spinner = ui.spinner();
|
|
189
|
+
spinner.start(t('update.applyingComponents'));
|
|
192
190
|
try {
|
|
193
191
|
const filesApplied = await applyBaseComponents(projectDir);
|
|
194
192
|
if (filesApplied === 0) {
|
|
195
|
-
spinner.
|
|
193
|
+
spinner.stop(`⚠ ${t('update.noComponentFiles')}`);
|
|
194
|
+
ui.outro(t('update.cancelled'));
|
|
196
195
|
return;
|
|
197
196
|
}
|
|
198
|
-
spinner.
|
|
197
|
+
spinner.stop(t('update.appliedComponents', { count: filesApplied }));
|
|
199
198
|
} catch (err) {
|
|
200
|
-
spinner.
|
|
199
|
+
spinner.error(t('update.applyComponentsFailed'));
|
|
201
200
|
throw err;
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
{
|
|
205
|
-
const spinnerPubGet =
|
|
204
|
+
const spinnerPubGet = ui.spinner();
|
|
205
|
+
spinnerPubGet.start(t('update.pubGet'));
|
|
206
206
|
try {
|
|
207
207
|
await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
|
|
208
|
-
spinnerPubGet.
|
|
208
|
+
spinnerPubGet.stop(t('update.pubGetDone'));
|
|
209
209
|
} catch {
|
|
210
|
-
spinnerPubGet.
|
|
210
|
+
spinnerPubGet.stop(`⚠ ${t('update.pubGetFailed')}`);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
kitSetup.cliVersion = currentVersion;
|
|
215
215
|
await fs.outputFile(kitSetupPath, JSON.stringify(kitSetup, null, 2) + '\n', 'utf8');
|
|
216
|
-
|
|
216
|
+
ui.outro(t('update.componentsSuccess'));
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
if (normalizedTarget === CORE_UPDATE_TARGET) {
|
|
221
|
+
printCompactHeader(t);
|
|
222
|
+
ui.intro(t('update.applyingCore'));
|
|
221
223
|
if (!options.yes) {
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
ui.log.warn(t('update.warn.commitComponents'));
|
|
225
|
+
const confirmed = await ui.confirm({
|
|
226
|
+
message: t('update.confirmCore'),
|
|
227
|
+
initialValue: false,
|
|
228
|
+
onCancel: cancel,
|
|
229
|
+
});
|
|
227
230
|
if (!confirmed) {
|
|
228
|
-
|
|
231
|
+
ui.outro(t('update.cancelled'));
|
|
229
232
|
return;
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
|
|
234
|
-
|
|
236
|
+
const spinner = ui.spinner();
|
|
237
|
+
spinner.start(t('update.applyingCore'));
|
|
235
238
|
try {
|
|
236
239
|
const filesApplied = await applyCoreFiles(projectDir);
|
|
237
240
|
if (filesApplied === 0) {
|
|
238
|
-
spinner.
|
|
241
|
+
spinner.stop(`⚠ ${t('update.noComponentFiles')}`);
|
|
242
|
+
ui.outro(t('update.cancelled'));
|
|
239
243
|
return;
|
|
240
244
|
}
|
|
241
|
-
spinner.
|
|
245
|
+
spinner.stop(t('update.appliedCore', { count: filesApplied }));
|
|
242
246
|
} catch (err) {
|
|
243
|
-
spinner.
|
|
247
|
+
spinner.error(t('update.applyComponentsFailed'));
|
|
244
248
|
throw err;
|
|
245
249
|
}
|
|
246
250
|
|
|
247
251
|
{
|
|
248
|
-
const spinnerPubGet =
|
|
252
|
+
const spinnerPubGet = ui.spinner();
|
|
253
|
+
spinnerPubGet.start(t('update.pubGet'));
|
|
249
254
|
try {
|
|
250
255
|
await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
|
|
251
|
-
spinnerPubGet.
|
|
256
|
+
spinnerPubGet.stop(t('update.pubGetDone'));
|
|
252
257
|
} catch {
|
|
253
|
-
spinnerPubGet.
|
|
258
|
+
spinnerPubGet.stop(`⚠ ${t('update.pubGetFailed')}`);
|
|
254
259
|
}
|
|
255
260
|
}
|
|
256
261
|
|
|
257
262
|
kitSetup.cliVersion = currentVersion;
|
|
258
263
|
await fs.outputFile(kitSetupPath, JSON.stringify(kitSetup, null, 2) + '\n', 'utf8');
|
|
259
|
-
|
|
264
|
+
ui.outro(t('update.coreSuccess'));
|
|
260
265
|
return;
|
|
261
266
|
}
|
|
262
267
|
|
|
@@ -265,24 +270,22 @@ async function runUpdate(module, options = {}) {
|
|
|
265
270
|
if (!(await fs.pathExists(patchDir))) {
|
|
266
271
|
throw new Error(t('update.error.noIosReleasePatch'));
|
|
267
272
|
}
|
|
273
|
+
printCompactHeader(t);
|
|
274
|
+
ui.intro(t('update.applying', { module: IOS_RELEASE_UPDATE_TARGET }));
|
|
268
275
|
if (!options.yes) {
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
initial: false,
|
|
276
|
-
},
|
|
277
|
-
{ onCancel: () => { throw new Error(t('update.cancelled')); } }
|
|
278
|
-
);
|
|
276
|
+
ui.log.warn(t('update.warn.commit', { module: IOS_RELEASE_UPDATE_TARGET }));
|
|
277
|
+
const confirmed = await ui.confirm({
|
|
278
|
+
message: t('update.confirm', { module: IOS_RELEASE_UPDATE_TARGET }),
|
|
279
|
+
initialValue: false,
|
|
280
|
+
onCancel: cancel,
|
|
281
|
+
});
|
|
279
282
|
if (!confirmed) {
|
|
280
|
-
|
|
283
|
+
ui.outro(t('update.cancelled'));
|
|
281
284
|
return;
|
|
282
285
|
}
|
|
283
286
|
}
|
|
284
|
-
|
|
285
|
-
|
|
287
|
+
const spinner = ui.spinner();
|
|
288
|
+
spinner.start(t('update.applying', { module: IOS_RELEASE_UPDATE_TARGET }));
|
|
286
289
|
try {
|
|
287
290
|
const { tokens, pathReplacements } = buildTokens({
|
|
288
291
|
appName: kitSetup.appName,
|
|
@@ -290,14 +293,14 @@ async function runUpdate(module, options = {}) {
|
|
|
290
293
|
});
|
|
291
294
|
await applyPatch(patchDir, projectDir, tokens, pathReplacements);
|
|
292
295
|
await localizeReleaseDocs(projectDir, options.language || detectDefaultLanguage());
|
|
293
|
-
spinner.
|
|
296
|
+
spinner.stop(t('update.iosRelease.success'));
|
|
294
297
|
} catch (err) {
|
|
295
|
-
spinner.
|
|
298
|
+
spinner.error(t('update.applyFailed', { module: IOS_RELEASE_UPDATE_TARGET }));
|
|
296
299
|
throw err;
|
|
297
300
|
}
|
|
298
301
|
kitSetup.cliVersion = currentVersion;
|
|
299
302
|
await fs.outputFile(kitSetupPath, JSON.stringify(kitSetup, null, 2) + '\n', 'utf8');
|
|
300
|
-
|
|
303
|
+
ui.outro(t('update.iosRelease.success'));
|
|
301
304
|
return;
|
|
302
305
|
}
|
|
303
306
|
|
|
@@ -311,74 +314,76 @@ async function runUpdate(module, options = {}) {
|
|
|
311
314
|
);
|
|
312
315
|
}
|
|
313
316
|
if (!activeModules.includes(normalized)) {
|
|
314
|
-
|
|
317
|
+
printCompactHeader(t);
|
|
318
|
+
ui.log.warn(t('update.error.notActive', { module: normalized }));
|
|
315
319
|
return;
|
|
316
320
|
}
|
|
317
321
|
|
|
318
322
|
const patchDir = path.join(FEATURES_PATCH_DIR, normalized);
|
|
319
323
|
if (!(await fs.pathExists(patchDir))) {
|
|
320
|
-
|
|
324
|
+
printCompactHeader(t);
|
|
325
|
+
ui.log.message(kleur.dim(t('update.noPatch', { module: normalized })));
|
|
321
326
|
return;
|
|
322
327
|
}
|
|
323
328
|
|
|
329
|
+
printCompactHeader(t);
|
|
330
|
+
ui.intro(t('update.applying', { module: normalized }));
|
|
331
|
+
|
|
324
332
|
// Warn and confirm
|
|
325
333
|
if (!options.yes) {
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
{
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
initial: false,
|
|
333
|
-
},
|
|
334
|
-
{ onCancel: () => { throw new Error(t('update.cancelled')); } }
|
|
335
|
-
);
|
|
334
|
+
ui.log.warn(t('update.warn.commit', { module: normalized }));
|
|
335
|
+
const confirmed = await ui.confirm({
|
|
336
|
+
message: t('update.confirm', { module: normalized }),
|
|
337
|
+
initialValue: false,
|
|
338
|
+
onCancel: cancel,
|
|
339
|
+
});
|
|
336
340
|
if (!confirmed) {
|
|
337
|
-
|
|
341
|
+
ui.outro(t('update.cancelled'));
|
|
338
342
|
return;
|
|
339
343
|
}
|
|
340
344
|
}
|
|
341
345
|
|
|
342
|
-
console.log('');
|
|
343
|
-
|
|
344
346
|
// Re-apply patch
|
|
345
347
|
{
|
|
346
|
-
const spinner =
|
|
348
|
+
const spinner = ui.spinner();
|
|
349
|
+
spinner.start(t('update.applying', { module: normalized }));
|
|
347
350
|
try {
|
|
348
351
|
const { tokens, pathReplacements } = buildTokens({
|
|
349
352
|
appName: kitSetup.appName,
|
|
350
353
|
bundleId: kitSetup.bundleId,
|
|
351
354
|
});
|
|
352
355
|
await applyPatch(patchDir, projectDir, tokens, pathReplacements);
|
|
353
|
-
spinner.
|
|
356
|
+
spinner.stop(t('update.applied', { module: normalized }));
|
|
354
357
|
} catch (err) {
|
|
355
|
-
spinner.
|
|
358
|
+
spinner.error(t('update.applyFailed', { module: normalized }));
|
|
356
359
|
throw err;
|
|
357
360
|
}
|
|
358
361
|
}
|
|
359
362
|
|
|
360
363
|
// flutter pub get
|
|
361
364
|
{
|
|
362
|
-
const spinner =
|
|
365
|
+
const spinner = ui.spinner();
|
|
366
|
+
spinner.start(t('update.pubGet'));
|
|
363
367
|
try {
|
|
364
368
|
await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
|
|
365
|
-
spinner.
|
|
369
|
+
spinner.stop(t('update.pubGetDone'));
|
|
366
370
|
} catch {
|
|
367
|
-
spinner.
|
|
371
|
+
spinner.stop(`⚠ ${t('update.pubGetFailed')}`);
|
|
368
372
|
}
|
|
369
373
|
}
|
|
370
374
|
|
|
371
375
|
// build_runner (only for modules that generate code)
|
|
372
376
|
if (NEEDS_BUILD_RUNNER.includes(normalized)) {
|
|
373
|
-
const spinner =
|
|
377
|
+
const spinner = ui.spinner();
|
|
378
|
+
spinner.start(t('update.buildRunner'));
|
|
374
379
|
try {
|
|
375
380
|
await execAsync(
|
|
376
381
|
'dart run build_runner build --delete-conflicting-outputs',
|
|
377
382
|
{ cwd: projectDir, timeout: 600_000 }
|
|
378
383
|
);
|
|
379
|
-
spinner.
|
|
384
|
+
spinner.stop(t('update.buildRunnerDone'));
|
|
380
385
|
} catch {
|
|
381
|
-
spinner.
|
|
386
|
+
spinner.stop(`⚠ ${t('update.buildRunnerFailed')}`);
|
|
382
387
|
}
|
|
383
388
|
}
|
|
384
389
|
|
|
@@ -386,7 +391,7 @@ async function runUpdate(module, options = {}) {
|
|
|
386
391
|
kitSetup.cliVersion = currentVersion;
|
|
387
392
|
await fs.outputFile(kitSetupPath, JSON.stringify(kitSetup, null, 2) + '\n', 'utf8');
|
|
388
393
|
|
|
389
|
-
|
|
394
|
+
ui.outro(t('update.success', { module: normalized }));
|
|
390
395
|
return;
|
|
391
396
|
}
|
|
392
397
|
|
|
@@ -404,25 +409,26 @@ async function runUpdate(module, options = {}) {
|
|
|
404
409
|
}
|
|
405
410
|
|
|
406
411
|
if (alreadyUpToDate && Object.keys(changes).length === 0) {
|
|
407
|
-
|
|
412
|
+
printCompactHeader(t);
|
|
413
|
+
ui.outro(t('update.alreadyUpToDate', { version: currentVersion }));
|
|
408
414
|
return;
|
|
409
415
|
}
|
|
410
416
|
|
|
411
|
-
|
|
417
|
+
printCompactHeader(t);
|
|
412
418
|
const fromLabel = projectVersion || '?';
|
|
413
|
-
|
|
419
|
+
ui.intro(t('update.status', { from: fromLabel, to: currentVersion }));
|
|
414
420
|
|
|
415
421
|
// Modules/components with actual changelog entries — show what improved + the command
|
|
416
422
|
const modulesWithChanges = Object.keys(changes);
|
|
417
423
|
if (modulesWithChanges.length > 0) {
|
|
418
|
-
|
|
424
|
+
const lines = [kleur.bold(t('update.changesTitle'))];
|
|
419
425
|
for (const mod of modulesWithChanges) {
|
|
420
426
|
for (const { description } of changes[mod]) {
|
|
421
|
-
|
|
422
|
-
|
|
427
|
+
lines.push(`${kleur.cyan('✦')} ${kleur.bold(mod)} ${kleur.dim('→')} ${kleur.cyan(`kasy update ${mod}`)}`);
|
|
428
|
+
lines.push(` ${kleur.dim(description)}`);
|
|
423
429
|
}
|
|
424
430
|
}
|
|
425
|
-
|
|
431
|
+
ui.log.message(lines.join('\n'));
|
|
426
432
|
}
|
|
427
433
|
|
|
428
434
|
// Modules without changelog entries that can still be re-applied (advanced / recovery)
|
|
@@ -431,27 +437,29 @@ async function runUpdate(module, options = {}) {
|
|
|
431
437
|
|
|
432
438
|
if (modulesWithChanges.length === 0 && !hasComponentChanges) {
|
|
433
439
|
// Nothing new — show everything available
|
|
440
|
+
const lines = [];
|
|
434
441
|
if (patchableModules.length > 0) {
|
|
435
|
-
|
|
442
|
+
lines.push(t('update.howToUpdate'));
|
|
436
443
|
for (const m of patchableModules) {
|
|
437
|
-
|
|
444
|
+
lines.push(kleur.cyan(` kasy update ${m}`));
|
|
438
445
|
}
|
|
439
|
-
|
|
446
|
+
lines.push('');
|
|
440
447
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
448
|
+
lines.push(t('update.howToUpdateComponents'));
|
|
449
|
+
lines.push(kleur.cyan(` kasy update ${COMPONENTS_UPDATE_TARGET}`));
|
|
450
|
+
ui.log.message(lines.join('\n'));
|
|
444
451
|
} else if (modulesWithoutChanges.length > 0) {
|
|
445
452
|
// Some modules have no new changes — show as secondary info
|
|
446
|
-
|
|
453
|
+
const lines = [kleur.dim(t('update.reapplyTitle'))];
|
|
447
454
|
for (const m of modulesWithoutChanges) {
|
|
448
|
-
|
|
455
|
+
lines.push(kleur.dim(` kasy update ${m}`));
|
|
449
456
|
}
|
|
450
457
|
if (!hasComponentChanges) {
|
|
451
|
-
|
|
458
|
+
lines.push(kleur.dim(` kasy update ${COMPONENTS_UPDATE_TARGET}`));
|
|
452
459
|
}
|
|
453
|
-
|
|
460
|
+
ui.log.message(lines.join('\n'));
|
|
454
461
|
}
|
|
462
|
+
ui.outro('');
|
|
455
463
|
}
|
|
456
464
|
|
|
457
465
|
module.exports = { runUpdate };
|
package/lib/commands/validate.js
CHANGED
|
@@ -2,6 +2,8 @@ const path = require('node:path');
|
|
|
2
2
|
const { promisify } = require('node:util');
|
|
3
3
|
const { execFile } = require('node:child_process');
|
|
4
4
|
const kleur = require('kleur');
|
|
5
|
+
const ui = require('../utils/ui');
|
|
6
|
+
const { printCompactHeader } = require('../utils/brand');
|
|
5
7
|
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
6
8
|
const { pathExists } = require('../utils/fs');
|
|
7
9
|
|
|
@@ -58,12 +60,16 @@ async function runValidate({ language, analyzeOnly = false } = {}) {
|
|
|
58
60
|
).then(() => true).catch(() => false);
|
|
59
61
|
|
|
60
62
|
if (!anyExists) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
printCompactHeader(t);
|
|
64
|
+
ui.intro(t('validate.title'));
|
|
65
|
+
ui.log.warn('kasy validate is only available in the Kasy development environment.');
|
|
66
|
+
ui.log.message(kleur.dim('Generate test projects with `kasy new` first, then run this command.'));
|
|
67
|
+
ui.outro('Skipped');
|
|
63
68
|
return;
|
|
64
69
|
}
|
|
65
70
|
|
|
66
|
-
|
|
71
|
+
printCompactHeader(t);
|
|
72
|
+
ui.intro(t('validate.title'));
|
|
67
73
|
|
|
68
74
|
const commands = buildCommandsForPlatform(analyzeOnly);
|
|
69
75
|
const failures = [];
|
|
@@ -75,54 +81,53 @@ async function runValidate({ language, analyzeOnly = false } = {}) {
|
|
|
75
81
|
if (!exists) {
|
|
76
82
|
const projectName = path.basename(combo.projectPath);
|
|
77
83
|
failures.push({ combo: combo.id, reason: `${t('validate.projectNotFound')}: ${combo.projectPath}` });
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
ui.log.error(label);
|
|
85
|
+
ui.log.message(
|
|
86
|
+
`${kleur.dim(combo.projectPath)}\n` +
|
|
81
87
|
kleur.yellow(
|
|
82
|
-
|
|
83
|
-
`
|
|
84
|
-
`
|
|
88
|
+
`⚠ Projeto base não encontrado. Gere-o antes de validar:\n` +
|
|
89
|
+
` kasy new ${projectName} --backend ${combo.backend}\n` +
|
|
90
|
+
` O projeto deve ser gerado uma vez e mantido localmente para CI.`
|
|
85
91
|
)
|
|
86
92
|
);
|
|
87
93
|
continue;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
console.log(kleur.gray(` ${combo.projectPath}`));
|
|
96
|
+
ui.log.step(`${kleur.cyan('•')} ${label}\n${kleur.dim(combo.projectPath)}`);
|
|
92
97
|
|
|
93
98
|
let comboFailed = false;
|
|
94
99
|
for (const [cmd, args] of commands) {
|
|
95
100
|
const printable = `${cmd} ${args.join(' ')}`;
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
const spinner = ui.spinner();
|
|
102
|
+
spinner.start(printable);
|
|
98
103
|
try {
|
|
99
104
|
await runCommand(cmd, args, combo.projectPath);
|
|
100
|
-
|
|
105
|
+
spinner.stop(`${printable} ${kleur.green(t('validate.ok'))}`);
|
|
101
106
|
} catch (error) {
|
|
102
107
|
comboFailed = true;
|
|
103
108
|
failures.push({
|
|
104
109
|
combo: combo.id,
|
|
105
110
|
reason: `${printable} ${t('validate.fail')}`
|
|
106
111
|
});
|
|
107
|
-
|
|
112
|
+
spinner.error(`${printable} ${t('validate.fail')}`);
|
|
108
113
|
break;
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
|
|
112
117
|
if (!comboFailed) {
|
|
113
|
-
|
|
118
|
+
ui.log.success(`${combo.id} ${t('validate.passed')}`);
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
if (failures.length > 0) {
|
|
118
|
-
|
|
123
|
+
ui.log.error(t('validate.failed'));
|
|
119
124
|
failures.forEach((failure) => {
|
|
120
|
-
|
|
125
|
+
ui.log.error(`- ${failure.combo}: ${failure.reason}`);
|
|
121
126
|
});
|
|
122
127
|
throw new Error(t('validate.error'));
|
|
123
128
|
}
|
|
124
129
|
|
|
125
|
-
|
|
130
|
+
ui.outro(t('validate.success'));
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
module.exports = {
|
|
@@ -4,6 +4,7 @@ const path = require('node:path');
|
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const kleur = require('kleur');
|
|
7
|
+
const ui = require('./ui');
|
|
7
8
|
const { exec } = require('node:child_process');
|
|
8
9
|
const { promisify } = require('node:util');
|
|
9
10
|
|
|
@@ -202,17 +203,21 @@ function isXcodeCacheBuildError(output) {
|
|
|
202
203
|
|
|
203
204
|
function printBuildFailureHints(t, projectDir) {
|
|
204
205
|
const name = path.basename(projectDir);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
const cleanArg = projectDir !== process.cwd() ? ` ${projectDir}` : '';
|
|
207
|
+
const lines = [
|
|
208
|
+
`1. ${t('ios.hints.closeXcode')}`,
|
|
209
|
+
`2. ${kleur.cyan(`kasy ios clean${cleanArg}`)}`,
|
|
210
|
+
`3. ${t('ios.hints.retry')}: ${kleur.cyan('kasy ios release')}`,
|
|
211
|
+
];
|
|
209
212
|
if (name) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push(kleur.dim(`${t('ios.hints.manual')}:`));
|
|
215
|
+
lines.push(kleur.dim(` cd ${projectDir}`));
|
|
216
|
+
lines.push(kleur.dim(' flutter clean && flutter pub get'));
|
|
217
|
+
lines.push(kleur.dim(' rm -rf ~/Library/Developer/Xcode/DerivedData/Runner-*'));
|
|
218
|
+
lines.push(kleur.dim(' cd ios && pod install'));
|
|
215
219
|
}
|
|
220
|
+
ui.note(lines.join('\n'), t('ios.hints.title'));
|
|
216
221
|
}
|
|
217
222
|
|
|
218
223
|
async function runIosClean(projectDir, t) {
|
|
@@ -224,10 +229,17 @@ async function runIosClean(projectDir, t) {
|
|
|
224
229
|
{ label: t('ios.clean.step.pods'), cmd: 'cd ios && pod install' },
|
|
225
230
|
];
|
|
226
231
|
|
|
232
|
+
const stepper = ui.makeStepper();
|
|
227
233
|
for (const step of steps) {
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
stepper.next(step.label);
|
|
235
|
+
try {
|
|
236
|
+
await execAsync(step.cmd, { cwd: projectDir, maxBuffer: 50 * 1024 * 1024, shell: '/bin/bash' });
|
|
237
|
+
} catch (err) {
|
|
238
|
+
stepper.fail(`${step.label}: ${err.message}`);
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
230
241
|
}
|
|
242
|
+
stepper.succeed();
|
|
231
243
|
}
|
|
232
244
|
|
|
233
245
|
async function runReleaseScript(projectDir, args, t) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kasy brand kit for the terminal.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes logo, banner, headers, and boxed messages so every
|
|
5
|
+
* command projects a consistent identity. Two header sizes:
|
|
6
|
+
* - printBanner(): full ASCII logo + gradient + tagline (kasy new, kasy add)
|
|
7
|
+
* - printCompactHeader(): one-line ✦ KASY + tagline (kasy doctor, upgrade, run)
|
|
8
|
+
*
|
|
9
|
+
* Box helpers wrap `boxen` so commands stop drawing ─── lines by hand.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const kleur = require('kleur');
|
|
13
|
+
const gradient = require('gradient-string');
|
|
14
|
+
const boxenPackage = require('boxen');
|
|
15
|
+
const boxen = boxenPackage.default || boxenPackage;
|
|
16
|
+
|
|
17
|
+
// Kept around for accents and boxes; the wordmark itself is now plain white.
|
|
18
|
+
const BRAND_GRADIENT = ['#a78bfa', '#60a5fa'];
|
|
19
|
+
const brandGradient = gradient(BRAND_GRADIENT);
|
|
20
|
+
|
|
21
|
+
const DOMAIN_SUFFIX = kleur.gray('.dev');
|
|
22
|
+
|
|
23
|
+
function printBanner(_tr) {
|
|
24
|
+
const bar = kleur.gray('─────────────────────────────────────────────────');
|
|
25
|
+
const logo = [
|
|
26
|
+
kleur.white(' ╦╔═ ╔═╗ ╔═╗ ╦ ╦'),
|
|
27
|
+
kleur.white(' ╠╩╗ ╠═╣ ╚═╗ ╚╦╝'),
|
|
28
|
+
`${kleur.white(' ╩ ╩ ╩ ╩ ╚═╝ ╩ ')} ${DOMAIN_SUFFIX}`,
|
|
29
|
+
].join('\n');
|
|
30
|
+
|
|
31
|
+
console.log(`\n${bar}\n`);
|
|
32
|
+
console.log(logo);
|
|
33
|
+
console.log(`\n${bar}\n`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function printCompactHeader(_tr) {
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(` ${kleur.white('✦ KASY')}${DOMAIN_SUFFIX}`);
|
|
39
|
+
console.log('');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function successBox(title, body, { padding = 1, marginTop = 1, marginBottom = 1 } = {}) {
|
|
43
|
+
return boxen(
|
|
44
|
+
`${brandGradient(`✦ ${title}`)}\n\n${body}`,
|
|
45
|
+
{
|
|
46
|
+
padding,
|
|
47
|
+
margin: { top: marginTop, bottom: marginBottom, left: 1, right: 1 },
|
|
48
|
+
borderStyle: 'round',
|
|
49
|
+
borderColor: 'cyan',
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function infoBox(title, body, { padding = 1, marginTop = 1, marginBottom = 1 } = {}) {
|
|
55
|
+
return boxen(
|
|
56
|
+
`${kleur.bold(title)}\n\n${body}`,
|
|
57
|
+
{
|
|
58
|
+
padding,
|
|
59
|
+
margin: { top: marginTop, bottom: marginBottom, left: 1, right: 1 },
|
|
60
|
+
borderStyle: 'round',
|
|
61
|
+
borderColor: 'gray',
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
brandGradient,
|
|
68
|
+
printBanner,
|
|
69
|
+
printCompactHeader,
|
|
70
|
+
successBox,
|
|
71
|
+
infoBox,
|
|
72
|
+
};
|