├── test └── inno_bundle_test.dart ├── lib ├── inno_bundle.dart ├── models │ ├── build_type.dart │ ├── language.dart │ └── config.dart ├── utils │ ├── constants.dart │ ├── functions.dart │ ├── cli_logger.dart │ └── installer_icon.dart └── builders │ ├── app_builder.dart │ ├── installer_builder.dart │ └── script_builder.dart ├── example ├── README.md └── demo_app │ ├── assets │ ├── images │ │ ├── installer.ico │ │ └── installer.svg │ └── LICENSE.txt │ ├── windows │ ├── runner │ │ ├── resources │ │ │ └── app_icon.ico │ │ ├── resource.h │ │ ├── utils.h │ │ ├── runner.exe.manifest │ │ ├── flutter_window.h │ │ ├── main.cpp │ │ ├── CMakeLists.txt │ │ ├── utils.cpp │ │ ├── flutter_window.cpp │ │ ├── Runner.rc │ │ ├── win32_window.h │ │ └── win32_window.cpp │ ├── flutter │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ ├── generated_plugins.cmake │ │ └── CMakeLists.txt │ ├── .gitignore │ └── CMakeLists.txt │ ├── README.md │ ├── .gitignore │ ├── .metadata │ ├── test │ └── widget_test.dart │ ├── analysis_options.yaml │ ├── pubspec.yaml │ ├── lib │ └── main.dart │ └── pubspec.lock ├── analysis_options.yaml ├── .metadata ├── pubspec.yaml ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── bin ├── id.dart └── build.dart ├── LICENSE ├── CHANGELOG.md └── README.md /test/inno_bundle_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | -------------------------------------------------------------------------------- /lib/inno_bundle.dart: -------------------------------------------------------------------------------- 1 | void main(List args) {} 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Inno bundle examples 2 | 3 | - [demo_app](https://github.com/hahouari/inno_bundle/tree/dev/example/demo_app) 4 | -------------------------------------------------------------------------------- /example/demo_app/assets/images/installer.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/inno_bundle/dev/example/demo_app/assets/images/installer.ico -------------------------------------------------------------------------------- /example/demo_app/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rivafarabi/inno_bundle/dev/example/demo_app/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | 6 | linter: 7 | rules: 8 | avoid_print: false -------------------------------------------------------------------------------- /example/demo_app/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void RegisterPlugins(flutter::PluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/demo_app/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/demo_app/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: inno_bundle 2 | description: CLI tool for automating Windows installer creation using Inno Setup. 3 | maintainer: Hocine Abdellatif Houari 4 | version: 0.6.0 5 | homepage: https://github.com/hahouari/inno_bundle 6 | 7 | environment: 8 | sdk: ">=3.2.3 <4.0.0" 9 | flutter: ">=1.17.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | args: ^2.5.0 15 | yaml: ^3.1.2 16 | uuid: ^4.4.0 17 | path: ^1.9.0 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | flutter_lints: ^4.0.0 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /example/demo_app/README.md: -------------------------------------------------------------------------------- 1 | # demo_app 2 | 3 | A demo app with flutter that is bundled into exe installer using inno_bundle package. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /lib/models/build_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:args/args.dart'; 2 | import 'package:inno_bundle/utils/functions.dart'; 3 | 4 | /// An enum representing the different build types supported for the software. 5 | enum BuildType { 6 | debug, 7 | profile, 8 | release; 9 | 10 | /// Returns the directory name associated with the build type. 11 | String get dirName => capitalize(name); 12 | 13 | /// Parses the command-line arguments using [args] and determines the desired [BuildType]. 14 | /// 15 | /// Prioritizes `release` over `profile` over `debug` if multiple flags are present. 16 | static BuildType fromArgs(ArgResults args) { 17 | return args[BuildType.release.name] 18 | ? BuildType.release 19 | : args[BuildType.profile.name] 20 | ? BuildType.profile 21 | : BuildType.debug; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/demo_app/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /example/demo_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /bin/id.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:args/args.dart'; 4 | import 'package:inno_bundle/utils/constants.dart'; 5 | import 'package:uuid/uuid.dart'; 6 | 7 | const uuid = Uuid(); 8 | 9 | /// Run to generate an App ID (as GUID) 10 | void main(List arguments) { 11 | final parser = ArgParser() 12 | ..addOption('ns', help: "Namespace, ex: google.com") 13 | ..addFlag('hf', defaultsTo: true, help: 'Print header and footer') 14 | ..addFlag('help', abbr: 'h', negatable: false, help: 'Print help and exit'); 15 | 16 | final parsedArgs = parser.parse(arguments); 17 | final ns = parsedArgs['ns'] as String?; 18 | final hf = parsedArgs['hf'] as bool; 19 | final help = parsedArgs['help'] as bool; 20 | 21 | if (hf) print(START_MESSAGE); 22 | 23 | if (help) { 24 | print("${parser.usage}\n"); 25 | exit(0); 26 | } 27 | 28 | if (ns != null) { 29 | print(uuid.v5(Uuid.NAMESPACE_URL, ns)); 30 | } else { 31 | print(uuid.v1()); 32 | } 33 | 34 | if (hf) print(GUID_END_MESSAGE); 35 | } 36 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/demo_app/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 17 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 18 | - platform: windows 19 | create_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 20 | base_revision: 2e9cb0aa71a386a91f73f7088d115c0d96654829 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023-2024 Hocine Abdellatif Houari 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /example/demo_app/assets/LICENSE.txt: -------------------------------------------------------------------------------- 1 | THIS LICENSE FILE IS ONLY FOR DEMONSTRATION PURPOSES. 2 | 3 | Copyright 2024 Hocine Abdellatif Houari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /example/demo_app/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:demo_app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"demo_app", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /example/demo_app/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /lib/models/language.dart: -------------------------------------------------------------------------------- 1 | enum Language { 2 | english("Default.isl"), 3 | armenian("Languages\\Armenian.isl"), 4 | brazilianportuguese("Languages\\BrazilianPortuguese.isl"), 5 | bulgarian("Languages\\Bulgarian.isl"), 6 | catalan("Languages\\Catalan.isl"), 7 | corsican("Languages\\Corsican.isl"), 8 | czech("Languages\\Czech.isl"), 9 | danish("Languages\\Danish.isl"), 10 | dutch("Languages\\Dutch.isl"), 11 | finnish("Languages\\Finnish.isl"), 12 | french("Languages\\French.isl"), 13 | german("Languages\\German.isl"), 14 | hebrew("Languages\\Hebrew.isl"), 15 | hungarian("Languages\\Hungarian.isl"), 16 | icelandic("Languages\\Icelandic.isl"), 17 | italian("Languages\\Italian.isl"), 18 | japanese("Languages\\Japanese.isl"), 19 | norwegian("Languages\\Norwegian.isl"), 20 | polish("Languages\\Polish.isl"), 21 | portuguese("Languages\\Portuguese.isl"), 22 | russian("Languages\\Russian.isl"), 23 | slovak("Languages\\Slovak.isl"), 24 | slovenian("Languages\\Slovenian.isl"), 25 | spanish("Languages\\Spanish.isl"), 26 | turkish("Languages\\Turkish.isl"), 27 | ukrainian("Languages\\Ukrainian.isl"); 28 | 29 | /// The filename of the language-specific Inno Setup language file. 30 | final String file; 31 | 32 | /// Creates a [Language] instance with the associated [file] name. 33 | const Language(this.file); 34 | 35 | /// Retrieves a [Language] instance by its name, or `null` if not found. 36 | static Language? getByNameOrNull(String name) { 37 | final index = Language.values.indexWhere((l) => l.name == name); 38 | return index != -1 ? Language.values[index] : null; 39 | } 40 | 41 | /// Generates the Inno Setup language item for this language. 42 | String toInnoItem() { 43 | return "Name: \"$name\"; MessagesFile: \"compiler:$file\""; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/utils/constants.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | import 'package:inno_bundle/utils/functions.dart'; 4 | 5 | /// Start message for the CLI 6 | const String START_MESSAGE = '''\n 7 | ╔════════════════════════════════════════════════════╗ 8 | ║ ║ 9 | ║ ✨✨ INNO BUNDLE ✨✨ ║ 10 | ║ ║ 11 | ╚════════════════════════════════════════════════════╝ 12 | \n'''; 13 | 14 | /// Header message for ISS script 15 | const String scriptHeader = ''' 16 | ; Script generated by inno_bundle package for a flutter app. 17 | ; Maintainer: https://github.com/hahouari 18 | ; YOU DO NOT NEED THIS FILE AFTER YOU GENERATE THE INSTALLER. 19 | \n'''; 20 | 21 | /// End message for the CLI 22 | const String BUILD_END_MESSAGE = '''\n 23 | =========> INSTALLER GENERATED SUCCESSFULLY <========= 24 | ❤️ THANK YOU! ❤️ 25 | '''; 26 | 27 | /// End message for the CLI 28 | const String GUID_END_MESSAGE = '''\n 29 | ============> GUID GENERATED SUCCESSFULLY <=========== 30 | ❤️ THANK YOU! ❤️ 31 | '''; 32 | 33 | const readmeDownloadStepLink = "https://github.com/hahouari/inno_bundle" 34 | "?tab=readme-ov-file#1-download-inno-setup"; 35 | const appBuildDir = ["build", "windows", "x64", "runner"]; 36 | const installerBuildDir = ["build", "windows", "x64", "installer"]; 37 | const system32 = ["C:", "Windows", "System32"]; 38 | const vcDllFiles = ["msvcp140.dll", "vcruntime140.dll", "vcruntime140_1.dll"]; 39 | const innoSysDirPath = ["C:", "Program Files (x86)", "Inno Setup 6"]; 40 | final innoUserDirPath = [ 41 | getHomeDir(), 42 | "AppData", 43 | "Local", 44 | "Programs", 45 | "Inno Setup 6" 46 | ]; 47 | 48 | const defaultInstallerIconPlaceholder = "__default_installer__icon__"; 49 | 50 | final validFilenameRegex = RegExp(r'^[^<>:"/\\|?*\x00-\x1F]+$'); 51 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /example/demo_app/assets/images/installer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /lib/builders/app_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:inno_bundle/models/config.dart'; 4 | import 'package:inno_bundle/utils/constants.dart'; 5 | import 'package:path/path.dart' as p; 6 | import 'package:inno_bundle/utils/cli_logger.dart'; 7 | 8 | /// A class responsible for building the app based on the provided configuration. 9 | class AppBuilder { 10 | /// Configuration guiding the build process. 11 | final Config config; 12 | 13 | /// Creates an instance of [AppBuilder] with the given [config]. 14 | AppBuilder(this.config); 15 | 16 | /// Builds the app using Flutter and returns the path to the build directory. 17 | /// 18 | /// If [config.app] is `false` and a valid build already exists, it skips the 19 | /// build process and returns the existing directory. Otherwise, it executes 20 | /// the Flutter build command and returns the newly generated build directory. 21 | Future build() async { 22 | final buildDirPath = p.joinAll([ 23 | Directory.current.path, 24 | ...appBuildDir, 25 | config.type.dirName, 26 | ]); 27 | final buildDir = Directory(buildDirPath); 28 | final versionParts = config.version.split("+"); 29 | final buildName = versionParts[0]; 30 | final buildNumber = 31 | versionParts.length == 1 ? "1" : versionParts.sublist(1).join("+"); 32 | 33 | if (!config.app) { 34 | if (!buildDir.existsSync() || buildDir.listSync().isEmpty) { 35 | CliLogger.warning( 36 | "${config.type.dirName} build is not available, " 37 | "--no-app is ignored.", 38 | ); 39 | } else { 40 | CliLogger.info("Skipping app..."); 41 | return buildDir; 42 | } 43 | } 44 | 45 | final process = await Process.start( 46 | "flutter", 47 | [ 48 | 'build', 49 | 'windows', 50 | './lib/main.dart', 51 | '--${config.type.name}', 52 | '--obfuscate', 53 | '--split-debug-info=build/obfuscate', 54 | '--build-name', 55 | buildName, 56 | '--build-number', 57 | buildNumber, 58 | config.buildArgs ?? "", 59 | ], 60 | runInShell: true, 61 | workingDirectory: Directory.current.path, 62 | mode: ProcessStartMode.inheritStdio, 63 | ); 64 | 65 | final exitCode = await process.exitCode; 66 | 67 | if (exitCode != 0) exit(exitCode); 68 | return buildDir; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /lib/utils/functions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:inno_bundle/utils/installer_icon.dart'; 6 | import 'package:yaml/yaml.dart'; 7 | import 'package:path/path.dart' as p; 8 | 9 | /// Convert yaml to map 10 | Map yamlToMap(YamlMap yamlMap) { 11 | final map = {}; 12 | for (final entry in yamlMap.entries) { 13 | if (entry.value is YamlList) { 14 | final list = []; 15 | for (final value in entry.value as YamlList) { 16 | if (value is String) { 17 | list.add(value); 18 | } 19 | } 20 | map[entry.key as String] = list; 21 | } else if (entry.value is YamlMap) { 22 | map[entry.key as String] = yamlToMap(entry.value as YamlMap); 23 | } else { 24 | map[entry.key as String] = entry.value; 25 | } 26 | } 27 | return map; 28 | } 29 | 30 | /// Converts a string to camelCase. 31 | /// 32 | /// Example: `camelCase("hello-world_out there")` returns "helloWorldOutThere". 33 | String camelCase(String value) { 34 | return value 35 | .split(RegExp(r'[-_]|\s')) 36 | .map((word) => capitalize(word)) 37 | .join(''); 38 | } 39 | 40 | /// Capitalizes the first letter of a string. 41 | /// 42 | /// Example: `capitalize("hello")` returns "Hello". 43 | String capitalize(String value) { 44 | if (value.isEmpty) return ""; 45 | return value[0].toUpperCase() + value.substring(1); 46 | } 47 | 48 | /// Persists the default installer icon to a file in the given directory. 49 | /// 50 | /// Decodes a Base64-encoded icon string and writes it to a file in the 51 | /// system temp directory. 52 | /// 53 | /// Returns the absolute path of the saved icon file. 54 | String persistDefaultInstallerIcon(String dirPath) { 55 | Directory(dirPath).createSync(); 56 | final iconPath = p.join(dirPath, defaultInstallerIconFileName); 57 | File file = File(iconPath); 58 | Uint8List bytes = base64.decode(defaultInstallerIcon); 59 | file.writeAsBytesSync(bytes); 60 | return file.absolute.path; 61 | } 62 | 63 | /// Retrieves the user's home directory path. 64 | /// 65 | /// Uses environment variables to determine the home directory based on the operating system. 66 | String getHomeDir() { 67 | String home = ""; 68 | Map envVars = Platform.environment; 69 | if (Platform.isMacOS || Platform.isLinux) { 70 | home = envVars['HOME'] ?? home; 71 | } else if (Platform.isWindows) { 72 | home = envVars['UserProfile'] ?? home; 73 | } 74 | return home; 75 | } 76 | -------------------------------------------------------------------------------- /lib/utils/cli_logger.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | /// Log levels 4 | enum CliLoggerLevel { 5 | /// Level one 6 | one, 7 | 8 | /// Level two 9 | two, 10 | 11 | /// Level three 12 | three, 13 | } 14 | 15 | // Reset: \x1B[0m 16 | // Black: \x1B[30m 17 | // White: \x1B[37m 18 | // Red: \x1B[31m 19 | // Green: \x1B[32m 20 | // Yellow: \x1B[33m 21 | // Blue: \x1B[34m 22 | // Cyan: \x1B[36m 23 | 24 | /// Cli Logger 25 | class CliLogger { 26 | /// Constructor 27 | CliLogger(); 28 | 29 | /// Log info 30 | static void info( 31 | String message, { 32 | CliLoggerLevel level = CliLoggerLevel.one, 33 | }) { 34 | final space = _getSpace(level); 35 | print('\x1B[34m$space🌱 $message\x1B[0m'); 36 | } 37 | 38 | /// Logs a error message at the given level. 39 | static void error( 40 | String message, { 41 | CliLoggerLevel level = CliLoggerLevel.one, 42 | }) { 43 | final space = _getSpace(level); 44 | print('$space❌ $message'); 45 | } 46 | 47 | /// Logs a error message at the given level and exits with given code. 48 | static void exitError( 49 | String message, { 50 | CliLoggerLevel level = CliLoggerLevel.one, 51 | int exitCode = 1, 52 | }) { 53 | final space = _getSpace(level); 54 | print('$space❌ $message'); 55 | exit(exitCode); 56 | } 57 | 58 | /// Logs a warning message at the given level. 59 | static void warning( 60 | String message, { 61 | CliLoggerLevel level = CliLoggerLevel.one, 62 | }) { 63 | final space = _getSpace(level); 64 | print('\x1B[33m$space🚧 $message\x1B[0m'); 65 | } 66 | 67 | /// Logs a success message at the given level. 68 | static void success( 69 | String message, { 70 | CliLoggerLevel level = CliLoggerLevel.one, 71 | }) { 72 | final space = _getSpace(level); 73 | print('\x1B[32m$space✅ $message\x1B[0m'); 74 | } 75 | 76 | static String sLink( 77 | String link, { 78 | CliLoggerLevel level = CliLoggerLevel.one, 79 | }) { 80 | final space = _getSpace(level); 81 | return '\x1B[34m$space🔗 $link\x1B[0m'; 82 | } 83 | 84 | static String _getSpace(CliLoggerLevel level) { 85 | var space = ''; 86 | switch (level) { 87 | case CliLoggerLevel.one: 88 | space = ''; 89 | break; 90 | case CliLoggerLevel.two: 91 | space = ' '; 92 | break; 93 | case CliLoggerLevel.three: 94 | space = ' '; 95 | break; 96 | } 97 | return space; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/builders/installer_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:inno_bundle/models/config.dart'; 4 | import 'package:inno_bundle/utils/cli_logger.dart'; 5 | import 'package:inno_bundle/utils/constants.dart'; 6 | import 'package:path/path.dart' as p; 7 | 8 | /// A class responsible for building the installer using Inno Setup. 9 | class InstallerBuilder { 10 | /// The configuration guiding the build process. 11 | final Config config; 12 | 13 | /// The Inno Setup script file to be used for building the installer. 14 | final File scriptFile; 15 | 16 | /// Creates an instance of [InstallerBuilder] with the given [config] and [scriptFile]. 17 | const InstallerBuilder(this.config, this.scriptFile); 18 | 19 | /// Locates the Inno Setup executable file, ensuring its proper installation. 20 | /// 21 | /// Throws a [ProcessException] if Inno Setup is not found or is corrupted. 22 | File _getInnoSetupExec() { 23 | if (!Directory(p.joinAll(innoSysDirPath)).existsSync() && 24 | !Directory(p.joinAll(innoUserDirPath)).existsSync()) { 25 | CliLogger.error("Inno Setup is not detected in your machine, " 26 | "checkout our README on how to correctly install it:\n" 27 | "${CliLogger.sLink(readmeDownloadStepLink, level: CliLoggerLevel.two)}"); 28 | exit(1); 29 | } 30 | 31 | final sysExec = p.joinAll([...innoSysDirPath, "ISCC.exe"]); 32 | final sysExecFile = File(sysExec); 33 | final userExec = p.joinAll([...innoUserDirPath, "ISCC.exe"]); 34 | final userExecFile = File(userExec); 35 | 36 | if (sysExecFile.existsSync()) return sysExecFile; 37 | if (userExecFile.existsSync()) return userExecFile; 38 | 39 | CliLogger.error("Inno Setup installation in your machine is corrupted " 40 | "or incomplete, checkout our README on how to correctly install it:\n" 41 | "${CliLogger.sLink(readmeDownloadStepLink, level: CliLoggerLevel.two)}"); 42 | exit(1); 43 | } 44 | 45 | /// Builds the installer using Inno Setup and returns the directory containing the output files. 46 | /// 47 | /// Skips the build process if [config.installer] is `false`. 48 | /// Throws a [ProcessException] if the Inno Setup process fails. 49 | Future build() async { 50 | if (!config.installer) { 51 | CliLogger.info("Skipping installer..."); 52 | return Directory(""); 53 | } 54 | 55 | final execFile = _getInnoSetupExec(); 56 | 57 | final process = await Process.start( 58 | execFile.path, 59 | [scriptFile.path], 60 | runInShell: true, 61 | workingDirectory: Directory.current.path, 62 | mode: ProcessStartMode.inheritStdio, 63 | ); 64 | final exitCode = await process.exitCode; 65 | if (exitCode != 0) exit(exitCode); 66 | return Directory.current; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /bin/build.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:args/args.dart'; 4 | import 'package:inno_bundle/builders/app_builder.dart'; 5 | import 'package:inno_bundle/models/build_type.dart'; 6 | import 'package:inno_bundle/models/config.dart'; 7 | import 'package:inno_bundle/builders/installer_builder.dart'; 8 | import 'package:inno_bundle/builders/script_builder.dart'; 9 | import 'package:inno_bundle/utils/constants.dart'; 10 | 11 | /// Builds the application using the provided configuration. 12 | /// 13 | /// Returns the directory containing the built application files. 14 | Future _buildApp(Config config) async { 15 | final builder = AppBuilder(config); 16 | return await builder.build(); 17 | } 18 | 19 | /// Generates the Inno Setup script file for the installer. 20 | /// 21 | /// Returns the generated Inno Setup script file. 22 | Future _buildScript(Config config, Directory appDir) async { 23 | final builder = ScriptBuilder(config, appDir); 24 | return await builder.build(); 25 | } 26 | 27 | /// Builds the installer using the provided configuration and Inno Setup script file. 28 | Future _buildInstaller(Config config, File scriptFile) async { 29 | final builder = InstallerBuilder(config, scriptFile); 30 | await builder.build(); 31 | } 32 | 33 | /// Run to build installer 34 | void main(List arguments) async { 35 | final parser = ArgParser() 36 | ..addFlag(BuildType.release.name, negatable: false) 37 | ..addFlag(BuildType.profile.name, negatable: false) 38 | ..addFlag(BuildType.debug.name, negatable: false, help: 'Default flag') 39 | ..addFlag('app', defaultsTo: true, help: 'Build app') 40 | ..addFlag('installer', defaultsTo: true, help: 'Build installer') 41 | ..addOption("build-args", help: "Appended to `flutter build`") 42 | ..addOption("app-version", help: "Override app version") 43 | ..addFlag( 44 | 'envs', 45 | defaultsTo: false, 46 | negatable: false, 47 | help: "Print env variables and exit", 48 | ) 49 | ..addFlag('hf', defaultsTo: true, help: 'Print header and footer') 50 | ..addFlag('help', abbr: 'h', negatable: false, help: 'Print help and exit'); 51 | final parsedArgs = parser.parse(arguments); 52 | final envs = parsedArgs['envs'] as bool; 53 | final hf = parsedArgs['hf'] as bool; 54 | final help = parsedArgs['help'] as bool; 55 | 56 | if (hf) print(START_MESSAGE); 57 | 58 | if (help) { 59 | print("${parser.usage}\n"); 60 | exit(0); 61 | } 62 | 63 | final config = Config.fromFile( 64 | type: BuildType.fromArgs(parsedArgs), 65 | app: parsedArgs['app'], 66 | installer: parsedArgs['installer'], 67 | buildArgs: parsedArgs['build-args'], 68 | appVersion: parsedArgs['app-version'], 69 | ); 70 | 71 | if (envs) { 72 | print(config.toEnvironmentVariables()); 73 | exit(0); 74 | } 75 | 76 | final appBuildDir = await _buildApp(config); 77 | final scriptFile = await _buildScript(config, appBuildDir); 78 | await _buildInstaller(config, scriptFile); 79 | 80 | if (hf) print(BUILD_END_MESSAGE); 81 | } 82 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | - Add a working bundler and example. 4 | 5 | ## 0.0.2 6 | 7 | - Add a README.md to example and update main README.md. 8 | 9 | ## 0.0.3 10 | 11 | - Fix `languages` attribute name in README.md example. 12 | 13 | ## 0.1.0 14 | 15 | - Make `installer_icon` optional attribute. 16 | 17 | ## 0.1.1 18 | 19 | - Update README.md. 20 | 21 | ## 0.2.0 22 | 23 | - Update default icon, old one is not premissable to use for commercial use 24 | without license, so I created new one from **license free** resources. 25 | 26 | - Clean cuportino icons from example as it is unused. 27 | 28 | - Clean `lib\inno_bundle.dart` and `test\inno_bundle_test.dart`. 29 | 30 | - When default icon is used, the new one is copied to `%TEMP%` folder on every 31 | installer build, I did not find an efficient way to update the old one. 32 | 33 | ## 0.3.0 34 | 35 | - Replace `--skip-app` with `--app` and `--no-app` flags, default to `--app`. 36 | 37 | - Add `--installer` and `--no-installer` flags, default to `--installer`. 38 | 39 | - Add `--help` and `-h` with descriptive messages to each flag. 40 | 41 | - Refactor `Config` to include cli arguments as well. 42 | 43 | - Make `.iss script` generation happen under 44 | `build\windows\x64\installer\\.iss`. 45 | 46 | - Add `%UserProfile%\Local\Programs\Inno Setup 6` as a possible path to find 47 | Inno Setup installation. 48 | 49 | - Update error message for option to install Inno Setup using `winget` 50 | when not detected on the machine. 51 | 52 | ## 0.3.1 53 | 54 | - Update README.md for winget installation option of Inno Setup. 55 | 56 | - Replace LinkedIn link in maintainer clause for generated `iss script` with 57 | GitHub link. 58 | 59 | ## 0.3.2 60 | 61 | - Add documentation to codebase. 62 | 63 | ## 0.3.3 64 | 65 | - Fix issue icon not persisting in the system temp directory when using 66 | default installer icon. 67 | 68 | - Update iss script maintainer clause. 69 | 70 | - Rename generated iss script to `inno-script.iss`. 71 | 72 | ## 0.4.0 73 | 74 | - Add `--hf` and `--no-hf` flags to control printing of header and footer text. 75 | 76 | - Add `--envs` to `build` command to print resolved config as environment 77 | variables and exit. 78 | 79 | - Add `--help` to `id` command. 80 | 81 | ## 0.4.1 82 | 83 | - Add guide to setup GitHub Workflows and automate installer build as GitHub releases. 84 | 85 | ## 0.4.2 86 | 87 | - Update packages version and minimum dart and flutter version to latest. 88 | 89 | ## 0.5.0 90 | 91 | - Update packages and lower back minimum dart and flutter versions. 92 | 93 | - rework usage of app name and pubspec name props. See [#2](https://github.com/hahouari/inno_bundle/issues/2). 94 | 95 | ## 0.6.0 96 | 97 | - Add `--app-version` argument to `build` command to override app version from CLI. 98 | 99 | - Add `--build-args` argument to `build` command to append more args into `flutter build`. 100 | 101 | - Improve building app to include obfuscation during app build. 102 | 103 | - Improve uninstaller UI/UX info. 104 | 105 | - Add installer SVG file to demo assets under MIT license. (not a feature, but worth tracking) 106 | 107 | - Add Inno Setup installation step through `Chocolatey`. 108 | 109 | - Improve error messages and suggest repo link as guide for corrupted installs of Inno Setup. 110 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.hahouari" "\0" 93 | VALUE "FileDescription", "demo_app" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "demo_app" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 com.hahouari. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "demo_app.exe" "\0" 98 | VALUE "ProductName", "demo_app" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /example/demo_app/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /example/demo_app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: demo_app 2 | description: "A demo app with flutter that is bundled into exe installer using inno_bundle package." 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 1.0.0+1 20 | 21 | environment: 22 | sdk: ">=3.2.3 <4.0.0" 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | 34 | dev_dependencies: 35 | flutter_test: 36 | sdk: flutter 37 | inno_bundle: 38 | path: ../.. 39 | 40 | # The "flutter_lints" package below contains a set of recommended lints to 41 | # encourage good coding practices. The lint set provided by the package is 42 | # activated in the `analysis_options.yaml` file located at the root of your 43 | # package. See that file for information about deactivating specific lint 44 | # rules and activating additional ones. 45 | flutter_lints: ^2.0.0 46 | 47 | # For information on the generic Dart part of this file, see the 48 | # following page: https://dart.dev/tools/pub/pubspec 49 | 50 | # The following section is specific to Flutter packages. 51 | flutter: 52 | # The following line ensures that the Material Icons font is 53 | # included with your application, so that you can use the icons in 54 | # the material Icons class. 55 | uses-material-design: true 56 | 57 | # To add assets to your application, add an assets section, like this: 58 | # assets: 59 | # - images/a_dot_burr.jpeg 60 | # - images/a_dot_ham.jpeg 61 | 62 | # An image asset can refer to one or more resolution-specific "variants", see 63 | # https://flutter.dev/assets-and-images/#resolution-aware 64 | 65 | # For details regarding adding assets from package dependencies, see 66 | # https://flutter.dev/assets-and-images/#from-packages 67 | 68 | # To add custom fonts to your application, add a fonts section here, 69 | # in this "flutter" section. Each entry in this list should have a 70 | # "family" key with the font family name, and a "fonts" key with a 71 | # list giving the asset and other descriptors for the font. For 72 | # example: 73 | # fonts: 74 | # - family: Schyler 75 | # fonts: 76 | # - asset: fonts/Schyler-Regular.ttf 77 | # - asset: fonts/Schyler-Italic.ttf 78 | # style: italic 79 | # - family: Trajan Pro 80 | # fonts: 81 | # - asset: fonts/TrajanPro.ttf 82 | # - asset: fonts/TrajanPro_Bold.ttf 83 | # weight: 700 84 | # 85 | # For details regarding fonts from package dependencies, 86 | # see https://flutter.dev/custom-fonts/#from-packages 87 | 88 | inno_bundle: 89 | id: 5ec949d0-0582-1e06-b073-b5d1161f6fff 90 | publisher: Hocine Abdellatif Houari 91 | installer_icon: assets/images/installer.ico 92 | license_file: assets/LICENSE.txt 93 | languages: 94 | - french 95 | -------------------------------------------------------------------------------- /example/demo_app/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(demo_app LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "demo_app") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /example/demo_app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() { 4 | runApp(const MyApp()); 5 | } 6 | 7 | class MyApp extends StatelessWidget { 8 | const MyApp({super.key}); 9 | 10 | // This widget is the root of your application. 11 | @override 12 | Widget build(BuildContext context) { 13 | return MaterialApp( 14 | title: 'Flutter Demo', 15 | theme: ThemeData( 16 | // This is the theme of your application. 17 | // 18 | // TRY THIS: Try running your application with "flutter run". You'll see 19 | // the application has a purple toolbar. Then, without quitting the app, 20 | // try changing the seedColor in the colorScheme below to Colors.green 21 | // and then invoke "hot reload" (save your changes or press the "hot 22 | // reload" button in a Flutter-supported IDE, or press "r" if you used 23 | // the command line to start the app). 24 | // 25 | // Notice that the counter didn't reset back to zero; the application 26 | // state is not lost during the reload. To reset the state, use hot 27 | // restart instead. 28 | // 29 | // This works for code too, not just values: Most code changes can be 30 | // tested with just a hot reload. 31 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 32 | useMaterial3: true, 33 | ), 34 | home: const MyHomePage(title: 'Flutter Demo Home Page'), 35 | ); 36 | } 37 | } 38 | 39 | class MyHomePage extends StatefulWidget { 40 | const MyHomePage({super.key, required this.title}); 41 | 42 | // This widget is the home page of your application. It is stateful, meaning 43 | // that it has a State object (defined below) that contains fields that affect 44 | // how it looks. 45 | 46 | // This class is the configuration for the state. It holds the values (in this 47 | // case the title) provided by the parent (in this case the App widget) and 48 | // used by the build method of the State. Fields in a Widget subclass are 49 | // always marked "final". 50 | 51 | final String title; 52 | 53 | @override 54 | State createState() => _MyHomePageState(); 55 | } 56 | 57 | class _MyHomePageState extends State { 58 | int _counter = 0; 59 | 60 | void _incrementCounter() { 61 | setState(() { 62 | // This call to setState tells the Flutter framework that something has 63 | // changed in this State, which causes it to rerun the build method below 64 | // so that the display can reflect the updated values. If we changed 65 | // _counter without calling setState(), then the build method would not be 66 | // called again, and so nothing would appear to happen. 67 | _counter++; 68 | }); 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | // This method is rerun every time setState is called, for instance as done 74 | // by the _incrementCounter method above. 75 | // 76 | // The Flutter framework has been optimized to make rerunning build methods 77 | // fast, so that you can just rebuild anything that needs updating rather 78 | // than having to individually change instances of widgets. 79 | return Scaffold( 80 | appBar: AppBar( 81 | // TRY THIS: Try changing the color here to a specific color (to 82 | // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar 83 | // change color while the other colors stay the same. 84 | backgroundColor: Theme.of(context).colorScheme.inversePrimary, 85 | // Here we take the value from the MyHomePage object that was created by 86 | // the App.build method, and use it to set our appbar title. 87 | title: Text(widget.title), 88 | ), 89 | body: Center( 90 | // Center is a layout widget. It takes a single child and positions it 91 | // in the middle of the parent. 92 | child: Column( 93 | // Column is also a layout widget. It takes a list of children and 94 | // arranges them vertically. By default, it sizes itself to fit its 95 | // children horizontally, and tries to be as tall as its parent. 96 | // 97 | // Column has various properties to control how it sizes itself and 98 | // how it positions its children. Here we use mainAxisAlignment to 99 | // center the children vertically; the main axis here is the vertical 100 | // axis because Columns are vertical (the cross axis would be 101 | // horizontal). 102 | // 103 | // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" 104 | // action in the IDE, or press "p" in the console), to see the 105 | // wireframe for each widget. 106 | mainAxisAlignment: MainAxisAlignment.center, 107 | children: [ 108 | const Text( 109 | 'You have pushed the button this many times:', 110 | ), 111 | Text( 112 | '$_counter', 113 | style: Theme.of(context).textTheme.headlineMedium, 114 | ), 115 | ], 116 | ), 117 | ), 118 | floatingActionButton: FloatingActionButton( 119 | onPressed: _incrementCounter, 120 | tooltip: 'Increment', 121 | child: const Icon(Icons.add), 122 | ), // This trailing comma makes auto-formatting nicer for build methods. 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/builders/script_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:inno_bundle/models/config.dart'; 4 | import 'package:inno_bundle/utils/cli_logger.dart'; 5 | import 'package:inno_bundle/utils/constants.dart'; 6 | import 'package:inno_bundle/utils/functions.dart'; 7 | import 'package:path/path.dart' as p; 8 | 9 | /// A class responsible for generating the Inno Setup Script (ISS) file for the installer. 10 | class ScriptBuilder { 11 | /// The configuration guiding the script generation process. 12 | final Config config; 13 | 14 | /// The directory containing the application files to be included in the installer. 15 | final Directory appDir; 16 | 17 | /// Creates a [ScriptBuilder] instance with the given [config] and [appDir]. 18 | ScriptBuilder(this.config, this.appDir); 19 | 20 | String _setup() { 21 | final id = config.id; 22 | final name = config.name; 23 | final version = config.version; 24 | final publisher = config.publisher; 25 | final url = config.url; 26 | final supportUrl = config.supportUrl; 27 | final updatesUrl = config.updatesUrl; 28 | final privileges = config.admin ? 'admin' : 'lowest'; 29 | final installerName = '${camelCase(name)}-x86_64-$version-Installer'; 30 | final licenseFile = config.licenseFile; 31 | var installerIcon = config.installerIcon; 32 | var uninstallIcon = "{app}\\${config.exeName}"; 33 | 34 | final outputDir = p.joinAll([ 35 | Directory.current.path, 36 | ...installerBuildDir, 37 | config.type.dirName, 38 | ]); 39 | 40 | // save default icon into temp directory to use its path. 41 | if (installerIcon == defaultInstallerIconPlaceholder) { 42 | final installerIconDirPath = p.joinAll([ 43 | Directory.systemTemp.absolute.path, 44 | "${camelCase(name)}Installer", 45 | ]); 46 | installerIcon = persistDefaultInstallerIcon(installerIconDirPath); 47 | } 48 | 49 | return ''' 50 | [Setup] 51 | AppId=$id 52 | AppName=$name 53 | UninstallDisplayName=$name 54 | UninstallDisplayIcon=$uninstallIcon 55 | AppVersion=$version 56 | AppPublisher=$publisher 57 | AppPublisherURL=$url 58 | AppSupportURL=$supportUrl 59 | AppUpdatesURL=$updatesUrl 60 | LicenseFile=$licenseFile 61 | DefaultDirName={autopf}\\$name 62 | PrivilegesRequired=$privileges 63 | OutputDir=$outputDir 64 | OutputBaseFilename=$installerName 65 | SetupIconFile=$installerIcon 66 | Compression=lzma2/max 67 | SolidCompression=yes 68 | WizardStyle=modern 69 | ArchitecturesInstallIn64BitMode=x64 70 | DisableDirPage=auto 71 | DisableProgramGroupPage=auto 72 | \n'''; 73 | } 74 | 75 | String _installDelete() { 76 | return ''' 77 | [InstallDelete] 78 | Type: filesandordirs; Name: "{app}\\*" 79 | \n'''; 80 | } 81 | 82 | String _languages() { 83 | String section = "[Languages]\n"; 84 | for (final language in config.languages) { 85 | section += '${language.toInnoItem()}\n'; 86 | } 87 | return '$section\n'; 88 | } 89 | 90 | String _tasks() { 91 | return ''' 92 | [Tasks] 93 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; 94 | \n'''; 95 | } 96 | 97 | String _files() { 98 | String section = "[Files]\n"; 99 | 100 | // adding app build files 101 | final files = appDir.listSync(); 102 | for (final file in files) { 103 | final filePath = file.absolute.path; 104 | if (FileSystemEntity.isDirectorySync(filePath)) { 105 | final fileName = p.basename(file.path); 106 | section += "Source: \"$filePath\\*\"; DestDir: \"{app}\\$fileName\"; " 107 | "Flags: ignoreversion recursesubdirs createallsubdirs\n"; 108 | } else { 109 | // override the default exe file name from the name provided by 110 | // flutter build, to the inno_bundle.name property value (if provided) 111 | if (p.basename(filePath) == config.exePubspecName && 112 | config.exeName != config.exePubspecName) { 113 | print("Renamed ${config.exePubspecName} ${config.exeName}"); 114 | section += "Source: \"$filePath\"; DestDir: \"{app}\"; " 115 | "DestName: \"${config.exeName}\"; Flags: ignoreversion\n"; 116 | } else { 117 | section += "Source: \"$filePath\"; DestDir: \"{app}\"; " 118 | "Flags: ignoreversion\n"; 119 | } 120 | } 121 | } 122 | 123 | // adding optional DLL files from System32 (if they are available), 124 | // so that the end user is not required to install 125 | // MS Visual C++ redistributable to run the app. 126 | final scriptDirPath = p.joinAll([ 127 | Directory.systemTemp.absolute.path, 128 | "${camelCase(config.name)}Installer", 129 | config.type.dirName, 130 | ]); 131 | Directory(scriptDirPath).createSync(recursive: true); 132 | for (final fileName in vcDllFiles) { 133 | final file = File(p.joinAll([...system32, fileName])); 134 | if (!file.existsSync()) continue; 135 | final fileNewPath = p.join(scriptDirPath, p.basename(file.path)); 136 | file.copySync(fileNewPath); 137 | section += "Source: \"$fileNewPath\"; DestDir: \"{app}\";\n"; 138 | } 139 | 140 | return '$section\n'; 141 | } 142 | 143 | String _icons() { 144 | final name = config.name; 145 | final exeName = config.exeName; 146 | return ''' 147 | [Icons] 148 | Name: "{autoprograms}\\$name"; Filename: "{app}\\$exeName" 149 | Name: "{autodesktop}\\$name"; Filename: "{app}\\$exeName"; Tasks: desktopicon 150 | \n'''; 151 | } 152 | 153 | String _run() { 154 | final name = config.name; 155 | final exeName = config.exeName; 156 | return ''' 157 | [Run] 158 | Filename: "{app}\\$exeName"; Description: "{cm:LaunchProgram,{#StringChange('$name', '&', '&&')}}"; Flags: nowait postinstall skipifsilent 159 | \n'''; 160 | } 161 | 162 | /// Generates the ISS script file and returns its path. 163 | Future build() async { 164 | CliLogger.info("Generating ISS script..."); 165 | final script = scriptHeader + 166 | _setup() + 167 | _installDelete() + 168 | _languages() + 169 | _tasks() + 170 | _files() + 171 | _icons() + 172 | _run(); 173 | final relScriptPath = p.joinAll([ 174 | ...installerBuildDir, 175 | config.type.dirName, 176 | "inno-script.iss", 177 | ]); 178 | final absScriptPath = p.join(Directory.current.path, relScriptPath); 179 | final scriptFile = File(absScriptPath); 180 | scriptFile.createSync(recursive: true); 181 | scriptFile.writeAsStringSync(script); 182 | CliLogger.success("Script generated $relScriptPath"); 183 | return scriptFile; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inno Bundle 2 | 3 | [![pub package](https://img.shields.io/pub/v/inno_bundle.svg)](https://pub.dev/packages/inno_bundle) 4 | [![inno setup](https://img.shields.io/badge/Inno_Setup-v6.2.2-blue)](https://jrsoftware.org/isinfo.php) 5 | ![dz flutter community](https://img.shields.io/badge/hahouari-Inno_Setup-blue) 6 | 7 | A command-line tool that simplifies bundling your app into an EXE installer for 8 | Microsoft Windows. Customizable with options to configure the installer 9 | capabilities. 10 | 11 | ## Guide 12 | 13 | ### 1. Download Inno Setup 14 | 15 | - **Option 1: Using winget (Recommended)** 16 | 17 | ```ps 18 | winget install -e --id JRSoftware.InnoSetup 19 | ``` 20 | 21 | - **Option 2: Using chocolatey** 22 | 23 | ```ps 24 | choco install innosetup 25 | ``` 26 | 27 | - **Option 3: From official website** 28 | 29 | Download Inno Setup from official 30 | website. Then install it in your machine. 31 | 32 | _Note: This package is tested on Inno Setup version `6.2.2`._ 33 | 34 | ### 2. Install `inno_bundle` package into your project 35 | 36 | ```ps 37 | dart pub add dev:inno_bundle 38 | ``` 39 | 40 | ### 3. Generate App ID 41 | 42 | To generate a random id run: 43 | 44 | ```ps 45 | dart run inno_bundle:id 46 | ``` 47 | 48 | Or, if you want your app id based upon a namespace, that is also possible: 49 | 50 | ```ps 51 | dart run inno_bundle:id --ns "www.example.com" 52 | ``` 53 | 54 | The output id is going to be something similar to this: 55 | 56 | > f887d5f0-4690-1e07-8efc-d16ea7711bfb 57 | 58 | Copy & Paste the output to your `pubspec.yaml` as shown in the next step. 59 | 60 | ### 4. Set up the Configuration 61 | 62 | Add your configuration to your `pubspec.yaml`. example: 63 | 64 | ```yaml 65 | inno_bundle: 66 | id: f887d5f0-4690-1e07-8efc-d16ea7711bfb # <-- Put your own generated id here 67 | publisher: Your Name # Optional, but recommended. 68 | name: Demo App # Also optional, but recommended. 69 | ``` 70 | 71 | ### 5. Build the Installer 72 | 73 | After setting up the configuration, all that is left to do is run the package. 74 | 75 | ```ps 76 | flutter pub get 77 | dart run inno_bundle:build --release 78 | ``` 79 | 80 | _Note: `--release` flag is required if you want to build for `release` mode, see 81 | below for other options._ 82 | 83 | ## Using GitHub Workflow? 84 | 85 | To automate building the installer with GitHub actions, 86 | check out [the demo](https://github.com/hahouari/flutter_inno_workflows_demo). 87 | 88 | You can copy the [build.yaml](https://github.com/hahouari/flutter_inno_workflows_demo/blob/dev/.github/workflows/build.yaml) 89 | file to your project and make sure to update 90 | [the push branch](https://github.com/hahouari/flutter_inno_workflows_demo/blob/fb49da23996161acc80f0e9f4c169a01908a29a7/.github/workflows/build.yaml#L5). 91 | It will build the installer and push it to 92 | [GitHub Releases](https://github.com/hahouari/flutter_inno_workflows_demo/releases) with correct versioning. 93 | 94 | ## Attributes 95 | 96 | Full list of attributes which you can use into your configuration. 97 | All attributes should be under `inno_bundle` in `pubspec.yaml`. 98 | 99 | - `id`: `Required` A valid GUID that serves as an AppId. 100 | - `name`: App display name. Defaults to camel cased `name` from `pubspec.yaml`. 101 | - `description`: Defaults to `description` from `pubspec.yaml`. 102 | - `version`: Defaults to `version` from `pubspec.yaml`. 103 | - `publisher`: Defaults to `maintainer` from `pubspec.yaml`. Otherwise, an empty 104 | string. 105 | - `url`: Defaults to `homepage` from `pubspec.yaml`. Otherwise, an empty string. 106 | - `support_url`: Defaults to `url`. 107 | - `updates_url`: Defaults to `url`. 108 | - `installer_icon`: A path relative to the project that points to an ico image. 109 | Defaults 110 | to 111 | installer icon provided with the demo. 112 |  1  113 | - `languages`: List of installer's display languages. Defaults to all available languages. 2  114 | - `admin`: (`true` or `false`) Defaults to `true`. 115 | - `true`: Require elevated privileges during installation. App will install 116 | globally on the end user machine. 117 | - `false`: Don't require elevated privileges during installation. App will 118 | install into user-specific folder. 119 | - `license_file`: A path relative to the project that points to a text license file, if not provided, `inno_bundle` will look up for `LICENSE` file in your project root folder. Otherwise, it is set to an empty string. 120 | 121 | 1 Only **.ico** images were 122 | tested. 123 | 124 | 2 All supported languages are: 125 | english, armenian, 126 | brazilianportuguese, bulgarian, catalan, corsican, czech, danish, dutch, 127 | finnish, french, german, 128 | hebrew, hungarian, icelandic, italian, japanese, norwegian, polish, portuguese, 129 | russian, slovak, 130 | slovenian, spanish, turkish, ukrainian. 131 | 132 | ## Examples to CLI options 133 | 134 | This will skip building the app if it exists: 135 | 136 | ```ps 137 | dart run inno_bundle:build --no-app 138 | ``` 139 | 140 | This will skip building the installer, useful if you want to generate 141 | `.iss script` only: 142 | 143 | ```ps 144 | dart run inno_bundle:build --no-installer 145 | ``` 146 | 147 | This build is `release` mode: 148 | 149 | ```ps 150 | dart run inno_bundle:build --release 151 | ``` 152 | 153 | Other mode flags are `--profile`, `--debug` (Default). 154 | 155 | ## Other configuration examples 156 | 157 | ```yaml 158 | inno_bundle: 159 | id: f887d5f0-4690-1e07-8efc-d16ea7711bfb 160 | publisher: Jane Doe 161 | installer_icon: assets/images/installer.ico 162 | languages: 163 | - english 164 | - french 165 | - german 166 | admin: false 167 | ``` 168 | 169 | ```yaml 170 | inno_bundle: 171 | id: f887d5f0-4690-1e07-8efc-d16ea7711bfb 172 | name: Google Flutter Framework 173 | description: Flutter makes it easy and fast to build beautiful apps for mobile and beyond. 174 | publisher: Google LLC 175 | url: https://github.com/flutter/flutter 176 | support_url: https://github.com/flutter/flutter/wiki 177 | updates_url: https://github.com/flutter/flutter/releases 178 | ``` 179 | 180 | ## Additional Feature 181 | 182 | DLL files `msvcp140.dll`, `vcruntime140.dll`, `vcruntime140_1.dll` are also 183 | bundled (if detected in your machine) with the app during installer creation. 184 | This helps end-users avoid issues of missing DLL files when running app 185 | after install. To know more about it, visit 186 | this 187 | Stack Overflow issue. 188 | 189 | ![image](https://github.com/hahouari/inno_bundle/assets/39862612/a9d258a4-074c-47fc-973e-e307f3af7a9b) 190 | 191 | ## Reporting Issues 192 | 193 | If you encounter any 194 | issues 195 | please report them here. 196 | -------------------------------------------------------------------------------- /lib/utils/installer_icon.dart: -------------------------------------------------------------------------------- 1 | const defaultInstallerIconFileName = "installer.ico"; 2 | const defaultInstallerIcon = 3 | "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"; 4 | -------------------------------------------------------------------------------- /example/demo_app/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.5.0" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | characters: 29 | dependency: transitive 30 | description: 31 | name: characters 32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.3.0" 36 | clock: 37 | dependency: transitive 38 | description: 39 | name: clock 40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.18.0" 52 | crypto: 53 | dependency: transitive 54 | description: 55 | name: crypto 56 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "3.0.3" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.3.1" 68 | fixnum: 69 | dependency: transitive 70 | description: 71 | name: fixnum 72 | sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.1.0" 76 | flutter: 77 | dependency: "direct main" 78 | description: flutter 79 | source: sdk 80 | version: "0.0.0" 81 | flutter_lints: 82 | dependency: "direct dev" 83 | description: 84 | name: flutter_lints 85 | sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 86 | url: "https://pub.dev" 87 | source: hosted 88 | version: "2.0.3" 89 | flutter_test: 90 | dependency: "direct dev" 91 | description: flutter 92 | source: sdk 93 | version: "0.0.0" 94 | inno_bundle: 95 | dependency: "direct dev" 96 | description: 97 | path: "../.." 98 | relative: true 99 | source: path 100 | version: "0.6.0" 101 | leak_tracker: 102 | dependency: transitive 103 | description: 104 | name: leak_tracker 105 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 106 | url: "https://pub.dev" 107 | source: hosted 108 | version: "10.0.5" 109 | leak_tracker_flutter_testing: 110 | dependency: transitive 111 | description: 112 | name: leak_tracker_flutter_testing 113 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 114 | url: "https://pub.dev" 115 | source: hosted 116 | version: "3.0.5" 117 | leak_tracker_testing: 118 | dependency: transitive 119 | description: 120 | name: leak_tracker_testing 121 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 122 | url: "https://pub.dev" 123 | source: hosted 124 | version: "3.0.1" 125 | lints: 126 | dependency: transitive 127 | description: 128 | name: lints 129 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 130 | url: "https://pub.dev" 131 | source: hosted 132 | version: "2.1.1" 133 | matcher: 134 | dependency: transitive 135 | description: 136 | name: matcher 137 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 138 | url: "https://pub.dev" 139 | source: hosted 140 | version: "0.12.16+1" 141 | material_color_utilities: 142 | dependency: transitive 143 | description: 144 | name: material_color_utilities 145 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 146 | url: "https://pub.dev" 147 | source: hosted 148 | version: "0.11.1" 149 | meta: 150 | dependency: transitive 151 | description: 152 | name: meta 153 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 154 | url: "https://pub.dev" 155 | source: hosted 156 | version: "1.15.0" 157 | path: 158 | dependency: transitive 159 | description: 160 | name: path 161 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 162 | url: "https://pub.dev" 163 | source: hosted 164 | version: "1.9.0" 165 | sky_engine: 166 | dependency: transitive 167 | description: flutter 168 | source: sdk 169 | version: "0.0.99" 170 | source_span: 171 | dependency: transitive 172 | description: 173 | name: source_span 174 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 175 | url: "https://pub.dev" 176 | source: hosted 177 | version: "1.10.0" 178 | sprintf: 179 | dependency: transitive 180 | description: 181 | name: sprintf 182 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 183 | url: "https://pub.dev" 184 | source: hosted 185 | version: "7.0.0" 186 | stack_trace: 187 | dependency: transitive 188 | description: 189 | name: stack_trace 190 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 191 | url: "https://pub.dev" 192 | source: hosted 193 | version: "1.11.1" 194 | stream_channel: 195 | dependency: transitive 196 | description: 197 | name: stream_channel 198 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 199 | url: "https://pub.dev" 200 | source: hosted 201 | version: "2.1.2" 202 | string_scanner: 203 | dependency: transitive 204 | description: 205 | name: string_scanner 206 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 207 | url: "https://pub.dev" 208 | source: hosted 209 | version: "1.2.0" 210 | term_glyph: 211 | dependency: transitive 212 | description: 213 | name: term_glyph 214 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 215 | url: "https://pub.dev" 216 | source: hosted 217 | version: "1.2.1" 218 | test_api: 219 | dependency: transitive 220 | description: 221 | name: test_api 222 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 223 | url: "https://pub.dev" 224 | source: hosted 225 | version: "0.7.2" 226 | typed_data: 227 | dependency: transitive 228 | description: 229 | name: typed_data 230 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 231 | url: "https://pub.dev" 232 | source: hosted 233 | version: "1.3.2" 234 | uuid: 235 | dependency: transitive 236 | description: 237 | name: uuid 238 | sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" 239 | url: "https://pub.dev" 240 | source: hosted 241 | version: "4.4.0" 242 | vector_math: 243 | dependency: transitive 244 | description: 245 | name: vector_math 246 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 247 | url: "https://pub.dev" 248 | source: hosted 249 | version: "2.1.4" 250 | vm_service: 251 | dependency: transitive 252 | description: 253 | name: vm_service 254 | sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc 255 | url: "https://pub.dev" 256 | source: hosted 257 | version: "14.2.4" 258 | yaml: 259 | dependency: transitive 260 | description: 261 | name: yaml 262 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 263 | url: "https://pub.dev" 264 | source: hosted 265 | version: "3.1.2" 266 | sdks: 267 | dart: ">=3.3.0 <4.0.0" 268 | flutter: ">=3.18.0-18.0.pre.54" 269 | -------------------------------------------------------------------------------- /example/demo_app/windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "resource.h" 7 | 8 | namespace { 9 | 10 | /// Window attribute that enables dark mode window decorations. 11 | /// 12 | /// Redefined in case the developer's machine has a Windows SDK older than 13 | /// version 10.0.22000.0. 14 | /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 15 | #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE 16 | #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 17 | #endif 18 | 19 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 20 | 21 | /// Registry key for app theme preference. 22 | /// 23 | /// A value of 0 indicates apps should use dark mode. A non-zero or missing 24 | /// value indicates apps should use light mode. 25 | constexpr const wchar_t kGetPreferredBrightnessRegKey[] = 26 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; 27 | constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; 28 | 29 | // The number of Win32Window objects that currently exist. 30 | static int g_active_window_count = 0; 31 | 32 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 33 | 34 | // Scale helper to convert logical scaler values to physical using passed in 35 | // scale factor 36 | int Scale(int source, double scale_factor) { 37 | return static_cast(source * scale_factor); 38 | } 39 | 40 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 41 | // This API is only needed for PerMonitor V1 awareness mode. 42 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 43 | HMODULE user32_module = LoadLibraryA("User32.dll"); 44 | if (!user32_module) { 45 | return; 46 | } 47 | auto enable_non_client_dpi_scaling = 48 | reinterpret_cast( 49 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 50 | if (enable_non_client_dpi_scaling != nullptr) { 51 | enable_non_client_dpi_scaling(hwnd); 52 | } 53 | FreeLibrary(user32_module); 54 | } 55 | 56 | } // namespace 57 | 58 | // Manages the Win32Window's window class registration. 59 | class WindowClassRegistrar { 60 | public: 61 | ~WindowClassRegistrar() = default; 62 | 63 | // Returns the singleton registrar instance. 64 | static WindowClassRegistrar* GetInstance() { 65 | if (!instance_) { 66 | instance_ = new WindowClassRegistrar(); 67 | } 68 | return instance_; 69 | } 70 | 71 | // Returns the name of the window class, registering the class if it hasn't 72 | // previously been registered. 73 | const wchar_t* GetWindowClass(); 74 | 75 | // Unregisters the window class. Should only be called if there are no 76 | // instances of the window. 77 | void UnregisterWindowClass(); 78 | 79 | private: 80 | WindowClassRegistrar() = default; 81 | 82 | static WindowClassRegistrar* instance_; 83 | 84 | bool class_registered_ = false; 85 | }; 86 | 87 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 88 | 89 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 90 | if (!class_registered_) { 91 | WNDCLASS window_class{}; 92 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 93 | window_class.lpszClassName = kWindowClassName; 94 | window_class.style = CS_HREDRAW | CS_VREDRAW; 95 | window_class.cbClsExtra = 0; 96 | window_class.cbWndExtra = 0; 97 | window_class.hInstance = GetModuleHandle(nullptr); 98 | window_class.hIcon = 99 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 100 | window_class.hbrBackground = 0; 101 | window_class.lpszMenuName = nullptr; 102 | window_class.lpfnWndProc = Win32Window::WndProc; 103 | RegisterClass(&window_class); 104 | class_registered_ = true; 105 | } 106 | return kWindowClassName; 107 | } 108 | 109 | void WindowClassRegistrar::UnregisterWindowClass() { 110 | UnregisterClass(kWindowClassName, nullptr); 111 | class_registered_ = false; 112 | } 113 | 114 | Win32Window::Win32Window() { 115 | ++g_active_window_count; 116 | } 117 | 118 | Win32Window::~Win32Window() { 119 | --g_active_window_count; 120 | Destroy(); 121 | } 122 | 123 | bool Win32Window::Create(const std::wstring& title, 124 | const Point& origin, 125 | const Size& size) { 126 | Destroy(); 127 | 128 | const wchar_t* window_class = 129 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 130 | 131 | const POINT target_point = {static_cast(origin.x), 132 | static_cast(origin.y)}; 133 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 134 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 135 | double scale_factor = dpi / 96.0; 136 | 137 | HWND window = CreateWindow( 138 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW, 139 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 140 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 141 | nullptr, nullptr, GetModuleHandle(nullptr), this); 142 | 143 | if (!window) { 144 | return false; 145 | } 146 | 147 | UpdateTheme(window); 148 | 149 | return OnCreate(); 150 | } 151 | 152 | bool Win32Window::Show() { 153 | return ShowWindow(window_handle_, SW_SHOWNORMAL); 154 | } 155 | 156 | // static 157 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 158 | UINT const message, 159 | WPARAM const wparam, 160 | LPARAM const lparam) noexcept { 161 | if (message == WM_NCCREATE) { 162 | auto window_struct = reinterpret_cast(lparam); 163 | SetWindowLongPtr(window, GWLP_USERDATA, 164 | reinterpret_cast(window_struct->lpCreateParams)); 165 | 166 | auto that = static_cast(window_struct->lpCreateParams); 167 | EnableFullDpiSupportIfAvailable(window); 168 | that->window_handle_ = window; 169 | } else if (Win32Window* that = GetThisFromHandle(window)) { 170 | return that->MessageHandler(window, message, wparam, lparam); 171 | } 172 | 173 | return DefWindowProc(window, message, wparam, lparam); 174 | } 175 | 176 | LRESULT 177 | Win32Window::MessageHandler(HWND hwnd, 178 | UINT const message, 179 | WPARAM const wparam, 180 | LPARAM const lparam) noexcept { 181 | switch (message) { 182 | case WM_DESTROY: 183 | window_handle_ = nullptr; 184 | Destroy(); 185 | if (quit_on_close_) { 186 | PostQuitMessage(0); 187 | } 188 | return 0; 189 | 190 | case WM_DPICHANGED: { 191 | auto newRectSize = reinterpret_cast(lparam); 192 | LONG newWidth = newRectSize->right - newRectSize->left; 193 | LONG newHeight = newRectSize->bottom - newRectSize->top; 194 | 195 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 196 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 197 | 198 | return 0; 199 | } 200 | case WM_SIZE: { 201 | RECT rect = GetClientArea(); 202 | if (child_content_ != nullptr) { 203 | // Size and position the child window. 204 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 205 | rect.bottom - rect.top, TRUE); 206 | } 207 | return 0; 208 | } 209 | 210 | case WM_ACTIVATE: 211 | if (child_content_ != nullptr) { 212 | SetFocus(child_content_); 213 | } 214 | return 0; 215 | 216 | case WM_DWMCOLORIZATIONCOLORCHANGED: 217 | UpdateTheme(hwnd); 218 | return 0; 219 | } 220 | 221 | return DefWindowProc(window_handle_, message, wparam, lparam); 222 | } 223 | 224 | void Win32Window::Destroy() { 225 | OnDestroy(); 226 | 227 | if (window_handle_) { 228 | DestroyWindow(window_handle_); 229 | window_handle_ = nullptr; 230 | } 231 | if (g_active_window_count == 0) { 232 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 233 | } 234 | } 235 | 236 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 237 | return reinterpret_cast( 238 | GetWindowLongPtr(window, GWLP_USERDATA)); 239 | } 240 | 241 | void Win32Window::SetChildContent(HWND content) { 242 | child_content_ = content; 243 | SetParent(content, window_handle_); 244 | RECT frame = GetClientArea(); 245 | 246 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 247 | frame.bottom - frame.top, true); 248 | 249 | SetFocus(child_content_); 250 | } 251 | 252 | RECT Win32Window::GetClientArea() { 253 | RECT frame; 254 | GetClientRect(window_handle_, &frame); 255 | return frame; 256 | } 257 | 258 | HWND Win32Window::GetHandle() { 259 | return window_handle_; 260 | } 261 | 262 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 263 | quit_on_close_ = quit_on_close; 264 | } 265 | 266 | bool Win32Window::OnCreate() { 267 | // No-op; provided for subclasses. 268 | return true; 269 | } 270 | 271 | void Win32Window::OnDestroy() { 272 | // No-op; provided for subclasses. 273 | } 274 | 275 | void Win32Window::UpdateTheme(HWND const window) { 276 | DWORD light_mode; 277 | DWORD light_mode_size = sizeof(light_mode); 278 | LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, 279 | kGetPreferredBrightnessRegValue, 280 | RRF_RT_REG_DWORD, nullptr, &light_mode, 281 | &light_mode_size); 282 | 283 | if (result == ERROR_SUCCESS) { 284 | BOOL enable_dark_mode = light_mode == 0; 285 | DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, 286 | &enable_dark_mode, sizeof(enable_dark_mode)); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /lib/models/config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:inno_bundle/models/build_type.dart'; 3 | import 'package:inno_bundle/models/language.dart'; 4 | import 'package:inno_bundle/utils/cli_logger.dart'; 5 | import 'package:inno_bundle/utils/constants.dart'; 6 | import 'package:inno_bundle/utils/functions.dart'; 7 | import 'package:path/path.dart' as p; 8 | import 'package:uuid/uuid.dart'; 9 | import 'package:yaml/yaml.dart'; 10 | 11 | /// A class representing the configuration for building a Windows installer using Inno Setup. 12 | class Config { 13 | /// The unique identifier (UUID) for the app being packaged. 14 | final String id; 15 | 16 | /// The global pubspec name attribute, same name of the exe generated from flutter build. 17 | final String pubspecName; 18 | 19 | /// The name of the app after packaging. 20 | final String name; 21 | 22 | /// A description of the app being packaged. 23 | final String description; 24 | 25 | /// The app's version. 26 | final String version; 27 | 28 | /// The name of the publisher or maintainer. 29 | final String publisher; 30 | 31 | /// The app's homepage URL. 32 | final String url; 33 | 34 | /// The URL for support resources. 35 | final String supportUrl; 36 | 37 | /// The URL for checking for updates. 38 | final String updatesUrl; 39 | 40 | /// The path to the installer icon file. 41 | final String installerIcon; 42 | 43 | // The path to the text license file. 44 | final String licenseFile; 45 | 46 | /// The supported languages for the installer. 47 | final List languages; 48 | 49 | /// Whether the installer requires administrator privileges. 50 | final bool admin; 51 | 52 | /// The build type (debug or release). 53 | final BuildType type; 54 | 55 | /// Whether to include the app in the installer. 56 | final bool app; 57 | 58 | /// Whether to create an installer file. 59 | final bool installer; 60 | 61 | /// Arguments to be passed to flutter build. 62 | final String? buildArgs; 63 | 64 | /// Creates a [Config] instance with default values. 65 | const Config({ 66 | required this.buildArgs, 67 | required this.id, 68 | required this.pubspecName, 69 | required this.name, 70 | required this.description, 71 | required this.version, 72 | required this.publisher, 73 | required this.url, 74 | required this.supportUrl, 75 | required this.updatesUrl, 76 | required this.installerIcon, 77 | required this.languages, 78 | required this.admin, 79 | required this.licenseFile, 80 | this.type = BuildType.debug, 81 | this.app = true, 82 | this.installer = true, 83 | }); 84 | 85 | /// The name of the executable file that is created with flutter build. 86 | String get exePubspecName => "$pubspecName.exe"; 87 | 88 | /// The name of the executable file that will be created. 89 | String get exeName => "$name.exe"; 90 | 91 | /// Creates a [Config] instance from a JSON map, typically read from `pubspec.yaml`. 92 | /// 93 | /// Validates the configuration and exits with an error if invalid values are found. 94 | factory Config.fromJson( 95 | Map json, { 96 | BuildType type = BuildType.debug, 97 | bool app = true, 98 | bool installer = true, 99 | required String? buildArgs, 100 | required String? appVersion, 101 | }) { 102 | if (json['inno_bundle'] is! Map) { 103 | CliLogger.exitError("inno_bundle section is missing from pubspec.yaml."); 104 | } 105 | final Map inno = json['inno_bundle']; 106 | 107 | if (inno['id'] is! String) { 108 | CliLogger.exitError( 109 | "inno_bundle.id attribute is missing from pubspec.yaml. " 110 | "Run `dart run inno_bundle:guid` to generate a new one, " 111 | "then put it in your pubspec.yaml."); 112 | } else if (!Uuid.isValidUUID(fromString: inno['id'])) { 113 | CliLogger.exitError("inno_bundle.id from pubspec.yaml is not valid. " 114 | "Run `dart run inno_bundle:guid` to generate a new one, " 115 | "then put it in your pubspec.yaml."); 116 | } 117 | final String id = inno['id']; 118 | 119 | if (json['name'] is! String) { 120 | CliLogger.exitError("name attribute is missing from pubspec.yaml."); 121 | } 122 | final String pubspecName = json['name']; 123 | 124 | if (inno['name'] != null && !validFilenameRegex.hasMatch(inno['name'])) { 125 | CliLogger.exitError("inno_bundle.name from pubspec.yaml is not valid. " 126 | "`${inno['name']}` is not a valid file name."); 127 | } 128 | final String name = inno['name'] ?? pubspecName; 129 | 130 | if ((appVersion ?? inno['version'] ?? json['version']) is! String) { 131 | CliLogger.exitError("version attribute is missing from pubspec.yaml."); 132 | } 133 | final String version = appVersion ?? inno['version'] ?? json['version']; 134 | 135 | if ((inno['description'] ?? json['description']) is! String) { 136 | CliLogger.exitError( 137 | "description attribute is missing from pubspec.yaml."); 138 | } 139 | final String description = inno['description'] ?? json['description']; 140 | 141 | if ((inno['publisher'] ?? json['maintainer']) is! String) { 142 | CliLogger.exitError("maintainer or inno_bundle.publisher attributes are " 143 | "missing from pubspec.yaml."); 144 | } 145 | final String publisher = inno['publisher'] ?? json['maintainer']; 146 | 147 | final url = (inno['url'] ?? json['homepage'] ?? "") as String; 148 | final supportUrl = (inno['support_url'] as String?) ?? url; 149 | final updatesUrl = (inno['updates_url'] as String?) ?? url; 150 | 151 | if (inno['installer_icon'] != null && inno['installer_icon'] is! String) { 152 | CliLogger.exitError("inno_bundle.installer_icon attribute is invalid " 153 | "in pubspec.yaml."); 154 | } 155 | final installerIcon = inno['installer_icon'] != null 156 | ? p.join( 157 | Directory.current.path, 158 | p.fromUri(inno['installer_icon']), 159 | ) 160 | : defaultInstallerIconPlaceholder; 161 | if (installerIcon != defaultInstallerIconPlaceholder && 162 | !File(installerIcon).existsSync()) { 163 | CliLogger.exitError( 164 | "inno_bundle.installer_icon attribute value is invalid, " 165 | "`$installerIcon` file does not exist."); 166 | } 167 | 168 | if (inno['languages'] != null && inno['languages'] is! List) { 169 | CliLogger.exitError("inno_bundle.languages attribute is invalid " 170 | "in pubspec.yaml, only a list of strings is allowed."); 171 | } 172 | final languages = (inno['languages'] as List?)?.map((l) { 173 | final language = Language.getByNameOrNull(l); 174 | if (language == null) { 175 | CliLogger.exitError("problem in inno_bundle.languages attribute " 176 | "in pubspec.yaml, language `$l` is not supported."); 177 | } 178 | return language!; 179 | }).toList(growable: false) ?? 180 | Language.values; 181 | 182 | if (inno['admin'] != null && inno['admin'] is! bool) { 183 | CliLogger.exitError( 184 | "inno_bundle.admin attribute is invalid boolean value " 185 | "in pubspec.yaml"); 186 | } 187 | final bool admin = inno['admin'] ?? true; 188 | 189 | if (inno['license_file'] != null && inno['license_file'] is! String) { 190 | CliLogger.exitError("inno_bundle.license_file attribute is invalid " 191 | "in pubspec.yaml."); 192 | } 193 | 194 | final licenseFilePath = p.join( 195 | Directory.current.path, 196 | inno['license_file'] != null 197 | ? p.fromUri(inno['license_file']) 198 | : 'LICENSE', 199 | ); 200 | final licenseFile = 201 | File(licenseFilePath).existsSync() ? licenseFilePath : ''; 202 | 203 | return Config( 204 | buildArgs: buildArgs, 205 | id: id, 206 | pubspecName: pubspecName, 207 | name: name, 208 | description: description, 209 | version: version, 210 | publisher: publisher, 211 | url: url, 212 | supportUrl: supportUrl, 213 | updatesUrl: updatesUrl, 214 | installerIcon: installerIcon, 215 | languages: languages, 216 | admin: admin, 217 | type: type, 218 | app: app, 219 | installer: installer, 220 | licenseFile: licenseFile, 221 | ); 222 | } 223 | 224 | /// Creates a [Config] instance directly from the `pubspec.yaml` file. 225 | /// 226 | /// Provides a convenient way to load configuration without manual JSON parsing. 227 | factory Config.fromFile({ 228 | BuildType type = BuildType.debug, 229 | bool app = true, 230 | bool installer = true, 231 | required String? buildArgs, 232 | required String? appVersion, 233 | }) { 234 | const filePath = 'pubspec.yaml'; 235 | final yamlMap = loadYaml(File(filePath).readAsStringSync()) as Map; 236 | // yamlMap has the type YamlMap, which has several unwanted side effects 237 | final yamlConfig = yamlToMap(yamlMap as YamlMap); 238 | return Config.fromJson( 239 | yamlConfig, 240 | type: type, 241 | app: app, 242 | installer: installer, 243 | buildArgs: buildArgs, 244 | appVersion: appVersion, 245 | ); 246 | } 247 | 248 | /// Returns a string containing the config attributes as environment variables. 249 | String toEnvironmentVariables() { 250 | final variables = { 251 | 'APP_ID': id, 252 | 'PUBSPEC_NAME': pubspecName, 253 | 'APP_NAME': name, 254 | 'APP_NAME_CAMEL_CASE': camelCase(name), 255 | 'APP_DESCRIPTION': description, 256 | 'APP_VERSION': version, 257 | 'APP_PUBLISHER': publisher, 258 | 'APP_URL': url, 259 | 'APP_SUPPORT_URL': supportUrl, 260 | 'APP_UPDATES_URL': updatesUrl, 261 | 'APP_INSTALLER_ICON': installerIcon, 262 | 'APP_LANGUAGES': languages.map((l) => l.name).join(','), 263 | 'APP_ADMIN': admin.toString(), 264 | 'APP_TYPE': type.name, 265 | 'APP_BUILD_APP': app.toString(), 266 | 'APP_BUILD_INSTALLER': installer.toString(), 267 | }; 268 | 269 | return variables.entries 270 | .map((entry) => '${entry.key}=${entry.value}') 271 | .join('\n'); 272 | } 273 | } 274 | --------------------------------------------------------------------------------