Moved CLI and graphQL

This commit is contained in:
thomashii 2021-06-13 18:11:45 +08:00
parent 19e060800e
commit 1b28887608
234 changed files with 4 additions and 13313 deletions

BIN
packages/cli/.DS_Store vendored

Binary file not shown.

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -1 +0,0 @@
language: dart

View file

@ -1,2 +0,0 @@
Tobe O <thosakwe@gmail.com>
Thomas Hii <thomashii@dukefirehawk.com>

View file

@ -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.

View file

@ -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.

View file

@ -1,26 +1,3 @@
# angel_cli
# Angel3 CLI
![Screenshot of Terminal](screenshots/screenshot.png)
Command-line tools for the Angel framework.
Includes functionality such as:
* Project scaffolding
* Generating service models, plugins, tests and more
* Renaming projects
* Much more...
* To install:
```bash
$ pub global activate angel_cli
```
* 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
```
Moved to [`Angel3 CLI Repo`](https://github.com/dukefirehawk/angel3-cli)

View file

@ -1,8 +0,0 @@
# Todo
* Migrate inflection2, mustache4dart2 and prompts packages to NNBD
* `service`
* Add tests
* `migration`
* `deploy`
* Call these from Grinder script :)

View file

@ -1,3 +0,0 @@
analyzer:
strong-mode:
implicit-casts: false

View file

@ -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 = '''
____________ ________________________
___ |__ | / /_ ____/__ ____/__ /
__ /| |_ |/ /_ / __ __ __/ __ /
_ ___ | /| / / /_/ / _ /___ _ /___
/_/ |_/_/ |_/ \____/ /_____/ /_____/
''';

View file

@ -1,3 +0,0 @@
void main() {
// This package isn't usable from code.
}

View file

@ -1,3 +0,0 @@
library angel_cli;
export 'src/commands/commands.dart';

View file

@ -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";

View file

@ -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());
}
}

View file

@ -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}'."));
}
}
}

View file

@ -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}'."));
}
}
}

View file

@ -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"));
}
}
}

View file

@ -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)';
}

View file

@ -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.';
}
}
}

View file

@ -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"');
}
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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';
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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...
};
}
''';
}
}

View file

@ -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);
}
}

View file

@ -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));
});
}
''';
}
}

View file

@ -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;
}

View file

@ -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) + ';';
}
}
}
}

View file

@ -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([]);
}
}

View file

@ -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')])
]);
}
}

View file

@ -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;
}

View file

@ -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([]);
}
}

View file

@ -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))])
]);
}
}

View file

@ -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))])
]);
}
}

View file

@ -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()
];

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -1,6 +0,0 @@
language: dart
dart:
- dev
- stable
script:
- bash -ex travis.sh

View file

@ -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.

View file

@ -1,39 +1,3 @@
![Logo](https://github.com/angel-dart/graphql/raw/master/img/angel_logo.png)
# GraphQL
<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>
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.
Moved to [`GraphQL Repo`](https://github.com/dukefirehawk/graphql_dart)

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -1,4 +0,0 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

View file

@ -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>

View file

@ -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 }

View file

@ -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');
}

View file

@ -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});
}

View file

@ -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();
}

View file

@ -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();
}
};
}

View file

@ -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);
}
}
}

View file

@ -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);
};
}

View file

@ -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

View file

@ -1,4 +0,0 @@
.packages
pubspec.lock
.dart_tool
doc/api

View file

@ -1,2 +0,0 @@
# 1.0.0
* Initial version.

View file

@ -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.

View file

@ -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'));
```

View file

@ -1,4 +0,0 @@
include: package:pedantic/analysis_options.yaml
analyzer:
strong-mode:
implicit-casts: false

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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.

View file

@ -1,3 +0,0 @@
analyzer:
strong-mode:
implicit-casts: false

View file

@ -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');
}

View file

@ -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>

View file

@ -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;
}

View file

@ -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)]);

View file

@ -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;
}

View file

@ -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