Moved CLI and graphQL
This commit is contained in:
parent
19e060800e
commit
1b28887608
234 changed files with 4 additions and 13313 deletions
BIN
packages/cli/.DS_Store
vendored
BIN
packages/cli/.DS_Store
vendored
Binary file not shown.
80
packages/cli/.gitignore
vendored
80
packages/cli/.gitignore
vendored
|
@ -1,80 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<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 +0,0 @@
|
||||||
language: dart
|
|
|
@ -1,2 +0,0 @@
|
||||||
Tobe O <thosakwe@gmail.com>
|
|
||||||
Thomas Hii <thomashii@dukefirehawk.com>
|
|
|
@ -1,92 +0,0 @@
|
||||||
# 3.0.0
|
|
||||||
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
|
||||||
|
|
||||||
# 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.
|
|
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
|
@ -1,26 +1,3 @@
|
||||||
# angel_cli
|
# Angel3 CLI
|
||||||
|
|
||||||
![Screenshot of Terminal](screenshots/screenshot.png)
|
Moved to [`Angel3 CLI Repo`](https://github.com/dukefirehawk/angel3-cli)
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
* Install development version
|
|
||||||
`dart pub global activate --source path ./packages/cli`
|
|
||||||
`dart pub global activate --source git https://github.com/dukefirehawk/angel/packages/cli`
|
|
||||||
|
|
||||||
And then, for information on each command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ angel help
|
|
||||||
```
|
|
|
@ -1,8 +0,0 @@
|
||||||
# Todo
|
|
||||||
* Migrate inflection2, mustache4dart2 and prompts packages to NNBD
|
|
||||||
|
|
||||||
* `service`
|
|
||||||
* Add tests
|
|
||||||
* `migration`
|
|
||||||
* `deploy`
|
|
||||||
* Call these from Grinder script :)
|
|
|
@ -1,3 +0,0 @@
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,55 +0,0 @@
|
||||||
#!/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 = '''
|
|
||||||
____________ ________________________
|
|
||||||
___ |__ | / /_ ____/__ ____/__ /
|
|
||||||
__ /| |_ |/ /_ / __ __ __/ __ /
|
|
||||||
_ ___ | /| / / /_/ / _ /___ _ /___
|
|
||||||
/_/ |_/_/ |_/ \____/ /_____/ /_____/
|
|
||||||
|
|
||||||
''';
|
|
|
@ -1,3 +0,0 @@
|
||||||
void main() {
|
|
||||||
// This package isn't usable from code.
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
library angel_cli;
|
|
||||||
|
|
||||||
export 'src/commands/commands.dart';
|
|
|
@ -1,9 +0,0 @@
|
||||||
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";
|
|
|
@ -1,17 +0,0 @@
|
||||||
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(NginxCommand());
|
|
||||||
addSubcommand(SystemdCommand());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
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}'."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
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}'."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
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 Exception("Git executable exit code not 0");
|
|
||||||
} catch (exc) {
|
|
||||||
print(red.wrap("$ballot Git executable not found"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,324 +0,0 @@
|
||||||
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 = 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 =
|
|
||||||
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(
|
|
||||||
File.fromUri(projectDir.uri.resolve('config/default.yaml')), secret);
|
|
||||||
|
|
||||||
secret = rs.randomAlphaNumeric(32);
|
|
||||||
print('Generated new production JWT secret: $secret');
|
|
||||||
await _key.changeSecret(
|
|
||||||
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(Directory(path));
|
|
||||||
case FileSystemEntityType.file:
|
|
||||||
return await _deleteRecursive(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 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 = 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 Exception('Failed to pre-build resources.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const RepoArchiveLocation = "https://github.com/angel-dart";
|
|
||||||
const RepoLocation = "https://github.com/dukefirehawk";
|
|
||||||
|
|
||||||
const BoilerplateInfo graphQLBoilerplate = const BoilerplateInfo(
|
|
||||||
'GraphQL',
|
|
||||||
"A starting point for GraphQL API servers.",
|
|
||||||
'${RepoLocation}/boilerplates.git',
|
|
||||||
ref: 'graphql-sdk-2.12.x',
|
|
||||||
);
|
|
||||||
|
|
||||||
const BoilerplateInfo ormBoilerplate = const BoilerplateInfo(
|
|
||||||
'ORM',
|
|
||||||
"A starting point for applications that use Angel's ORM.",
|
|
||||||
'${RepoLocation}/boilerplates.git',
|
|
||||||
ref: 'orm-sdk-2.12.x',
|
|
||||||
);
|
|
||||||
|
|
||||||
const BoilerplateInfo basicBoilerplate = const BoilerplateInfo(
|
|
||||||
'Basic',
|
|
||||||
'Minimal starting point for Angel 2.x - A simple server with only a few additional packages.',
|
|
||||||
'${RepoLocation}/boilerplates.git',
|
|
||||||
ref: 'basic-sdk-2.12.x');
|
|
||||||
|
|
||||||
const BoilerplateInfo legacyBoilerplate = const BoilerplateInfo(
|
|
||||||
'Legacy',
|
|
||||||
'Minimal starting point for applications running Angel 1.1.x.',
|
|
||||||
'${RepoArchiveLocation}/angel.git',
|
|
||||||
ref: '1.1.x',
|
|
||||||
);
|
|
||||||
|
|
||||||
const BoilerplateInfo sharedBoilerplate = const BoilerplateInfo(
|
|
||||||
'Shared',
|
|
||||||
'Holds common models and files shared across multiple Dart projects.',
|
|
||||||
'${RepoLocation}/boilerplate_shared.git');
|
|
||||||
|
|
||||||
const BoilerplateInfo sharedOrmBoilerplate = const BoilerplateInfo(
|
|
||||||
'Shared (ORM)',
|
|
||||||
'Holds common models and files shared across multiple Dart projects.',
|
|
||||||
'${RepoLocation}/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)';
|
|
||||||
}
|
|
|
@ -1,247 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:glob/glob.dart';
|
|
||||||
import 'package:io/ansi.dart';
|
|
||||||
import 'package:mustache4dart2/mustache4dart2.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 =
|
|
||||||
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 =
|
|
||||||
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 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 MakerDependency(k, dep.version.toString(), dev: true);
|
|
||||||
return null;
|
|
||||||
}).where((d) => d != null));
|
|
||||||
|
|
||||||
await depend(deps);
|
|
||||||
|
|
||||||
var promptFile = 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) => 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 = 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 = 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(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.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
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(File('config/default.yaml'), secret);
|
|
||||||
|
|
||||||
secret = rs.randomAlphaNumeric(32);
|
|
||||||
print('Generated new production JWT secret: $secret');
|
|
||||||
await changeSecret(File('config/production.yaml'), secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeSecret(File file, String secret) async {
|
|
||||||
if (await file.exists()) {
|
|
||||||
var contents = await file.readAsString();
|
|
||||||
contents = contents.replaceAll(RegExp(r'jwt_secret:[^\n]+\n?'), '');
|
|
||||||
await file.writeAsString(contents.trim() + '\njwt_secret: "$secret"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
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(ControllerCommand());
|
|
||||||
addSubcommand(MigrationCommand());
|
|
||||||
addSubcommand(ModelCommand());
|
|
||||||
addSubcommand(PluginCommand());
|
|
||||||
addSubcommand(TestCommand());
|
|
||||||
addSubcommand(ServiceCommand());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
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';
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
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...
|
|
||||||
};
|
|
||||||
}
|
|
||||||
''';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
''';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
final RegExp _leadingSlashes = RegExp(r'^/+');
|
|
||||||
|
|
||||||
String resolvePub() {
|
|
||||||
var exec = 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;
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:analyzer/dart/ast/ast.dart';
|
|
||||||
import 'package:analyzer/dart/ast/visitor.dart';
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:dart_style/dart_style.dart';
|
|
||||||
import 'package:glob/glob.dart';
|
|
||||||
import 'package:glob/list_local_fs.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 (To be available).';
|
|
||||||
|
|
||||||
@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 = ReCase(newName).snakeCase;
|
|
||||||
|
|
||||||
var choice = prompts.getBool('Rename the project to `$newName`?');
|
|
||||||
|
|
||||||
// TODO: To be available once the issue is fixed
|
|
||||||
if (choice) {
|
|
||||||
print('Rename the project is currently not available');
|
|
||||||
/*
|
|
||||||
print('Renaming project to `$newName`...');
|
|
||||||
var pubspecFile =
|
|
||||||
File.fromUri(Directory.current.uri.resolve('pubspec.yaml'));
|
|
||||||
|
|
||||||
if (!await pubspecFile.exists()) {
|
|
||||||
throw 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 = File.fromUri(dir.uri.resolve('pubspec.yaml'));
|
|
||||||
|
|
||||||
if (await pubspecFile.exists()) {
|
|
||||||
var contents = await pubspecFile.readAsString(), oldContents = contents;
|
|
||||||
contents =
|
|
||||||
contents.replaceAll(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 = 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 = 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}"...');
|
|
||||||
if (yamlFile is File) {
|
|
||||||
var contents = (yamlFile as File).readAsStringSync();
|
|
||||||
contents = contents.replaceAll(oldName, newName);
|
|
||||||
(yamlFile as File).writeAsStringSync(contents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
var entry = 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 = DartFormatter();
|
|
||||||
await for (FileSystemEntity file in dir.list(recursive: true)) {
|
|
||||||
if (file is File && file.path.endsWith('.dart')) {
|
|
||||||
var contents = await file.readAsString();
|
|
||||||
|
|
||||||
// TODO: Issue to be fixed: parseCompilationUnit uses Hubbub library which uses discontinued Google front_end library
|
|
||||||
// front_end package. Temporarily commeted out
|
|
||||||
//var ast = parseCompilationUnit(contents);
|
|
||||||
var visitor = 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) + ';';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
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(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([]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
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')])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
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([]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
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))])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
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_dart/rethinkdb_dart.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))])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
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()
|
|
||||||
];
|
|
|
@ -1,15 +0,0 @@
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
const String _valid =
|
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
final Random _rnd = Random.secure();
|
|
||||||
|
|
||||||
String randomAlphaNumeric(int length) {
|
|
||||||
var b = StringBuffer();
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
b.writeCharCode(_valid.codeUnitAt(_rnd.nextInt(_valid.length)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.toString();
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
#author: Tobe O <thosakwe@gmail.com>
|
|
||||||
description: Command-line tools for the Angel framework, including scaffolding.
|
|
||||||
homepage: https://github.com/dukefirehawk/angel/packages/angel_cli
|
|
||||||
name: angel_cli
|
|
||||||
version: 3.0.0
|
|
||||||
environment:
|
|
||||||
sdk: ">=2.10.0 <3.0.0"
|
|
||||||
dependencies:
|
|
||||||
analyzer: ^1.1.0
|
|
||||||
args: ^2.0.0
|
|
||||||
code_builder: ^3.0.0
|
|
||||||
dart_style: ^1.0.0
|
|
||||||
glob: ^2.0.0
|
|
||||||
http: ^0.13.0
|
|
||||||
io: ^0.3.5
|
|
||||||
inflection2: ^0.4.2
|
|
||||||
mustache4dart2: ^0.1.0
|
|
||||||
path: ^1.0.0
|
|
||||||
prompts: ^1.3.1
|
|
||||||
pubspec_parse: ^1.0.0
|
|
||||||
quiver: ^3.0.0
|
|
||||||
recase: ^3.0.1
|
|
||||||
shutdown: ^0.4.0
|
|
||||||
watcher: ^1.0.0
|
|
||||||
yaml: ^3.0.0
|
|
||||||
executables:
|
|
||||||
angel: angel
|
|
Binary file not shown.
Before Width: | Height: | Size: 83 KiB |
56
packages/graphql/.gitignore
vendored
56
packages/graphql/.gitignore
vendored
|
@ -1,56 +0,0 @@
|
||||||
# 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/**/shelf
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-debug/
|
|
||||||
cmake-build-release/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
.dart_tool
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?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$/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/angel_graphql/angel_graphql.iml" filepath="$PROJECT_DIR$/angel_graphql/angel_graphql.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/example_star_wars/example_star_wars.iml" filepath="$PROJECT_DIR$/example_star_wars/example_star_wars.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/graphql.iml" filepath="$PROJECT_DIR$/graphql.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/graphql_parser/graphql_parser.iml" filepath="$PROJECT_DIR$/graphql_parser/graphql_parser.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/graphql_schema/graphql_schema.iml" filepath="$PROJECT_DIR$/graphql_schema/graphql_schema.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/graphql_server/graphql_server.iml" filepath="$PROJECT_DIR$/graphql_server/graphql_server.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="main.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/angel_graphql/example/main.dart" />
|
|
||||||
<option name="workingDirectory" value="$PROJECT_DIR$/angel_graphql" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="objects in equality_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_schema/test/equality_test.dart" />
|
|
||||||
<option name="scope" value="GROUP_OR_TEST_BY_NAME" />
|
|
||||||
<option name="testName" value="objects" />
|
|
||||||
<option name="testRunnerOptions" value="-j4" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="server.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="VMOptions" value="--observe" />
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/example_star_wars/bin/server.dart" />
|
|
||||||
<option name="workingDirectory" value="$PROJECT_DIR$/example_star_wars" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="tests in comment_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_parser/test/comment_test.dart" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="tests in graphql_parser" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_parser" />
|
|
||||||
<option name="scope" value="FOLDER" />
|
|
||||||
<option name="testRunnerOptions" value="-j 4" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="tests in graphql_schema" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_schema" />
|
|
||||||
<option name="scope" value="FOLDER" />
|
|
||||||
<option name="testRunnerOptions" value="-j4" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="tests in mirrors_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_server/test/mirrors_test.dart" />
|
|
||||||
<option name="testRunnerOptions" value="-j4" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="tests in query_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_server/test/query_test.dart" />
|
|
||||||
<option name="testRunnerOptions" value="-j4" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="tests in validation_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_schema/test/validation_test.dart" />
|
|
||||||
<option name="testRunnerOptions" value="-j4" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="tests in value_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/graphql_parser/test/value_test.dart" />
|
|
||||||
<option name="testRunnerOptions" value="-j4" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -1,6 +0,0 @@
|
||||||
language: dart
|
|
||||||
dart:
|
|
||||||
- dev
|
|
||||||
- stable
|
|
||||||
script:
|
|
||||||
- bash -ex travis.sh
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 The Angel Framework
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,39 +1,3 @@
|
||||||
![Logo](https://github.com/angel-dart/graphql/raw/master/img/angel_logo.png)
|
# GraphQL
|
||||||
|
|
||||||
<div style="text-align: center">
|
Moved to [`GraphQL Repo`](https://github.com/dukefirehawk/graphql_dart)
|
||||||
<hr>
|
|
||||||
<a href="https://pub.dartlang.org/packages/angel_graphql" rel="nofollow"><img src="https://img.shields.io/pub/v/angel_graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
|
||||||
<a href="https://travis-ci.org/angel-dart/graphql" rel="nofollow"><img src="https://travis-ci.org/angel-dart/graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
A complete implementation of the official
|
|
||||||
[GraphQL specification](https://graphql.github.io/graphql-spec/June2018/),
|
|
||||||
in the Dart programming language.
|
|
||||||
|
|
||||||
The goal of this project is to provide to server-side
|
|
||||||
users of Dart an alternative to REST API's.
|
|
||||||
|
|
||||||
Included is also
|
|
||||||
`package:angel_graphql`, which, when combined with the
|
|
||||||
[Angel](https://github.com/angel-dart) framework, allows
|
|
||||||
server-side Dart users to build backends with GraphQL and
|
|
||||||
virtually any database imaginable.
|
|
||||||
|
|
||||||
## Tutorial Demo (click to watch)
|
|
||||||
[![Youtube thumbnail](video.png)](https://youtu.be/5x6S4kDODa8)
|
|
||||||
|
|
||||||
## Projects
|
|
||||||
This mono repo is split into several sub-projects,
|
|
||||||
each with its own detailed documentation and examples:
|
|
||||||
* `angel_graphql` - Support for handling GraphQL via HTTP and
|
|
||||||
WebSockets in the [Angel](https://angel-dart.dev) framework. Also serves as the `package:graphql_server` reference implementation.
|
|
||||||
* `data_loader` - A Dart port of [`graphql/data_loader`](https://github.com/graphql/dataloader).
|
|
||||||
* `example_star_wars`: An example GraphQL API built using
|
|
||||||
`package:angel_graphql`.
|
|
||||||
* `graphql_generator`: Generates `package:graphql_schema` object types from concrete Dart classes.
|
|
||||||
* `graphql_parser`: A recursive descent parser for the GraphQL language.
|
|
||||||
* `graphql_schema`: An implementation of GraphQL's type system. This, combined with `package:graphql_parser`,
|
|
||||||
powers `package:graphql_server`.
|
|
||||||
* `graphql_server`: Base functionality for implementing GraphQL servers in Dart. Has no dependency on any
|
|
||||||
framework.
|
|
94
packages/graphql/angel_graphql/.gitignore
vendored
94
packages/graphql/angel_graphql/.gitignore
vendored
|
@ -1,94 +0,0 @@
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
.buildlog
|
|
||||||
.packages
|
|
||||||
.project
|
|
||||||
.pub/
|
|
||||||
.scripts-bin/
|
|
||||||
build/
|
|
||||||
**/packages/
|
|
||||||
posts.json
|
|
||||||
|
|
||||||
# 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
|
|
||||||
### Dart template
|
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
|
|
||||||
# SDK 1.20 and later (no longer creates packages directories)
|
|
||||||
|
|
||||||
# Older SDK versions
|
|
||||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
|
||||||
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
|
|
||||||
# Don't commit pubspec lock file
|
|
||||||
# (Library packages only! Remove pattern if developing an application package)
|
|
||||||
### 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
|
|
||||||
|
|
||||||
# Sensitive or high-churn files:
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.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
|
|
|
@ -1,17 +0,0 @@
|
||||||
# 1.1.0
|
|
||||||
* Support the GraphQL multipart spec: https://github.com/jaydenseric/graphql-multipart-request-spec
|
|
||||||
|
|
||||||
# 1.0.0
|
|
||||||
* Apply `package:pedantic`.
|
|
||||||
|
|
||||||
# 1.0.0-rc.0
|
|
||||||
* Finish `graphQLWS`.
|
|
||||||
|
|
||||||
# 1.0.0-beta.1
|
|
||||||
* Add `graphQLWS` handler, and support subscriptions.
|
|
||||||
|
|
||||||
# 1.0.0-beta
|
|
||||||
* Angel RC updates.
|
|
||||||
|
|
||||||
# 1.0.0-alpha
|
|
||||||
* First official release.
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 The Angel Framework
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,297 +0,0 @@
|
||||||
![Logo](https://github.com/angel-dart/graphql/raw/master/img/angel_logo.png)
|
|
||||||
|
|
||||||
<div style="text-align: center">
|
|
||||||
<hr>
|
|
||||||
<a href="https://pub.dartlang.org/packages/angel_graphql" rel="nofollow"><img src="https://img.shields.io/pub/v/angel_graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
|
||||||
<a href="https://travis-ci.org/angel-dart/graphql" rel="nofollow"><img src="https://travis-ci.org/angel-dart/graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
* [Installation](#installation)
|
|
||||||
* [Usage](#usage)
|
|
||||||
* [Subscriptions](#subscriptions)
|
|
||||||
* [Integration with Angel `Service`s](#using-services)
|
|
||||||
* [Documenting API's](#documentation)
|
|
||||||
* [Deprecated - Mirrors Usage](#mirrors)
|
|
||||||
|
|
||||||
A complete implementation of the official
|
|
||||||
[GraphQL specification](http://facebook.github.io/graphql/October2016/#sec-Language) - these
|
|
||||||
are the [Angel framework](https://angel-dart.github.io)-specific
|
|
||||||
bindings.
|
|
||||||
|
|
||||||
The goal of this project is to provide to server-side
|
|
||||||
users of Dart an alternative to REST API's. `package:angel_graphql`, which, when combined with the allows
|
|
||||||
server-side Dart users to build backends with GraphQL and
|
|
||||||
virtually any database imaginable.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
To install `package:angel_graphql`, add the following to your
|
|
||||||
`pubspec.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
dependencies:
|
|
||||||
angel_framework: ^2.0.0-alpha
|
|
||||||
angel_graphql: ^1.0.0-alpha
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
Using this package is very similar to GraphQL.js - you define
|
|
||||||
a schema, and then mount `graphQLHttp` in your router to start
|
|
||||||
serving. This implementation supports GraphQL features like
|
|
||||||
introspection, so you can play around with `graphiql` as well!
|
|
||||||
|
|
||||||
Firstly, define your schema. A GraphQL schema contains an
|
|
||||||
*object type* that defines all querying operations that can be
|
|
||||||
applied to the backend.
|
|
||||||
|
|
||||||
A GraphQL schema may also have a *mutation* object type,
|
|
||||||
which defines operations that change the backend's state, and
|
|
||||||
optionally a *subscription* type, which defines real-time
|
|
||||||
interactions (coming soon!).
|
|
||||||
|
|
||||||
You can use the `convertDartType` helper to wrap your existing
|
|
||||||
`Model`/PODO classes, and make GraphQL aware of them without duplicated
|
|
||||||
effort.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_graphql/angel_graphql.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
import 'package:graphql_server/graphql_server.dart';
|
|
||||||
import 'package:graphql_server/mirrors.dart';
|
|
||||||
|
|
||||||
Future configureServer(Angel app) async {
|
|
||||||
var queryType = objectType(
|
|
||||||
'Query',
|
|
||||||
description: 'A simple API that manages your to-do list.',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'todos',
|
|
||||||
listOf(convertDartType(Todo).nonNullable()),
|
|
||||||
resolve: resolveViaServiceIndex(todoService),
|
|
||||||
),
|
|
||||||
field(
|
|
||||||
'todo',
|
|
||||||
convertDartType(Todo),
|
|
||||||
resolve: resolveViaServiceRead(todoService),
|
|
||||||
inputs: [
|
|
||||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
var mutationType = objectType(
|
|
||||||
'Mutation',
|
|
||||||
description: 'Modify the to-do list.',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'create',
|
|
||||||
graphQLString,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
var schema = graphQLSchema(
|
|
||||||
queryType: queryType,
|
|
||||||
mutationType: mutationType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
After you've created your `GraphQLSchema`, you just need to
|
|
||||||
wrap in a call to `graphQLHttp`, a request handler that responds
|
|
||||||
to GraphQL.
|
|
||||||
|
|
||||||
In *development*, it's also highly recommended to mount the
|
|
||||||
`graphiQL` handler, which serves GraphQL's official visual
|
|
||||||
interface, for easy querying and feedback.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
app.all('/graphql', graphQLHttp(GraphQL(schema)));
|
|
||||||
app.get('/graphiql', graphiQL());
|
|
||||||
```
|
|
||||||
|
|
||||||
All that's left now is just to start the server!
|
|
||||||
|
|
||||||
```dart
|
|
||||||
var server = await http.startServer('127.0.0.1', 3000);
|
|
||||||
var uri =
|
|
||||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
|
||||||
var graphiqlUri = uri.replace(path: 'graphiql');
|
|
||||||
print('Listening at $uri');
|
|
||||||
print('Access graphiql at $graphiqlUri');
|
|
||||||
```
|
|
||||||
|
|
||||||
Visit your `/graphiql` endpoint, and you'll see the `graphiql`
|
|
||||||
UI, ready-to-go!
|
|
||||||
|
|
||||||
![Graphiql screenshot](https://github.com/angel-dart/graphql/raw/master/img/angel_graphql.png)
|
|
||||||
|
|
||||||
Now you're ready to build a GraphQL API!
|
|
||||||
|
|
||||||
## Subscriptions
|
|
||||||
Example:
|
|
||||||
https://github.com/angel-dart/graphql/blob/master/angel_graphql/example/subscription.dart
|
|
||||||
|
|
||||||
In GraphQL, as of the June 2018 spec, clients can subscribe to streams of events
|
|
||||||
from the server. In your schema, all you need to do is return a `Stream` from a `resolve`
|
|
||||||
callback, rather than a plain object:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
var postAdded = postService.afterCreated
|
|
||||||
.asStream()
|
|
||||||
.map((e) => {'postAdded': e.result})
|
|
||||||
.asBroadcastStream();
|
|
||||||
|
|
||||||
var schema = graphQLSchema(
|
|
||||||
// ...
|
|
||||||
subscriptionType: objectType(
|
|
||||||
'Subscription',
|
|
||||||
fields: [
|
|
||||||
field('postAdded', postType, resolve: (_, __) => postAdded),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, `graphQLHttp` has no support for subscriptions, because regular
|
|
||||||
HTTP requests are stateless, and are not ideal for continuous data pushing.
|
|
||||||
You can add your own handler:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
graphQLHttp(graphQL, onSubscription: (req, res, stream) {
|
|
||||||
// Do something with the stream here. It's up to you.
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
There is, however, `graphQLWS`, which implements Apollo's
|
|
||||||
`subscriptions-transport-ws` protocol:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
app.get('/subscriptions', graphQLWS(GraphQL(schema)));
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then use existing JavaScript clients to handle subscriptions.
|
|
||||||
|
|
||||||
The `graphiQL` handler also supports using subscriptions. In the following snippet, the
|
|
||||||
necessary scripts will be added to the rendered page, so that the `subscriptions-transport-ws`
|
|
||||||
client can be used by GraphiQL:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
app.get('/graphiql',
|
|
||||||
graphiQL(subscriptionsEndpoint: 'ws://localhost:3000/subscriptions'));
|
|
||||||
```
|
|
||||||
|
|
||||||
**NOTE: Apollo's spec for the aforementioned protocol is very far outdated, and completely inaccurate,**
|
|
||||||
**See this issue for more:**
|
|
||||||
**https://github.com/apollographql/subscriptions-transport-ws/issues/551**
|
|
||||||
|
|
||||||
## Using Services
|
|
||||||
What would Angel be without services? For those unfamiliar - in Angel,
|
|
||||||
`Service` is a base class that implements CRUD functionality, and serves
|
|
||||||
as the database interface within an Angel application. They are well-suited
|
|
||||||
for NoSQL or other databases without a schema (they can be used with
|
|
||||||
SQL, but that's not their primary focus).
|
|
||||||
|
|
||||||
`package:angel_graphql` has functionality to resolve fields by interacting with
|
|
||||||
services.
|
|
||||||
|
|
||||||
Consider our previous example, and note the calls to
|
|
||||||
`resolveViaServiceIndex` and `resolveViaServiceRead`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
var queryType = objectType(
|
|
||||||
'Query',
|
|
||||||
description: 'A simple API that manages your to-do list.',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'todos',
|
|
||||||
listOf(convertDartType(Todo).nonNullable()),
|
|
||||||
resolve: resolveViaServiceIndex(todoService),
|
|
||||||
),
|
|
||||||
field(
|
|
||||||
'todo',
|
|
||||||
convertDartType(Todo),
|
|
||||||
resolve: resolveViaServiceRead(todoService),
|
|
||||||
inputs: [
|
|
||||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
In all, there are:
|
|
||||||
* `resolveViaServiceIndex`
|
|
||||||
* `resolveViaServiceFindOne`
|
|
||||||
* `resolveViaServiceRead`
|
|
||||||
* `resolveViaServiceCreate`
|
|
||||||
* `resolveViaServiceModify`
|
|
||||||
* `resolveViaServiceUpdate`
|
|
||||||
* `resolveViaServiceRemove`
|
|
||||||
|
|
||||||
As one might imagine, using these convenience helpers makes
|
|
||||||
it much quicker to implement CRUD functionality in a GraphQL
|
|
||||||
API.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
Using `package:graphql_generator`, you can generate GraphQL schemas for concrete Dart
|
|
||||||
types:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
configureServer(Angel app) async {
|
|
||||||
var schema = graphQLSchema(
|
|
||||||
queryType: objectType('Query', fields: [
|
|
||||||
field('todos', listOf(todoGraphQLType), resolve: (_, __) => ...)
|
|
||||||
]);
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@graphQLClass
|
|
||||||
class Todo {
|
|
||||||
String text;
|
|
||||||
|
|
||||||
@GraphQLDocumentation(description: 'Whether this item is complete.')
|
|
||||||
bool isComplete;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For more documentation, see:
|
|
||||||
https://pub.dartlang.org/packages/graphql_generator
|
|
||||||
|
|
||||||
## Mirrors
|
|
||||||
**NOTE: Mirrors support is deprecated, and will not be updated further.**
|
|
||||||
|
|
||||||
The `convertDartType` function can automatically read the documentation
|
|
||||||
from a type like the following:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
@GraphQLDocumentation(description: 'Any object with a .text (String) property.')
|
|
||||||
abstract class HasText {
|
|
||||||
String get text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@serializable
|
|
||||||
@GraphQLDocumentation(
|
|
||||||
description: 'A task that might not be completed yet. **Yay! Markdown!**')
|
|
||||||
class Todo extends Model implements HasText {
|
|
||||||
String text;
|
|
||||||
|
|
||||||
@GraphQLDocumentation(deprecationReason: 'Use `completion_status` instead.')
|
|
||||||
bool completed;
|
|
||||||
|
|
||||||
CompletionStatus completionStatus;
|
|
||||||
|
|
||||||
Todo({this.text, this.completed, this.completionStatus});
|
|
||||||
}
|
|
||||||
|
|
||||||
@GraphQLDocumentation(description: 'The completion status of a to-do item.')
|
|
||||||
enum CompletionStatus { COMPLETE, INCOMPLETE }
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also manually provide documentation for
|
|
||||||
parameters and endpoints, via a `description` parameter on almost
|
|
||||||
all related functions.
|
|
||||||
|
|
||||||
See [`package:graphql_schema`](https://github.com/angel-dart/graphql/tree/master/graphql_schema)
|
|
||||||
for more documentation.
|
|
|
@ -1,4 +0,0 @@
|
||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,15 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
|
||||||
</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>
|
|
|
@ -1,105 +0,0 @@
|
||||||
// ignore_for_file: deprecated_member_use
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_framework/http.dart';
|
|
||||||
import 'package:angel_graphql/angel_graphql.dart';
|
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
import 'package:graphql_server/graphql_server.dart';
|
|
||||||
import 'package:graphql_server/mirrors.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
var logger = Logger('angel_graphql');
|
|
||||||
var app = Angel(
|
|
||||||
logger: logger
|
|
||||||
..onRecord.listen((rec) {
|
|
||||||
print(rec);
|
|
||||||
if (rec.error != null) print(rec.error);
|
|
||||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
|
||||||
}));
|
|
||||||
var http = AngelHttp(app);
|
|
||||||
|
|
||||||
var todoService = app.use('api/todos', MapService());
|
|
||||||
|
|
||||||
var queryType = objectType(
|
|
||||||
'Query',
|
|
||||||
description: 'A simple API that manages your to-do list.',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'todos',
|
|
||||||
listOf(convertDartType(Todo).nonNullable()),
|
|
||||||
resolve: resolveViaServiceIndex(todoService),
|
|
||||||
),
|
|
||||||
field(
|
|
||||||
'todo',
|
|
||||||
convertDartType(Todo),
|
|
||||||
resolve: resolveViaServiceRead(todoService),
|
|
||||||
inputs: [
|
|
||||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
var mutationType = objectType(
|
|
||||||
'Mutation',
|
|
||||||
description: 'Modify the to-do list.',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'createTodo',
|
|
||||||
convertDartType(Todo),
|
|
||||||
inputs: [
|
|
||||||
GraphQLFieldInput(
|
|
||||||
'data', convertDartType(Todo).coerceToInputObject()),
|
|
||||||
],
|
|
||||||
resolve: resolveViaServiceCreate(todoService),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
var schema = graphQLSchema(
|
|
||||||
queryType: queryType,
|
|
||||||
mutationType: mutationType,
|
|
||||||
);
|
|
||||||
|
|
||||||
app.all('/graphql', graphQLHttp(GraphQL(schema)));
|
|
||||||
app.get('/graphiql', graphiQL());
|
|
||||||
|
|
||||||
await todoService
|
|
||||||
.create({'text': 'Clean your room!', 'completion_status': 'COMPLETE'});
|
|
||||||
await todoService.create(
|
|
||||||
{'text': 'Take out the trash', 'completion_status': 'INCOMPLETE'});
|
|
||||||
await todoService.create({
|
|
||||||
'text': 'Become a billionaire at the age of 5',
|
|
||||||
'completion_status': 'INCOMPLETE'
|
|
||||||
});
|
|
||||||
|
|
||||||
var server = await http.startServer('127.0.0.1', 3000);
|
|
||||||
var uri =
|
|
||||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
|
||||||
var graphiqlUri = uri.replace(path: 'graphiql');
|
|
||||||
print('Listening at $uri');
|
|
||||||
print('Access graphiql at $graphiqlUri');
|
|
||||||
}
|
|
||||||
|
|
||||||
@GraphQLDocumentation(description: 'Any object with a .text (String) property.')
|
|
||||||
abstract class HasText {
|
|
||||||
String get text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@serializable
|
|
||||||
@GraphQLDocumentation(
|
|
||||||
description: 'A task that might not be completed yet. **Yay! Markdown!**')
|
|
||||||
class Todo extends Model implements HasText {
|
|
||||||
String text;
|
|
||||||
|
|
||||||
@GraphQLDocumentation(deprecationReason: 'Use `completion_status` instead.')
|
|
||||||
bool completed;
|
|
||||||
|
|
||||||
CompletionStatus completionStatus;
|
|
||||||
|
|
||||||
Todo({this.text, this.completed, this.completionStatus});
|
|
||||||
}
|
|
||||||
|
|
||||||
@GraphQLDocumentation(description: 'The completion status of a to-do item.')
|
|
||||||
enum CompletionStatus { COMPLETE, INCOMPLETE }
|
|
|
@ -1,98 +0,0 @@
|
||||||
// Inspired by:
|
|
||||||
// https://www.apollographql.com/docs/apollo-server/features/subscriptions/#subscriptions-example
|
|
||||||
|
|
||||||
import 'package:angel_file_service/angel_file_service.dart';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_framework/http.dart';
|
|
||||||
import 'package:angel_graphql/angel_graphql.dart';
|
|
||||||
import 'package:file/local.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
import 'package:graphql_server/graphql_server.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
var logger = Logger('angel_graphql');
|
|
||||||
var app = Angel(logger: logger);
|
|
||||||
var http = AngelHttp(app);
|
|
||||||
app.logger.onRecord.listen((rec) {
|
|
||||||
print(rec);
|
|
||||||
if (rec.error != null) print(rec.error);
|
|
||||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create an in-memory service.
|
|
||||||
var fs = LocalFileSystem();
|
|
||||||
var postService =
|
|
||||||
app.use('/api/posts', JsonFileService(fs.file('posts.json')));
|
|
||||||
|
|
||||||
// Also get a [Stream] of item creation events.
|
|
||||||
var postAdded = postService.afterCreated
|
|
||||||
.asStream()
|
|
||||||
.map((e) => {'postAdded': e.result})
|
|
||||||
.asBroadcastStream();
|
|
||||||
|
|
||||||
// GraphQL setup.
|
|
||||||
var postType = objectType('Post', fields: [
|
|
||||||
field('author', graphQLString),
|
|
||||||
field('comment', graphQLString),
|
|
||||||
]);
|
|
||||||
|
|
||||||
var schema = graphQLSchema(
|
|
||||||
// Hooked up to the postService:
|
|
||||||
// type Query { posts: [Post] }
|
|
||||||
queryType: objectType(
|
|
||||||
'Query',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'posts',
|
|
||||||
listOf(postType),
|
|
||||||
resolve: resolveViaServiceIndex(postService),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// Hooked up to the postService:
|
|
||||||
// type Mutation {
|
|
||||||
// addPost(author: String!, comment: String!): Post
|
|
||||||
// }
|
|
||||||
mutationType: objectType(
|
|
||||||
'Mutation',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'addPost',
|
|
||||||
postType,
|
|
||||||
inputs: [
|
|
||||||
GraphQLFieldInput(
|
|
||||||
'data', postType.toInputObject('PostInput').nonNullable()),
|
|
||||||
],
|
|
||||||
resolve: resolveViaServiceCreate(postService),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// Hooked up to `postAdded`:
|
|
||||||
// type Subscription { postAdded: Post }
|
|
||||||
subscriptionType: objectType(
|
|
||||||
'Subscription',
|
|
||||||
fields: [
|
|
||||||
field('postAdded', postType, resolve: (_, __) => postAdded),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mount GraphQL routes; we'll support HTTP and WebSockets transports.
|
|
||||||
app.all('/graphql', graphQLHttp(GraphQL(schema)));
|
|
||||||
app.get('/subscriptions',
|
|
||||||
graphQLWS(GraphQL(schema), keepAliveInterval: Duration(seconds: 3)));
|
|
||||||
app.get('/graphiql',
|
|
||||||
graphiQL(subscriptionsEndpoint: 'ws://localhost:3000/subscriptions'));
|
|
||||||
|
|
||||||
var server = await http.startServer('127.0.0.1', 3000);
|
|
||||||
var uri =
|
|
||||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
|
||||||
var graphiqlUri = uri.replace(path: 'graphiql');
|
|
||||||
var postsUri = uri.replace(pathSegments: ['api', 'posts']);
|
|
||||||
print('Listening at $uri');
|
|
||||||
print('Access graphiql at $graphiqlUri');
|
|
||||||
print('Access posts service at $postsUri');
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
export 'src/graphiql.dart';
|
|
||||||
export 'src/graphql_http.dart';
|
|
||||||
export 'src/graphql_ws.dart';
|
|
||||||
export 'src/resolvers.dart';
|
|
||||||
|
|
||||||
/// The canonical [GraphQLUploadType] instance.
|
|
||||||
final GraphQLUploadType graphQLUpload = GraphQLUploadType();
|
|
||||||
|
|
||||||
/// A [GraphQLScalarType] that is used to read uploaded files from
|
|
||||||
/// `multipart/form-data` requests.
|
|
||||||
class GraphQLUploadType extends GraphQLScalarType<UploadedFile, UploadedFile> {
|
|
||||||
@override
|
|
||||||
String get name => 'Upload';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get description =>
|
|
||||||
'Represents a file that has been uploaded to the server.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
GraphQLType<UploadedFile, UploadedFile> coerceToInputObject() => this;
|
|
||||||
|
|
||||||
@override
|
|
||||||
UploadedFile deserialize(UploadedFile serialized) => serialized;
|
|
||||||
|
|
||||||
@override
|
|
||||||
UploadedFile serialize(UploadedFile value) => value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ValidationResult<UploadedFile> validate(String key, UploadedFile input) {
|
|
||||||
if (input != null && input is! UploadedFile) {
|
|
||||||
return _Vr(false, errors: ['Expected "$key" to be a boolean.']);
|
|
||||||
}
|
|
||||||
return _Vr(true, value: input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Really need to make the validation result constructors *public*
|
|
||||||
class _Vr<T> implements ValidationResult<T> {
|
|
||||||
final bool successful;
|
|
||||||
final List<String> errors;
|
|
||||||
final T value;
|
|
||||||
|
|
||||||
_Vr(this.successful, {this.errors, this.value});
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:http_parser/http_parser.dart';
|
|
||||||
|
|
||||||
/// Returns a simple [RequestHandler] that renders the GraphiQL visual interface for GraphQL.
|
|
||||||
///
|
|
||||||
/// By default, the interface expects your backend to be mounted at `/graphql`; this is configurable
|
|
||||||
/// via [graphQLEndpoint].
|
|
||||||
RequestHandler graphiQL(
|
|
||||||
{String graphQLEndpoint = '/graphql', String subscriptionsEndpoint}) {
|
|
||||||
return (req, res) {
|
|
||||||
res
|
|
||||||
..contentType = MediaType('text', 'html')
|
|
||||||
..write(renderGraphiql(
|
|
||||||
graphqlEndpoint: graphQLEndpoint,
|
|
||||||
subscriptionsEndpoint: subscriptionsEndpoint))
|
|
||||||
..close();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
String renderGraphiql(
|
|
||||||
{String graphqlEndpoint = '/graphql', String subscriptionsEndpoint}) {
|
|
||||||
var subscriptionsScripts = '',
|
|
||||||
subscriptionsFetcher = '',
|
|
||||||
fetcherName = 'graphQLFetcher';
|
|
||||||
|
|
||||||
if (subscriptionsEndpoint != null) {
|
|
||||||
fetcherName = 'subscriptionsFetcher';
|
|
||||||
subscriptionsScripts = '''
|
|
||||||
<script src="//unpkg.com/subscriptions-transport-ws@0.8.3/browser/client.js"></script>
|
|
||||||
<script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
|
|
||||||
''';
|
|
||||||
subscriptionsFetcher = '''
|
|
||||||
let subscriptionsClient = window.SubscriptionsTransportWs.SubscriptionClient('$subscriptionsEndpoint', {
|
|
||||||
reconnect: true
|
|
||||||
});
|
|
||||||
let $fetcherName = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher);
|
|
||||||
''';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '''
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Angel GraphQL</title>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.css">
|
|
||||||
<style>
|
|
||||||
html, body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
html, body, #app {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.js"></script>
|
|
||||||
$subscriptionsScripts
|
|
||||||
<script>
|
|
||||||
window.onload = function() {
|
|
||||||
function graphQLFetcher(graphQLParams) {
|
|
||||||
return fetch('$graphqlEndpoint', {
|
|
||||||
method: 'post',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(graphQLParams)
|
|
||||||
}).then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$subscriptionsFetcher
|
|
||||||
ReactDOM.render(
|
|
||||||
React.createElement(
|
|
||||||
GraphiQL,
|
|
||||||
{fetcher: $fetcherName}
|
|
||||||
),
|
|
||||||
document.getElementById('app')
|
|
||||||
);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
'''
|
|
||||||
.trim();
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_validate/server.dart';
|
|
||||||
import 'package:graphql_parser/graphql_parser.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
import 'package:graphql_server/graphql_server.dart';
|
|
||||||
|
|
||||||
final ContentType graphQlContentType = ContentType('application', 'graphql');
|
|
||||||
|
|
||||||
final Validator graphQlPostBody = Validator({
|
|
||||||
'query*': isNonEmptyString,
|
|
||||||
'operation_name': isNonEmptyString,
|
|
||||||
'variables': predicate((v) => v == null || v is String || v is Map),
|
|
||||||
});
|
|
||||||
|
|
||||||
final RegExp _num = RegExp(r'^[0-9]+$');
|
|
||||||
|
|
||||||
/// A [RequestHandler] that serves a spec-compliant GraphQL backend.
|
|
||||||
///
|
|
||||||
/// Follows the guidelines listed here:
|
|
||||||
/// https://graphql.org/learn/serving-over-http/
|
|
||||||
RequestHandler graphQLHttp(GraphQL graphQL,
|
|
||||||
{Function(RequestContext, ResponseContext, Stream<Map<String, dynamic>>)
|
|
||||||
onSubscription}) {
|
|
||||||
return (req, res) async {
|
|
||||||
var globalVariables = <String, dynamic>{
|
|
||||||
'__requestctx': req,
|
|
||||||
'__responsectx': res,
|
|
||||||
};
|
|
||||||
|
|
||||||
sendGraphQLResponse(result) async {
|
|
||||||
if (result is Stream<Map<String, dynamic>>) {
|
|
||||||
if (onSubscription == null) {
|
|
||||||
throw StateError(
|
|
||||||
'The GraphQL backend returned a Stream, but no `onSubscription` callback was provided.');
|
|
||||||
} else {
|
|
||||||
return await onSubscription(req, res, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'data': result,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
executeMap(Map map) async {
|
|
||||||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
|
||||||
var text = body['query'] as String;
|
|
||||||
var operationName = body['operation_name'] as String;
|
|
||||||
var variables = body['variables'];
|
|
||||||
|
|
||||||
if (variables is String) {
|
|
||||||
variables = json.decode(variables as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await sendGraphQLResponse(await graphQL.parseAndExecute(
|
|
||||||
text,
|
|
||||||
sourceUrl: 'input',
|
|
||||||
operationName: operationName,
|
|
||||||
variableValues: foldToStringDynamic(variables as Map),
|
|
||||||
globalVariables: globalVariables,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (req.method == 'GET') {
|
|
||||||
if (await validateQuery(graphQlPostBody)(req, res) as bool) {
|
|
||||||
return await executeMap(req.queryParameters);
|
|
||||||
}
|
|
||||||
} else if (req.method == 'POST') {
|
|
||||||
if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) {
|
|
||||||
var text = await req.body.transform(utf8.decoder).join();
|
|
||||||
return sendGraphQLResponse(await graphQL.parseAndExecute(
|
|
||||||
text,
|
|
||||||
sourceUrl: 'input',
|
|
||||||
globalVariables: globalVariables,
|
|
||||||
));
|
|
||||||
} else if (req.headers.contentType?.mimeType == 'application/json') {
|
|
||||||
if (await validate(graphQlPostBody)(req, res) as bool) {
|
|
||||||
return await executeMap(req.bodyAsMap);
|
|
||||||
}
|
|
||||||
} else if (req.headers.contentType?.mimeType == 'multipart/form-data') {
|
|
||||||
var fields = await req.parseBody().then((_) => req.bodyAsMap);
|
|
||||||
var operations = fields['operations'] as String;
|
|
||||||
if (operations == null) {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message: 'Missing "operations" field.');
|
|
||||||
}
|
|
||||||
var map = fields.containsKey('map')
|
|
||||||
? json.decode(fields['map'] as String)
|
|
||||||
: null;
|
|
||||||
if (map is! Map) {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message: '"map" field must decode to a JSON object.');
|
|
||||||
}
|
|
||||||
var variables = Map<String, dynamic>.from(globalVariables);
|
|
||||||
for (var entry in (map as Map).entries) {
|
|
||||||
var file = req.uploadedFiles
|
|
||||||
.firstWhere((f) => f.name == entry.key, orElse: () => null);
|
|
||||||
if (file == null) {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message:
|
|
||||||
'"map" contained key "${entry.key}", but no uploaded file '
|
|
||||||
'has that name.');
|
|
||||||
}
|
|
||||||
if (entry.value is! List) {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message:
|
|
||||||
'The value for "${entry.key}" in the "map" field was not a JSON array.');
|
|
||||||
}
|
|
||||||
var objectPaths = entry.value as List;
|
|
||||||
for (var objectPath in objectPaths) {
|
|
||||||
var subPaths = (objectPath as String).split('.');
|
|
||||||
if (subPaths[0] == 'variables') {
|
|
||||||
Object current = variables;
|
|
||||||
for (int i = 1; i < subPaths.length; i++) {
|
|
||||||
var name = subPaths[i];
|
|
||||||
var parent = subPaths.take(i).join('.');
|
|
||||||
if (_num.hasMatch(name)) {
|
|
||||||
if (current is! List) {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message:
|
|
||||||
'Object "$parent" is not a JSON array, but the '
|
|
||||||
'"map" field contained a mapping to $parent.$name.');
|
|
||||||
}
|
|
||||||
(current as List)[int.parse(name)] = file;
|
|
||||||
} else {
|
|
||||||
if (current is! Map) {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message:
|
|
||||||
'Object "$parent" is not a JSON object, but the '
|
|
||||||
'"map" field contained a mapping to $parent.$name.');
|
|
||||||
}
|
|
||||||
(current as Map)[name] = file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message:
|
|
||||||
'All array values in the "map" field must begin with "variables.".');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await sendGraphQLResponse(await graphQL.parseAndExecute(
|
|
||||||
operations,
|
|
||||||
sourceUrl: 'input',
|
|
||||||
globalVariables: variables,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
throw AngelHttpException.badRequest();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw AngelHttpException.badRequest();
|
|
||||||
}
|
|
||||||
} on ValidationException catch (e) {
|
|
||||||
var errors = <GraphQLExceptionError>[GraphQLExceptionError(e.message)];
|
|
||||||
|
|
||||||
errors.addAll(e.errors.map((ee) => GraphQLExceptionError(ee)).toList());
|
|
||||||
return GraphQLException(errors).toJson();
|
|
||||||
} on AngelHttpException catch (e) {
|
|
||||||
var errors = <GraphQLExceptionError>[GraphQLExceptionError(e.message)];
|
|
||||||
|
|
||||||
errors.addAll(e.errors.map((ee) => GraphQLExceptionError(ee)).toList());
|
|
||||||
return GraphQLException(errors).toJson();
|
|
||||||
} on SyntaxError catch (e) {
|
|
||||||
return GraphQLException.fromSourceSpan(e.message, e.span);
|
|
||||||
} on GraphQLException catch (e) {
|
|
||||||
return e.toJson();
|
|
||||||
} catch (e, st) {
|
|
||||||
if (req.app?.logger != null) {
|
|
||||||
req.app.logger.severe(
|
|
||||||
'An error occurred while processing GraphQL query at ${req.uri}.',
|
|
||||||
e,
|
|
||||||
st);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GraphQLException.fromMessage(e.toString()).toJson();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_framework/http.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
import 'package:graphql_server/graphql_server.dart';
|
|
||||||
import 'package:graphql_server/subscriptions_transport_ws.dart' as stw;
|
|
||||||
import 'package:web_socket_channel/io.dart';
|
|
||||||
|
|
||||||
/// A [RequestHandler] that serves a spec-compliant GraphQL backend, over WebSockets.
|
|
||||||
/// This endpoint only supports WebSockets, and can be used to deliver subscription events.
|
|
||||||
///
|
|
||||||
/// `graphQLWS` uses the Apollo WebSocket protocol, for the sake of compatibility with
|
|
||||||
/// existing tooling.
|
|
||||||
///
|
|
||||||
/// See:
|
|
||||||
/// * https://github.com/apollographql/subscriptions-transport-ws
|
|
||||||
RequestHandler graphQLWS(GraphQL graphQL, {Duration keepAliveInterval}) {
|
|
||||||
return (req, res) async {
|
|
||||||
if (req is HttpRequestContext) {
|
|
||||||
if (WebSocketTransformer.isUpgradeRequest(req.rawRequest)) {
|
|
||||||
await res.detach();
|
|
||||||
var socket = await WebSocketTransformer.upgrade(req.rawRequest,
|
|
||||||
protocolSelector: (protocols) {
|
|
||||||
if (protocols.contains('graphql-ws')) {
|
|
||||||
return 'graphql-ws';
|
|
||||||
} else {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message: 'Only the "graphql-ws" protocol is allowed.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var channel = IOWebSocketChannel(socket);
|
|
||||||
var client = stw.RemoteClient(channel.cast<String>());
|
|
||||||
var server =
|
|
||||||
_GraphQLWSServer(client, graphQL, req, res, keepAliveInterval);
|
|
||||||
await server.done;
|
|
||||||
} else {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message: 'The `graphQLWS` endpoint only accepts WebSockets.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw AngelHttpException.badRequest(
|
|
||||||
message: 'The `graphQLWS` endpoint only accepts HTTP/1.1 requests.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GraphQLWSServer extends stw.Server {
|
|
||||||
final GraphQL graphQL;
|
|
||||||
final RequestContext req;
|
|
||||||
final ResponseContext res;
|
|
||||||
|
|
||||||
_GraphQLWSServer(stw.RemoteClient client, this.graphQL, this.req, this.res,
|
|
||||||
Duration keepAliveInterval)
|
|
||||||
: super(client, keepAliveInterval: keepAliveInterval);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool onConnect(stw.RemoteClient client, [Map connectionParams]) => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<stw.GraphQLResult> onOperation(String id, String query,
|
|
||||||
[Map<String, dynamic> variables, String operationName]) async {
|
|
||||||
try {
|
|
||||||
var globalVariables = <String, dynamic>{
|
|
||||||
'__requestctx': req,
|
|
||||||
'__responsectx': res,
|
|
||||||
};
|
|
||||||
var data = await graphQL.parseAndExecute(
|
|
||||||
query,
|
|
||||||
operationName: operationName,
|
|
||||||
sourceUrl: 'input',
|
|
||||||
globalVariables: globalVariables,
|
|
||||||
variableValues: variables,
|
|
||||||
);
|
|
||||||
return stw.GraphQLResult(data);
|
|
||||||
} on GraphQLException catch (e) {
|
|
||||||
return stw.GraphQLResult(null, errors: e.errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
|
|
||||||
Map<String, dynamic> _fetchRequestInfo(Map<String, dynamic> arguments) {
|
|
||||||
return <String, dynamic>{
|
|
||||||
'__requestctx': arguments.remove('__requestctx'),
|
|
||||||
'__responsectx': arguments.remove('__responsectx'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> _getQuery(Map<String, dynamic> arguments) {
|
|
||||||
var f = Map<String, dynamic>.from(arguments)..remove('id')..remove('data');
|
|
||||||
return f.isEmpty ? null : f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A GraphQL resolver that `index`es an Angel service.
|
|
||||||
///
|
|
||||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
|
||||||
/// service will receive [Providers.graphql].
|
|
||||||
GraphQLFieldResolver<List<Value>, Serialized>
|
|
||||||
resolveViaServiceIndex<Value, Serialized>(Service<dynamic, Value> service) {
|
|
||||||
return (_, arguments) async {
|
|
||||||
var _requestInfo = _fetchRequestInfo(arguments);
|
|
||||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
|
||||||
..addAll(_requestInfo);
|
|
||||||
|
|
||||||
return await service.index(params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A GraphQL resolver that calls `findOne` on an Angel service.
|
|
||||||
///
|
|
||||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
|
||||||
/// service will receive [Providers.graphql].
|
|
||||||
GraphQLFieldResolver<Value, Serialized>
|
|
||||||
resolveViaServiceFindOne<Value, Serialized>(
|
|
||||||
Service<dynamic, Value> service) {
|
|
||||||
return (_, arguments) async {
|
|
||||||
var _requestInfo = _fetchRequestInfo(arguments);
|
|
||||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
|
||||||
..addAll(_requestInfo);
|
|
||||||
return await service.findOne(params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A GraphQL resolver that `read`s a single value from an Angel service.
|
|
||||||
///
|
|
||||||
/// This resolver should be used on a field with at least the following inputs:
|
|
||||||
/// * `id`: a [graphQLId] or [graphQLString]
|
|
||||||
///
|
|
||||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
|
||||||
/// service will receive [Providers.graphql].
|
|
||||||
GraphQLFieldResolver<Value, Serialized>
|
|
||||||
resolveViaServiceRead<Value, Serialized>(Service<dynamic, Value> service,
|
|
||||||
{String idField = 'id'}) {
|
|
||||||
return (_, arguments) async {
|
|
||||||
var _requestInfo = _fetchRequestInfo(arguments);
|
|
||||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
|
||||||
..addAll(_requestInfo);
|
|
||||||
var id = arguments.remove(idField);
|
|
||||||
return await service.read(id, params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A GraphQL resolver that `creates` a single value in an Angel service.
|
|
||||||
///
|
|
||||||
/// This resolver should be used on a field with at least the following input:
|
|
||||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `create`
|
|
||||||
///
|
|
||||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
|
||||||
/// service will receive [Providers.graphql].
|
|
||||||
GraphQLFieldResolver<Value, Serialized>
|
|
||||||
resolveViaServiceCreate<Value, Serialized>(
|
|
||||||
Service<dynamic, Value> service) {
|
|
||||||
return (_, arguments) async {
|
|
||||||
var _requestInfo = _fetchRequestInfo(arguments);
|
|
||||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
|
||||||
..addAll(_requestInfo);
|
|
||||||
return await service.create(arguments['data'] as Value, params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A GraphQL resolver that `modifies` a single value from an Angel service.
|
|
||||||
///
|
|
||||||
/// This resolver should be used on a field with at least the following inputs:
|
|
||||||
/// * `id`: a [graphQLId] or [graphQLString]
|
|
||||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `modify`
|
|
||||||
///
|
|
||||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
|
||||||
/// service will receive [Providers.graphql].
|
|
||||||
GraphQLFieldResolver<Value, Serialized>
|
|
||||||
resolveViaServiceModify<Value, Serialized>(Service<dynamic, Value> service,
|
|
||||||
{String idField = 'id'}) {
|
|
||||||
return (_, arguments) async {
|
|
||||||
var _requestInfo = _fetchRequestInfo(arguments);
|
|
||||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
|
||||||
..addAll(_requestInfo);
|
|
||||||
var id = arguments.remove(idField);
|
|
||||||
return await service.modify(id, arguments['data'] as Value, params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A GraphQL resolver that `update`s a single value from an Angel service.
|
|
||||||
///
|
|
||||||
/// This resolver should be used on a field with at least the following inputs:
|
|
||||||
/// * `id`: a [graphQLId] or [graphQLString]
|
|
||||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `update`
|
|
||||||
///
|
|
||||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
|
||||||
/// service will receive [Providers.graphql].
|
|
||||||
///
|
|
||||||
/// Keep in mind that `update` **overwrites** existing contents.
|
|
||||||
/// To avoid this, use [resolveViaServiceModify] instead.
|
|
||||||
GraphQLFieldResolver<Value, Serialized>
|
|
||||||
resolveViaServiceUpdate<Value, Serialized>(Service<dynamic, Value> service,
|
|
||||||
{String idField = 'id'}) {
|
|
||||||
return (_, arguments) async {
|
|
||||||
var _requestInfo = _fetchRequestInfo(arguments);
|
|
||||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
|
||||||
..addAll(_requestInfo);
|
|
||||||
var id = arguments.remove(idField);
|
|
||||||
return await service.update(id, arguments['data'] as Value, params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A GraphQL resolver that `remove`s a single value from an Angel service.
|
|
||||||
///
|
|
||||||
/// This resolver should be used on a field with at least the following inputs:
|
|
||||||
/// * `id`: a [graphQLId] or [graphQLString]
|
|
||||||
///
|
|
||||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
|
||||||
/// service will receive [Providers.graphql].
|
|
||||||
GraphQLFieldResolver<Value, Serialized>
|
|
||||||
resolveViaServiceRemove<Value, Serialized>(Service<dynamic, Value> service,
|
|
||||||
{String idField = 'id'}) {
|
|
||||||
return (_, arguments) async {
|
|
||||||
var _requestInfo = _fetchRequestInfo(arguments);
|
|
||||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
|
||||||
..addAll(_requestInfo);
|
|
||||||
var id = arguments.remove(idField);
|
|
||||||
return await service.remove(id, params);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
name: angel_graphql
|
|
||||||
version: 1.1.0
|
|
||||||
description: The fastest + easiest way to get a GraphQL backend in Dart, using Angel.
|
|
||||||
homepage: https://github.com/angel-dart/graphql
|
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.10.0 <2.12.0'
|
|
||||||
dependencies:
|
|
||||||
angel_file_service: #^2.0.0
|
|
||||||
path: ../../file_service
|
|
||||||
angel_framework: #^2.0.0
|
|
||||||
path: ../../framework
|
|
||||||
angel_websocket: #^2.0.0
|
|
||||||
path: ../../websocket
|
|
||||||
angel_validate: #^2.0.0
|
|
||||||
path: ../../validate
|
|
||||||
graphql_parser: #^1.0.0
|
|
||||||
path: ../graphql_parser
|
|
||||||
graphql_schema: #^1.0.0
|
|
||||||
path: ../graphql_schema
|
|
||||||
graphql_server: #^1.0.0
|
|
||||||
path: ../graphql_server
|
|
||||||
http_parser: ^3.0.0
|
|
||||||
web_socket_channel: ^1.0.0
|
|
||||||
dev_dependencies:
|
|
||||||
angel_serialize: #^2.0.0
|
|
||||||
path: ../../serialize/angel_serialize
|
|
||||||
file: ^5.0.0
|
|
||||||
logging: ^0.11.0
|
|
||||||
pedantic: ^1.0.0
|
|
||||||
|
|
||||||
|
|
4
packages/graphql/data_loader/.gitignore
vendored
4
packages/graphql/data_loader/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
||||||
.packages
|
|
||||||
pubspec.lock
|
|
||||||
.dart_tool
|
|
||||||
doc/api
|
|
|
@ -1,2 +0,0 @@
|
||||||
# 1.0.0
|
|
||||||
* Initial version.
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 The Angel Framework
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,26 +0,0 @@
|
||||||
# data_loader
|
|
||||||
[![Pub](https://img.shields.io/pub/v/data_loader.svg)](https://pub.dartlang.org/packages/data_loader)
|
|
||||||
[![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql)
|
|
||||||
|
|
||||||
|
|
||||||
Batch and cache database lookups. Works well with GraphQL.
|
|
||||||
Ported from the original JS version:
|
|
||||||
https://github.com/graphql/dataloader
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
In your pubspec.yaml:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
dependencies:
|
|
||||||
data_loader: ^1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
Complete example:
|
|
||||||
https://github.com/angel-dart/graphql/blob/master/data_loader/example/main.dart
|
|
||||||
|
|
||||||
```dart
|
|
||||||
var userLoader = new DataLoader((key) => myBatchGetUsers(keys));
|
|
||||||
var invitedBy = await userLoader.load(1)then(user => userLoader.load(user.invitedByID))
|
|
||||||
print('User 1 was invited by $invitedBy'));
|
|
||||||
```
|
|
|
@ -1,4 +0,0 @@
|
||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,44 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:data_loader/data_loader.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
|
|
||||||
external Future<List<Todo>> fetchTodos(Iterable<int> ids);
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
// Create a DataLoader. By default, it caches lookups.
|
|
||||||
var todoLoader = DataLoader(fetchTodos); // DataLoader<int, Todo>
|
|
||||||
|
|
||||||
// type Todo { id: Int, text: String, is_complete: Boolean }
|
|
||||||
var todoType = objectType(
|
|
||||||
'Todo',
|
|
||||||
fields: [
|
|
||||||
field('id', graphQLInt),
|
|
||||||
field('text', graphQLString),
|
|
||||||
field('is_complete', graphQLBoolean),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// type Query { todo($id: Int!) Todo }
|
|
||||||
// ignore: unused_local_variable
|
|
||||||
var schema = graphQLSchema(
|
|
||||||
queryType: objectType(
|
|
||||||
'Query',
|
|
||||||
fields: [
|
|
||||||
field(
|
|
||||||
'todo',
|
|
||||||
listOf(todoType),
|
|
||||||
inputs: [GraphQLFieldInput('id', graphQLInt.nonNullable())],
|
|
||||||
resolve: (_, args) => todoLoader.load(args['id'] as int),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Do something with your schema...
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class Todo {
|
|
||||||
int get id;
|
|
||||||
String get text;
|
|
||||||
bool get isComplete;
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:collection';
|
|
||||||
|
|
||||||
/// A utility for batching multiple requests together, to improve application performance.
|
|
||||||
///
|
|
||||||
/// Enqueues batches of requests until the next tick, when they are processed in bulk.
|
|
||||||
///
|
|
||||||
/// Port of Facebook's `DataLoader`:
|
|
||||||
/// https://github.com/graphql/dataloader
|
|
||||||
class DataLoader<Id, Data> {
|
|
||||||
/// Invoked to fetch a batch of keys simultaneously.
|
|
||||||
final FutureOr<Iterable<Data>> Function(Iterable<Id>) loadMany;
|
|
||||||
|
|
||||||
/// Whether to use a memoization cache to store the results of past lookups.
|
|
||||||
final bool cache;
|
|
||||||
|
|
||||||
var _cache = <Id, Data>{};
|
|
||||||
var _queue = Queue<_QueueItem<Id, Data>>();
|
|
||||||
bool _started = false;
|
|
||||||
|
|
||||||
DataLoader(this.loadMany, {this.cache = true});
|
|
||||||
|
|
||||||
Future<void> _onTick() async {
|
|
||||||
if (_queue.isNotEmpty) {
|
|
||||||
var current = _queue.toList();
|
|
||||||
_queue.clear();
|
|
||||||
|
|
||||||
List<Id> loadIds =
|
|
||||||
current.map((i) => i.id).toSet().toList(growable: false);
|
|
||||||
|
|
||||||
var data = await loadMany(
|
|
||||||
loadIds,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (int i = 0; i < loadIds.length; i++) {
|
|
||||||
var id = loadIds[i];
|
|
||||||
var value = data.elementAt(i);
|
|
||||||
|
|
||||||
if (cache) _cache[id] = value;
|
|
||||||
|
|
||||||
current
|
|
||||||
.where((item) => item.id == id)
|
|
||||||
.forEach((item) => item.completer.complete(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_started = false;
|
|
||||||
// if (!_closed) scheduleMicrotask(_onTick);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the value at [key], if it exists.
|
|
||||||
void clear(Id key) => _cache.remove(key);
|
|
||||||
|
|
||||||
/// Clears the entire cache.
|
|
||||||
void clearAll() => _cache.clear();
|
|
||||||
|
|
||||||
/// Primes the cache with the provided key and value. If the key already exists, no change is made.
|
|
||||||
///
|
|
||||||
/// To forcefully prime the cache, clear the key first with
|
|
||||||
/// `loader..clear(key)..prime(key, value)`.
|
|
||||||
void prime(Id key, Data value) => _cache.putIfAbsent(key, () => value);
|
|
||||||
|
|
||||||
/// Closes this [DataLoader], cancelling all pending requests.
|
|
||||||
void close() {
|
|
||||||
while (_queue.isNotEmpty) {
|
|
||||||
_queue.removeFirst().completer.completeError(
|
|
||||||
StateError('The DataLoader was closed before the item was loaded.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
_queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a [Future] that completes when the next batch of requests completes.
|
|
||||||
Future<Data> load(Id id) {
|
|
||||||
if (cache && _cache.containsKey(id)) {
|
|
||||||
return Future<Data>.value(_cache[id]);
|
|
||||||
} else {
|
|
||||||
var item = _QueueItem<Id, Data>(id);
|
|
||||||
_queue.add(item);
|
|
||||||
if (!_started) {
|
|
||||||
_started = true;
|
|
||||||
scheduleMicrotask(_onTick);
|
|
||||||
}
|
|
||||||
return item.completer.future;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _QueueItem<Id, Data> {
|
|
||||||
final Id id;
|
|
||||||
final Completer<Data> completer = Completer();
|
|
||||||
|
|
||||||
_QueueItem(this.id);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
name: data_loader
|
|
||||||
version: 1.0.0
|
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
|
||||||
description: Batch and cache database lookups. Works well with GraphQL. Ported from JS.
|
|
||||||
homepage: https://github.com/angel-dart/graphql
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.10.0 <2.12.0'
|
|
||||||
dev_dependencies:
|
|
||||||
graphql_schema: #^1.0.0
|
|
||||||
path: ../graphql_schema
|
|
||||||
pedantic: ^1.0.0
|
|
||||||
test: ^1.15.7
|
|
|
@ -1,100 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:data_loader/data_loader.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
var numbers = List.generate(10, (i) => i.toStringAsFixed(2));
|
|
||||||
var numberLoader = DataLoader<int, String>((ids) {
|
|
||||||
print('ID batch: $ids');
|
|
||||||
return ids.map((i) => numbers[i]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('batch', () async {
|
|
||||||
var zero = numberLoader.load(0);
|
|
||||||
var one = numberLoader.load(1);
|
|
||||||
var two = numberLoader.load(2);
|
|
||||||
var batch = await Future.wait([zero, one, two]);
|
|
||||||
print('Fetched result: $batch');
|
|
||||||
expect(batch, ['0.00', '1.00', '2.00']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('dedupe', () async {
|
|
||||||
var loader = DataLoader<int, Map<int, List<int>>>((ids) {
|
|
||||||
return ids.map(
|
|
||||||
(i) => {i: ids.toList()},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
var zero = loader.load(0);
|
|
||||||
var one = loader.load(1);
|
|
||||||
var two = loader.load(2);
|
|
||||||
var anotherZero = loader.load(0);
|
|
||||||
var batch = await Future.wait([zero, one, two, anotherZero]);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
batch,
|
|
||||||
[
|
|
||||||
{ 0: [0, 1, 2]},
|
|
||||||
{ 1: [0, 1, 2]},
|
|
||||||
{ 2: [0, 1, 2]},
|
|
||||||
{ 0: [0, 1, 2]},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('cache', () {
|
|
||||||
DataLoader<int, _Unique> uniqueLoader, noCache;
|
|
||||||
|
|
||||||
setUp(() {
|
|
||||||
uniqueLoader = DataLoader<int, _Unique>((ids) async {
|
|
||||||
var numbers = await numberLoader.loadMany(ids);
|
|
||||||
return numbers.map((s) => _Unique(s));
|
|
||||||
});
|
|
||||||
noCache = DataLoader(uniqueLoader.loadMany, cache: false);
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDown(() {
|
|
||||||
uniqueLoader.close();
|
|
||||||
noCache.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('only lookup once', () async {
|
|
||||||
var a = await uniqueLoader.load(3);
|
|
||||||
var b = await uniqueLoader.load(3);
|
|
||||||
expect(a, b);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can be disabled', () async {
|
|
||||||
var a = await noCache.load(3);
|
|
||||||
var b = await noCache.load(3);
|
|
||||||
expect(a, isNot(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('clear', () async {
|
|
||||||
var a = await uniqueLoader.load(3);
|
|
||||||
uniqueLoader.clear(3);
|
|
||||||
var b = await uniqueLoader.load(3);
|
|
||||||
expect(a, isNot(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('clearAll', () async {
|
|
||||||
var a = await uniqueLoader.load(3);
|
|
||||||
uniqueLoader.clearAll();
|
|
||||||
var b = await uniqueLoader.load(3);
|
|
||||||
expect(a, isNot(b));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prime', () async {
|
|
||||||
uniqueLoader.prime(3, _Unique('hey'));
|
|
||||||
var a = await uniqueLoader.load(3);
|
|
||||||
expect(a.value, 'hey');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Unique {
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
_Unique(this.value);
|
|
||||||
}
|
|
93
packages/graphql/example_star_wars/.gitignore
vendored
93
packages/graphql/example_star_wars/.gitignore
vendored
|
@ -1,93 +0,0 @@
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
.buildlog
|
|
||||||
.packages
|
|
||||||
.project
|
|
||||||
.pub/
|
|
||||||
.scripts-bin/
|
|
||||||
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
|
|
||||||
### Dart template
|
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
|
|
||||||
# SDK 1.20 and later (no longer creates packages directories)
|
|
||||||
|
|
||||||
# Older SDK versions
|
|
||||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
|
||||||
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
|
|
||||||
# Don't commit pubspec lock file
|
|
||||||
# (Library packages only! Remove pattern if developing an application package)
|
|
||||||
### 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
|
|
||||||
|
|
||||||
# Sensitive or high-churn files:
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.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
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 The Angel Framework
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,3 +0,0 @@
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,28 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
|
||||||
import 'package:angel_hot/angel_hot.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:star_wars/src/pretty_logging.dart' as star_wars;
|
|
||||||
import 'package:star_wars/star_wars.dart' as star_wars;
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
Future<Angel> createServer() async {
|
|
||||||
hierarchicalLoggingEnabled = true;
|
|
||||||
var logger = Logger.detached('star_wars')
|
|
||||||
..onRecord.listen(star_wars.prettyLog);
|
|
||||||
var app = Angel(logger: logger);
|
|
||||||
await app.configure(star_wars.configureServer);
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hot = HotReloader(createServer, [Directory('lib')]);
|
|
||||||
|
|
||||||
var server = await hot.startServer('127.0.0.1', 3000);
|
|
||||||
var serverUrl =
|
|
||||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
|
||||||
var graphiQLUrl = serverUrl.replace(path: '/graphiql');
|
|
||||||
print('Listening at $serverUrl');
|
|
||||||
print('GraphiQL endpoint: $graphiQLUrl');
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
|
||||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
|
@ -1,12 +0,0 @@
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
import 'episode.dart';
|
|
||||||
part 'character.g.dart';
|
|
||||||
|
|
||||||
@graphQLClass
|
|
||||||
abstract class Character {
|
|
||||||
String get id;
|
|
||||||
|
|
||||||
String get name;
|
|
||||||
|
|
||||||
// List<Episode> get appearsIn;
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'character.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// _GraphQLGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
/// Auto-generated from [Character].
|
|
||||||
final GraphQLObjectType characterGraphQLType = objectType('Character',
|
|
||||||
isInterface: true,
|
|
||||||
interfaces: [],
|
|
||||||
fields: [field('id', graphQLString), field('name', graphQLString)]);
|
|
|
@ -1,23 +0,0 @@
|
||||||
import 'package:angel_model/angel_model.dart';
|
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
|
||||||
import 'character.dart';
|
|
||||||
import 'episode.dart';
|
|
||||||
part 'droid.g.dart';
|
|
||||||
|
|
||||||
@serializable
|
|
||||||
@graphQLClass
|
|
||||||
@GraphQLDocumentation(description: 'Beep! Boop!')
|
|
||||||
abstract class _Droid extends Model implements Character {
|
|
||||||
String get id;
|
|
||||||
|
|
||||||
String get name;
|
|
||||||
|
|
||||||
@GraphQLDocumentation(
|
|
||||||
description: 'The list of episodes this droid appears in.')
|
|
||||||
List<Episode> get appearsIn;
|
|
||||||
|
|
||||||
/// Doc comments automatically become GraphQL descriptions.
|
|
||||||
List<Character> get friends;
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'droid.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonModelGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
@generatedSerializable
|
|
||||||
class Droid extends _Droid {
|
|
||||||
Droid(
|
|
||||||
{this.id,
|
|
||||||
this.name,
|
|
||||||
List<Episode> appearsIn,
|
|
||||||
List<Character> friends,
|
|
||||||
this.createdAt,
|
|
||||||
this.updatedAt})
|
|
||||||
: this.appearsIn = new List.unmodifiable(appearsIn ?? []),
|
|
||||||
this.friends = new List.unmodifiable(friends ?? []);
|
|
||||||
|
|
||||||
@override
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final String name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final List<Episode> appearsIn;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final List<Character> friends;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final DateTime createdAt;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final DateTime updatedAt;
|
|
||||||
|
|
||||||
Droid copyWith(
|
|
||||||
{String id,
|
|
||||||
String name,
|
|
||||||
List<Episode> appearsIn,
|
|
||||||
List<Character> friends,
|
|
||||||
DateTime createdAt,
|
|
||||||
DateTime updatedAt}) {
|
|
||||||
return new Droid(
|
|
||||||
id: id ?? this.id,
|
|
||||||
name: name ?? this.name,
|
|
||||||
appearsIn: appearsIn ?? this.appearsIn,
|
|
||||||
friends: friends ?? this.friends,
|
|
||||||
createdAt: createdAt ?? this.createdAt,
|
|
||||||
updatedAt: updatedAt ?? this.updatedAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator ==(other) {
|
|
||||||
return other is _Droid &&
|
|
||||||
other.id == id &&
|
|
||||||
other.name == name &&
|
|
||||||
const ListEquality<Episode>(const DefaultEquality<Episode>())
|
|
||||||
.equals(other.appearsIn, appearsIn) &&
|
|
||||||
const ListEquality<Character>(const DefaultEquality<Character>())
|
|
||||||
.equals(other.friends, friends) &&
|
|
||||||
other.createdAt == createdAt &&
|
|
||||||
other.updatedAt == updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
return hashObjects([id, name, appearsIn, friends, createdAt, updatedAt]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return DroidSerializer.toMap(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// SerializerGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
abstract class DroidSerializer {
|
|
||||||
static Droid fromMap(Map map) {
|
|
||||||
return new Droid(
|
|
||||||
id: map['id'] as String,
|
|
||||||
name: map['name'] as String,
|
|
||||||
appearsIn: map['appears_in'] is Iterable
|
|
||||||
? (map['appears_in'] as Iterable).cast<Episode>().toList()
|
|
||||||
: null,
|
|
||||||
friends: map['friends'] is Iterable
|
|
||||||
? (map['friends'] as Iterable).cast<Character>().toList()
|
|
||||||
: null,
|
|
||||||
createdAt: map['created_at'] != null
|
|
||||||
? (map['created_at'] is DateTime
|
|
||||||
? (map['created_at'] as DateTime)
|
|
||||||
: DateTime.parse(map['created_at'].toString()))
|
|
||||||
: null,
|
|
||||||
updatedAt: map['updated_at'] != null
|
|
||||||
? (map['updated_at'] is DateTime
|
|
||||||
? (map['updated_at'] as DateTime)
|
|
||||||
: DateTime.parse(map['updated_at'].toString()))
|
|
||||||
: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic> toMap(_Droid model) {
|
|
||||||
if (model == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
'id': model.id,
|
|
||||||
'name': model.name,
|
|
||||||
'appears_in': model.appearsIn,
|
|
||||||
'friends': model.friends,
|
|
||||||
'created_at': model.createdAt?.toIso8601String(),
|
|
||||||
'updated_at': model.updatedAt?.toIso8601String()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DroidFields {
|
|
||||||
static const List<String> allFields = <String>[
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
appearsIn,
|
|
||||||
friends,
|
|
||||||
createdAt,
|
|
||||||
updatedAt
|
|
||||||
];
|
|
||||||
|
|
||||||
static const String id = 'id';
|
|
||||||
|
|
||||||
static const String name = 'name';
|
|
||||||
|
|
||||||
static const String appearsIn = 'appears_in';
|
|
||||||
|
|
||||||
static const String friends = 'friends';
|
|
||||||
|
|
||||||
static const String createdAt = 'created_at';
|
|
||||||
|
|
||||||
static const String updatedAt = 'updated_at';
|
|
||||||
}
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// _GraphQLGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
/// Auto-generated from [Droid].
|
|
||||||
final GraphQLObjectType droidGraphQLType = objectType('Droid',
|
|
||||||
isInterface: false,
|
|
||||||
description: 'Beep! Boop!',
|
|
||||||
interfaces: [
|
|
||||||
characterGraphQLType
|
|
||||||
],
|
|
||||||
fields: [
|
|
||||||
field('id', graphQLString),
|
|
||||||
field('name', graphQLString),
|
|
||||||
field('appears_in', listOf(episodeGraphQLType),
|
|
||||||
description: 'The list of episodes this droid appears in.'),
|
|
||||||
field('friends', listOf(characterGraphQLType),
|
|
||||||
description:
|
|
||||||
'Doc comments automatically become GraphQL descriptions.'),
|
|
||||||
field('created_at', graphQLDate),
|
|
||||||
field('updated_at', graphQLDate),
|
|
||||||
field('idAsInt', graphQLInt)
|
|
||||||
]);
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue