offerCreateIfUnknown function
Offers to create an unknown package on AppFlight from init (#30), via
AppsService.ins.create.
init makes zero network calls today — isCI/hasTerminal are checked
FIRST, before Credentials.load() and before any Dio is built, so a
scripted init under CI gets no new HTTP call, no new latency, and no
new failure mode. Taken as parameters rather than read from
Platform.environment/stdin internally, the way
util/terms_acceptance.dart takes hasTerminal — so the test drives
both without a subprocess.
configDir, apiDio and readAnswer exist only for
init_offer_test.dart — production callers never pass them, so real
credentials, a real Dio, and real stdin are untouched.
Everything past the CI/terminal check is wrapped in a catch-all that
logs and returns rather than throwing — the one place in this ticket
where swallowing an error is correct: writing appflight.json must
never fail because this offer's own network call did.
Implementation
Future<void> offerCreateIfUnknown({
required String packageName,
required bool isCI,
required bool hasTerminal,
String? configDir,
Dio? apiDio,
String Function()? readAnswer,
}) async {
if (isCI || !hasTerminal) return;
final creds = Credentials.load(configDir: configDir);
if (creds == null) {
AppLogger.info(loginNudge);
return;
}
try {
final dio = apiDio ?? ApiClient.instance.forKey(creds.apiKey);
final isRegistered = await AppsService.ins
.isRegistered(apiDio: dio, packageName: packageName);
if (isRegistered) return;
stdout.write(createPromptText(packageName));
final answer = (readAnswer ?? _readStdinAnswer)();
if (answer != 'y' && answer != 'yes') return;
final created = await AppsService.ins.create(
apiDio: dio,
name: packageName,
packageName: packageName,
);
AppLogger.success('Created app: ${created['id']}');
} catch (e) {
AppLogger.debug('offerCreateIfUnknown($packageName): $e');
}
}