Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Artifacts Folder Structure #1843

Merged
merged 13 commits into from
Feb 7, 2025
27 changes: 23 additions & 4 deletions modules/ensemble/lib/ensemble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:ensemble/framework/app_info.dart';
import 'package:ensemble/framework/logging/console_log_provider.dart';
import 'package:ensemble/framework/logging/log_manager.dart';
import 'package:ensemble/framework/logging/log_provider.dart';
import 'package:ensemble/framework/ensemble_config_service.dart';
import 'package:ensemble/framework/route_observer.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:ensemble/framework/secrets.dart';
Expand Down Expand Up @@ -105,7 +106,7 @@ class Ensemble extends WithEnsemble with EnsembleRouteObserver {
try {
// this code block is guaranteed to run at most once
await StorageManager().init();
await SecretsStore().initialize();
// await SecretsStore().initialize();
Device().initDeviceInfo();
AppInfo().initPackageInfo(_config);
_completer!.complete();
Expand Down Expand Up @@ -139,10 +140,12 @@ class Ensemble extends WithEnsemble with EnsembleRouteObserver {
if (_config != null) {
return Future<EnsembleConfig>.value(_config);
}
// Intialize the config service to get `ensemble-config.yaml` file to access the configuration using static property as `EnsembleConfigService.config`
await EnsembleConfigService.initialize();
await SecretsStore().initialize();

final yamlString =
await rootBundle.loadString('ensemble/ensemble-config.yaml');
final YamlMap yamlMap = loadYaml(yamlString);
// get the config YAML
final YamlMap yamlMap = EnsembleConfigService.config;

Account account = Account.fromYaml(yamlMap['accounts']);
dynamic analyticsConfig = yamlMap['analytics'];
Expand Down Expand Up @@ -174,6 +177,20 @@ class Ensemble extends WithEnsemble with EnsembleRouteObserver {
envOverrides = {};
env.forEach((key, value) => envOverrides![key.toString()] = value);
}
// Read environmental variables from config/appConfig.json
try {
dynamic path = yamlMap["definitions"]?['local']?["path"];
final configString = await rootBundle
.loadString('${path}/config/appConfig.json');
final Map<String, dynamic> configMap = json.decode(configString);
// Loop through the envVariables from appConfig.json file and add them to the envOverrides
configMap["envVariables"].forEach((key, value) {
envOverrides![key] = value;
});
} catch(e) {
debugPrint("appConfig.json file doesn't exist");
}

DefinitionProvider definitionProvider = DefinitionProvider.from(yamlMap);
_config = EnsembleConfig(
definitionProvider: await definitionProvider.init(),
Expand Down Expand Up @@ -452,9 +469,11 @@ class EnsembleConfig {
ThemeData getAppTheme() {
return ThemeManager().getAppTheme(appBundle?.theme);
}

bool hasLegacyCustomAppTheme() {
return ThemeManager().hasLegacyCustomAppTheme(appBundle?.theme);
}

/// retrieve the global widgets/codes/APIs
Map? getResources() {
return appBundle?.resources;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import 'dart:io';
import 'dart:ui';

import 'package:ensemble/ensemble.dart';
import 'package:ensemble/framework/definition_providers/provider.dart';
import 'package:ensemble/framework/widget/screen.dart';
import 'package:ensemble/util/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_i18n/loaders/decoders/yaml_decode_strategy.dart';
import 'package:yaml/yaml.dart';
import 'dart:convert';
import 'package:flutter/foundation.dart' as foundation;

/**
Expand All @@ -34,9 +37,8 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
{String? screenId, String? screenName}) async {
// Note: Web with local definition caches even if we disable browser cache
// so you may need to re-run the app on definition changes
var pageStr = await rootBundle.loadString(
'$path${screenId ?? screenName ?? appHome}.yaml',
cache: foundation.kReleaseMode);
var pageStr = await rootBundle
.loadString('${path}screens/${screenId ?? screenName ?? appHome}.yaml');
if (pageStr.isEmpty) {
return ScreenDefinition(YamlMap());
}
Expand All @@ -45,15 +47,24 @@ class LocalDefinitionProvider extends FileDefinitionProvider {

@override
Future<AppBundle> getAppBundle({bool? bypassCache = false}) async {
YamlMap? config = await _readFile('config.ensemble');
if (config != null) {
appConfig = UserAppConfig(
baseUrl: config['app']?['baseUrl'],
useBrowserUrl: Utils.optionalBool(config['app']?['useBrowserUrl']));
try {
final configString =
await rootBundle.loadString('${path}/config/appConfig.json');
final Map<String, dynamic> appConfigMap = json.decode(configString);
if (appConfigMap.isNotEmpty) {
appConfig = UserAppConfig(
baseUrl: appConfigMap["baseUrl"],
useBrowserUrl: Utils.optionalBool(appConfigMap['useBrowserUrl']),
envVariables: appConfigMap["envVariables"]);
}
} catch (e) {
// ignore error
}

return AppBundle(
theme: await _readFile('theme.ensemble'),
resources: await _readFile('resources.ensemble'));
theme: await _readFile('theme.yaml'),
resources: await getCombinedAppBundle(), // get the combined app bundle for local scripts and widgets
);
}

Future<YamlMap?> _readFile(String file) async {
Expand All @@ -66,6 +77,72 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
return null;
}

Future<Map?> getCombinedAppBundle() async {
Map code = {};
Map output = {};
Map widgets = {};

try {
// Get the manifest content
final manifestContent =
await rootBundle.loadString(path + '.manifest.json');
final Map<String, dynamic> manifestMap = json.decode(manifestContent);
sharjeelyunus marked this conversation as resolved.
Show resolved Hide resolved

// Process App Widgets
try {
if (manifestMap['widgets'] != null) {
final List<Map<String, dynamic>> widgetsList =
List<Map<String, dynamic>>.from(manifestMap['widgets']);

for (var widgetItem in widgetsList) {
try {
// Load the widget content in YamlMap
final widgetContent =
await _readFile("widgets/${widgetItem["name"]}.yaml");
if (widgetContent is YamlMap) {
widgets[widgetItem["name"]] = widgetContent["Widget"];
} else {
debugPrint('Content in ${widgetItem["name"]} is not a YamlMap');
}
} catch (e) {
// ignore error
}
}
}
} catch (e) {
debugPrint('Error processing widgets: $e');
}

// Process App Scripts
try {
if (manifestMap['scripts'] != null) {
final List<Map<String, dynamic>> scriptsList =
List<Map<String, dynamic>>.from(manifestMap['scripts']);

for (var script in scriptsList) {
try {
// Load the script content in string
final scriptContent = await rootBundle
.loadString("${path}scripts/${script["name"]}.yaml");
code[script["name"]] = scriptContent;
} catch (e) {
// ignore error
}
}
}
} catch (e) {
debugPrint('Error processing scripts: $e');
}

output[ResourceArtifactEntry.Widgets.name] = widgets;
output[ResourceArtifactEntry.Scripts.name] = code;

return output;
} catch (e) {
return null;
}
}

@override
UserAppConfig? getAppConfig() {
return appConfig;
Expand Down
19 changes: 19 additions & 0 deletions modules/ensemble/lib/framework/ensemble_config_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter/services.dart';
import 'package:yaml/yaml.dart';

// EnsembleConfigService is a service that provides access to the ensemble-config.yaml file through static property once initialized
class EnsembleConfigService {
static YamlMap? _config;

static Future<void> initialize() async {
final yamlString = await rootBundle.loadString('ensemble/ensemble-config.yaml');
_config = loadYaml(yamlString);
}

static YamlMap get config {
if (_config == null) {
throw StateError('EnsembleConfig not initialized');
}
return _config!;
}
}
16 changes: 14 additions & 2 deletions modules/ensemble/lib/framework/secrets.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Secrets Configuration

import 'dart:convert';

import 'package:ensemble/framework/ensemble_config_service.dart';
import 'package:ensemble/framework/storage_manager.dart';
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';

import '../ensemble.dart';
Expand All @@ -24,8 +28,16 @@ class SecretsStore with Invokable {

// add local overrides
try {
await dotenv.load();
secrets.addAll(dotenv.env);
String path =
EnsembleConfigService.config["definitions"]?['local']?["path"];
final secretsString =
await rootBundle.loadString('${path}/config/secrets.json');
final Map<String, dynamic> appSecretsMap = json.decode(secretsString);
// await dotenv.load();
Copy link
Member

@sharjeelyunus sharjeelyunus Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this unnecessary comment

appSecretsMap["secrets"].forEach((key, value) {
secrets![key] = value;
// secrets.addAll(appSecretsMap['secrets']);
});
} catch (_) {}

for (var entry in secrets.entries) {
Expand Down
6 changes: 4 additions & 2 deletions modules/ensemble/lib/util/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:math';
import 'dart:ui';
import 'package:ensemble/ensemble.dart';
import 'package:ensemble/ensemble_app.dart';
import 'package:ensemble/framework/ensemble_config_service.dart';
import 'package:ensemble/framework/stub/location_manager.dart';
import 'package:ensemble/framework/theme/theme_manager.dart';
import 'package:ensemble_ts_interpreter/invokables/UserLocale.dart';
Expand Down Expand Up @@ -921,10 +922,11 @@ class Utils {
return strings[0];
}

/// prefix the asset with the root directory (i.e. ensemble/assets/), plus
/// prefix the asset with the app root directory (i.e. ensemble/apps/<app-name>/assets/), plus
/// stripping any unnecessary query params (e.g. anything after the first ?)
static String getLocalAssetFullPath(String asset) {
return 'ensemble/assets/${stripQueryParamsFromAsset(asset)}';
String path = EnsembleConfigService.config["definitions"]?['local']?["path"];
return '${path}/asset/${stripQueryParamsFromAsset(asset)}';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be assets? instead of asset?

also the assets are stored locally in ensemble/assets folder in case of ensemble provider. wouldn't this break the assets for ensemble provider?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it should be assets

and for Ensemble provider, No it won't because for ensemble provider source of each assets is cloud-based. we are not storing assets locally in case of ensemble provider, so in case of ensemble provider this function will not be called to create a local path

Copy link
Member

@sharjeelyunus sharjeelyunus Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NO, we are storing local assets in ensemble provider as well, for studio yes we provide the cloud-based urls, but when user creates a build, we store the assets in the old assets folder for ensemble provider

}

static bool isMemoryPath(String path) {
Expand Down