kasy-cli 1.9.2 → 1.12.1
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 +4 -4
- package/lib/commands/check.js +40 -50
- package/lib/commands/deploy.js +25 -25
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/features/README.md +1 -1
- package/lib/utils/i18n.js +427 -256
- package/package.json +3 -2
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +29 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +9 -1
- package/templates/firebase/lib/core/initializer/onstart_widget.dart +7 -1
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_background_task.dart +0 -41
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart +0 -98
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_service.dart +0 -54
- package/lib/scaffold/features/widget/lib/features/settings/ui/components/admin/admin_home_widgets.dart +0 -32
package/bin/kasy.js
CHANGED
|
@@ -242,14 +242,15 @@ function buildProgram(language) {
|
|
|
242
242
|
const langName = t('cli.command.setup.langName');
|
|
243
243
|
applyLocalizedHelp(
|
|
244
244
|
program
|
|
245
|
-
.command('setup')
|
|
245
|
+
.command('setup', { hidden: true })
|
|
246
246
|
.argument(`[${directoryName}]`, t('cli.command.setup.directoryArg'), '.')
|
|
247
247
|
.option(`-l, --lang <${langName}>`, t('cli.command.setup.langOption'))
|
|
248
248
|
.option('-b, --backend <backend>', t('cli.command.setup.backendOption'))
|
|
249
249
|
.option('--with <features>', t('cli.command.setup.featuresOption'))
|
|
250
250
|
.description(t('cli.command.setup.description'))
|
|
251
251
|
.action(async (directory, options) => {
|
|
252
|
-
// `setup` is an alias for `new` — unified flow
|
|
252
|
+
// `setup` is an alias for `new` — unified flow.
|
|
253
|
+
// Hidden from root help to reduce noise; still callable.
|
|
253
254
|
await runNew(directory, {
|
|
254
255
|
language: options.lang,
|
|
255
256
|
backend: options.backend,
|
|
@@ -516,7 +517,6 @@ function buildProgram(language) {
|
|
|
516
517
|
notificationsCmd
|
|
517
518
|
.command('text')
|
|
518
519
|
.argument('[directory]', 'Project folder (default: current directory)', '.')
|
|
519
|
-
.option('-d, --directory <path>', 'Project folder (default: current directory)')
|
|
520
520
|
.option('--locale <code>', 'i18n files to update: pt, en, es, or all (default: all)', 'all')
|
|
521
521
|
.option('--demo-title <text>', 'Home → Features demo notification title')
|
|
522
522
|
.option('--demo-body <text>', 'Home → Features demo card description / instant notification body')
|
|
@@ -524,7 +524,7 @@ function buildProgram(language) {
|
|
|
524
524
|
.option('--reminder-body <text>', 'Scheduled reminder notification body')
|
|
525
525
|
.description(t('cli.command.notifications.text.description'))
|
|
526
526
|
.action(async (directory, options) => {
|
|
527
|
-
const dir =
|
|
527
|
+
const dir = directory || '.';
|
|
528
528
|
await runNotificationsText(dir, {
|
|
529
529
|
language,
|
|
530
530
|
directory: dir,
|
package/lib/commands/check.js
CHANGED
|
@@ -110,67 +110,67 @@ async function runCheck(options = {}) {
|
|
|
110
110
|
const t = createTranslator(lang);
|
|
111
111
|
|
|
112
112
|
printCompactHeader(t);
|
|
113
|
-
ui.intro('
|
|
113
|
+
ui.intro(t('check.intro'));
|
|
114
114
|
|
|
115
115
|
// ── Detect backend ────────────────────────────────────────────────────────
|
|
116
116
|
const isFirebase = await fs.pathExists(path.join(projectDir, 'firebase.json'));
|
|
117
117
|
const isSupabase = await fs.pathExists(path.join(projectDir, 'supabase'));
|
|
118
118
|
|
|
119
119
|
if (isFirebase && !isSupabase) {
|
|
120
|
-
ok('
|
|
121
|
-
info('
|
|
120
|
+
ok(t('check.firebase.detected'));
|
|
121
|
+
info(t('check.firebase.adcInfo'));
|
|
122
122
|
const firebaseProjectId = await readFirebaseProjectId(projectDir);
|
|
123
123
|
const apnsLines = [
|
|
124
|
-
kleur.yellow(
|
|
125
|
-
kleur.dim('
|
|
124
|
+
kleur.yellow(t('check.apns.warn')),
|
|
125
|
+
kleur.dim(t('check.apns.where')),
|
|
126
126
|
];
|
|
127
127
|
if (firebaseProjectId) {
|
|
128
128
|
apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
|
|
129
129
|
}
|
|
130
130
|
ui.note(apnsLines.join('\n'));
|
|
131
|
-
ui.outro('
|
|
131
|
+
ui.outro(t('check.done'));
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (!isSupabase) {
|
|
136
|
-
ui.log.error('
|
|
137
|
-
ui.cancel('
|
|
136
|
+
ui.log.error(t('check.notKasy'));
|
|
137
|
+
ui.cancel(t('check.aborted'));
|
|
138
138
|
process.exit(1);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// ── 1. Project linked? ────────────────────────────────────────────────────
|
|
142
142
|
const projectRef = await readProjectRef(projectDir);
|
|
143
143
|
if (!projectRef) {
|
|
144
|
-
fail('
|
|
145
|
-
ui.cancel('
|
|
144
|
+
fail(t('check.supabase.notLinked'), t('check.supabase.notLinkedHint'));
|
|
145
|
+
ui.cancel(t('check.aborted'));
|
|
146
146
|
process.exit(1);
|
|
147
147
|
}
|
|
148
|
-
ok('
|
|
148
|
+
ok(t('check.supabase.linked'), projectRef);
|
|
149
149
|
|
|
150
150
|
// ── Read secrets list ─────────────────────────────────────────────────────
|
|
151
151
|
const spinner = ui.spinner();
|
|
152
|
-
spinner.start('
|
|
152
|
+
spinner.start(t('check.spin.secrets'));
|
|
153
153
|
const secrets = await listSecretNames(projectDir);
|
|
154
|
-
spinner.stop('
|
|
154
|
+
spinner.stop(t('check.spin.secretsDone'));
|
|
155
155
|
|
|
156
156
|
if (!secrets) {
|
|
157
|
-
warn('
|
|
157
|
+
warn(t('check.secrets.listFailed'), t('check.secrets.checkLogin'));
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// ── 2. FIREBASE_PROJECT_ID ────────────────────────────────────────────────
|
|
161
161
|
const firebaseProjectId = await readFirebaseProjectId(projectDir);
|
|
162
162
|
if (secrets) {
|
|
163
163
|
if (secrets.has('FIREBASE_PROJECT_ID')) {
|
|
164
|
-
ok('
|
|
164
|
+
ok(t('check.fbProjId.ok'));
|
|
165
165
|
} else {
|
|
166
|
-
fail('
|
|
166
|
+
fail(t('check.fbProjId.missing'));
|
|
167
167
|
if (firebaseProjectId) {
|
|
168
|
-
ui.log.message(kleur.gray(
|
|
168
|
+
ui.log.message(kleur.gray(t('check.fbProjId.fixHint', { id: firebaseProjectId })));
|
|
169
169
|
if (options.fix) {
|
|
170
170
|
const r = await runCmd(`supabase secrets set FIREBASE_PROJECT_ID="${firebaseProjectId}"`, projectDir);
|
|
171
171
|
r.ok
|
|
172
|
-
? ok('
|
|
173
|
-
: fail('
|
|
172
|
+
? ok(t('check.fbProjId.fixed'))
|
|
173
|
+
: fail(t('check.fbProjId.fixFailed'), r.error);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
}
|
|
@@ -179,15 +179,15 @@ async function runCheck(options = {}) {
|
|
|
179
179
|
// ── 3. FIREBASE_SERVICE_ACCOUNT_JSON ─────────────────────────────────────
|
|
180
180
|
if (secrets) {
|
|
181
181
|
if (secrets.has('FIREBASE_SERVICE_ACCOUNT_JSON')) {
|
|
182
|
-
ok('
|
|
182
|
+
ok(t('check.fbSak.ok'));
|
|
183
183
|
} else {
|
|
184
|
-
fail('
|
|
184
|
+
fail(t('check.fbSak.missing'));
|
|
185
185
|
|
|
186
186
|
if (options.fix && firebaseProjectId) {
|
|
187
187
|
const fixSpinner = ui.spinner();
|
|
188
|
-
fixSpinner.start('
|
|
188
|
+
fixSpinner.start(t('check.fbSak.spin'));
|
|
189
189
|
const fcmResult = await createFcmServiceAccountKey(firebaseProjectId);
|
|
190
|
-
fixSpinner.stop('
|
|
190
|
+
fixSpinner.stop(t('check.fbSak.spinDone'));
|
|
191
191
|
|
|
192
192
|
if (fcmResult.ok) {
|
|
193
193
|
const secretSteps = await setSupabaseSecrets(projectDir, {
|
|
@@ -196,62 +196,52 @@ async function runCheck(options = {}) {
|
|
|
196
196
|
});
|
|
197
197
|
const setStep = secretSteps.find((s) => s.name === 'secret FIREBASE_SERVICE_ACCOUNT_JSON');
|
|
198
198
|
setStep?.ok
|
|
199
|
-
? ok('
|
|
200
|
-
: fail('
|
|
199
|
+
? ok(t('check.fbSak.fixed'))
|
|
200
|
+
: fail(t('check.fbSak.fixFailed'), setStep?.error);
|
|
201
201
|
} else {
|
|
202
|
-
fail('
|
|
203
|
-
ui.log.message(kleur.gray(
|
|
204
|
-
'Manual: Firebase Console → Configurações → Contas de serviço → Gerar chave\n' +
|
|
205
|
-
"Depois: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='$(cat chave.json)'"
|
|
206
|
-
));
|
|
202
|
+
fail(t('check.fbSak.genFailed'), fcmResult.error);
|
|
203
|
+
ui.log.message(kleur.gray(t('check.fbSak.manual')));
|
|
207
204
|
}
|
|
208
205
|
} else {
|
|
209
|
-
ui.log.message(kleur.gray(
|
|
210
|
-
'Corrija automaticamente: kasy check --fix\n' +
|
|
211
|
-
'Ou manualmente: Firebase Console → Configurações → Contas de serviço → Gerar chave\n' +
|
|
212
|
-
"Depois: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='$(cat chave.json)'"
|
|
213
|
-
));
|
|
206
|
+
ui.log.message(kleur.gray(t('check.fbSak.hint')));
|
|
214
207
|
}
|
|
215
208
|
}
|
|
216
209
|
}
|
|
217
210
|
|
|
218
211
|
// ── 4. Edge function send-push-notification ────────────────────────────────
|
|
219
212
|
const fnSpinner = ui.spinner();
|
|
220
|
-
fnSpinner.start('
|
|
213
|
+
fnSpinner.start(t('check.fn.spin'));
|
|
221
214
|
const functions = await listDeployedFunctions(projectDir);
|
|
222
|
-
fnSpinner.stop('
|
|
215
|
+
fnSpinner.stop(t('check.fn.spinDone'));
|
|
223
216
|
|
|
224
217
|
if (!functions) {
|
|
225
|
-
warn('
|
|
218
|
+
warn(t('check.fn.listFailed'), t('check.secrets.checkLogin'));
|
|
226
219
|
} else if (functions.has('send-push-notification')) {
|
|
227
|
-
ok('
|
|
220
|
+
ok(t('check.fn.deployed'));
|
|
228
221
|
} else {
|
|
229
|
-
fail('
|
|
222
|
+
fail(t('check.fn.missing'));
|
|
230
223
|
if (options.fix) {
|
|
231
224
|
const deploySpinner = ui.spinner();
|
|
232
|
-
deploySpinner.start('
|
|
225
|
+
deploySpinner.start(t('check.fn.deploySpin'));
|
|
233
226
|
const r = await runCmd('supabase functions deploy send-push-notification', projectDir);
|
|
234
227
|
r.ok
|
|
235
|
-
? deploySpinner.stop('
|
|
236
|
-
: deploySpinner.error(
|
|
228
|
+
? deploySpinner.stop(t('check.fn.deployDone'))
|
|
229
|
+
: deploySpinner.error(`${t('check.fn.deployFailed')}${r.error ? ` — ${r.error}` : ''}`);
|
|
237
230
|
} else {
|
|
238
|
-
ui.log.message(kleur.gray(
|
|
239
|
-
'Corrija automaticamente: kasy check --fix\n' +
|
|
240
|
-
'Ou manualmente: supabase functions deploy send-push-notification'
|
|
241
|
-
));
|
|
231
|
+
ui.log.message(kleur.gray(t('check.fn.hint')));
|
|
242
232
|
}
|
|
243
233
|
}
|
|
244
234
|
|
|
245
235
|
// ── 5. APNs reminder ─────────────────────────────────────────────────────
|
|
246
236
|
const apnsLines = [
|
|
247
|
-
kleur.yellow(
|
|
248
|
-
kleur.dim('
|
|
237
|
+
kleur.yellow(t('check.apns.warn')),
|
|
238
|
+
kleur.dim(t('check.apns.where')),
|
|
249
239
|
];
|
|
250
240
|
if (firebaseProjectId) {
|
|
251
241
|
apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
|
|
252
242
|
}
|
|
253
243
|
ui.note(apnsLines.join('\n'));
|
|
254
|
-
ui.outro('
|
|
244
|
+
ui.outro(t('check.done'));
|
|
255
245
|
}
|
|
256
246
|
|
|
257
247
|
module.exports = { runCheck };
|
package/lib/commands/deploy.js
CHANGED
|
@@ -123,14 +123,14 @@ async function deployFirebase(projectDir, options, tr) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
const spinner = ui.spinner();
|
|
126
|
-
spinner.start('
|
|
126
|
+
spinner.start(tr('deploy.firebase.spin'));
|
|
127
127
|
let steps;
|
|
128
128
|
try {
|
|
129
129
|
steps = await runFirebaseDeploy(projectDir, null, firebaseProjectId, {
|
|
130
130
|
functionsRegion,
|
|
131
131
|
onProgress: (key) => { spinner.message(String(key)); },
|
|
132
132
|
});
|
|
133
|
-
spinner.stop('
|
|
133
|
+
spinner.stop(tr('deploy.firebase.spinDone'));
|
|
134
134
|
} catch (err) {
|
|
135
135
|
spinner.error(err.message);
|
|
136
136
|
throw err;
|
|
@@ -145,9 +145,9 @@ async function deployFirebase(projectDir, options, tr) {
|
|
|
145
145
|
// ── APNs reminder — required for iOS push notifications ──────────────────
|
|
146
146
|
// Cannot be automated: the .p8 key only exists in Apple Developer Portal.
|
|
147
147
|
const apnsBody = [
|
|
148
|
-
kleur.yellow('
|
|
149
|
-
kleur.dim('
|
|
150
|
-
kleur.dim('
|
|
148
|
+
kleur.yellow(tr('deploy.apns.title')),
|
|
149
|
+
kleur.dim(tr('deploy.apns.step1')),
|
|
150
|
+
kleur.dim(tr('deploy.apns.step2')),
|
|
151
151
|
kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`),
|
|
152
152
|
].join('\n');
|
|
153
153
|
ui.note(apnsBody);
|
|
@@ -155,28 +155,28 @@ async function deployFirebase(projectDir, options, tr) {
|
|
|
155
155
|
|
|
156
156
|
// ── Supabase deploy ───────────────────────────────────────────────────────────
|
|
157
157
|
|
|
158
|
-
async function deploySupabase(projectDir) {
|
|
158
|
+
async function deploySupabase(projectDir, tr) {
|
|
159
159
|
// ── 1. Get project ref ──────────────────────────────────────────────────
|
|
160
160
|
const projectRef = await readSupabaseProjectRef(projectDir);
|
|
161
161
|
if (!projectRef) {
|
|
162
|
-
ui.log.error('
|
|
163
|
-
ui.log.message(kleur.gray('
|
|
162
|
+
ui.log.error(tr('deploy.supabase.notLinked'));
|
|
163
|
+
ui.log.message(kleur.gray(tr('deploy.supabase.linkHint')));
|
|
164
164
|
process.exit(1);
|
|
165
165
|
}
|
|
166
|
-
ui.log.message(kleur.gray(
|
|
166
|
+
ui.log.message(kleur.gray(tr('deploy.supabase.projectRef', { ref: kleur.cyan(projectRef) })));
|
|
167
167
|
|
|
168
168
|
// ── 2. FCM Service Account JSON ─────────────────────────────────────────
|
|
169
169
|
const alreadySet = await isServiceAccountJsonSet(projectDir);
|
|
170
170
|
if (alreadySet) {
|
|
171
|
-
printStep(true, false, '
|
|
171
|
+
printStep(true, false, tr('deploy.supabase.sakAlready'));
|
|
172
172
|
} else {
|
|
173
173
|
const firebaseProjectId = await readFirebaseProjectId(projectDir);
|
|
174
174
|
|
|
175
175
|
if (firebaseProjectId) {
|
|
176
176
|
const fcmSpinner = ui.spinner();
|
|
177
|
-
fcmSpinner.start(
|
|
177
|
+
fcmSpinner.start(tr('deploy.supabase.sakSpin'));
|
|
178
178
|
const fcmResult = await createFcmServiceAccountKey(firebaseProjectId);
|
|
179
|
-
fcmSpinner.stop('
|
|
179
|
+
fcmSpinner.stop(tr('deploy.supabase.sakSpinDone'));
|
|
180
180
|
|
|
181
181
|
if (fcmResult.ok) {
|
|
182
182
|
const secretSteps = await setSupabaseSecrets(projectDir, {
|
|
@@ -186,23 +186,23 @@ async function deploySupabase(projectDir) {
|
|
|
186
186
|
for (const s of secretSteps) printStep(s.ok, false, s.name, s.error);
|
|
187
187
|
} else {
|
|
188
188
|
printStep(false, false, 'FIREBASE_SERVICE_ACCOUNT_JSON', fcmResult.error);
|
|
189
|
-
ui.log.warn(
|
|
189
|
+
ui.log.warn(tr('deploy.supabase.sakManual'));
|
|
190
190
|
}
|
|
191
191
|
} else {
|
|
192
|
-
printStep(false, false, 'FIREBASE_SERVICE_ACCOUNT_JSON', '
|
|
192
|
+
printStep(false, false, 'FIREBASE_SERVICE_ACCOUNT_JSON', tr('deploy.supabase.sakNoGS'));
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// ── 3. Deploy edge functions ────────────────────────────────────────────
|
|
197
197
|
const fnSpinner = ui.spinner();
|
|
198
|
-
fnSpinner.start('
|
|
198
|
+
fnSpinner.start(tr('deploy.supabase.fnSpin'));
|
|
199
199
|
const fnResult = await deployFunctions(projectDir);
|
|
200
|
-
fnSpinner.stop('
|
|
200
|
+
fnSpinner.stop(tr('deploy.supabase.fnSpinDone'));
|
|
201
201
|
|
|
202
202
|
if (Array.isArray(fnResult)) {
|
|
203
203
|
fnResult.forEach((s) => printStep(s.ok, s.skipped, s.name, s.error));
|
|
204
204
|
} else if (fnResult.skipped) {
|
|
205
|
-
printStep(true, true,
|
|
205
|
+
printStep(true, true, tr('deploy.supabase.fnNone'));
|
|
206
206
|
} else {
|
|
207
207
|
printStep(fnResult.ok, false, 'supabase functions deploy', fnResult.error);
|
|
208
208
|
}
|
|
@@ -210,9 +210,9 @@ async function deploySupabase(projectDir) {
|
|
|
210
210
|
// ── 4. APNs reminder ────────────────────────────────────────────────────
|
|
211
211
|
const firebaseProjectId = await readFirebaseProjectId(projectDir);
|
|
212
212
|
const apnsLines = [
|
|
213
|
-
kleur.yellow('
|
|
214
|
-
kleur.dim('
|
|
215
|
-
kleur.dim('
|
|
213
|
+
kleur.yellow(tr('deploy.apns.title')),
|
|
214
|
+
kleur.dim(tr('deploy.apns.step1')),
|
|
215
|
+
kleur.dim(tr('deploy.apns.step2')),
|
|
216
216
|
];
|
|
217
217
|
if (firebaseProjectId) {
|
|
218
218
|
apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
|
|
@@ -240,17 +240,17 @@ async function runDeployCommand(directory, options = {}, { language: langHint }
|
|
|
240
240
|
|
|
241
241
|
if (isFirebase) {
|
|
242
242
|
printCompactHeader(tr);
|
|
243
|
-
ui.intro('
|
|
243
|
+
ui.intro(tr('deploy.firebase.intro'));
|
|
244
244
|
await deployFirebase(projectDir, options, tr);
|
|
245
|
-
ui.outro('
|
|
245
|
+
ui.outro(tr('deploy.outro'));
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
if (isSupabase) {
|
|
250
250
|
printCompactHeader(tr);
|
|
251
|
-
ui.intro('
|
|
252
|
-
await deploySupabase(projectDir);
|
|
253
|
-
ui.outro('
|
|
251
|
+
ui.intro(tr('deploy.supabase.intro'));
|
|
252
|
+
await deploySupabase(projectDir, tr);
|
|
253
|
+
ui.outro(tr('deploy.outro'));
|
|
254
254
|
return;
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.10.0": {
|
|
3
|
+
"modules": {
|
|
4
|
+
"widget": {
|
|
5
|
+
"pt": "Widget agora funciona ponta a ponta: idioma do app (pt/en/es), saudação correta pelo horário, nome real do usuário, status PRO/Free em tempo real (com fallback do RevenueCat quando o webhook atrasa), auto-refresh quando o estado do usuário muda — sem esperar o background task de 15 min. App Group iOS corrigido em Runner.entitlements (sem isso a escrita ia pro vazio). Textos hardcoded em Swift/Kotlin removidos — o nativo só renderiza o que o Dart manda.",
|
|
6
|
+
"en": "Widget now works end-to-end: app language (pt/en/es), correct greeting for time of day, real user name, real-time PRO/Free status (with RevenueCat fallback when the webhook is delayed), auto-refresh when user state changes — no need to wait for the 15-min background task. iOS App Group fixed in Runner.entitlements (without it, writes went to nowhere). Hardcoded strings removed from Swift/Kotlin — native side only renders what Dart sends.",
|
|
7
|
+
"es": "El widget ahora funciona de punta a punta: idioma de la app (pt/en/es), saludo correcto según el horario, nombre real del usuario, estado PRO/Free en tiempo real (con fallback de RevenueCat cuando el webhook se atrasa), auto-refresh cuando el estado del usuario cambia — sin esperar la background task de 15 min. App Group iOS corregido en Runner.entitlements (sin esto las escrituras iban al vacío). Textos hardcoded en Swift/Kotlin eliminados — el código nativo solo renderiza lo que Dart envía."
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
2
11
|
"1.8.0": {
|
|
3
12
|
"modules": {
|
|
4
13
|
"components": {
|
|
@@ -31,7 +31,7 @@ Ao gerar um projeto, o engine copia recursivamente o conteúdo de `features/{mod
|
|
|
31
31
|
|-------------|------------------|-----------------------------------------------------|
|
|
32
32
|
| `ci` | ✅ Sim | Adiciona `.github/`, `.gitlab-ci.yml`, etc. |
|
|
33
33
|
| `web` | ✅ Sim | Adiciona pasta `web/` e configurações de plataforma |
|
|
34
|
-
| `widget` |
|
|
34
|
+
| `widget` | Não | Os arquivos do widget (iOS + Android + Dart) já vão no template base. Quando o módulo não é selecionado, `removeAndroidWidgetArtifacts` + `writeNoOpAdminHomeWidgets` em `generate.js` limpam o que sobra. |
|
|
35
35
|
| `llm_chat` | Não | Apenas `LLM_CHAT_ENDPOINT` via dart-define. A chave da API LLM fica no servidor (Firebase Secret / Supabase Secret) — nunca no app. |
|
|
36
36
|
| `sentry` | Não | Apenas dart-define (`SENTRY_DSN`) |
|
|
37
37
|
| `analytics` | Não | Apenas dart-define |
|