chore: rename product_id to app_id (#77)
This commit is contained in:
@@ -1,39 +1,44 @@
|
||||
## A reminder why
|
||||
* We're here to make multi-platform the default.
|
||||
* We believe Flutter is that default, but has missing pieces for enterprises.
|
||||
* The first hole we're filling is the ability to push updates to Flutter apps.
|
||||
|
||||
- We're here to make multi-platform the default.
|
||||
- We believe Flutter is that default, but has missing pieces for enterprises.
|
||||
- The first hole we're filling is the ability to push updates to Flutter apps.
|
||||
|
||||
## This Week's Demo
|
||||
* Platform support (e.g. push to both arm7 and arm64)
|
||||
* API Keys and/or product ids (allows multiple users)
|
||||
* Teach the server how to decline to update (e.g. platform/version mismatch).
|
||||
* Instructions on how to build/use.
|
||||
|
||||
- Platform support (e.g. push to both arm7 and arm64)
|
||||
- API Keys and/or app ids (allows multiple users)
|
||||
- Teach the server how to decline to update (e.g. platform/version mismatch).
|
||||
- Instructions on how to build/use.
|
||||
|
||||
## User journey
|
||||
|
||||
Someone can:
|
||||
* download and install Shorebird.
|
||||
* `shorebird build` their existing Flutter app.
|
||||
* Push an update to their app.
|
||||
|
||||
- download and install Shorebird.
|
||||
- `shorebird build` their existing Flutter app.
|
||||
- Push an update to their app.
|
||||
|
||||
## Shipping to users (first, do no harm)
|
||||
* Need a way to know if the update failed (and both report it and roll back?)
|
||||
* Need to not send updates to incompatible devices or base versions.
|
||||
* Do we need to worry about different chipsets?
|
||||
* How do we educate users/developers about data storage updates (e.g. updating a database schema locally) and how that affects version compatibility / ability to roll back?
|
||||
* How does the Dart code know that it's running a patched version?
|
||||
|
||||
- Need a way to know if the update failed (and both report it and roll back?)
|
||||
- Need to not send updates to incompatible devices or base versions.
|
||||
- Do we need to worry about different chipsets?
|
||||
- How do we educate users/developers about data storage updates (e.g. updating a database schema locally) and how that affects version compatibility / ability to roll back?
|
||||
- How does the Dart code know that it's running a patched version?
|
||||
|
||||
## Later
|
||||
* Way to see what builds have been published so far.
|
||||
* See what % of devices are running what builds.
|
||||
* Make package:updater API work from Dart
|
||||
* Example of using Dart API from Dart/Flutter.
|
||||
* Ability to roll back to past push.
|
||||
* Build update from source (in the cloud).
|
||||
* GitHub integration / action trigger.
|
||||
* Quantify update download sizes.
|
||||
* Be able to create an account / API key.
|
||||
* Web interface to see pushes?
|
||||
* Security (signing, 2FA, 2-person approval, etc.)
|
||||
* Quantify how much bandwidth a push will use.
|
||||
* What % of device population has taken push.
|
||||
|
||||
- Way to see what builds have been published so far.
|
||||
- See what % of devices are running what builds.
|
||||
- Make package:updater API work from Dart
|
||||
- Example of using Dart API from Dart/Flutter.
|
||||
- Ability to roll back to past push.
|
||||
- Build update from source (in the cloud).
|
||||
- GitHub integration / action trigger.
|
||||
- Quantify update download sizes.
|
||||
- Be able to create an account / API key.
|
||||
- Web interface to see pushes?
|
||||
- Security (signing, 2FA, 2-person approval, etc.)
|
||||
- Quantify how much bandwidth a push will use.
|
||||
- What % of device population has taken push.
|
||||
|
||||
@@ -1 +1 @@
|
||||
product_id: shorebird-counter
|
||||
app_id: shorebird-counter
|
||||
@@ -76,7 +76,7 @@ shorebird logout
|
||||
|
||||
### Create App
|
||||
|
||||
To create an app use the `shorebird apps create` command. An app-id can be specified as a CLI option but shorebird will default to the product_id defined in the `shorebird.yaml`
|
||||
To create an app use the `shorebird apps create` command. An app-id can be specified as a CLI option but shorebird will default to the app_id defined in the `shorebird.yaml`
|
||||
|
||||
```bash
|
||||
# Create an app using default app id
|
||||
@@ -96,7 +96,7 @@ Created new app: my-app-id
|
||||
|
||||
### Delete App
|
||||
|
||||
To delete an existing app on Shorebird, use the `shorebird apps delete` command. An app-id can be specified as a CLI option but shorebird will default to the product_id defined in the `shorebird.yaml`
|
||||
To delete an existing app on Shorebird, use the `shorebird apps delete` command. An app-id can be specified as a CLI option but shorebird will default to the app_id defined in the `shorebird.yaml`
|
||||
|
||||
```bash
|
||||
# Create an app using default app id
|
||||
|
||||
@@ -20,7 +20,7 @@ class CreateAppCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
'app-id',
|
||||
help: '''
|
||||
The unique application identifier.
|
||||
Defaults to the product_id in "shorebird.yaml".''',
|
||||
Defaults to the app_id in "shorebird.yaml".''',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,34 +38,34 @@ Defaults to the product_id in "shorebird.yaml".''',
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
final appId = results['app-id'] as String?;
|
||||
late final String productId;
|
||||
final appIdArg = results['app-id'] as String?;
|
||||
late final String appId;
|
||||
|
||||
if (appId == null) {
|
||||
String? defaultProductId;
|
||||
if (appIdArg == null) {
|
||||
String? defaultAppId;
|
||||
try {
|
||||
defaultProductId = getShorebirdYaml()?.productId;
|
||||
defaultAppId = getShorebirdYaml()?.appId;
|
||||
} catch (_) {}
|
||||
|
||||
productId = logger.prompt(
|
||||
appId = logger.prompt(
|
||||
'${lightGreen.wrap('?')} Enter the App ID',
|
||||
defaultValue: defaultProductId,
|
||||
defaultValue: defaultAppId,
|
||||
);
|
||||
} else {
|
||||
productId = appId;
|
||||
appId = appIdArg;
|
||||
}
|
||||
|
||||
final client = buildCodePushClient(apiKey: session.apiKey);
|
||||
|
||||
try {
|
||||
await client.createApp(productId: productId);
|
||||
await client.createApp(appId: appId);
|
||||
} catch (error) {
|
||||
logger.err('Unable to create app\n$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'${lightGreen.wrap('Created new app: ${cyan.wrap(productId)}')}',
|
||||
'${lightGreen.wrap('Created new app: ${cyan.wrap(appId)}')}',
|
||||
);
|
||||
|
||||
return ExitCode.success.code;
|
||||
|
||||
@@ -20,7 +20,7 @@ class DeleteAppCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
'app-id',
|
||||
help: '''
|
||||
The unique application identifier.
|
||||
Defaults to the product_id in "shorebird.yaml".''',
|
||||
Defaults to the app_id in "shorebird.yaml".''',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,21 +38,21 @@ Defaults to the product_id in "shorebird.yaml".''',
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
final appId = results['app-id'] as String?;
|
||||
late final String productId;
|
||||
final appIdArg = results['app-id'] as String?;
|
||||
late final String appId;
|
||||
|
||||
if (appId == null) {
|
||||
String? defaultProductId;
|
||||
if (appIdArg == null) {
|
||||
String? defaultAppId;
|
||||
try {
|
||||
defaultProductId = getShorebirdYaml()?.productId;
|
||||
defaultAppId = getShorebirdYaml()?.appId;
|
||||
} catch (_) {}
|
||||
|
||||
productId = logger.prompt(
|
||||
appId = logger.prompt(
|
||||
'${lightGreen.wrap('?')} Enter the App ID',
|
||||
defaultValue: defaultProductId,
|
||||
defaultValue: defaultAppId,
|
||||
);
|
||||
} else {
|
||||
productId = appId;
|
||||
appId = appIdArg;
|
||||
}
|
||||
|
||||
final client = buildCodePushClient(apiKey: session.apiKey);
|
||||
@@ -64,14 +64,14 @@ Defaults to the product_id in "shorebird.yaml".''',
|
||||
}
|
||||
|
||||
try {
|
||||
await client.deleteApp(productId: productId);
|
||||
await client.deleteApp(appId: appId);
|
||||
} catch (error) {
|
||||
logger.err('Unable to delete app\n$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'${lightGreen.wrap('Deleted app: ${cyan.wrap(productId)}')}',
|
||||
'${lightGreen.wrap('Deleted app: ${cyan.wrap(appId)}')}',
|
||||
);
|
||||
|
||||
return ExitCode.success.code;
|
||||
|
||||
@@ -67,6 +67,6 @@ extension on App {
|
||||
final latestPatchPart =
|
||||
latestPatch != null ? '(patch #${latestPatch.number})' : '';
|
||||
|
||||
return '$productId: $latestReleasePart $latestPatchPart';
|
||||
return '$appId: $latestReleasePart $latestPatchPart';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ For more information about Shorebird, visit ${link(uri: Uri.parse('https://shore
|
||||
}
|
||||
|
||||
ShorebirdYaml _addShorebirdYamlToProject() {
|
||||
final productId = buildUuid();
|
||||
final appId = buildUuid();
|
||||
File(
|
||||
p.join(Directory.current.path, 'shorebird.yaml'),
|
||||
).writeAsStringSync('''
|
||||
@@ -91,10 +91,10 @@ For more information about Shorebird, visit ${link(uri: Uri.parse('https://shore
|
||||
# Learn more at https://shorebird.dev
|
||||
|
||||
# This is the unique identifier for your app.
|
||||
product_id: $productId
|
||||
app_id: $appId
|
||||
''');
|
||||
|
||||
return ShorebirdYaml(productId: productId);
|
||||
return ShorebirdYaml(appId: appId);
|
||||
}
|
||||
|
||||
void _addShorebirdYamlToPubspecAssets() {
|
||||
|
||||
@@ -71,13 +71,13 @@ class PublishCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
final shorebirdYaml = getShorebirdYaml()!;
|
||||
final codePushClient = buildCodePushClient(apiKey: session.apiKey);
|
||||
logger.detail(
|
||||
'''Deploying ${artifact.path} to ${shorebirdYaml.productId} (${pubspecYaml.version})''',
|
||||
'''Deploying ${artifact.path} to ${shorebirdYaml.appId} (${pubspecYaml.version})''',
|
||||
);
|
||||
final version = pubspecYaml.version!;
|
||||
await codePushClient.createPatch(
|
||||
artifactPath: artifact.path,
|
||||
baseVersion: '${version.major}.${version.minor}.${version.patch}',
|
||||
productId: shorebirdYaml.productId,
|
||||
appId: shorebirdYaml.appId,
|
||||
channel: 'stable',
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -8,10 +8,10 @@ part 'shorebird_yaml.g.dart';
|
||||
createToJson: false,
|
||||
)
|
||||
class ShorebirdYaml {
|
||||
const ShorebirdYaml({required this.productId});
|
||||
const ShorebirdYaml({required this.appId});
|
||||
|
||||
factory ShorebirdYaml.fromJson(Map<dynamic, dynamic> json) =>
|
||||
_$ShorebirdYamlFromJson(json);
|
||||
|
||||
final String productId;
|
||||
final String appId;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ ShorebirdYaml _$ShorebirdYamlFromJson(Map json) => $checkedCreate(
|
||||
($checkedConvert) {
|
||||
$checkKeys(
|
||||
json,
|
||||
allowedKeys: const ['product_id'],
|
||||
allowedKeys: const ['app_id'],
|
||||
);
|
||||
final val = ShorebirdYaml(
|
||||
productId: $checkedConvert('product_id', (v) => v as String),
|
||||
appId: $checkedConvert('app_id', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {'productId': 'product_id'},
|
||||
fieldKeyMap: const {'appId': 'app_id'},
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ class _MockLogger extends Mock implements Logger {}
|
||||
void main() {
|
||||
group('create', () {
|
||||
const apiKey = 'test-api-key';
|
||||
const productId = 'example';
|
||||
const appId = 'example';
|
||||
const session = Session(apiKey: apiKey);
|
||||
|
||||
late ArgResults argResults;
|
||||
@@ -52,32 +52,32 @@ void main() {
|
||||
});
|
||||
|
||||
test('prompts for app-id when not provided', () async {
|
||||
when(() => logger.prompt(any())).thenReturn(productId);
|
||||
when(() => logger.prompt(any())).thenReturn(appId);
|
||||
await command.run();
|
||||
verify(() => logger.prompt(any())).called(1);
|
||||
verify(() => codePushClient.createApp(productId: productId)).called(1);
|
||||
verify(() => codePushClient.createApp(appId: appId)).called(1);
|
||||
});
|
||||
|
||||
test('uses provided app-id when provided', () async {
|
||||
when(() => argResults['app-id']).thenReturn(productId);
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
await command.run();
|
||||
verifyNever(() => logger.prompt(any()));
|
||||
verify(() => codePushClient.createApp(productId: productId)).called(1);
|
||||
verify(() => codePushClient.createApp(appId: appId)).called(1);
|
||||
});
|
||||
|
||||
test('returns success when app is created', () async {
|
||||
when(() => argResults['app-id']).thenReturn(productId);
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(
|
||||
() => codePushClient.createApp(productId: productId),
|
||||
() => codePushClient.createApp(appId: appId),
|
||||
).thenAnswer((_) async {});
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.success.code);
|
||||
});
|
||||
|
||||
test('returns software error when app creation fails', () async {
|
||||
when(() => argResults['app-id']).thenReturn(productId);
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(
|
||||
() => codePushClient.createApp(productId: productId),
|
||||
() => codePushClient.createApp(appId: appId),
|
||||
).thenThrow(Exception());
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.software.code);
|
||||
|
||||
@@ -18,7 +18,7 @@ class _MockLogger extends Mock implements Logger {}
|
||||
void main() {
|
||||
group('delete', () {
|
||||
const apiKey = 'test-api-key';
|
||||
const productId = 'example';
|
||||
const appId = 'example';
|
||||
const session = Session(apiKey: apiKey);
|
||||
|
||||
late ArgResults argResults;
|
||||
@@ -56,32 +56,32 @@ void main() {
|
||||
|
||||
test('prompts for app-id when not provided', () async {
|
||||
when(() => logger.confirm(any())).thenReturn(false);
|
||||
when(() => logger.prompt(any())).thenReturn(productId);
|
||||
when(() => logger.prompt(any())).thenReturn(appId);
|
||||
await command.run();
|
||||
verify(() => logger.prompt(any())).called(1);
|
||||
});
|
||||
|
||||
test('uses provided app-id when provided', () async {
|
||||
when(() => logger.confirm(any())).thenReturn(false);
|
||||
when(() => argResults['app-id']).thenReturn(productId);
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
await command.run();
|
||||
verifyNever(() => logger.prompt(any()));
|
||||
});
|
||||
|
||||
test('aborts when user does not confirm', () async {
|
||||
when(() => logger.confirm(any())).thenReturn(false);
|
||||
when(() => argResults['app-id']).thenReturn(productId);
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.success.code);
|
||||
verifyNever(() => codePushClient.deleteApp(productId: productId));
|
||||
verifyNever(() => codePushClient.deleteApp(appId: appId));
|
||||
verify(() => logger.info('Aborted.')).called(1);
|
||||
});
|
||||
|
||||
test('returns success when app is deleted', () async {
|
||||
when(() => logger.confirm(any())).thenReturn(true);
|
||||
when(() => argResults['app-id']).thenReturn(productId);
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(
|
||||
() => codePushClient.deleteApp(productId: productId),
|
||||
() => codePushClient.deleteApp(appId: appId),
|
||||
).thenAnswer((_) async {});
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.success.code);
|
||||
@@ -89,9 +89,9 @@ void main() {
|
||||
|
||||
test('returns software error when app deletion fails', () async {
|
||||
when(() => logger.confirm(any())).thenReturn(true);
|
||||
when(() => argResults['app-id']).thenReturn(productId);
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(
|
||||
() => codePushClient.deleteApp(productId: productId),
|
||||
() => codePushClient.deleteApp(appId: appId),
|
||||
).thenThrow(Exception());
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.software.code);
|
||||
|
||||
@@ -58,7 +58,7 @@ void main() {
|
||||
test('returns ExitCode.success when apps are not empty', () async {
|
||||
final apps = [
|
||||
App(
|
||||
productId: 'shorebird-counter',
|
||||
appId: 'shorebird-counter',
|
||||
releases: [
|
||||
Release(
|
||||
version: '1.0.0',
|
||||
|
||||
@@ -18,7 +18,7 @@ name: example
|
||||
version: $version
|
||||
environment:
|
||||
sdk: ">=2.19.0 <3.0.0"''';
|
||||
const productId = 'test-product-id';
|
||||
const appId = 'test-app-id';
|
||||
|
||||
late Logger logger;
|
||||
late Progress progress;
|
||||
@@ -27,10 +27,7 @@ environment:
|
||||
setUp(() {
|
||||
logger = _MockLogger();
|
||||
progress = _MockProgress();
|
||||
command = InitCommand(
|
||||
logger: logger,
|
||||
buildUuid: () => productId,
|
||||
);
|
||||
command = InitCommand(logger: logger, buildUuid: () => appId);
|
||||
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
});
|
||||
@@ -77,21 +74,21 @@ environment:
|
||||
});
|
||||
|
||||
test('detects existing shorebird.yaml', () async {
|
||||
const existingProductId = 'existing-product-id';
|
||||
const existingAppId = 'existing-app-id';
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
File(
|
||||
p.join(tempDir.path, 'pubspec.yaml'),
|
||||
).writeAsStringSync(pubspecYamlContent);
|
||||
File(
|
||||
p.join(tempDir.path, 'shorebird.yaml'),
|
||||
).writeAsStringSync('product_id: $existingProductId');
|
||||
).writeAsStringSync('app_id: $existingAppId');
|
||||
await IOOverrides.runZoned(
|
||||
command.run,
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
expect(
|
||||
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
|
||||
contains('product_id: $existingProductId'),
|
||||
contains('app_id: $existingAppId'),
|
||||
);
|
||||
verify(() => progress.update('"shorebird.yaml" already exists.'));
|
||||
});
|
||||
@@ -107,7 +104,7 @@ environment:
|
||||
);
|
||||
expect(
|
||||
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
|
||||
contains('product_id: $productId'),
|
||||
contains('app_id: $appId'),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -127,7 +124,7 @@ flutter:
|
||||
);
|
||||
expect(
|
||||
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
|
||||
contains('product_id: $productId'),
|
||||
contains('app_id: $appId'),
|
||||
);
|
||||
verify(
|
||||
() => progress.update(
|
||||
|
||||
@@ -29,7 +29,7 @@ class _FakeCommandRunner extends Fake implements CommandRunner<int> {
|
||||
void main() {
|
||||
group('publish', () {
|
||||
const session = Session(apiKey: 'test-api-key');
|
||||
const productId = 'test-product-id';
|
||||
const appId = 'test-app-id';
|
||||
const version = '1.2.3';
|
||||
const pubspecYamlContent = '''
|
||||
name: example
|
||||
@@ -54,7 +54,7 @@ flutter:
|
||||
).writeAsStringSync(pubspecYamlContent);
|
||||
File(
|
||||
p.join(tempDir.path, 'shorebird.yaml'),
|
||||
).writeAsStringSync('product_id: $productId');
|
||||
).writeAsStringSync('app_id: $appId');
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ flutter:
|
||||
baseVersion: any(named: 'baseVersion'),
|
||||
artifactPath: any(named: 'artifactPath'),
|
||||
channel: any(named: 'channel'),
|
||||
productId: any(named: 'productId'),
|
||||
appId: any(named: 'appId'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
});
|
||||
@@ -159,7 +159,7 @@ flutter:
|
||||
baseVersion: any(named: 'baseVersion'),
|
||||
artifactPath: any(named: 'artifactPath'),
|
||||
channel: any(named: 'channel'),
|
||||
productId: any(named: 'productId'),
|
||||
appId: any(named: 'appId'),
|
||||
),
|
||||
).thenThrow(error);
|
||||
final tempDir = setUpTempDir();
|
||||
@@ -173,8 +173,7 @@ flutter:
|
||||
expect(exitCode, ExitCode.software.code);
|
||||
});
|
||||
|
||||
test('succeeds when publish is successful using existing product id',
|
||||
() async {
|
||||
test('succeeds when publish is successful using existing app id', () async {
|
||||
final tempDir = setUpTempDir();
|
||||
final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync();
|
||||
when(() => argResults.rest).thenReturn([artifact.path]);
|
||||
@@ -186,7 +185,7 @@ flutter:
|
||||
verify(
|
||||
() => codePushClient.createPatch(
|
||||
baseVersion: version,
|
||||
productId: productId,
|
||||
appId: appId,
|
||||
artifactPath: artifact.path,
|
||||
channel: 'stable',
|
||||
),
|
||||
|
||||
@@ -26,7 +26,7 @@ Future<void> main() async {
|
||||
final engine = await client.downloadEngine('latest');
|
||||
|
||||
// Create a new Shorebird application.
|
||||
await client.createApp(productId: '<PRODUCT ID>');
|
||||
await client.createApp(appId: '<APP ID>');
|
||||
|
||||
// List all apps.
|
||||
final apps = await client.getApps();
|
||||
@@ -35,7 +35,7 @@ Future<void> main() async {
|
||||
await client.createPatch(
|
||||
artifactPath: '<PATH TO ARTIFACT>', // e.g. 'libapp.so'
|
||||
baseVersion: '<BASE VERSION>', // e.g. '1.0.0'
|
||||
productId: '<PRODUCT ID>', // e.g. 'shorebird-example'
|
||||
appId: '<APP ID>', // e.g. 'shorebird-example'
|
||||
channel: '<CHANNEL>', // e.g. 'stable'
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Future<void> main() async {
|
||||
final engine = await client.downloadEngine('latest');
|
||||
|
||||
// Create a new Shorebird application.
|
||||
await client.createApp(productId: '<PRODUCT ID>');
|
||||
await client.createApp(appId: '<APP ID>');
|
||||
|
||||
// List all apps.
|
||||
final apps = await client.getApps();
|
||||
@@ -18,7 +18,7 @@ Future<void> main() async {
|
||||
await client.createPatch(
|
||||
artifactPath: '<PATH TO ARTIFACT>', // e.g. 'libapp.so'
|
||||
baseVersion: '<BASE VERSION>', // e.g. '1.0.0'
|
||||
productId: '<PRODUCT ID>', // e.g. 'shorebird-example'
|
||||
appId: '<APP ID>', // e.g. 'shorebird-example'
|
||||
channel: '<CHANNEL>', // e.g. 'stable'
|
||||
);
|
||||
|
||||
|
||||
@@ -27,12 +27,12 @@ class CodePushClient {
|
||||
|
||||
Map<String, String> get _apiKeyHeader => {'x-api-key': _apiKey};
|
||||
|
||||
/// Create a new app with the provided [productId].
|
||||
Future<void> createApp({required String productId}) async {
|
||||
/// Create a new app with the provided [appId].
|
||||
Future<void> createApp({required String appId}) async {
|
||||
final response = await _httpClient.post(
|
||||
Uri.parse('$hostedUri/api/v1/apps'),
|
||||
headers: _apiKeyHeader,
|
||||
body: json.encode({'product_id': productId}),
|
||||
body: json.encode({'app_id': appId}),
|
||||
);
|
||||
|
||||
if (response.statusCode != HttpStatus.created) {
|
||||
@@ -43,7 +43,7 @@ class CodePushClient {
|
||||
/// Create a new patch.
|
||||
Future<void> createPatch({
|
||||
required String baseVersion,
|
||||
required String productId,
|
||||
required String appId,
|
||||
required String channel,
|
||||
required String artifactPath,
|
||||
}) async {
|
||||
@@ -55,7 +55,7 @@ class CodePushClient {
|
||||
request.files.add(file);
|
||||
request.fields.addAll({
|
||||
'base_version': baseVersion,
|
||||
'product_id': productId,
|
||||
'app_id': appId,
|
||||
'channel': channel,
|
||||
});
|
||||
request.headers.addAll(_apiKeyHeader);
|
||||
@@ -66,10 +66,10 @@ class CodePushClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the app with the provided [productId].
|
||||
Future<void> deleteApp({required String productId}) async {
|
||||
/// Delete the app with the provided [appId].
|
||||
Future<void> deleteApp({required String appId}) async {
|
||||
final response = await _httpClient.delete(
|
||||
Uri.parse('$hostedUri/api/v1/apps/$productId'),
|
||||
Uri.parse('$hostedUri/api/v1/apps/$appId'),
|
||||
headers: _apiKeyHeader,
|
||||
);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class _FakeBaseRequest extends Fake implements http.BaseRequest {}
|
||||
void main() {
|
||||
group('CodePushClient', () {
|
||||
const apiKey = 'api-key';
|
||||
const productId = 'shorebird-example';
|
||||
const appId = 'shorebird-example';
|
||||
|
||||
late http.Client httpClient;
|
||||
late CodePushClient codePushClient;
|
||||
@@ -48,7 +48,7 @@ void main() {
|
||||
).thenAnswer((_) async => http.Response('', HttpStatus.badRequest));
|
||||
|
||||
expect(
|
||||
codePushClient.createApp(productId: productId),
|
||||
codePushClient.createApp(appId: appId),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
@@ -62,7 +62,7 @@ void main() {
|
||||
),
|
||||
).thenAnswer((_) async => http.Response('', HttpStatus.created));
|
||||
|
||||
await codePushClient.createApp(productId: productId);
|
||||
await codePushClient.createApp(appId: appId);
|
||||
|
||||
final uri = verify(
|
||||
() => httpClient.post(
|
||||
@@ -96,7 +96,7 @@ void main() {
|
||||
codePushClient.createPatch(
|
||||
artifactPath: fixture.path,
|
||||
baseVersion: '1.0.0',
|
||||
productId: 'shorebird-example',
|
||||
appId: 'shorebird-example',
|
||||
channel: 'stable',
|
||||
),
|
||||
throwsA(isA<Exception>()),
|
||||
@@ -117,7 +117,7 @@ void main() {
|
||||
await codePushClient.createPatch(
|
||||
artifactPath: fixture.path,
|
||||
baseVersion: '1.0.0',
|
||||
productId: 'shorebird-example',
|
||||
appId: 'shorebird-example',
|
||||
channel: 'stable',
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ void main() {
|
||||
).thenAnswer((_) async => http.Response('', HttpStatus.badRequest));
|
||||
|
||||
expect(
|
||||
codePushClient.deleteApp(productId: productId),
|
||||
codePushClient.deleteApp(appId: appId),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
@@ -152,7 +152,7 @@ void main() {
|
||||
),
|
||||
).thenAnswer((_) async => http.Response('', HttpStatus.noContent));
|
||||
|
||||
await codePushClient.deleteApp(productId: productId);
|
||||
await codePushClient.deleteApp(appId: appId);
|
||||
|
||||
final uri = verify(
|
||||
() => httpClient.delete(
|
||||
@@ -164,7 +164,7 @@ void main() {
|
||||
expect(
|
||||
uri,
|
||||
codePushClient.hostedUri.replace(
|
||||
path: '/api/v1/apps/$productId',
|
||||
path: '/api/v1/apps/$appId',
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -241,7 +241,7 @@ void main() {
|
||||
test('completes when request succeeds (populated)', () async {
|
||||
final expected = [
|
||||
App(
|
||||
productId: 'shorebird-example',
|
||||
appId: 'shorebird-example',
|
||||
releases: [
|
||||
Release(
|
||||
version: '1.0.0',
|
||||
@@ -276,7 +276,7 @@ void main() {
|
||||
],
|
||||
),
|
||||
App(
|
||||
productId: 'shorebird-counter',
|
||||
appId: 'shorebird-counter',
|
||||
releases: [
|
||||
Release(
|
||||
version: '1.0.0',
|
||||
|
||||
@@ -9,7 +9,7 @@ part 'app.g.dart';
|
||||
@JsonSerializable()
|
||||
class App {
|
||||
/// {@macro app}
|
||||
App({required this.productId, List<Release>? releases})
|
||||
App({required this.appId, List<Release>? releases})
|
||||
: releases = releases ?? [];
|
||||
|
||||
/// Converts a Map<String, dynamic> to an [App]
|
||||
@@ -18,8 +18,8 @@ class App {
|
||||
/// Converts a [App] to a Map<String, dynamic>
|
||||
Map<String, dynamic> toJson() => _$AppToJson(this);
|
||||
|
||||
/// The product ID of the app.
|
||||
final String productId;
|
||||
/// The ID of the app.
|
||||
final String appId;
|
||||
|
||||
/// List of releases associated with this app.
|
||||
final List<Release> releases;
|
||||
|
||||
@@ -13,7 +13,7 @@ App _$AppFromJson(Map<String, dynamic> json) => $checkedCreate(
|
||||
json,
|
||||
($checkedConvert) {
|
||||
final val = App(
|
||||
productId: $checkedConvert('product_id', (v) => v as String),
|
||||
appId: $checkedConvert('app_id', (v) => v as String),
|
||||
releases: $checkedConvert(
|
||||
'releases',
|
||||
(v) => (v as List<dynamic>?)
|
||||
@@ -22,10 +22,10 @@ App _$AppFromJson(Map<String, dynamic> json) => $checkedCreate(
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {'productId': 'product_id'},
|
||||
fieldKeyMap: const {'appId': 'app_id'},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppToJson(App instance) => <String, dynamic>{
|
||||
'product_id': instance.productId,
|
||||
'app_id': instance.appId,
|
||||
'releases': instance.releases.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ void main() {
|
||||
apiKey: 'api_key1',
|
||||
apps: [
|
||||
App(
|
||||
productId: 'app1',
|
||||
appId: 'app1',
|
||||
releases: [
|
||||
Release(
|
||||
version: '1.0.0',
|
||||
@@ -44,14 +44,14 @@ void main() {
|
||||
Release(version: '1.0.1'),
|
||||
],
|
||||
),
|
||||
App(productId: 'app2'),
|
||||
App(appId: 'app2'),
|
||||
],
|
||||
),
|
||||
Account(
|
||||
apiKey: 'api_key2',
|
||||
apps: [
|
||||
App(
|
||||
productId: 'app2',
|
||||
appId: 'app2',
|
||||
releases: [
|
||||
Release(
|
||||
version: '1.0.0',
|
||||
|
||||
@@ -26,7 +26,7 @@ fn main() {
|
||||
vm_path: "libflutter.so".to_owned(),
|
||||
};
|
||||
let yaml_str = "
|
||||
product_id: demo
|
||||
app_id: demo
|
||||
channel: stable
|
||||
base_url: http://localhost:8000
|
||||
";
|
||||
|
||||
@@ -12,7 +12,7 @@ class AppParameters extends ffi.Struct {
|
||||
// ignore: non_constant_identifier_names
|
||||
external ffi.Pointer<Utf8> client_id;
|
||||
// ignore: non_constant_identifier_names
|
||||
external ffi.Pointer<Utf8> product_id;
|
||||
external ffi.Pointer<Utf8> app_id;
|
||||
// ignore: non_constant_identifier_names
|
||||
external ffi.Pointer<Utf8> base_version;
|
||||
// ignore: non_constant_identifier_names
|
||||
@@ -26,7 +26,7 @@ class AppParameters extends ffi.Struct {
|
||||
|
||||
static ffi.Pointer<AppParameters> allocate(
|
||||
{required String clientId,
|
||||
required String productId,
|
||||
required String appId,
|
||||
required String version,
|
||||
required String channel,
|
||||
required String? updateUrl,
|
||||
@@ -35,7 +35,7 @@ class AppParameters extends ffi.Struct {
|
||||
required String cacheDir}) {
|
||||
var config = calloc<AppParameters>();
|
||||
config.ref.client_id = clientId.toNativeUtf8();
|
||||
config.ref.product_id = productId.toNativeUtf8();
|
||||
config.ref.app_id = appId.toNativeUtf8();
|
||||
config.ref.base_version = version.toNativeUtf8();
|
||||
config.ref.channel = channel.toNativeUtf8();
|
||||
if (updateUrl != null) {
|
||||
@@ -49,7 +49,7 @@ class AppParameters extends ffi.Struct {
|
||||
|
||||
static void free(ffi.Pointer<AppParameters> config) {
|
||||
calloc.free(config.ref.client_id);
|
||||
calloc.free(config.ref.product_id);
|
||||
calloc.free(config.ref.app_id);
|
||||
calloc.free(config.ref.base_version);
|
||||
calloc.free(config.ref.channel);
|
||||
calloc.free(config.ref.update_url);
|
||||
|
||||
@@ -51,7 +51,7 @@ class Updater {
|
||||
// inside a Flutter app.
|
||||
static void initUpdaterLibrary({
|
||||
required String clientId,
|
||||
required String productId,
|
||||
required String appId,
|
||||
required String version,
|
||||
required String channel,
|
||||
required String? updateUrl,
|
||||
@@ -60,7 +60,7 @@ class Updater {
|
||||
required String cacheDir,
|
||||
}) {
|
||||
var config = AppParameters.allocate(
|
||||
productId: productId,
|
||||
appId: appId,
|
||||
version: version,
|
||||
channel: channel,
|
||||
updateUrl: updateUrl,
|
||||
|
||||
@@ -11,7 +11,7 @@ void main(List<String> args) async {
|
||||
|
||||
Updater.initUpdaterLibrary(
|
||||
clientId: 'my-client-id',
|
||||
productId: 'product',
|
||||
appId: 'demo',
|
||||
version: '1.0.0',
|
||||
channel: 'stable',
|
||||
updateUrl: null,
|
||||
|
||||
@@ -37,7 +37,7 @@ pub struct ResolvedConfig {
|
||||
is_initialized: bool,
|
||||
pub cache_dir: String,
|
||||
pub channel: String,
|
||||
pub product_id: String,
|
||||
pub app_id: String,
|
||||
pub base_version: String,
|
||||
pub original_libapp_path: String,
|
||||
pub vm_path: String,
|
||||
@@ -50,7 +50,7 @@ impl ResolvedConfig {
|
||||
is_initialized: false,
|
||||
cache_dir: String::new(),
|
||||
channel: String::new(),
|
||||
product_id: String::new(),
|
||||
app_id: String::new(),
|
||||
base_version: String::new(),
|
||||
original_libapp_path: String::new(),
|
||||
vm_path: String::new(),
|
||||
@@ -76,7 +76,7 @@ pub fn set_config(config: AppConfig, yaml: YamlConfig) {
|
||||
.unwrap_or(DEFAULT_CHANNEL)
|
||||
.to_owned();
|
||||
lock.cache_dir = config.cache_dir.to_string();
|
||||
lock.product_id = yaml.product_id.to_string();
|
||||
lock.app_id = yaml.app_id.to_string();
|
||||
lock.base_version = config.base_version.to_string();
|
||||
lock.original_libapp_path = config.original_libapp_path.to_string();
|
||||
lock.vm_path = config.vm_path.to_string();
|
||||
|
||||
@@ -39,7 +39,7 @@ pub fn send_patch_check_request(
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let mut body = HashMap::new();
|
||||
body.insert("client_id", state.client_id());
|
||||
body.insert("product_id", config.product_id.clone());
|
||||
body.insert("app_id", config.app_id.clone());
|
||||
body.insert("channel", config.channel.clone());
|
||||
body.insert("base_version", config.base_version.clone());
|
||||
if let Some(patch) = patch {
|
||||
|
||||
@@ -3,9 +3,9 @@ use serde::Deserialize;
|
||||
/// Struct for parsing shorebird.yaml.
|
||||
#[derive(Deserialize)]
|
||||
pub struct YamlConfig {
|
||||
/// Product ID. Required. Generated by Shorebird and included
|
||||
/// App ID. Required. Generated by Shorebird and included
|
||||
/// in your app to identify which app/channel/version triple to update.
|
||||
pub product_id: String,
|
||||
pub app_id: String,
|
||||
/// Update channel name. Defaults to "stable" if not set.
|
||||
pub channel: Option<String>,
|
||||
/// Update URL. Defaults to the default update URL if not set.
|
||||
|
||||
Reference in New Issue
Block a user