Add 'packages/cli/' from commit '63e5c99c96809070c18af0c3803141d0436dd992'
git-subtree-dir: packages/cli git-subtree-mainline:cf8e83552a
git-subtree-split:63e5c99c96
This commit is contained in:
commit
0aab437ca1
48 changed files with 2512 additions and 0 deletions
BIN
packages/cli/.DS_Store
vendored
Normal file
BIN
packages/cli/.DS_Store
vendored
Normal file
Binary file not shown.
80
packages/cli/.gitignore
vendored
Normal file
80
packages/cli/.gitignore
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/dataSources.xml
|
||||||
|
.idea/dataSources.local.xml
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
### Dart template
|
||||||
|
# See https://www.dartlang.org/tools/private-files.html
|
||||||
|
|
||||||
|
# Files and directories created by pub
|
||||||
|
.buildlog
|
||||||
|
.packages
|
||||||
|
.project
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
**/packages/
|
||||||
|
|
||||||
|
# Files created by dart2js
|
||||||
|
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||||
|
# rules if you intend to use dart2js directly
|
||||||
|
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||||
|
# differentiate from explicit Javascript files)
|
||||||
|
*.dart.js
|
||||||
|
*.part.js
|
||||||
|
*.js.deps
|
||||||
|
*.js.map
|
||||||
|
*.info.json
|
||||||
|
|
||||||
|
# Directory created by dartdoc
|
||||||
|
doc/api/
|
||||||
|
|
||||||
|
# Don't commit pubspec lock file
|
||||||
|
# (Library packages only! Remove pattern if developing an application package)
|
||||||
|
pubspec.lock
|
||||||
|
/sample_project/lib/src/services/
|
||||||
|
/sample_project/test/services/
|
||||||
|
/sample_project/
|
||||||
|
sample_project/
|
||||||
|
sample-project
|
||||||
|
.dart_tool
|
19
packages/cli/.idea/angel_cli.iml
Normal file
19
packages/cli/.idea/angel_cli.iml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/sample_project/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/sample_project/build" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
8
packages/cli/.idea/modules.xml
Normal file
8
packages/cli/.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/angel_cli.iml" filepath="$PROJECT_DIR$/.idea/angel_cli.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
8
packages/cli/.idea/runConfigurations/Controller.xml
Normal file
8
packages/cli/.idea/runConfigurations/Controller.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Controller" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
|
||||||
|
<option name="arguments" value="controller" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/bin/angel.dart" />
|
||||||
|
<option name="workingDirectory" value="$PROJECT_DIR$/sample_project" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
7
packages/cli/.idea/runConfigurations/Doctor.xml
Normal file
7
packages/cli/.idea/runConfigurations/Doctor.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Doctor" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
|
||||||
|
<option name="arguments" value="doctor" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/bin/angel.dart" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
8
packages/cli/.idea/runConfigurations/Init.xml
Normal file
8
packages/cli/.idea/runConfigurations/Init.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Init" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
|
||||||
|
<option name="arguments" value="init sample_project" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/bin/angel.dart" />
|
||||||
|
<option name="workingDirectory" value="$PROJECT_DIR$" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
7
packages/cli/.idea/runConfigurations/Show_Help.xml
Normal file
7
packages/cli/.idea/runConfigurations/Show_Help.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Show Help" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
|
||||||
|
<option name="arguments" value="--help" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/bin/angel.dart" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
8
packages/cli/.idea/runConfigurations/Update.xml
Normal file
8
packages/cli/.idea/runConfigurations/Update.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Update" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
|
||||||
|
<option name="arguments" value="update" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/bin/angel.dart" />
|
||||||
|
<option name="workingDirectory" value="$PROJECT_DIR$" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
7
packages/cli/.idea/runConfigurations/Version.xml
Normal file
7
packages/cli/.idea/runConfigurations/Version.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Version" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
|
||||||
|
<option name="arguments" value="version" />
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/bin/angel.dart" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
1
packages/cli/.travis.yml
Normal file
1
packages/cli/.travis.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
language: dart
|
89
packages/cli/CHANGELOG.md
Normal file
89
packages/cli/CHANGELOG.md
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# 2.1.7+1
|
||||||
|
* Fix a bug where new directories were not being created in
|
||||||
|
`init`.
|
||||||
|
|
||||||
|
# 2.1.7
|
||||||
|
* Fix a bug where `ArgResults.arguments` was used in `init` instead of the
|
||||||
|
intended `ArgResults.rest`.
|
||||||
|
* Stop including `package:angel_model` imports in `make model`.
|
||||||
|
* Update dependencies in `make` commands.
|
||||||
|
* Fix `make model` to generate ORM + migration by default.
|
||||||
|
* Fix `MakerDependency` logic to print missing dependencies.
|
||||||
|
|
||||||
|
# 2.1.6
|
||||||
|
* Fix a bug where models always defaulted to ORM.
|
||||||
|
* Add GraphQL boilerplate.
|
||||||
|
* Automatically restore terminal colors on shutdown.
|
||||||
|
|
||||||
|
# 2.1.5+1
|
||||||
|
* Update to `inflection2`.
|
||||||
|
|
||||||
|
# 2.1.5
|
||||||
|
* Add `shared` boilerplates.
|
||||||
|
* Remove uncecessary `angel_model` imports.
|
||||||
|
|
||||||
|
# 2.1.4+1
|
||||||
|
* Patch `part of 'path'` renames.
|
||||||
|
|
||||||
|
# 2.1.4
|
||||||
|
* The `migration` argument to `model` just emits an annotation now.
|
||||||
|
* Add the ORM boilerplate.
|
||||||
|
|
||||||
|
# 2.1.3
|
||||||
|
* Fix generation of ORM models.
|
||||||
|
* A `--project-name` to `init` command.
|
||||||
|
|
||||||
|
# 2.1.2
|
||||||
|
* No migrations-by-default.
|
||||||
|
|
||||||
|
# 2.1.1
|
||||||
|
* Edit the way `rename` runs, leaving no corner unturned.
|
||||||
|
|
||||||
|
# 2.1.0
|
||||||
|
* Deprecate `angel install`.
|
||||||
|
* Rename projects using `snake_case`.
|
||||||
|
* `init` now fetches from `master`.
|
||||||
|
* Remove the `1.x` option.
|
||||||
|
* Add `make migration` command.
|
||||||
|
* Replace `{{oldName}}` in the `rename` command.
|
||||||
|
* `pub get` now runs with `inheritStdio`.
|
||||||
|
|
||||||
|
# 2.0.1
|
||||||
|
* `deploy systemd` now has an `--install` option, where you can immediately
|
||||||
|
spawn the service.
|
||||||
|
|
||||||
|
# 2.0.0
|
||||||
|
* `init` can now produce either 1.x or 2.x projects.
|
||||||
|
* Fixed deps for compatibility with Dart2 stable.
|
||||||
|
|
||||||
|
# 1.3.4
|
||||||
|
* Fix another typo.
|
||||||
|
|
||||||
|
# 1.3.3
|
||||||
|
* Fix a small typo in the model generator.
|
||||||
|
|
||||||
|
# 1.3.2
|
||||||
|
* Restore `part` directives in generated models.
|
||||||
|
|
||||||
|
# 1.3.1
|
||||||
|
* Add `deploy nginx` and `deploy systemd`.
|
||||||
|
|
||||||
|
# 1.3.0
|
||||||
|
* Focus on Dart2 from here on out.
|
||||||
|
* Update `code_builder`.
|
||||||
|
* More changes...
|
||||||
|
|
||||||
|
# 1.1.5
|
||||||
|
Deprecated several commands, in favor of the `make`
|
||||||
|
command:
|
||||||
|
* `controller`
|
||||||
|
* `plugin`
|
||||||
|
* `service`
|
||||||
|
* `test`
|
||||||
|
|
||||||
|
The `rename` command will now replace *all* occurrences
|
||||||
|
of the old project names with the new one in `config/`
|
||||||
|
YAML files, and also operates on the glob `config/**/*.yaml`.
|
||||||
|
|
||||||
|
Changed the call to run `angel start` to run `dart bin/server.dart` instead, after an
|
||||||
|
`init` command.
|
21
packages/cli/LICENSE
Normal file
21
packages/cli/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) [year] [fullname]
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
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.
|
22
packages/cli/README.md
Normal file
22
packages/cli/README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# angel_cli
|
||||||
|
|
||||||
|
![Screenshot of Terminal](screenshots/screenshot.png)
|
||||||
|
|
||||||
|
Command-line tools for the Angel framework.
|
||||||
|
Includes functionality such as:
|
||||||
|
* Project scaffolding
|
||||||
|
* Generating service models, plugins, tests and more
|
||||||
|
* Renaming projects
|
||||||
|
* Much more...
|
||||||
|
|
||||||
|
To install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pub global activate angel_cli
|
||||||
|
```
|
||||||
|
|
||||||
|
And then, for information on each command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ angel help
|
||||||
|
```
|
7
packages/cli/TODO.md
Normal file
7
packages/cli/TODO.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Todo
|
||||||
|
|
||||||
|
* `service`
|
||||||
|
* Add tests
|
||||||
|
* `migration`
|
||||||
|
* `deploy`
|
||||||
|
* Call these from Grinder script :)
|
3
packages/cli/analysis_options.yaml
Normal file
3
packages/cli/analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
55
packages/cli/bin/angel.dart
Normal file
55
packages/cli/bin/angel.dart
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/usr/bin/env dart
|
||||||
|
library angel_cli.tool;
|
||||||
|
|
||||||
|
import "dart:io";
|
||||||
|
import "package:args/command_runner.dart";
|
||||||
|
import 'package:angel_cli/angel_cli.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
|
||||||
|
final String DOCTOR = "doctor";
|
||||||
|
|
||||||
|
main(List<String> args) async {
|
||||||
|
var runner = new CommandRunner(
|
||||||
|
"angel",
|
||||||
|
asciiArt.trim() +
|
||||||
|
'\n\n' +
|
||||||
|
"Command-line tools for the Angel framework." +
|
||||||
|
'\n\n' +
|
||||||
|
'https://angel-dart.github.io');
|
||||||
|
|
||||||
|
runner.argParser
|
||||||
|
.addFlag('verbose', help: 'Print verbose output.', negatable: false);
|
||||||
|
|
||||||
|
runner
|
||||||
|
..addCommand(new DeployCommand())
|
||||||
|
..addCommand(new DoctorCommand())
|
||||||
|
..addCommand(new KeyCommand())
|
||||||
|
..addCommand(new InitCommand())
|
||||||
|
..addCommand(new InstallCommand())
|
||||||
|
..addCommand(new RenameCommand())
|
||||||
|
..addCommand(new MakeCommand());
|
||||||
|
|
||||||
|
return await runner.run(args).catchError((exc, st) {
|
||||||
|
if (exc is String) {
|
||||||
|
stdout.writeln(exc);
|
||||||
|
} else {
|
||||||
|
stderr.writeln("Oops, something went wrong: $exc");
|
||||||
|
if (args.contains('--verbose')) {
|
||||||
|
stderr.writeln(st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = 1;
|
||||||
|
}).whenComplete(() {
|
||||||
|
stdout.write(resetAll.wrap(''));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const String asciiArt = '''
|
||||||
|
____________ ________________________
|
||||||
|
___ |__ | / /_ ____/__ ____/__ /
|
||||||
|
__ /| |_ |/ /_ / __ __ __/ __ /
|
||||||
|
_ ___ | /| / / /_/ / _ /___ _ /___
|
||||||
|
/_/ |_/_/ |_/ \____/ /_____/ /_____/
|
||||||
|
|
||||||
|
''';
|
3
packages/cli/example/main.dart
Normal file
3
packages/cli/example/main.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
void main() {
|
||||||
|
// This package isn't usable from code.
|
||||||
|
}
|
3
packages/cli/lib/angel_cli.dart
Normal file
3
packages/cli/lib/angel_cli.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
library angel_cli;
|
||||||
|
|
||||||
|
export 'src/commands/commands.dart';
|
9
packages/cli/lib/src/commands/commands.dart
Normal file
9
packages/cli/lib/src/commands/commands.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
library angel_cli.commands;
|
||||||
|
|
||||||
|
export "deploy.dart";
|
||||||
|
export "doctor.dart";
|
||||||
|
export "key.dart";
|
||||||
|
export "init.dart";
|
||||||
|
export "install.dart";
|
||||||
|
export "make.dart";
|
||||||
|
export "rename.dart";
|
17
packages/cli/lib/src/commands/deploy.dart
Normal file
17
packages/cli/lib/src/commands/deploy.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'deploy/nginx.dart';
|
||||||
|
import 'deploy/systemd.dart';
|
||||||
|
|
||||||
|
class DeployCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'deploy';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'Generates scaffolding + helper functionality for deploying servers. Run this in your project root.';
|
||||||
|
|
||||||
|
DeployCommand() {
|
||||||
|
addSubcommand(new NginxCommand());
|
||||||
|
addSubcommand(new SystemdCommand());
|
||||||
|
}
|
||||||
|
}
|
52
packages/cli/lib/src/commands/deploy/nginx.dart
Normal file
52
packages/cli/lib/src/commands/deploy/nginx.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import '../../util.dart';
|
||||||
|
|
||||||
|
class NginxCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'nginx';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'Generates a NGINX configuration for a reverse proxy + static server.';
|
||||||
|
|
||||||
|
NginxCommand() {
|
||||||
|
argParser.addOption('out',
|
||||||
|
abbr: 'o',
|
||||||
|
help:
|
||||||
|
'An optional output file to write to; otherwise prints to stdout.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
var webPath = p.join(p.current, 'web');
|
||||||
|
var nginxText = '''
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
root ${p.absolute(webPath)}; # Set to your static files directory
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files \$uri @proxy; # Try to serve static files; fallback to proxied Angel server
|
||||||
|
}
|
||||||
|
|
||||||
|
location @proxy {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_http_version 1.1; # Important, do not omit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (!argResults.wasParsed('out')) {
|
||||||
|
print(nginxText);
|
||||||
|
} else {
|
||||||
|
var file = new File(argResults['out'] as String);
|
||||||
|
await file.create(recursive: true);
|
||||||
|
await file.writeAsString(nginxText);
|
||||||
|
print(green.wrap(
|
||||||
|
"$checkmark Successfully generated nginx configuration in '${file.path}'."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
packages/cli/lib/src/commands/deploy/systemd.dart
Normal file
86
packages/cli/lib/src/commands/deploy/systemd.dart
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import '../../util.dart';
|
||||||
|
|
||||||
|
class SystemdCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'systemd';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'Generates a systemd service to continuously run your server.';
|
||||||
|
|
||||||
|
SystemdCommand() {
|
||||||
|
argParser
|
||||||
|
..addOption('install',
|
||||||
|
abbr: 'i', help: 'A name to install this service as on the system.')
|
||||||
|
..addOption('user',
|
||||||
|
abbr: 'u',
|
||||||
|
defaultsTo: 'web',
|
||||||
|
help: 'The name of the unprivileged account to run the server as.')
|
||||||
|
..addOption('out',
|
||||||
|
abbr: 'o',
|
||||||
|
help:
|
||||||
|
'An optional output file to write to; otherwise prints to stdout.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
var projectPath = p.absolute(p.current);
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
var user = argResults['user'];
|
||||||
|
var systemdText = '''
|
||||||
|
[Unit]
|
||||||
|
Description=`${pubspec.name}` server
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=ANGEL_ENV=production
|
||||||
|
User=$user # Name of unprivileged `$user` user
|
||||||
|
WorkingDirectory=$projectPath # Path to `${pubspec.name}` project
|
||||||
|
ExecStart=${Platform.resolvedExecutable} bin/prod.dart
|
||||||
|
Restart=always # Restart process on crash
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
'''
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (!argResults.wasParsed('out') && !argResults.wasParsed('install')) {
|
||||||
|
print(systemdText);
|
||||||
|
} else if (argResults.wasParsed('install')) {
|
||||||
|
var systemdPath = argResults.wasParsed('out')
|
||||||
|
? argResults['out'] as String
|
||||||
|
: p.join('etc', 'systemd', 'system');
|
||||||
|
var serviceFilename = p.join(systemdPath,
|
||||||
|
p.setExtension(argResults['install'] as String, '.service'));
|
||||||
|
var file = new File(serviceFilename);
|
||||||
|
await file.create(recursive: true);
|
||||||
|
await file.writeAsString(systemdText);
|
||||||
|
print(green.wrap(
|
||||||
|
"$checkmark Successfully generated systemd service in '${file.path}'."));
|
||||||
|
|
||||||
|
// sudo systemctl daemon-reload
|
||||||
|
if (await runCommand('sudo', ['systemctl', 'daemon-reload'])) {
|
||||||
|
// sudo service <name> start
|
||||||
|
if (await runCommand('sudo', [
|
||||||
|
'service',
|
||||||
|
p.basenameWithoutExtension(serviceFilename),
|
||||||
|
'start'
|
||||||
|
])) {
|
||||||
|
} else {
|
||||||
|
print(red.wrap('$ballot Failed to install service system-wide.'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(red.wrap('$ballot Failed to install service system-wide.'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var file = new File(argResults['out'] as String);
|
||||||
|
await file.create(recursive: true);
|
||||||
|
await file.writeAsString(systemdText);
|
||||||
|
print(green.wrap(
|
||||||
|
"$checkmark Successfully generated systemd service in '${file.path}'."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
packages/cli/lib/src/commands/doctor.dart
Normal file
34
packages/cli/lib/src/commands/doctor.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import "dart:convert";
|
||||||
|
import "dart:io";
|
||||||
|
import "package:args/command_runner.dart";
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import '../util.dart';
|
||||||
|
|
||||||
|
class DoctorCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => "doctor";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
"Ensures that the current system is capable of running Angel.";
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
print("Checking your system for dependencies...");
|
||||||
|
await _checkForGit();
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkForGit() async {
|
||||||
|
try {
|
||||||
|
var git = await Process.start("git", ["--version"]);
|
||||||
|
if (await git.exitCode == 0) {
|
||||||
|
var version = await git.stdout.transform(utf8.decoder).join();
|
||||||
|
print(green.wrap(
|
||||||
|
"$checkmark Git executable found: v${version.replaceAll('git version', '').trim()}"));
|
||||||
|
} else
|
||||||
|
throw new Exception("Git executable exit code not 0");
|
||||||
|
} catch (exc) {
|
||||||
|
print(red.wrap("$ballot Git executable not found"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
322
packages/cli/lib/src/commands/init.dart
Normal file
322
packages/cli/lib/src/commands/init.dart
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import "dart:io";
|
||||||
|
import "package:args/command_runner.dart";
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../random_string.dart' as rs;
|
||||||
|
import '../util.dart';
|
||||||
|
import 'key.dart';
|
||||||
|
import 'pub.dart';
|
||||||
|
import 'rename.dart';
|
||||||
|
|
||||||
|
class InitCommand extends Command {
|
||||||
|
final KeyCommand _key = new KeyCommand();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => "init";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
"Initializes a new Angel project in the current directory.";
|
||||||
|
|
||||||
|
InitCommand() {
|
||||||
|
argParser
|
||||||
|
..addFlag('offline',
|
||||||
|
help:
|
||||||
|
'Disable online fetching of boilerplates. Also disables `pub-get`.',
|
||||||
|
negatable: false)
|
||||||
|
..addFlag('pub-get', defaultsTo: true)
|
||||||
|
..addOption('project-name',
|
||||||
|
abbr: 'n', help: 'The name for this project.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
Directory projectDir =
|
||||||
|
new Directory(argResults.rest.isEmpty ? "." : argResults.rest[0]);
|
||||||
|
print("Creating new Angel project in ${projectDir.absolute.path}...");
|
||||||
|
await _cloneRepo(projectDir);
|
||||||
|
// await preBuild(projectDir);
|
||||||
|
var secret = rs.randomAlphaNumeric(32);
|
||||||
|
print('Generated new development JWT secret: $secret');
|
||||||
|
await _key.changeSecret(
|
||||||
|
new File.fromUri(projectDir.uri.resolve('config/default.yaml')),
|
||||||
|
secret);
|
||||||
|
|
||||||
|
secret = rs.randomAlphaNumeric(32);
|
||||||
|
print('Generated new production JWT secret: $secret');
|
||||||
|
await _key.changeSecret(
|
||||||
|
new File.fromUri(projectDir.uri.resolve('config/production.yaml')),
|
||||||
|
secret);
|
||||||
|
|
||||||
|
var name = argResults.wasParsed('project-name')
|
||||||
|
? argResults['project-name'] as String
|
||||||
|
: p.basenameWithoutExtension(
|
||||||
|
projectDir.absolute.uri.normalizePath().toFilePath());
|
||||||
|
|
||||||
|
name = ReCase(name).snakeCase;
|
||||||
|
print('Renaming project from "angel" to "$name"...');
|
||||||
|
await renamePubspec(projectDir, 'angel', name);
|
||||||
|
await renameDartFiles(projectDir, 'angel', name);
|
||||||
|
|
||||||
|
if (argResults['pub-get'] != false && argResults['offline'] == false) {
|
||||||
|
print('Now running pub get...');
|
||||||
|
await _pubGet(projectDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
print(green.wrap("$checkmark Successfully initialized Angel project."));
|
||||||
|
|
||||||
|
stdout
|
||||||
|
..writeln()
|
||||||
|
..writeln(
|
||||||
|
'Congratulations! You are ready to start developing with Angel!')
|
||||||
|
..write('To start the server (with ')
|
||||||
|
..write(cyan.wrap('hot-reloading'))
|
||||||
|
..write('), run ')
|
||||||
|
..write(magenta.wrap('`dart --observe bin/dev.dart`'))
|
||||||
|
..writeln(' in your terminal.')
|
||||||
|
..writeln()
|
||||||
|
..writeln('Find more documentation about Angel:')
|
||||||
|
..writeln(' * https://angel-dart.github.io')
|
||||||
|
..writeln(' * https://github.com/angel-dart/angel/wiki')
|
||||||
|
..writeln(
|
||||||
|
' * https://www.youtube.com/playlist?list=PLl3P3tmiT-frEV50VdH_cIrA2YqIyHkkY')
|
||||||
|
..writeln(' * https://medium.com/the-angel-framework')
|
||||||
|
..writeln(' * https://dart.academy/tag/angel')
|
||||||
|
..writeln()
|
||||||
|
..writeln('Happy coding!');
|
||||||
|
}
|
||||||
|
|
||||||
|
_deleteRecursive(FileSystemEntity entity, [bool self = true]) async {
|
||||||
|
if (entity is Directory) {
|
||||||
|
await for (var entity in entity.list(recursive: true)) {
|
||||||
|
try {
|
||||||
|
await _deleteRecursive(entity);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (self != false) await entity.delete(recursive: true);
|
||||||
|
} catch (e) {}
|
||||||
|
} else if (entity is File) {
|
||||||
|
try {
|
||||||
|
await entity.delete(recursive: true);
|
||||||
|
} catch (e) {}
|
||||||
|
} else if (entity is Link) {
|
||||||
|
var path = await entity.resolveSymbolicLinks();
|
||||||
|
var stat = await FileStat.stat(path);
|
||||||
|
|
||||||
|
switch (stat.type) {
|
||||||
|
case FileSystemEntityType.directory:
|
||||||
|
return await _deleteRecursive(new Directory(path));
|
||||||
|
case FileSystemEntityType.file:
|
||||||
|
return await _deleteRecursive(new File(path));
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cloneRepo(Directory projectDir) async {
|
||||||
|
Directory boilerplateDir;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (await projectDir.exists()) {
|
||||||
|
var shouldDelete = prompts.getBool(
|
||||||
|
"Directory '${projectDir.absolute.path}' already exists. Overwrite it?");
|
||||||
|
|
||||||
|
if (!shouldDelete)
|
||||||
|
throw "Chose not to overwrite existing directory.";
|
||||||
|
else if (projectDir.absolute.uri.normalizePath().toFilePath() !=
|
||||||
|
Directory.current.absolute.uri.normalizePath().toFilePath())
|
||||||
|
await projectDir.delete(recursive: true);
|
||||||
|
else {
|
||||||
|
await _deleteRecursive(projectDir, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// var boilerplate = basicBoilerplate;
|
||||||
|
print('Choose a project type before continuing:');
|
||||||
|
var boilerplate = prompts.choose(
|
||||||
|
'Choose a project type before continuing', boilerplates);
|
||||||
|
|
||||||
|
// Ultimately, we want a clone of every boilerplate locally on the system.
|
||||||
|
var boilerplateRootDir = Directory(p.join(angelDir.path, 'boilerplates'));
|
||||||
|
var boilerplateBasename = p.basenameWithoutExtension(boilerplate.url);
|
||||||
|
if (boilerplate.ref != null) boilerplateBasename += '.${boilerplate.ref}';
|
||||||
|
boilerplateDir =
|
||||||
|
Directory(p.join(boilerplateRootDir.path, boilerplateBasename));
|
||||||
|
await boilerplateRootDir.create(recursive: true);
|
||||||
|
|
||||||
|
var branch = boilerplate.ref ?? 'master';
|
||||||
|
|
||||||
|
// If there is no clone existing, clone it.
|
||||||
|
if (!await boilerplateDir.exists()) {
|
||||||
|
if (argResults['offline'] as bool) {
|
||||||
|
throw Exception(
|
||||||
|
'--offline was selected, but the "${boilerplate.name}" boilerplate has not yet been downloaded.');
|
||||||
|
}
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Cloning "${boilerplate.name}" boilerplate from "${boilerplate.url}"...');
|
||||||
|
Process git;
|
||||||
|
|
||||||
|
if (boilerplate.ref == null) {
|
||||||
|
print(darkGray.wrap(
|
||||||
|
'\$ git clone --depth 1 ${boilerplate.url} ${boilerplateDir.absolute.path}'));
|
||||||
|
git = await Process.start(
|
||||||
|
"git",
|
||||||
|
[
|
||||||
|
"clone",
|
||||||
|
"--depth",
|
||||||
|
"1",
|
||||||
|
boilerplate.url,
|
||||||
|
boilerplateDir.absolute.path
|
||||||
|
],
|
||||||
|
mode: ProcessStartMode.inheritStdio,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// git clone --single-branch -b branch host:/dir.git
|
||||||
|
print(darkGray.wrap(
|
||||||
|
'\$ git clone --depth 1 --single-branch -b ${boilerplate.ref} ${boilerplate.url} ${boilerplateDir.absolute.path}'));
|
||||||
|
git = await Process.start(
|
||||||
|
"git",
|
||||||
|
[
|
||||||
|
"clone",
|
||||||
|
"--depth",
|
||||||
|
"1",
|
||||||
|
"--single-branch",
|
||||||
|
"-b",
|
||||||
|
boilerplate.ref,
|
||||||
|
boilerplate.url,
|
||||||
|
boilerplateDir.absolute.path
|
||||||
|
],
|
||||||
|
mode: ProcessStartMode.inheritStdio,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await git.exitCode != 0) {
|
||||||
|
throw new Exception("Could not clone repo.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, pull from git.
|
||||||
|
else if (!(argResults['offline'] as bool)) {
|
||||||
|
print(darkGray.wrap('\$ git pull origin $branch'));
|
||||||
|
var git = await Process.start("git", ['pull', 'origin', '$branch'],
|
||||||
|
mode: ProcessStartMode.inheritStdio,
|
||||||
|
workingDirectory: boilerplateDir.absolute.path);
|
||||||
|
if (await git.exitCode != 0) {
|
||||||
|
print(yellow.wrap(
|
||||||
|
"Update of $branch failed. Attempting to continue with existing contents."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(darkGray.wrap(
|
||||||
|
'Using existing contents of "${boilerplate.name}" boilerplate.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, just copy everything into the given directory.
|
||||||
|
await copyDirectory(boilerplateDir, projectDir);
|
||||||
|
|
||||||
|
if (boilerplate.needsPrebuild) {
|
||||||
|
await preBuild(projectDir).catchError((_) => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var gitDir = new Directory.fromUri(projectDir.uri.resolve(".git"));
|
||||||
|
if (await gitDir.exists()) await gitDir.delete(recursive: true);
|
||||||
|
} catch (e) {
|
||||||
|
await boilerplateDir.delete(recursive: true).catchError((_) => null);
|
||||||
|
|
||||||
|
if (e is! String) {
|
||||||
|
print(red.wrap("$ballot Could not initialize Angel project."));
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_pubGet(Directory projectDir) async {
|
||||||
|
var pubPath = resolvePub();
|
||||||
|
print(darkGray.wrap('Running pub at "$pubPath"...'));
|
||||||
|
print(darkGray.wrap('\$ $pubPath get'));
|
||||||
|
var pub = await Process.start(pubPath, ["get"],
|
||||||
|
workingDirectory: projectDir.absolute.path,
|
||||||
|
mode: ProcessStartMode.inheritStdio);
|
||||||
|
var code = await pub.exitCode;
|
||||||
|
print("Pub process exited with code $code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future preBuild(Directory projectDir) async {
|
||||||
|
// Run build
|
||||||
|
// print('Running `pub run build_runner build`...');
|
||||||
|
print(darkGray.wrap('\$ pub run build_runner build'));
|
||||||
|
|
||||||
|
var build = await Process.start(
|
||||||
|
resolvePub(), ['run', 'build_runner', 'build'],
|
||||||
|
workingDirectory: projectDir.absolute.path,
|
||||||
|
mode: ProcessStartMode.inheritStdio);
|
||||||
|
|
||||||
|
var buildCode = await build.exitCode;
|
||||||
|
|
||||||
|
if (buildCode != 0) throw new Exception('Failed to pre-build resources.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoilerplateInfo graphQLBoilerplate = const BoilerplateInfo(
|
||||||
|
'GraphQL',
|
||||||
|
"A starting point for GraphQL API servers.",
|
||||||
|
'https://github.com/angel-dart/angel.git',
|
||||||
|
ref: 'graphql',
|
||||||
|
);
|
||||||
|
|
||||||
|
const BoilerplateInfo ormBoilerplate = const BoilerplateInfo(
|
||||||
|
'ORM',
|
||||||
|
"A starting point for applications that use Angel's ORM.",
|
||||||
|
'https://github.com/angel-dart/angel.git',
|
||||||
|
ref: 'orm',
|
||||||
|
);
|
||||||
|
|
||||||
|
const BoilerplateInfo basicBoilerplate = const BoilerplateInfo(
|
||||||
|
'Basic',
|
||||||
|
'Minimal starting point for Angel 2.x - A simple server with only a few additional packages.',
|
||||||
|
'https://github.com/angel-dart/angel.git');
|
||||||
|
|
||||||
|
const BoilerplateInfo legacyBoilerplate = const BoilerplateInfo(
|
||||||
|
'Legacy',
|
||||||
|
'Minimal starting point for applications running Angel 1.1.x.',
|
||||||
|
'https://github.com/angel-dart/angel.git',
|
||||||
|
ref: '1.1.x',
|
||||||
|
);
|
||||||
|
|
||||||
|
const BoilerplateInfo sharedBoilerplate = const BoilerplateInfo(
|
||||||
|
'Shared',
|
||||||
|
'Holds common models and files shared across multiple Dart projects.',
|
||||||
|
'https://github.com/angel-dart/boilerplate_shared.git');
|
||||||
|
|
||||||
|
const BoilerplateInfo sharedOrmBoilerplate = const BoilerplateInfo(
|
||||||
|
'Shared (ORM)',
|
||||||
|
'Holds common models and files shared across multiple Dart projects.',
|
||||||
|
'https://github.com/angel-dart/boilerplate_shared.git',
|
||||||
|
ref: 'orm',
|
||||||
|
);
|
||||||
|
|
||||||
|
const List<BoilerplateInfo> boilerplates = const [
|
||||||
|
basicBoilerplate,
|
||||||
|
//legacyBoilerplate,
|
||||||
|
ormBoilerplate,
|
||||||
|
graphQLBoilerplate,
|
||||||
|
sharedBoilerplate,
|
||||||
|
sharedOrmBoilerplate,
|
||||||
|
];
|
||||||
|
|
||||||
|
class BoilerplateInfo {
|
||||||
|
final String name, description, url, ref;
|
||||||
|
final bool needsPrebuild;
|
||||||
|
|
||||||
|
const BoilerplateInfo(this.name, this.description, this.url,
|
||||||
|
{this.ref, this.needsPrebuild: false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '$name ($description)';
|
||||||
|
}
|
248
packages/cli/lib/src/commands/install.dart
Normal file
248
packages/cli/lib/src/commands/install.dart
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:glob/glob.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:mustache4dart/mustache4dart.dart' as mustache;
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||||
|
import 'package:yaml/yaml.dart' as yaml;
|
||||||
|
import '../util.dart';
|
||||||
|
import 'make/maker.dart';
|
||||||
|
|
||||||
|
class InstallCommand extends Command {
|
||||||
|
static const String repo = 'https://github.com/angel-dart/install.git';
|
||||||
|
static final Directory installRepo =
|
||||||
|
new Directory.fromUri(homeDir.uri.resolve('./.angel/addons'));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'install';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'Installs additional add-ons to minimize boilerplate.';
|
||||||
|
|
||||||
|
InstallCommand() {
|
||||||
|
argParser
|
||||||
|
..addFlag(
|
||||||
|
'list',
|
||||||
|
help: 'List all currently-installed add-ons.',
|
||||||
|
negatable: false,
|
||||||
|
defaultsTo: false,
|
||||||
|
)
|
||||||
|
..addFlag(
|
||||||
|
'update',
|
||||||
|
help: 'Update the local add-on repository.',
|
||||||
|
negatable: false,
|
||||||
|
defaultsTo: false,
|
||||||
|
)
|
||||||
|
..addFlag(
|
||||||
|
'wipe',
|
||||||
|
help: 'Wipe the local add-on repository.',
|
||||||
|
negatable: false,
|
||||||
|
defaultsTo: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
print(yellow.wrap(
|
||||||
|
'WARNING: The `install` command is no longer considered necessary, and has been deprecated.\n'
|
||||||
|
'Expect it to be removed in an upcoming release.\n\n'
|
||||||
|
'See here: https://github.com/angel-dart/install.git\n\n'
|
||||||
|
'To stop seeing this, downgrade to `package:angel_cli@<=2.0.0`.'));
|
||||||
|
|
||||||
|
if (argResults['wipe'] as bool) {
|
||||||
|
if (await installRepo.exists()) await installRepo.delete(recursive: true);
|
||||||
|
} else if (argResults['list'] as bool) {
|
||||||
|
var addons = await list();
|
||||||
|
print('${addons.length} add-on(s) installed:');
|
||||||
|
|
||||||
|
for (var addon in addons) {
|
||||||
|
print(' * ${addon.name}@${addon.version}: ${addon.description}');
|
||||||
|
}
|
||||||
|
} else if (argResults['update'] as bool) {
|
||||||
|
await update();
|
||||||
|
} else if (argResults.rest.isNotEmpty) {
|
||||||
|
if (!await installRepo.exists())
|
||||||
|
throw 'No local add-on database exists. Run `angel install --update` first.';
|
||||||
|
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
|
||||||
|
for (var packageName in argResults.rest) {
|
||||||
|
var packageDir =
|
||||||
|
new Directory.fromUri(installRepo.uri.resolve(packageName));
|
||||||
|
|
||||||
|
if (!await packageDir.exists())
|
||||||
|
throw 'No add-on named "$packageName" is installed. You might need to run `angel install --update`.';
|
||||||
|
print('Installing $packageName...');
|
||||||
|
|
||||||
|
Map values = {
|
||||||
|
'project_name': pubspec.name,
|
||||||
|
'pubspec': pubspec,
|
||||||
|
};
|
||||||
|
|
||||||
|
List<Glob> globs = [];
|
||||||
|
|
||||||
|
var projectPubspec = await loadPubspec(packageDir);
|
||||||
|
var deps = projectPubspec.dependencies.keys
|
||||||
|
.map((k) {
|
||||||
|
var dep = projectPubspec.dependencies[k];
|
||||||
|
if (dep is HostedDependency)
|
||||||
|
return new MakerDependency(k, dep.version.toString());
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.where((d) => d != null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
deps.addAll(projectPubspec.devDependencies.keys.map((k) {
|
||||||
|
var dep = projectPubspec.devDependencies[k];
|
||||||
|
if (dep is HostedDependency)
|
||||||
|
return new MakerDependency(k, dep.version.toString(), dev: true);
|
||||||
|
return null;
|
||||||
|
}).where((d) => d != null));
|
||||||
|
|
||||||
|
await depend(deps);
|
||||||
|
|
||||||
|
var promptFile =
|
||||||
|
new File.fromUri(packageDir.uri.resolve('angel_cli.yaml'));
|
||||||
|
|
||||||
|
if (await promptFile.exists()) {
|
||||||
|
var contents = await promptFile.readAsString();
|
||||||
|
var y = yaml.loadYamlDocument(contents);
|
||||||
|
var cfg = y.contents.value as Map;
|
||||||
|
|
||||||
|
// Loads globs
|
||||||
|
if (cfg['templates'] is List) {
|
||||||
|
globs.addAll(
|
||||||
|
(cfg['templates'] as List).map((p) => new Glob(p.toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg['values'] is Map) {
|
||||||
|
var val = cfg['values'] as Map;
|
||||||
|
|
||||||
|
for (var key in val.keys) {
|
||||||
|
var desc = val[key]['description'] ?? key;
|
||||||
|
|
||||||
|
if (val[key]['type'] == 'prompt') {
|
||||||
|
values[key] = prompts.get(desc.toString(),
|
||||||
|
defaultsTo: val[key]['default']?.toString());
|
||||||
|
} else if (val[key]['type'] == 'choice') {
|
||||||
|
values[key] = prompts.choose(
|
||||||
|
desc.toString(), val[key]['choices'] as Iterable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future merge(Directory src, Directory dst, String prefix) async {
|
||||||
|
if (!await src.exists()) return;
|
||||||
|
print('Copying ${src.absolute.path} into ${dst.absolute.path}...');
|
||||||
|
if (!await dst.exists()) await dst.create(recursive: true);
|
||||||
|
|
||||||
|
await for (var entity in src.list()) {
|
||||||
|
if (entity is Directory) {
|
||||||
|
var name = p.basename(entity.path);
|
||||||
|
var newDir = new Directory.fromUri(dst.uri.resolve(name));
|
||||||
|
await merge(
|
||||||
|
entity, newDir, prefix.isEmpty ? name : '$prefix/$name');
|
||||||
|
} else if (entity is File &&
|
||||||
|
!entity.path.endsWith('angel_cli.yaml')) {
|
||||||
|
var name = p.basename(entity.path);
|
||||||
|
var target = dst.uri.resolve(name);
|
||||||
|
var targetFile = new File.fromUri(target);
|
||||||
|
bool allClear = !await targetFile.exists();
|
||||||
|
|
||||||
|
if (!allClear) {
|
||||||
|
print('The file ${entity.absolute.path} already exists.');
|
||||||
|
allClear = prompts.getBool('Overwrite the existing file?');
|
||||||
|
if (allClear) await targetFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allClear) {
|
||||||
|
try {
|
||||||
|
var path = prefix.isEmpty ? name : '$prefix/$name';
|
||||||
|
|
||||||
|
if (globs.any((g) => g.matches(path))) {
|
||||||
|
print(
|
||||||
|
'Rendering Mustache template from ${entity.absolute.path} to ${targetFile.absolute.path}...');
|
||||||
|
var contents = await entity.readAsString();
|
||||||
|
var renderer = mustache.compile(contents);
|
||||||
|
var generated = renderer(values);
|
||||||
|
await targetFile.writeAsString(generated.toString());
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'Copying ${entity.absolute.path} to ${targetFile.absolute.path}...');
|
||||||
|
await targetFile.parent.create(recursive: true);
|
||||||
|
await entity.copy(targetFile.absolute.path);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
print('Failed to copy.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('Skipped ${entity.absolute.path}.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await merge(new Directory.fromUri(packageDir.uri.resolve('files')),
|
||||||
|
Directory.current, '');
|
||||||
|
print('Successfully installed $packageName@${projectPubspec.version}.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('No add-ons were specified to be installed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Pubspec>> list() async {
|
||||||
|
if (!await installRepo.exists()) {
|
||||||
|
throw 'No local add-on database exists. Run `angel install --update` first.';
|
||||||
|
} else {
|
||||||
|
List<Pubspec> repos = [];
|
||||||
|
|
||||||
|
await for (var entity in installRepo.list()) {
|
||||||
|
if (entity is Directory) {
|
||||||
|
try {
|
||||||
|
repos.add(await loadPubspec(entity));
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore failures...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future update() async {
|
||||||
|
Process git;
|
||||||
|
|
||||||
|
if (!await installRepo.exists()) {
|
||||||
|
git = await Process.start('git', [
|
||||||
|
'clone',
|
||||||
|
repo,
|
||||||
|
installRepo.absolute.path,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
git = await Process.start(
|
||||||
|
'git',
|
||||||
|
[
|
||||||
|
'pull',
|
||||||
|
'origin',
|
||||||
|
'master',
|
||||||
|
],
|
||||||
|
workingDirectory: installRepo.absolute.path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
git..stdout.listen(stdout.add)..stderr.listen(stderr.add);
|
||||||
|
|
||||||
|
var code = await git.exitCode;
|
||||||
|
|
||||||
|
if (code != 0) {
|
||||||
|
throw 'git exited with code $code.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
packages/cli/lib/src/commands/key.dart
Normal file
30
packages/cli/lib/src/commands/key.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import '../random_string.dart' as rs;
|
||||||
|
|
||||||
|
class KeyCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'key';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => 'Generates a new `angel_auth` key.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
var secret = rs.randomAlphaNumeric(32);
|
||||||
|
print('Generated new development JWT secret: $secret');
|
||||||
|
await changeSecret(new File('config/default.yaml'), secret);
|
||||||
|
|
||||||
|
secret = rs.randomAlphaNumeric(32);
|
||||||
|
print('Generated new production JWT secret: $secret');
|
||||||
|
await changeSecret(new File('config/production.yaml'), secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSecret(File file, String secret) async {
|
||||||
|
if (await file.exists()) {
|
||||||
|
var contents = await file.readAsString();
|
||||||
|
contents = contents.replaceAll(new RegExp(r'jwt_secret:[^\n]+\n?'), '');
|
||||||
|
await file.writeAsString(contents.trim() + '\njwt_secret: "$secret"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
packages/cli/lib/src/commands/make.dart
Normal file
25
packages/cli/lib/src/commands/make.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'make/controller.dart';
|
||||||
|
import 'make/migration.dart';
|
||||||
|
import 'make/model.dart';
|
||||||
|
import 'make/plugin.dart';
|
||||||
|
import 'make/service.dart';
|
||||||
|
import 'make/test.dart';
|
||||||
|
|
||||||
|
class MakeCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'make';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'Generates common code for your project, such as projects and controllers.';
|
||||||
|
|
||||||
|
MakeCommand() {
|
||||||
|
addSubcommand(new ControllerCommand());
|
||||||
|
addSubcommand(new MigrationCommand());
|
||||||
|
addSubcommand(new ModelCommand());
|
||||||
|
addSubcommand(new PluginCommand());
|
||||||
|
addSubcommand(new TestCommand());
|
||||||
|
addSubcommand(new ServiceCommand());
|
||||||
|
}
|
||||||
|
}
|
123
packages/cli/lib/src/commands/make/controller.dart
Normal file
123
packages/cli/lib/src/commands/make/controller.dart
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
import 'maker.dart';
|
||||||
|
|
||||||
|
class ControllerCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'controller';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => 'Generates a controller class.';
|
||||||
|
|
||||||
|
ControllerCommand() {
|
||||||
|
argParser
|
||||||
|
..addFlag('websocket',
|
||||||
|
abbr: 'w',
|
||||||
|
help:
|
||||||
|
'Generates a WebSocketController, instead of an HTTP controller.',
|
||||||
|
negatable: false)
|
||||||
|
..addOption('name',
|
||||||
|
abbr: 'n', help: 'Specifies a name for the model class.')
|
||||||
|
..addOption('output-dir',
|
||||||
|
help: 'Specifies a directory to create the controller class in.',
|
||||||
|
defaultsTo: 'lib/src/routes/controllers');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
String name;
|
||||||
|
if (argResults.wasParsed('name')) name = argResults['name'] as String;
|
||||||
|
|
||||||
|
if (name?.isNotEmpty != true) {
|
||||||
|
name = prompts.get('Name of controller class');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MakerDependency> deps = [
|
||||||
|
const MakerDependency('angel_framework', '^2.0.0')
|
||||||
|
];
|
||||||
|
|
||||||
|
// ${pubspec.name}.src.models.${rc.snakeCase}
|
||||||
|
|
||||||
|
var rc = new ReCase(name);
|
||||||
|
var controllerLib = new Library((controllerLib) {
|
||||||
|
if (argResults['websocket'] as bool) {
|
||||||
|
deps.add(const MakerDependency('angel_websocket', '^2.0.0'));
|
||||||
|
controllerLib.directives
|
||||||
|
.add(new Directive.import('package:angel_websocket/server.dart'));
|
||||||
|
} else {
|
||||||
|
controllerLib.directives.add(new Directive.import(
|
||||||
|
'package:angel_framework/angel_framework.dart'));
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerLib.body.add(new Class((clazz) {
|
||||||
|
clazz
|
||||||
|
..name = '${rc.pascalCase}Controller'
|
||||||
|
..extend = refer(argResults['websocket'] as bool
|
||||||
|
? 'WebSocketController'
|
||||||
|
: 'Controller');
|
||||||
|
|
||||||
|
if (argResults['websocket'] as bool) {
|
||||||
|
// XController(AngelWebSocket ws) : super(ws);
|
||||||
|
clazz.constructors.add(new Constructor((b) {
|
||||||
|
b
|
||||||
|
..requiredParameters.add(new Parameter((b) => b
|
||||||
|
..name = 'ws'
|
||||||
|
..type = refer('AngelWebSocket')))
|
||||||
|
..initializers.add(new Code('super(ws)'));
|
||||||
|
}));
|
||||||
|
|
||||||
|
clazz.methods.add(new Method((meth) {
|
||||||
|
meth
|
||||||
|
..name = 'hello'
|
||||||
|
..returns = refer('void')
|
||||||
|
..annotations
|
||||||
|
.add(refer('ExposeWs').call([literal('get_${rc.snakeCase}')]))
|
||||||
|
..requiredParameters.add(new Parameter((b) => b
|
||||||
|
..name = 'socket'
|
||||||
|
..type = refer('WebSocketContext')))
|
||||||
|
..body = new Block((block) {
|
||||||
|
block.addExpression(refer('socket').property('send').call([
|
||||||
|
literal('got_${rc.snakeCase}'),
|
||||||
|
literalMap({'message': literal('Hello, world!')}),
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
clazz
|
||||||
|
..annotations
|
||||||
|
.add(refer('Expose').call([literal('/${rc.snakeCase}')]))
|
||||||
|
..methods.add(new Method((meth) {
|
||||||
|
meth
|
||||||
|
..name = 'hello'
|
||||||
|
..returns = refer('String')
|
||||||
|
..body = literal('Hello, world').returned.statement
|
||||||
|
..annotations.add(refer('Expose').call([
|
||||||
|
literal('/'),
|
||||||
|
]));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
var outputDir = new Directory.fromUri(
|
||||||
|
Directory.current.uri.resolve(argResults['output-dir'] as String));
|
||||||
|
var controllerFile =
|
||||||
|
new File.fromUri(outputDir.uri.resolve('${rc.snakeCase}.dart'));
|
||||||
|
if (!await controllerFile.exists())
|
||||||
|
await controllerFile.create(recursive: true);
|
||||||
|
await controllerFile.writeAsString(new DartFormatter()
|
||||||
|
.format(controllerLib.accept(new DartEmitter()).toString()));
|
||||||
|
|
||||||
|
print(green.wrap(
|
||||||
|
'$checkmark Created controller file "${controllerFile.absolute.path}"'));
|
||||||
|
|
||||||
|
if (deps.isNotEmpty) await depend(deps);
|
||||||
|
}
|
||||||
|
}
|
82
packages/cli/lib/src/commands/make/maker.dart
Normal file
82
packages/cli/lib/src/commands/make/maker.dart
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
|
||||||
|
class MakerDependency implements Comparable<MakerDependency> {
|
||||||
|
final String name, version;
|
||||||
|
final bool dev;
|
||||||
|
|
||||||
|
const MakerDependency(this.name, this.version, {this.dev: false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
int compareTo(MakerDependency other) => name.compareTo(other.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future depend(Iterable<MakerDependency> deps) async {
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
var missing = <MakerDependency>[];
|
||||||
|
|
||||||
|
for (var dep in deps) {
|
||||||
|
var isPresent = false;
|
||||||
|
if (dep.dev)
|
||||||
|
isPresent = pubspec.devDependencies.containsKey(dep.name);
|
||||||
|
else
|
||||||
|
isPresent = pubspec.dependencies.containsKey(dep.name);
|
||||||
|
|
||||||
|
if (!isPresent) {
|
||||||
|
missing.add(dep);
|
||||||
|
// TODO: https://github.com/dart-lang/pubspec_parse/issues/17:
|
||||||
|
// print('Installing ${dep.name}@${dep.version}...');
|
||||||
|
//
|
||||||
|
// if (dep.dev) {
|
||||||
|
// pubspec.devDependencies[dep.name] = new HostedDependency(
|
||||||
|
// version: new VersionConstraint.parse(dep.version),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// pubspec.dependencies[dep.name] = new HostedDependency(
|
||||||
|
// version: new VersionConstraint.parse(dep.version),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var missingDeps = missing.where((d) => !d.dev).toList()..sort();
|
||||||
|
var missingDevDeps = missing.where((d) => d.dev).toList()..sort();
|
||||||
|
var totalCount = missingDeps.length + missingDevDeps.length;
|
||||||
|
|
||||||
|
if (totalCount > 0) {
|
||||||
|
print(yellow.wrap(totalCount == 1
|
||||||
|
? 'You are missing one dependency.'
|
||||||
|
: 'You are missing $totalCount dependencies.'));
|
||||||
|
print(yellow.wrap(
|
||||||
|
'Update your `pubspec.yaml` to add the following dependencies:\n'));
|
||||||
|
|
||||||
|
void printMissing(String type, Iterable<MakerDependency> deps) {
|
||||||
|
if (deps.isNotEmpty) {
|
||||||
|
print(yellow.wrap(' $type:'));
|
||||||
|
for (var dep in deps) {
|
||||||
|
print(yellow.wrap(' ${dep.name}: ${dep.version}'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printMissing('dependencies', missingDeps);
|
||||||
|
printMissing('dev_dependencies', missingDevDeps);
|
||||||
|
print('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (isPresent) {
|
||||||
|
// TODO: https://github.com/dart-lang/pubspec_parse/issues/17
|
||||||
|
// await savePubspec(pubspec);
|
||||||
|
// var pubPath = resolvePub();
|
||||||
|
//
|
||||||
|
// print('Now running `$pubPath get`...');
|
||||||
|
//
|
||||||
|
// var pubGet = await Process.start(pubPath, ['get']);
|
||||||
|
// pubGet.stdout.listen(stdout.add);
|
||||||
|
// pubGet.stderr.listen(stderr.add);
|
||||||
|
//
|
||||||
|
// var code = await pubGet.exitCode;
|
||||||
|
//
|
||||||
|
// if (code != 0) throw 'pub get terminated with exit code $code';
|
||||||
|
}
|
133
packages/cli/lib/src/commands/make/migration.dart
Normal file
133
packages/cli/lib/src/commands/make/migration.dart
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:inflection2/inflection2.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
import 'maker.dart';
|
||||||
|
|
||||||
|
class MigrationCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'migration';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => 'Generates a migration class.';
|
||||||
|
|
||||||
|
MigrationCommand() {
|
||||||
|
argParser
|
||||||
|
..addOption('name',
|
||||||
|
abbr: 'n', help: 'Specifies a name for the model class.')
|
||||||
|
..addOption('output-dir',
|
||||||
|
help: 'Specifies a directory to create the migration class in.',
|
||||||
|
defaultsTo: 'tool/migrations');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr run() async {
|
||||||
|
String name;
|
||||||
|
if (argResults.wasParsed('name')) name = argResults['name'] as String;
|
||||||
|
|
||||||
|
if (name?.isNotEmpty != true) {
|
||||||
|
name = prompts.get('Name of model class');
|
||||||
|
}
|
||||||
|
|
||||||
|
var deps = [const MakerDependency('angel_migration', '^2.0.0')];
|
||||||
|
var rc = new ReCase(name);
|
||||||
|
|
||||||
|
var migrationLib = new Library((migrationLib) {
|
||||||
|
migrationLib
|
||||||
|
..directives.add(new Directive.import(
|
||||||
|
'package:angel_migration.dart/angel_migration.dart'))
|
||||||
|
..body.add(new Class((migrationClazz) {
|
||||||
|
migrationClazz
|
||||||
|
..name = '${rc.pascalCase}Migration'
|
||||||
|
..extend = refer('Migration');
|
||||||
|
|
||||||
|
var tableName = pluralize(rc.snakeCase);
|
||||||
|
|
||||||
|
// up()
|
||||||
|
migrationClazz.methods.add(new Method((up) {
|
||||||
|
up
|
||||||
|
..name = 'up'
|
||||||
|
..returns = refer('void')
|
||||||
|
..annotations.add(refer('override'))
|
||||||
|
..requiredParameters.add(new Parameter((b) => b
|
||||||
|
..name = 'schema'
|
||||||
|
..type = refer('Schema')))
|
||||||
|
..body = new Block((block) {
|
||||||
|
// (table) { ... }
|
||||||
|
var callback = new Method((callback) {
|
||||||
|
callback
|
||||||
|
..requiredParameters
|
||||||
|
.add(new Parameter((b) => b..name = 'table'))
|
||||||
|
..body = new Block((block) {
|
||||||
|
var table = refer('table');
|
||||||
|
|
||||||
|
block.addExpression(
|
||||||
|
(table.property('serial').call([literal('id')]))
|
||||||
|
.property('primaryKey')
|
||||||
|
.call([]),
|
||||||
|
);
|
||||||
|
|
||||||
|
block.addExpression(
|
||||||
|
table.property('date').call([
|
||||||
|
literal('created_at'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
block.addExpression(
|
||||||
|
table.property('date').call([
|
||||||
|
literal('updated_at'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
block.addExpression(refer('schema').property('create').call([
|
||||||
|
literal(tableName),
|
||||||
|
callback.closure,
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
// down()
|
||||||
|
migrationClazz.methods.add(new Method((down) {
|
||||||
|
down
|
||||||
|
..name = 'down'
|
||||||
|
..returns = refer('void')
|
||||||
|
..annotations.add(refer('override'))
|
||||||
|
..requiredParameters.add(new Parameter((b) => b
|
||||||
|
..name = 'schema'
|
||||||
|
..type = refer('Schema')))
|
||||||
|
..body = new Block((block) {
|
||||||
|
block.addExpression(
|
||||||
|
refer('schema').property('drop').call([
|
||||||
|
literal(tableName),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save migration file
|
||||||
|
var migrationDir = new Directory.fromUri(
|
||||||
|
Directory.current.uri.resolve(argResults['output-dir'] as String));
|
||||||
|
var migrationFile =
|
||||||
|
new File.fromUri(migrationDir.uri.resolve('${rc.snakeCase}.dart'));
|
||||||
|
if (!await migrationFile.exists())
|
||||||
|
await migrationFile.create(recursive: true);
|
||||||
|
|
||||||
|
await migrationFile.writeAsString(new DartFormatter()
|
||||||
|
.format(migrationLib.accept(new DartEmitter()).toString()));
|
||||||
|
|
||||||
|
print(green.wrap(
|
||||||
|
'$checkmark Created migration file "${migrationFile.absolute.path}".'));
|
||||||
|
|
||||||
|
await depend(deps);
|
||||||
|
}
|
||||||
|
}
|
122
packages/cli/lib/src/commands/make/model.dart
Normal file
122
packages/cli/lib/src/commands/make/model.dart
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
import 'maker.dart';
|
||||||
|
|
||||||
|
class ModelCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'model';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => 'Generates a model class.';
|
||||||
|
|
||||||
|
ModelCommand() {
|
||||||
|
argParser
|
||||||
|
..addFlag('migration',
|
||||||
|
abbr: 'm',
|
||||||
|
help: 'Generate migrations when running `build_runner`.',
|
||||||
|
defaultsTo: true)
|
||||||
|
..addFlag('orm', help: 'Generate angel_orm code.', negatable: false)
|
||||||
|
..addFlag('serializable',
|
||||||
|
help: 'Generate angel_serialize annotations.', defaultsTo: true)
|
||||||
|
..addOption('name',
|
||||||
|
abbr: 'n', help: 'Specifies a name for the model class.')
|
||||||
|
..addOption('output-dir',
|
||||||
|
help: 'Specifies a directory to create the model class in.',
|
||||||
|
defaultsTo: 'lib/src/models');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
String name;
|
||||||
|
if (argResults.wasParsed('name')) name = argResults['name'] as String;
|
||||||
|
|
||||||
|
if (name?.isNotEmpty != true) {
|
||||||
|
name = prompts.get('Name of model class');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MakerDependency> deps = [
|
||||||
|
const MakerDependency('angel_model', '^1.0.0'),
|
||||||
|
];
|
||||||
|
|
||||||
|
var rc = new ReCase(name);
|
||||||
|
|
||||||
|
var modelLib = new Library((modelLib) {
|
||||||
|
if (argResults['orm'] as bool && argResults['migration'] as bool) {
|
||||||
|
modelLib.directives.addAll([
|
||||||
|
new Directive.import('package:angel_migration/angel_migration.dart'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var needsSerialize =
|
||||||
|
argResults['serializable'] as bool || argResults['orm'] as bool;
|
||||||
|
// argResults['migration'] as bool;
|
||||||
|
|
||||||
|
if (needsSerialize) {
|
||||||
|
modelLib.directives.add(new Directive.import(
|
||||||
|
'package:angel_serialize/angel_serialize.dart'));
|
||||||
|
deps.add(const MakerDependency('angel_serialize', '^2.0.0'));
|
||||||
|
deps.add(const MakerDependency('angel_serialize_generator', '^2.0.0'));
|
||||||
|
deps.add(const MakerDependency('build_runner', '^1.0.0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// else {
|
||||||
|
// modelLib.directives
|
||||||
|
// .add(new Directive.import('package:angel_model/angel_model.dart'));
|
||||||
|
// deps.add(const MakerDependency('angel_model', '^1.0.0'));
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (argResults['orm'] as bool) {
|
||||||
|
modelLib.directives.addAll([
|
||||||
|
new Directive.import('package:angel_orm/angel_orm.dart'),
|
||||||
|
]);
|
||||||
|
deps.add(const MakerDependency('angel_orm', '^2.0.0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
modelLib.body.addAll([
|
||||||
|
new Code("part '${rc.snakeCase}.g.dart';"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
modelLib.body.add(new Class((modelClazz) {
|
||||||
|
modelClazz
|
||||||
|
..abstract = true
|
||||||
|
..name = needsSerialize ? '_${rc.pascalCase}' : rc.pascalCase
|
||||||
|
..extend = refer('Model');
|
||||||
|
|
||||||
|
if (needsSerialize) {
|
||||||
|
// modelLib.addDirective(new PartBuilder('${rc.snakeCase}.g.dart'));
|
||||||
|
modelClazz.annotations.add(refer('serializable'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argResults['orm'] as bool) {
|
||||||
|
if (argResults['migration'] as bool) {
|
||||||
|
modelClazz.annotations.add(refer('orm'));
|
||||||
|
} else {
|
||||||
|
modelClazz.annotations.add(
|
||||||
|
refer('Orm').call([], {'generateMigration': literalFalse}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save model file
|
||||||
|
var outputDir = new Directory.fromUri(
|
||||||
|
Directory.current.uri.resolve(argResults['output-dir'] as String));
|
||||||
|
var modelFile =
|
||||||
|
new File.fromUri(outputDir.uri.resolve('${rc.snakeCase}.dart'));
|
||||||
|
if (!await modelFile.exists()) await modelFile.create(recursive: true);
|
||||||
|
|
||||||
|
await modelFile.writeAsString(new DartFormatter()
|
||||||
|
.format(modelLib.accept(new DartEmitter()).toString()));
|
||||||
|
|
||||||
|
print(green
|
||||||
|
.wrap('$checkmark Created model file "${modelFile.absolute.path}".'));
|
||||||
|
|
||||||
|
if (deps.isNotEmpty) await depend(deps);
|
||||||
|
}
|
||||||
|
}
|
69
packages/cli/lib/src/commands/make/plugin.dart
Normal file
69
packages/cli/lib/src/commands/make/plugin.dart
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
import 'maker.dart';
|
||||||
|
|
||||||
|
class PluginCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => "plugin";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => "Creates a new plug-in within the given project.";
|
||||||
|
|
||||||
|
PluginCommand() {
|
||||||
|
argParser
|
||||||
|
..addOption('name',
|
||||||
|
abbr: 'n', help: 'Specifies a name for the plug-in class.')
|
||||||
|
..addOption('output-dir',
|
||||||
|
help: 'Specifies a directory to create the plug-in class in.',
|
||||||
|
defaultsTo: 'lib/src/config/plugins');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
String name;
|
||||||
|
if (argResults.wasParsed('name')) name = argResults['name'] as String;
|
||||||
|
|
||||||
|
if (name?.isNotEmpty != true) {
|
||||||
|
name = prompts.get('Name of plug-in class');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MakerDependency> deps = [
|
||||||
|
const MakerDependency('angel_framework', '^2.0.0')
|
||||||
|
];
|
||||||
|
|
||||||
|
var rc = new ReCase(name);
|
||||||
|
final pluginDir = new Directory.fromUri(
|
||||||
|
Directory.current.uri.resolve(argResults['output-dir'] as String));
|
||||||
|
final pluginFile =
|
||||||
|
new File.fromUri(pluginDir.uri.resolve("${rc.snakeCase}.dart"));
|
||||||
|
if (!await pluginFile.exists()) await pluginFile.create(recursive: true);
|
||||||
|
await pluginFile.writeAsString(
|
||||||
|
new DartFormatter().format(_generatePlugin(pubspec, rc)));
|
||||||
|
|
||||||
|
if (deps.isNotEmpty) await depend(deps);
|
||||||
|
|
||||||
|
print(green.wrap(
|
||||||
|
'$checkmark Successfully generated plug-in file "${pluginFile.absolute.path}".'));
|
||||||
|
}
|
||||||
|
|
||||||
|
String _generatePlugin(Pubspec pubspec, ReCase rc) {
|
||||||
|
return '''
|
||||||
|
library ${pubspec.name}.src.config.plugins.${rc.snakeCase};
|
||||||
|
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
|
||||||
|
AngelConfigurer ${rc.camelCase}() {
|
||||||
|
return (Angel app) async {
|
||||||
|
// Work some magic...
|
||||||
|
};
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
}
|
136
packages/cli/lib/src/commands/make/service.dart
Normal file
136
packages/cli/lib/src/commands/make/service.dart
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:inflection2/inflection2.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../service_generators/service_generators.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
import 'maker.dart';
|
||||||
|
|
||||||
|
class ServiceCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'service';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => 'Generates an Angel service.';
|
||||||
|
|
||||||
|
ServiceCommand() {
|
||||||
|
argParser
|
||||||
|
..addFlag('typed',
|
||||||
|
abbr: 't',
|
||||||
|
help: 'Wrap the generated service in a `TypedService` instance.',
|
||||||
|
negatable: false)
|
||||||
|
..addOption('name',
|
||||||
|
abbr: 'n', help: 'Specifies a name for the service file.')
|
||||||
|
..addOption('output-dir',
|
||||||
|
help: 'Specifies a directory to create the service file.',
|
||||||
|
defaultsTo: 'lib/src/services');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
String name;
|
||||||
|
if (argResults.wasParsed('name')) name = argResults['name'] as String;
|
||||||
|
|
||||||
|
if (name?.isNotEmpty != true) {
|
||||||
|
name = prompts.get('Name of service');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MakerDependency> deps = [
|
||||||
|
const MakerDependency('angel_framework', '^2.0.0')
|
||||||
|
];
|
||||||
|
|
||||||
|
// '${pubspec.name}.src.services.${rc.snakeCase}'
|
||||||
|
var rc = new ReCase(name);
|
||||||
|
var serviceLib = new Library((serviceLib) {
|
||||||
|
var generator = prompts.choose(
|
||||||
|
'Choose which type of service to create', serviceGenerators);
|
||||||
|
|
||||||
|
// if (generator == null) {
|
||||||
|
// _pen.red();
|
||||||
|
// _pen('${Icon.BALLOT_X} \'$type\' services are not yet implemented. :(');
|
||||||
|
// _pen();
|
||||||
|
// throw 'Unrecognized service type: "$type".';
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (var dep in generator.dependencies) {
|
||||||
|
if (!deps.any((d) => d.name == dep.name)) deps.add(dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generator.goesFirst) {
|
||||||
|
generator.applyToLibrary(serviceLib, name, rc.snakeCase);
|
||||||
|
serviceLib.directives.add(new Directive.import(
|
||||||
|
'package:angel_framework/angel_framework.dart'));
|
||||||
|
} else {
|
||||||
|
serviceLib.directives.add(new Directive.import(
|
||||||
|
'package:angel_framework/angel_framework.dart'));
|
||||||
|
generator.applyToLibrary(serviceLib, name, rc.snakeCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argResults['typed'] as bool) {
|
||||||
|
serviceLib.directives
|
||||||
|
.add(new Directive.import('../models/${rc.snakeCase}.dart'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureServer() {}
|
||||||
|
serviceLib.body.add(new Method((configureServer) {
|
||||||
|
configureServer
|
||||||
|
..name = 'configureServer'
|
||||||
|
..returns = refer('AngelConfigurer');
|
||||||
|
|
||||||
|
configureServer.body = new Block((block) {
|
||||||
|
generator.applyToConfigureServer(
|
||||||
|
serviceLib, configureServer, block, name, rc.snakeCase);
|
||||||
|
|
||||||
|
// return (Angel app) async {}
|
||||||
|
var closure = new Method((closure) {
|
||||||
|
closure
|
||||||
|
..modifier = MethodModifier.async
|
||||||
|
..requiredParameters.add(new Parameter((b) => b
|
||||||
|
..name = 'app'
|
||||||
|
..type = refer('Angel')));
|
||||||
|
closure.body = new Block((block) {
|
||||||
|
generator.beforeService(serviceLib, block, name, rc.snakeCase);
|
||||||
|
|
||||||
|
// app.use('/api/todos', new MapService());
|
||||||
|
var service = generator.createInstance(
|
||||||
|
serviceLib, closure, name, rc.snakeCase);
|
||||||
|
|
||||||
|
if (argResults['typed'] as bool) {
|
||||||
|
var tb = new TypeReference((b) => b
|
||||||
|
..symbol = 'TypedService'
|
||||||
|
..types.add(refer(rc.pascalCase)));
|
||||||
|
service = tb.newInstance([service]);
|
||||||
|
}
|
||||||
|
|
||||||
|
block.addExpression(refer('app').property('use').call([
|
||||||
|
literal('/api/${pluralize(rc.snakeCase)}'),
|
||||||
|
service,
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
block.addExpression(closure.closure.returned);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
final outputDir = new Directory.fromUri(
|
||||||
|
Directory.current.uri.resolve(argResults['output-dir'] as String));
|
||||||
|
final serviceFile =
|
||||||
|
new File.fromUri(outputDir.uri.resolve("${rc.snakeCase}.dart"));
|
||||||
|
if (!await serviceFile.exists()) await serviceFile.create(recursive: true);
|
||||||
|
await serviceFile.writeAsString(new DartFormatter()
|
||||||
|
.format(serviceLib.accept(new DartEmitter()).toString()));
|
||||||
|
|
||||||
|
print(green.wrap(
|
||||||
|
'$checkmark Successfully generated service file "${serviceFile.absolute.path}".'));
|
||||||
|
|
||||||
|
if (deps.isNotEmpty) await depend(deps);
|
||||||
|
}
|
||||||
|
}
|
110
packages/cli/lib/src/commands/make/test.dart
Normal file
110
packages/cli/lib/src/commands/make/test.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:prompts/prompts.dart' as prompter;
|
||||||
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
import 'maker.dart';
|
||||||
|
|
||||||
|
class TestCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => "test";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => "Creates a new test within the given project.";
|
||||||
|
|
||||||
|
TestCommand() {
|
||||||
|
argParser
|
||||||
|
..addFlag('run-configuration',
|
||||||
|
help: 'Generate a run configuration for JetBrains IDE\'s.',
|
||||||
|
defaultsTo: true)
|
||||||
|
..addOption('name',
|
||||||
|
abbr: 'n', help: 'Specifies a name for the plug-in class.')
|
||||||
|
..addOption('output-dir',
|
||||||
|
help: 'Specifies a directory to create the plug-in class in.',
|
||||||
|
defaultsTo: 'test');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
String name;
|
||||||
|
if (argResults.wasParsed('name')) name = argResults['name'] as String;
|
||||||
|
|
||||||
|
if (name?.isNotEmpty != true) {
|
||||||
|
name = prompter.get('Name of test');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MakerDependency> deps = [
|
||||||
|
const MakerDependency('angel_framework', '^2.0.0'),
|
||||||
|
const MakerDependency('angel_test', '^2.0.0', dev: true),
|
||||||
|
const MakerDependency('test', '^1.0.0', dev: true),
|
||||||
|
];
|
||||||
|
|
||||||
|
var rc = new ReCase(name);
|
||||||
|
final testDir = new Directory.fromUri(
|
||||||
|
Directory.current.uri.resolve(argResults['output-dir'] as String));
|
||||||
|
final testFile =
|
||||||
|
new File.fromUri(testDir.uri.resolve("${rc.snakeCase}_test.dart"));
|
||||||
|
if (!await testFile.exists()) await testFile.create(recursive: true);
|
||||||
|
await testFile
|
||||||
|
.writeAsString(new DartFormatter().format(_generateTest(pubspec, rc)));
|
||||||
|
|
||||||
|
if (deps.isNotEmpty) await depend(deps);
|
||||||
|
|
||||||
|
print(green.wrap(
|
||||||
|
'$checkmark Successfully generated test file "${testFile.absolute.path}".'));
|
||||||
|
|
||||||
|
if (argResults['run-configuration'] as bool) {
|
||||||
|
final runConfig = new File.fromUri(Directory.current.uri
|
||||||
|
.resolve('.idea/runConfigurations/${name}_Tests.xml'));
|
||||||
|
|
||||||
|
if (!await runConfig.exists()) await runConfig.create(recursive: true);
|
||||||
|
await runConfig.writeAsString(_generateRunConfiguration(name, rc));
|
||||||
|
|
||||||
|
print(green.wrap(
|
||||||
|
'$checkmark Successfully generated run configuration "$name Tests" at "${runConfig.absolute.path}".'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _generateRunConfiguration(String name, ReCase rc) {
|
||||||
|
return '''
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="$name Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||||
|
<option name="filePath" value="\$PROJECT_DIR\$/test/${rc.snakeCase}_test.dart" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
'''
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _generateTest(Pubspec pubspec, ReCase rc) {
|
||||||
|
return '''
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:${pubspec.name}/${pubspec.name}.dart' as ${pubspec.name};
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_test/angel_test.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
TestClient client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
var app = new Angel();
|
||||||
|
await app.configure(${pubspec.name}.configureServer);
|
||||||
|
client = await connectTo(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => client.close());
|
||||||
|
|
||||||
|
test('${rc.snakeCase}', () async {
|
||||||
|
final response = await client.get('/${rc.snakeCase}');
|
||||||
|
expect(response, hasStatus(HttpStatus.ok));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
}
|
12
packages/cli/lib/src/commands/pub.dart
Normal file
12
packages/cli/lib/src/commands/pub.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
final RegExp _leadingSlashes = new RegExp(r'^/+');
|
||||||
|
|
||||||
|
String resolvePub() {
|
||||||
|
var exec = new File(Platform.resolvedExecutable);
|
||||||
|
var pubPath = exec.parent.uri.resolve('pub').path;
|
||||||
|
if (Platform.isWindows)
|
||||||
|
pubPath = pubPath.replaceAll(_leadingSlashes, '') + '.bat';
|
||||||
|
pubPath = Uri.decodeFull(pubPath);
|
||||||
|
return pubPath;
|
||||||
|
}
|
183
packages/cli/lib/src/commands/rename.dart
Normal file
183
packages/cli/lib/src/commands/rename.dart
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:analyzer/analyzer.dart';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:dart_style/dart_style.dart';
|
||||||
|
import 'package:glob/glob.dart';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:prompts/prompts.dart' as prompts;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../util.dart';
|
||||||
|
import 'pub.dart';
|
||||||
|
|
||||||
|
class RenameCommand extends Command {
|
||||||
|
@override
|
||||||
|
String get name => 'rename';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => 'Renames the current project.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get invocation => '$name <new name>';
|
||||||
|
|
||||||
|
@override
|
||||||
|
run() async {
|
||||||
|
String newName;
|
||||||
|
|
||||||
|
if (argResults.rest.isNotEmpty)
|
||||||
|
newName = argResults.rest.first;
|
||||||
|
else {
|
||||||
|
newName = prompts.get('Rename project to');
|
||||||
|
}
|
||||||
|
|
||||||
|
newName = new ReCase(newName).snakeCase;
|
||||||
|
|
||||||
|
var choice = prompts.getBool('Rename the project to `$newName`?');
|
||||||
|
|
||||||
|
if (choice) {
|
||||||
|
print('Renaming project to `$newName`...');
|
||||||
|
var pubspecFile =
|
||||||
|
new File.fromUri(Directory.current.uri.resolve('pubspec.yaml'));
|
||||||
|
|
||||||
|
if (!await pubspecFile.exists()) {
|
||||||
|
throw new Exception('No pubspec.yaml found in current directory.');
|
||||||
|
} else {
|
||||||
|
var pubspec = await loadPubspec();
|
||||||
|
var oldName = pubspec.name;
|
||||||
|
await renamePubspec(Directory.current, oldName, newName);
|
||||||
|
await renameDartFiles(Directory.current, oldName, newName);
|
||||||
|
print('Now running `pub get`...');
|
||||||
|
var pubPath = resolvePub();
|
||||||
|
print('Pub path: $pubPath');
|
||||||
|
var pub = await Process.start(pubPath, ['get']);
|
||||||
|
stdout.addStream(pub.stdout);
|
||||||
|
stderr.addStream(pub.stderr);
|
||||||
|
await pub.exitCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renamePubspec(Directory dir, String oldName, String newName) async {
|
||||||
|
// var pubspec = await loadPubspec(dir);
|
||||||
|
print(cyan.wrap('Renaming your project to `$newName.`'));
|
||||||
|
|
||||||
|
var pubspecFile = new File.fromUri(dir.uri.resolve('pubspec.yaml'));
|
||||||
|
|
||||||
|
if (await pubspecFile.exists()) {
|
||||||
|
var contents = await pubspecFile.readAsString(), oldContents = contents;
|
||||||
|
contents =
|
||||||
|
contents.replaceAll(new RegExp('name:\\s*$oldName'), 'name: $newName');
|
||||||
|
|
||||||
|
if (contents != oldContents) {
|
||||||
|
await pubspecFile.writeAsString(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print(cyan
|
||||||
|
// .wrap('Note that this does not actually modify your `pubspec.yaml`.'));
|
||||||
|
// TODO: https://github.com/dart-lang/pubspec_parse/issues/17
|
||||||
|
// var newPubspec = new Pubspec.fromJson(pubspec.toJson()..['name'] = newName);
|
||||||
|
// await newPubspec.save(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
renameDartFiles(Directory dir, String oldName, String newName) async {
|
||||||
|
if (!await dir.exists()) return;
|
||||||
|
|
||||||
|
// Try to replace MongoDB URL
|
||||||
|
var configGlob = new Glob('config/**/*.yaml');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await for (var yamlFile in configGlob.list(root: dir.absolute.path)) {
|
||||||
|
if (yamlFile is File) {
|
||||||
|
print(
|
||||||
|
'Replacing occurrences of "$oldName" with "$newName" in file "${yamlFile.absolute.path}"...');
|
||||||
|
var contents = await yamlFile.readAsString();
|
||||||
|
contents = contents.replaceAll(oldName, newName);
|
||||||
|
await yamlFile.writeAsString(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
var entry = new File.fromUri(dir.uri.resolve('lib/$oldName.dart'));
|
||||||
|
|
||||||
|
if (await entry.exists()) {
|
||||||
|
await entry.rename(dir.uri.resolve('lib/$newName.dart').toFilePath());
|
||||||
|
print('Renaming library file `${entry.absolute.path}`...');
|
||||||
|
}
|
||||||
|
|
||||||
|
var fmt = new DartFormatter();
|
||||||
|
await for (FileSystemEntity file in dir.list(recursive: true)) {
|
||||||
|
if (file is File && file.path.endsWith('.dart')) {
|
||||||
|
var contents = await file.readAsString();
|
||||||
|
var ast = parseCompilationUnit(contents);
|
||||||
|
var visitor = new RenamingVisitor(oldName, newName)
|
||||||
|
..visitCompilationUnit(ast);
|
||||||
|
|
||||||
|
if (visitor.replace.isNotEmpty) {
|
||||||
|
visitor.replace.forEach((range, replacement) {
|
||||||
|
if (range.first is int) {
|
||||||
|
contents = contents.replaceRange(
|
||||||
|
range.first as int, range.last as int, replacement);
|
||||||
|
} else if (range.first is String) {
|
||||||
|
contents = contents.replaceAll(range.first as String, replacement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await file.writeAsString(fmt.format(contents));
|
||||||
|
print('Updated file `${file.absolute.path}`.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenamingVisitor extends RecursiveAstVisitor {
|
||||||
|
final String oldName, newName;
|
||||||
|
final Map<List, String> replace = {};
|
||||||
|
|
||||||
|
RenamingVisitor(this.oldName, this.newName) {
|
||||||
|
replace[['{{$oldName}}']] = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
String updateUri(String uri) {
|
||||||
|
if (uri == 'package:$oldName/$oldName.dart') {
|
||||||
|
return 'package:$newName/$newName.dart';
|
||||||
|
} else if (uri.startsWith('package:$oldName/')) {
|
||||||
|
return 'package:$newName/' + uri.replaceFirst('package:$oldName/', '');
|
||||||
|
} else
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
visitExportDirective(ExportDirective ctx) {
|
||||||
|
var uri = ctx.uri.stringValue, updated = updateUri(uri);
|
||||||
|
if (uri != updated) replace[[uri]] = updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
visitImportDirective(ImportDirective ctx) {
|
||||||
|
var uri = ctx.uri.stringValue, updated = updateUri(uri);
|
||||||
|
if (uri != updated) replace[[uri]] = updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
visitLibraryDirective(LibraryDirective ctx) {
|
||||||
|
var name = ctx.name.name;
|
||||||
|
|
||||||
|
if (name.startsWith(oldName)) {
|
||||||
|
replace[[ctx.offset, ctx.end]] =
|
||||||
|
'library ' + name.replaceFirst(oldName, newName) + ';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
visitPartOfDirective(PartOfDirective ctx) {
|
||||||
|
if (ctx.libraryName != null) {
|
||||||
|
var name = ctx.libraryName.name;
|
||||||
|
|
||||||
|
if (name.startsWith(oldName)) {
|
||||||
|
replace[[ctx.offset, ctx.end]] =
|
||||||
|
'part of ' + name.replaceFirst(oldName, newName) + ';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
packages/cli/lib/src/commands/service_generators/custom.dart
Normal file
27
packages/cli/lib/src/commands/service_generators/custom.dart
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'generator.dart';
|
||||||
|
|
||||||
|
class CustomServiceGenerator extends ServiceGenerator {
|
||||||
|
@override
|
||||||
|
bool get createsModel => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get createsValidator => false;
|
||||||
|
|
||||||
|
const CustomServiceGenerator() : super('Custom');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void applyToLibrary(LibraryBuilder library, String name, String lower) {
|
||||||
|
library.body.add(new Class((clazz) {
|
||||||
|
clazz
|
||||||
|
..name = '${name}Service'
|
||||||
|
..extend = refer('Service');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression createInstance(LibraryBuilder library, MethodBuilder methodBuilder,
|
||||||
|
String name, String lower) {
|
||||||
|
return refer('${name}Service').newInstance([]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import 'generator.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:inflection2/inflection2.dart';
|
||||||
|
import '../make/maker.dart';
|
||||||
|
|
||||||
|
class FileServiceGenerator extends ServiceGenerator {
|
||||||
|
const FileServiceGenerator() : super('Persistent JSON File');
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<MakerDependency> get dependencies =>
|
||||||
|
const [const MakerDependency('angel_file_service', '^2.0.0')];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get goesFirst => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void applyToConfigureServer(
|
||||||
|
LibraryBuilder library,
|
||||||
|
MethodBuilder configureServer,
|
||||||
|
BlockBuilder block,
|
||||||
|
String name,
|
||||||
|
String lower) {
|
||||||
|
configureServer.requiredParameters.add(new Parameter((b) => b
|
||||||
|
..name = 'dbDirectory'
|
||||||
|
..type = refer('Directory')));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void applyToLibrary(LibraryBuilder library, String name, String lower) {
|
||||||
|
library.directives.addAll([
|
||||||
|
new Directive.import(
|
||||||
|
'package:angel_file_service/angel_file_service.dart'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression createInstance(LibraryBuilder library, MethodBuilder methodBuilder,
|
||||||
|
String name, String lower) {
|
||||||
|
library.directives.addAll([
|
||||||
|
new Directive.import('package:file/file.dart'),
|
||||||
|
]);
|
||||||
|
return refer('JsonFileService').newInstance([
|
||||||
|
refer('dbDirectory')
|
||||||
|
.property('childFile')
|
||||||
|
.call([literal(pluralize(lower) + '_db.json')])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import '../make/maker.dart';
|
||||||
|
|
||||||
|
class ServiceGenerator {
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
const ServiceGenerator(this.name);
|
||||||
|
|
||||||
|
List<MakerDependency> get dependencies => [];
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
bool get createsModel => true;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
bool get createsValidator => true;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
bool get exportedInServiceLibrary => true;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
bool get injectsSingleton => false;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
bool get shouldRunBuild => false;
|
||||||
|
|
||||||
|
bool get goesFirst => false;
|
||||||
|
|
||||||
|
void applyToLibrary(LibraryBuilder library, String name, String lower) {}
|
||||||
|
|
||||||
|
void beforeService(LibraryBuilder library, BlockBuilder builder, String name,
|
||||||
|
String lower) {}
|
||||||
|
|
||||||
|
void applyToConfigureServer(
|
||||||
|
LibraryBuilder library,
|
||||||
|
MethodBuilder configureServer,
|
||||||
|
BlockBuilder block,
|
||||||
|
String name,
|
||||||
|
String lower) {}
|
||||||
|
|
||||||
|
Expression createInstance(LibraryBuilder library, MethodBuilder methodBuilder,
|
||||||
|
String name, String lower) =>
|
||||||
|
literal(null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => name;
|
||||||
|
}
|
15
packages/cli/lib/src/commands/service_generators/map.dart
Normal file
15
packages/cli/lib/src/commands/service_generators/map.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'generator.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
|
||||||
|
class MapServiceGenerator extends ServiceGenerator {
|
||||||
|
const MapServiceGenerator() : super('In-Memory');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get createsModel => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression createInstance(LibraryBuilder library, MethodBuilder methodBuilder,
|
||||||
|
String name, String lower) {
|
||||||
|
return refer('MapService').newInstance([]);
|
||||||
|
}
|
||||||
|
}
|
43
packages/cli/lib/src/commands/service_generators/mongo.dart
Normal file
43
packages/cli/lib/src/commands/service_generators/mongo.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import 'generator.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:inflection2/inflection2.dart';
|
||||||
|
import '../make/maker.dart';
|
||||||
|
|
||||||
|
class MongoServiceGenerator extends ServiceGenerator {
|
||||||
|
const MongoServiceGenerator() : super('MongoDB');
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<MakerDependency> get dependencies =>
|
||||||
|
const [const MakerDependency('angel_mongo', '^2.0.0')];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get createsModel => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void applyToConfigureServer(
|
||||||
|
LibraryBuilder library,
|
||||||
|
MethodBuilder configureServer,
|
||||||
|
BlockBuilder block,
|
||||||
|
String name,
|
||||||
|
String lower) {
|
||||||
|
configureServer.requiredParameters.add(new Parameter((b) => b
|
||||||
|
..name = 'db'
|
||||||
|
..type = refer('Db')));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void applyToLibrary(LibraryBuilder library, String name, String lower) {
|
||||||
|
library.directives.addAll([
|
||||||
|
new Directive.import('package:angel_mongo/angel_mongo.dart'),
|
||||||
|
new Directive.import('package:mongo_dart/mongo_dart.dart'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression createInstance(LibraryBuilder library, MethodBuilder methodBuilder,
|
||||||
|
String name, String lower) {
|
||||||
|
return refer('MongoService').newInstance([
|
||||||
|
refer('db').property('collection').call([literal(pluralize(lower))])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import 'generator.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:inflection2/inflection2.dart';
|
||||||
|
import '../make/maker.dart';
|
||||||
|
|
||||||
|
class RethinkServiceGenerator extends ServiceGenerator {
|
||||||
|
const RethinkServiceGenerator() : super('RethinkDB');
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<MakerDependency> get dependencies =>
|
||||||
|
const [const MakerDependency('angel_rethink', '^2.0.0')];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get createsModel => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void applyToConfigureServer(
|
||||||
|
LibraryBuilder library,
|
||||||
|
MethodBuilder configureServer,
|
||||||
|
BlockBuilder block,
|
||||||
|
String name,
|
||||||
|
String lower) {
|
||||||
|
configureServer.requiredParameters.addAll([
|
||||||
|
new Parameter((b) => b
|
||||||
|
..name = 'connection'
|
||||||
|
..type = refer('Connection')),
|
||||||
|
new Parameter((b) => b
|
||||||
|
..name = 'r'
|
||||||
|
..type = refer('Rethinkdb')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void applyToLibrary(LibraryBuilder library, String name, String lower) {
|
||||||
|
library.directives.addAll([
|
||||||
|
'package:angel_rethink/angel_rethink.dart',
|
||||||
|
'package:rethinkdb_driver/rethinkdb_driver.dart'
|
||||||
|
].map((str) => new Directive.import(str)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression createInstance(LibraryBuilder library, MethodBuilder methodBuilder,
|
||||||
|
String name, String lower) {
|
||||||
|
return refer('RethinkService').newInstance([
|
||||||
|
refer('connection'),
|
||||||
|
refer('r').property('table').call([literal(pluralize(lower))])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'custom.dart';
|
||||||
|
import 'file_service.dart';
|
||||||
|
import 'generator.dart';
|
||||||
|
import 'map.dart';
|
||||||
|
import 'mongo.dart';
|
||||||
|
import 'rethink.dart';
|
||||||
|
export 'generator.dart';
|
||||||
|
|
||||||
|
const List<ServiceGenerator> serviceGenerators = const [
|
||||||
|
const MapServiceGenerator(),
|
||||||
|
const FileServiceGenerator(),
|
||||||
|
const MongoServiceGenerator(),
|
||||||
|
const RethinkServiceGenerator(),
|
||||||
|
const CustomServiceGenerator()
|
||||||
|
];
|
15
packages/cli/lib/src/random_string.dart
Normal file
15
packages/cli/lib/src/random_string.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
const String _valid =
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
final Random _rnd = new Random.secure();
|
||||||
|
|
||||||
|
String randomAlphaNumeric(int length) {
|
||||||
|
var b = new StringBuffer();
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
b.writeCharCode(_valid.codeUnitAt(_rnd.nextInt(_valid.length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.toString();
|
||||||
|
}
|
77
packages/cli/lib/src/util.dart
Normal file
77
packages/cli/lib/src/util.dart
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:io/ansi.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||||
|
//import 'package:yamlicious/yamlicious.dart';
|
||||||
|
|
||||||
|
final String checkmark = ansiOutputEnabled ? '\u2714' : '[Success]';
|
||||||
|
|
||||||
|
final String ballot = ansiOutputEnabled ? '\u2717' : '[Failure]';
|
||||||
|
|
||||||
|
String get homeDirPath =>
|
||||||
|
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
|
||||||
|
|
||||||
|
Directory get homeDir => new Directory(homeDirPath);
|
||||||
|
|
||||||
|
Directory get angelDir => Directory(p.join(homeDir.path, '.angel'));
|
||||||
|
|
||||||
|
Future<Pubspec> loadPubspec([Directory directory]) {
|
||||||
|
directory ??= Directory.current;
|
||||||
|
var file = new File.fromUri(directory.uri.resolve('pubspec.yaml'));
|
||||||
|
return file
|
||||||
|
.readAsString()
|
||||||
|
.then((yaml) => new Pubspec.parse(yaml, sourceUrl: file.uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
// From: https://gist.github.com/tobischw/98dcd2563eec9a2a87bda8299055358a
|
||||||
|
Future<void> copyDirectory(Directory source, Directory destination) async {
|
||||||
|
// if (!topLevel) stdout.write('\r');
|
||||||
|
// print(darkGray
|
||||||
|
// .wrap('Copying dir "${source.path}" -> "${destination.path}..."'));
|
||||||
|
|
||||||
|
await for (var entity in source.list(recursive: false)) {
|
||||||
|
if (p.basename(entity.path) == '.git') continue;
|
||||||
|
if (entity is Directory) {
|
||||||
|
var newDirectory =
|
||||||
|
Directory(p.join(destination.absolute.path, p.basename(entity.path)));
|
||||||
|
await newDirectory.create(recursive: true);
|
||||||
|
await copyDirectory(entity.absolute, newDirectory);
|
||||||
|
} else if (entity is File) {
|
||||||
|
var newPath = p.join(destination.path, p.basename(entity.path));
|
||||||
|
// print(darkGray.wrap('\rCopying file "${entity.path}" -> "$newPath"'));
|
||||||
|
await File(newPath).create(recursive: true);
|
||||||
|
await entity.copy(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print('\rCopied "${source.path}" -> "${destination.path}.');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future savePubspec(Pubspec pubspec) async {
|
||||||
|
// TODO: Save pubspec for real?
|
||||||
|
//var text = toYamlString(pubspec);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> runCommand(String exec, List<String> args) async {
|
||||||
|
var s = '$exec ${args.join(' ')}'.trim();
|
||||||
|
stdout.write(darkGray.wrap('Running `$s`... '));
|
||||||
|
|
||||||
|
try {
|
||||||
|
var p = await Process.start(exec, args);
|
||||||
|
var code = await p.exitCode;
|
||||||
|
|
||||||
|
if (code == 0) {
|
||||||
|
print(green.wrap(checkmark));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
print(red.wrap(ballot));
|
||||||
|
await stdout.addStream(p.stdout);
|
||||||
|
await stderr.addStream(p.stderr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(red.wrap('$ballot Failed to run process.'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
28
packages/cli/pubspec.yaml
Normal file
28
packages/cli/pubspec.yaml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
description: Command-line tools for the Angel framework, including scaffolding.
|
||||||
|
homepage: https://github.com/angel-dart/angel_cli
|
||||||
|
name: angel_cli
|
||||||
|
version: 2.1.7+1
|
||||||
|
dependencies:
|
||||||
|
analyzer: ">=0.32.0 <2.0.0"
|
||||||
|
args: ^1.0.0
|
||||||
|
code_builder: ^3.0.0
|
||||||
|
dart_style: ^1.0.0
|
||||||
|
glob: ^1.1.0
|
||||||
|
http: ^0.12.0
|
||||||
|
io: ^0.3.2
|
||||||
|
inflection2: ^0.4.2
|
||||||
|
mustache4dart: ^3.0.0-dev.1.0
|
||||||
|
path: ^1.0.0
|
||||||
|
prompts: ^1.0.0
|
||||||
|
pubspec_parse: ^0.1.2
|
||||||
|
quiver: ^2.0.0
|
||||||
|
recase: ^2.0.0
|
||||||
|
shutdown: ^0.4.0
|
||||||
|
watcher: ^0.9.7
|
||||||
|
yaml: ^2.0.0
|
||||||
|
#yamlicious: ^0.0.5
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.0.0-dev <3.0.0"
|
||||||
|
executables:
|
||||||
|
angel: angel
|
BIN
packages/cli/screenshots/screenshot.png
Normal file
BIN
packages/cli/screenshots/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
Loading…
Reference in a new issue