feat: multi-package loading with base framework support
- Add RuntimeOptions.basePackages for loading framework packages before game package - Add ScriptEngine.loadPackages() for multi-package module merging - LuaDardoScriptEngine merges modules from all packages, game overrides framework - PackageActivationController loads base packages first, then game package - GamePackageManifest parses optional 'base' field - Update docs: README, quick-start, lua-package-format, architecture - Update all test mocks with loadPackages() implementation
This commit is contained in:
15
README.md
15
README.md
@@ -19,6 +19,7 @@ It is designed for Flutter apps that want to host Lua-authored 2D games or inter
|
|||||||
- Runtime commands for movement, fading, scaling, rotation, sequencing, audio, resources, toast, clipboard, and Spine animation.
|
- Runtime commands for movement, fading, scaling, rotation, sequencing, audio, resources, toast, clipboard, and Spine animation.
|
||||||
- Shared Lua helper modules under `assets/runtime/lua/`.
|
- Shared Lua helper modules under `assets/runtime/lua/`.
|
||||||
- Configurable Runtime Lua asset root via `RuntimeOptions.runtimeLuaRoot`.
|
- Configurable Runtime Lua asset root via `RuntimeOptions.runtimeLuaRoot`.
|
||||||
|
- Multi-package loading: shared framework packages loaded once, game packages loaded on top.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -59,6 +60,18 @@ LuaGameWidget(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
With a shared framework package:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
LuaGameWidget(
|
||||||
|
gameId: 'ludo',
|
||||||
|
runtimeOptions: const RuntimeOptions(
|
||||||
|
runtimeLuaRoot: 'packages/flame_lua_runtime/assets/runtime/lua',
|
||||||
|
basePackages: ['_framework'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
Your app should provide game package assets such as:
|
Your app should provide game package assets such as:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -105,7 +118,7 @@ For AI agents and maintainers, start with:
|
|||||||
- [`AGENTS.md`](AGENTS.md) — package boundaries, rules, public API, and validation commands.
|
- [`AGENTS.md`](AGENTS.md) — package boundaries, rules, public API, and validation commands.
|
||||||
- [`docs/quick-start.md`](docs/quick-start.md) — host app integration.
|
- [`docs/quick-start.md`](docs/quick-start.md) — host app integration.
|
||||||
- [`docs/architecture.md`](docs/architecture.md) — Dart/Lua/Flame responsibilities.
|
- [`docs/architecture.md`](docs/architecture.md) — Dart/Lua/Flame responsibilities.
|
||||||
- [`docs/lua-package-format.md`](docs/lua-package-format.md) — manifest and Lua package rules.
|
- [`docs/lua-package-format.md`](docs/lua-package-format.md) — manifest, Lua package rules, and multi-package loading.
|
||||||
- [`docs/protocol.md`](docs/protocol.md) — RuntimeEvent, GameDiff, RuntimeNode, RuntimeCommand boundary.
|
- [`docs/protocol.md`](docs/protocol.md) — RuntimeEvent, GameDiff, RuntimeNode, RuntimeCommand boundary.
|
||||||
- [`docs/validation.md`](docs/validation.md) — checks, smoke tests, and release flow.
|
- [`docs/validation.md`](docs/validation.md) — checks, smoke tests, and release flow.
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,28 @@ Package dependency root:
|
|||||||
packages/flame_lua_runtime/assets/runtime/lua
|
packages/flame_lua_runtime/assets/runtime/lua
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Multi-package loading
|
||||||
|
|
||||||
|
The runtime supports loading multiple packages in sequence, with module merging:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RuntimeOptions.basePackages = ['_framework']
|
||||||
|
-> load _framework package (modules: app, diff, ids, net, ...)
|
||||||
|
-> load game package (modules: state, rules, main, ...)
|
||||||
|
-> merge into flat _moduleScripts map
|
||||||
|
-> game modules override framework modules on name collision
|
||||||
|
-> execute game entry script
|
||||||
|
```
|
||||||
|
|
||||||
|
Key classes:
|
||||||
|
|
||||||
|
- `RuntimeOptions.basePackages` — ordered list of framework package IDs.
|
||||||
|
- `PackageActivationController._prepareCandidate()` — loads base packages first, then game package, passes combined list to `ScriptEngine.loadPackages()`.
|
||||||
|
- `LuaDardoScriptEngine.loadPackages()` — iterates all packages, merges `manifest.modules` into `_moduleScripts`, executes entry from last package.
|
||||||
|
- `GamePackageManifest.base` — optional metadata field declaring framework dependency.
|
||||||
|
|
||||||
|
Module resolution is flat: `runtime.import("xxx")` looks up `_moduleScripts[xxx]`. Game modules and framework modules share the same namespace. Later-loaded packages win on collision.
|
||||||
|
|
||||||
## Safety model
|
## Safety model
|
||||||
|
|
||||||
- Lua module loading is manifest-declared.
|
- Lua module loading is manifest-declared.
|
||||||
|
|||||||
@@ -64,6 +64,53 @@ runtime:*.lua
|
|||||||
|
|
||||||
`runtime:` paths must not contain `/`, `..`, or an empty filename.
|
`runtime:` paths must not contain `/`, `..`, or an empty filename.
|
||||||
|
|
||||||
|
## Base packages
|
||||||
|
|
||||||
|
A game manifest can declare a `base` field to indicate it depends on a framework package:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gameId": "ludo",
|
||||||
|
"base": "_framework",
|
||||||
|
"modules": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `base` field is metadata. Actual loading is controlled by `RuntimeOptions.basePackages`:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
LuaGameWidget(
|
||||||
|
gameId: 'ludo',
|
||||||
|
runtimeOptions: const RuntimeOptions(
|
||||||
|
basePackages: ['_framework'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Loading order:
|
||||||
|
|
||||||
|
1. Base packages are loaded first, in `basePackages` order.
|
||||||
|
2. The game package is loaded last.
|
||||||
|
3. All modules are merged into a flat map.
|
||||||
|
4. Later packages override earlier packages on name collision.
|
||||||
|
5. The entry script always comes from the last (game) package.
|
||||||
|
|
||||||
|
This means a game can override any framework module by declaring a module with the same name in its own manifest.
|
||||||
|
|
||||||
|
## Multi-package module resolution
|
||||||
|
|
||||||
|
When Lua code calls `runtime.import("xxx")`:
|
||||||
|
|
||||||
|
1. Look up `xxx` in the merged module map (game modules first, then framework).
|
||||||
|
2. If not found, throw `FormatException: Lua module is not declared in manifest.modules`.
|
||||||
|
|
||||||
|
Framework modules are transparent to game code:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local app = runtime.import("app") -- resolved from framework
|
||||||
|
local state = runtime.import("state") -- resolved from game
|
||||||
|
```
|
||||||
|
|
||||||
## Entry module
|
## Entry module
|
||||||
|
|
||||||
The manifest `entry` module should expose lifecycle/event functions expected by the script engine. Keep game-specific state in Lua modules and return runtime diffs/commands through the approved protocol.
|
The manifest `entry` module should expose lifecycle/event functions expected by the script engine. Keep game-specific state in Lua modules and return runtime diffs/commands through the approved protocol.
|
||||||
|
|||||||
@@ -98,3 +98,77 @@ flutter run --dart-define=LUA_GAME_ID=flight
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Multi-package loading (base + game)
|
||||||
|
|
||||||
|
When multiple games share common modules (event router, diff utilities, panel stack, network layer), extract them into a framework package and load it before the game package.
|
||||||
|
|
||||||
|
Framework package structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
assets/games/_framework/
|
||||||
|
manifest.json
|
||||||
|
scripts/
|
||||||
|
app.lua
|
||||||
|
diff.lua
|
||||||
|
event_router.lua
|
||||||
|
ids.lua
|
||||||
|
net.lua
|
||||||
|
panel_stack.lua
|
||||||
|
```
|
||||||
|
|
||||||
|
Framework manifest:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gameId": "_framework",
|
||||||
|
"name": "Lua Game Framework",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"runtimeApiVersion": 1,
|
||||||
|
"entry": "scripts/app.lua",
|
||||||
|
"assetsBase": "assets",
|
||||||
|
"modules": {
|
||||||
|
"app": "scripts/app.lua",
|
||||||
|
"diff": "scripts/diff.lua",
|
||||||
|
"event_router": "scripts/event_router.lua",
|
||||||
|
"ids": "scripts/ids.lua",
|
||||||
|
"net": "scripts/net.lua",
|
||||||
|
"panel_stack": "scripts/panel_stack.lua"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Game manifest (no framework modules needed):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gameId": "ludo",
|
||||||
|
"base": "_framework",
|
||||||
|
"modules": {
|
||||||
|
"state": "scripts/state.lua",
|
||||||
|
"rules": "scripts/rules.lua",
|
||||||
|
"main": "scripts/main.lua"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Game code imports framework modules transparently:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local app = runtime.import("app") -- from framework
|
||||||
|
local state = runtime.import("state") -- from game
|
||||||
|
```
|
||||||
|
|
||||||
|
Embed with base packages:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
LuaGameWidget(
|
||||||
|
gameId: 'ludo',
|
||||||
|
runtimeOptions: const RuntimeOptions(
|
||||||
|
runtimeLuaRoot: 'packages/flame_lua_runtime/assets/runtime/lua',
|
||||||
|
basePackages: ['_framework'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Module resolution order: game package modules override framework modules with the same name. The entry script always comes from the last package (game package).
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ class FlameLuaGame extends FlameGame with PanDetector, ScrollDetector {
|
|||||||
resources: _createResourceManager(),
|
resources: _createResourceManager(),
|
||||||
scriptEngine: _bootstrapScriptEngine,
|
scriptEngine: _bootstrapScriptEngine,
|
||||||
audio: _createAudioManager(),
|
audio: _createAudioManager(),
|
||||||
|
runtimeOptions: runtimeOptions,
|
||||||
resourceManagerFactory: _createResourceManager,
|
resourceManagerFactory: _createResourceManager,
|
||||||
audioManagerFactory: _createAudioManager,
|
audioManagerFactory: _createAudioManager,
|
||||||
scriptEngineFactory: _scriptEngineFactory,
|
scriptEngineFactory: _scriptEngineFactory,
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
class RuntimeOptions {
|
class RuntimeOptions {
|
||||||
const RuntimeOptions({this.runtimeLuaRoot = defaultRuntimeLuaRoot});
|
const RuntimeOptions({
|
||||||
|
this.runtimeLuaRoot = defaultRuntimeLuaRoot,
|
||||||
|
this.basePackages = const [],
|
||||||
|
});
|
||||||
|
|
||||||
static const defaultRuntimeLuaRoot = 'assets/runtime/lua';
|
static const defaultRuntimeLuaRoot = 'assets/runtime/lua';
|
||||||
|
|
||||||
final String runtimeLuaRoot;
|
final String runtimeLuaRoot;
|
||||||
|
|
||||||
|
// 框架包 gameId 列表,按顺序先于游戏包加载。
|
||||||
|
// 后加载的同名模块覆盖先加载的。
|
||||||
|
final List<String> basePackages;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import '../audio/runtime_audio_manager.dart';
|
import '../audio/runtime_audio_manager.dart';
|
||||||
|
import '../game/runtime_options.dart';
|
||||||
import '../models/game_diff.dart';
|
import '../models/game_diff.dart';
|
||||||
import '../resources/game_resource_manager.dart';
|
import '../resources/game_resource_manager.dart';
|
||||||
import '../scripting/runtime_script_services.dart';
|
import '../scripting/runtime_script_services.dart';
|
||||||
@@ -15,6 +16,7 @@ class PackageActivationController {
|
|||||||
required this.scriptEngine,
|
required this.scriptEngine,
|
||||||
this.audio,
|
this.audio,
|
||||||
this.runtimeApiVersion = 1,
|
this.runtimeApiVersion = 1,
|
||||||
|
this.runtimeOptions = const RuntimeOptions(),
|
||||||
this.store = const StablePackageStore(),
|
this.store = const StablePackageStore(),
|
||||||
this.assetFallback = const AssetGamePackageRepository(),
|
this.assetFallback = const AssetGamePackageRepository(),
|
||||||
this.resourceManagerFactory,
|
this.resourceManagerFactory,
|
||||||
@@ -28,6 +30,7 @@ class PackageActivationController {
|
|||||||
final ScriptEngine scriptEngine;
|
final ScriptEngine scriptEngine;
|
||||||
final RuntimeAudioManager? audio;
|
final RuntimeAudioManager? audio;
|
||||||
final int runtimeApiVersion;
|
final int runtimeApiVersion;
|
||||||
|
final RuntimeOptions runtimeOptions;
|
||||||
final StablePackageStore store;
|
final StablePackageStore store;
|
||||||
final GamePackageRepository assetFallback;
|
final GamePackageRepository assetFallback;
|
||||||
final GameResourceManager Function()? resourceManagerFactory;
|
final GameResourceManager Function()? resourceManagerFactory;
|
||||||
@@ -143,12 +146,34 @@ class PackageActivationController {
|
|||||||
try {
|
try {
|
||||||
await verifier.verify(candidate);
|
await verifier.verify(candidate);
|
||||||
_ensureContinue(shouldContinue);
|
_ensureContinue(shouldContinue);
|
||||||
|
|
||||||
|
// 加载 base packages(框架包),按 runtimeOptions.basePackages 顺序。
|
||||||
|
final basePackages = <GamePackage>[];
|
||||||
|
for (final baseId in runtimeOptions.basePackages) {
|
||||||
|
final baseCandidates = await _candidatePackages(
|
||||||
|
baseId,
|
||||||
|
shouldContinue,
|
||||||
|
);
|
||||||
|
for (final baseCandidate in baseCandidates) {
|
||||||
|
try {
|
||||||
|
await verifier.verify(baseCandidate);
|
||||||
|
basePackages.add(baseCandidate);
|
||||||
|
break;
|
||||||
|
} catch (_) {
|
||||||
|
// Try next candidate.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await preparedResources.mount(candidate);
|
await preparedResources.mount(candidate);
|
||||||
_ensureContinue(shouldContinue);
|
_ensureContinue(shouldContinue);
|
||||||
await preparedAudio?.mount(candidate);
|
await preparedAudio?.mount(candidate);
|
||||||
_ensureContinue(shouldContinue);
|
_ensureContinue(shouldContinue);
|
||||||
await preparedScriptEngine.loadPackage(
|
|
||||||
candidate,
|
// 合并 base + game 包,传给脚本引擎。
|
||||||
|
final allPackages = [...basePackages, candidate];
|
||||||
|
await preparedScriptEngine.loadPackages(
|
||||||
|
allPackages,
|
||||||
services: scriptServices,
|
services: scriptServices,
|
||||||
);
|
);
|
||||||
_ensureContinue(shouldContinue);
|
_ensureContinue(shouldContinue);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class GamePackageManifest {
|
|||||||
this.display = const GameDisplayConfig(),
|
this.display = const GameDisplayConfig(),
|
||||||
this.resources = const {},
|
this.resources = const {},
|
||||||
this.modules = const {},
|
this.modules = const {},
|
||||||
|
this.base,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String gameId;
|
final String gameId;
|
||||||
@@ -29,6 +30,9 @@ class GamePackageManifest {
|
|||||||
final Map<String, GameResource> resources;
|
final Map<String, GameResource> resources;
|
||||||
final Map<String, String> modules;
|
final Map<String, String> modules;
|
||||||
|
|
||||||
|
/// 依赖的框架包 gameId。加载时会先加载框架包,再加载游戏包。
|
||||||
|
final String? base;
|
||||||
|
|
||||||
static GamePackageManifest fromJsonString(String source) {
|
static GamePackageManifest fromJsonString(String source) {
|
||||||
return fromMap(jsonDecode(source) as Map<String, Object?>);
|
return fromMap(jsonDecode(source) as Map<String, Object?>);
|
||||||
}
|
}
|
||||||
@@ -58,6 +62,8 @@ class GamePackageManifest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final base = map['base'] as String?;
|
||||||
|
|
||||||
final defaultLocale = (map['defaultLocale'] as String?) ?? 'en';
|
final defaultLocale = (map['defaultLocale'] as String?) ?? 'en';
|
||||||
final supportedLocales = _stringList(
|
final supportedLocales = _stringList(
|
||||||
map,
|
map,
|
||||||
@@ -89,6 +95,7 @@ class GamePackageManifest {
|
|||||||
display: display,
|
display: display,
|
||||||
resources: resources,
|
resources: resources,
|
||||||
modules: modules,
|
modules: modules,
|
||||||
|
base: base,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,15 +27,34 @@ class LuaDardoScriptEngine implements ScriptEngine {
|
|||||||
Future<void> loadPackage(
|
Future<void> loadPackage(
|
||||||
GamePackage package, {
|
GamePackage package, {
|
||||||
RuntimeScriptServices services = const RuntimeScriptServices(),
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
|
}) {
|
||||||
|
return loadPackages([package], services: services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> loadPackages(
|
||||||
|
List<GamePackage> packages, {
|
||||||
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
}) async {
|
}) async {
|
||||||
|
if (packages.isEmpty) {
|
||||||
|
throw const FormatException('loadPackages requires at least one package');
|
||||||
|
}
|
||||||
|
|
||||||
_services = services;
|
_services = services;
|
||||||
_networkRequestCounter = 0;
|
_networkRequestCounter = 0;
|
||||||
_hostCallCounter = 0;
|
_hostCallCounter = 0;
|
||||||
final script = await package.readText(package.manifest.entry);
|
|
||||||
_moduleScripts = {};
|
_moduleScripts = {};
|
||||||
for (final entry in package.manifest.modules.entries) {
|
|
||||||
_moduleScripts[entry.key] = await package.readText(entry.value);
|
// 按顺序加载所有包的模块,后加载的同名模块覆盖先加载的。
|
||||||
|
for (final package in packages) {
|
||||||
|
for (final entry in package.manifest.modules.entries) {
|
||||||
|
_moduleScripts[entry.key] = await package.readText(entry.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 入口脚本使用最后一个包(游戏包)。
|
||||||
|
final entryPackage = packages.last;
|
||||||
|
final script = await entryPackage.readText(entryPackage.manifest.entry);
|
||||||
_loadingModules.clear();
|
_loadingModules.clear();
|
||||||
|
|
||||||
_lua = LuaState.newState();
|
_lua = LuaState.newState();
|
||||||
|
|||||||
@@ -4,11 +4,19 @@ import '../packages/game_package.dart';
|
|||||||
import 'runtime_script_services.dart';
|
import 'runtime_script_services.dart';
|
||||||
|
|
||||||
abstract interface class ScriptEngine {
|
abstract interface class ScriptEngine {
|
||||||
|
// 加载单个包(向后兼容,内部调 loadPackages([package]))。
|
||||||
Future<void> loadPackage(
|
Future<void> loadPackage(
|
||||||
GamePackage package, {
|
GamePackage package, {
|
||||||
RuntimeScriptServices services = const RuntimeScriptServices(),
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 加载多个包,按顺序合并模块,后加载的同名模块覆盖先加载的。
|
||||||
|
// 入口脚本使用最后一个包。
|
||||||
|
Future<void> loadPackages(
|
||||||
|
List<GamePackage> packages, {
|
||||||
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
|
});
|
||||||
|
|
||||||
bool smokeTest(Map<String, Object?> context);
|
bool smokeTest(Map<String, Object?> context);
|
||||||
|
|
||||||
GameDiff init(Map<String, Object?> context);
|
GameDiff init(Map<String, Object?> context);
|
||||||
|
|||||||
@@ -156,6 +156,12 @@ class _FakeScriptEngine implements ScriptEngine {
|
|||||||
RuntimeScriptServices services = const RuntimeScriptServices(),
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
}) async {}
|
}) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> loadPackages(
|
||||||
|
List<GamePackage> packages, {
|
||||||
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
|
}) async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool smokeTest(Map<String, Object?> context) => true;
|
bool smokeTest(Map<String, Object?> context) => true;
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ class _FakeScriptEngine implements ScriptEngine {
|
|||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> loadPackages(
|
||||||
|
List<GamePackage> packages, {
|
||||||
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
|
}) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GameDiff dispatchEvent(RuntimeEvent event) {
|
GameDiff dispatchEvent(RuntimeEvent event) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
|||||||
@@ -366,6 +366,17 @@ class _FakeScriptEngine implements ScriptEngine {
|
|||||||
loadedPackages.add(package.rootPath);
|
loadedPackages.add(package.rootPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> loadPackages(
|
||||||
|
List<GamePackage> packages, {
|
||||||
|
RuntimeScriptServices services = const RuntimeScriptServices(),
|
||||||
|
}) async {
|
||||||
|
for (final package in packages) {
|
||||||
|
_package = package;
|
||||||
|
loadedPackages.add(package.rootPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool smokeTest(Map<String, Object?> context) {
|
bool smokeTest(Map<String, Object?> context) {
|
||||||
return !smokeFailures.contains(_package?.rootPath);
|
return !smokeFailures.contains(_package?.rootPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user