├── test
├── fixtures
│ ├── package_0
│ │ ├── 0.0.1
│ │ │ ├── LICENSE
│ │ │ ├── CHANGELOG.md
│ │ │ ├── README.md
│ │ │ └── pubspec.yaml
│ │ ├── 0.0.2
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── CHANGELOG.md
│ │ │ └── pubspec.yaml
│ │ ├── 0.0.3
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── CHANGELOG.md
│ │ │ └── pubspec.yaml
│ │ ├── 1.0.0
│ │ │ ├── LICENSE
│ │ │ └── pubspec.yaml
│ │ ├── 0.0.3+1
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── CHANGELOG.md
│ │ │ └── pubspec.yaml
│ │ └── 1.0.0-noreadme
│ │ │ ├── LICENSE
│ │ │ └── pubspec.yaml
│ ├── package_1
│ │ └── 0.0.1
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ └── pubspec.yaml
│ └── file_store
│ │ └── .gitignore
├── file_store_test.dart
├── utils.dart
└── unpub_test.dart
├── .idea
├── .gitignore
├── misc.xml
├── kotlinc.xml
├── modules.xml
└── unpub-2.0.0.iml
├── assets
├── 1.png
├── 2.png
└── 3.png
├── lib
├── unpub_api
│ ├── pubspec.yaml
│ └── lib
│ │ ├── models.dart
│ │ └── models.g.dart
├── unpub.dart
└── src
│ ├── package_store.dart
│ ├── meta_store.dart
│ ├── utils.dart
│ ├── file_store.dart
│ ├── app.g.dart
│ ├── models.dart
│ ├── models.g.dart
│ ├── mongo_store.dart
│ ├── app.dart
│ └── static
│ └── index.html.dart
├── Dockerfile
├── .gitignore
├── docker-compose.yml
├── tool
└── pre_publish.dart
├── example
└── main.dart
├── CHANGELOG.md
├── pubspec.yaml
├── LICENSE
├── bin
└── unpub.dart
└── README.md
/test/fixtures/package_0/0.0.1/LICENSE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.2/LICENSE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3/LICENSE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/1.0.0/LICENSE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/package_1/0.0.1/LICENSE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3+1/LICENSE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/package_1/0.0.1/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/1.0.0-noreadme/LICENSE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/file_store/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.1/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.0.1
2 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.1/README.md:
--------------------------------------------------------------------------------
1 | # package0 0.0.1
2 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.2/README.md:
--------------------------------------------------------------------------------
1 | # package0 0.0.2
2 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3/README.md:
--------------------------------------------------------------------------------
1 | # package0 0.0.3
2 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3+1/README.md:
--------------------------------------------------------------------------------
1 | # package0 0.0.3+1
2 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.2/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.0.2
2 |
3 | # 0.0.1
4 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/assets/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeqinjie/unpub-2.0.0-docker/HEAD/assets/1.png
--------------------------------------------------------------------------------
/assets/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeqinjie/unpub-2.0.0-docker/HEAD/assets/2.png
--------------------------------------------------------------------------------
/assets/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zeqinjie/unpub-2.0.0-docker/HEAD/assets/3.png
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.0.3
2 |
3 | # 0.0.2
4 |
5 | # 0.0.1
6 |
--------------------------------------------------------------------------------
/lib/unpub_api/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: unpub_api
2 |
3 | environment:
4 | sdk: ">=2.12.0 <3.0.0"
5 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3+1/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.0.3+1
2 |
3 | # 0.0.3
4 |
5 | # 0.0.2
6 |
7 | # 0.0.1
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | from dart
2 |
3 | WORKDIR /app
4 | COPY . ./
5 | RUN dart pub get
6 | ENTRYPOINT ["dart", "run", "bin/unpub.dart", "--database", "mongodb://mongo:27017/dart_pub"]
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.1/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_0
2 | description: test
3 | version: 0.0.1
4 | homepage: https://example.com
5 | environment:
6 | sdk: ">=2.0.0 <3.0.0"
7 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.2/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_0
2 | description: test
3 | version: 0.0.2
4 | homepage: https://example.com
5 | environment:
6 | sdk: ">=2.0.0 <3.0.0"
7 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_0
2 | description: test
3 | version: 0.0.3
4 | homepage: https://example.com
5 | environment:
6 | sdk: ">=2.0.0 <3.0.0"
7 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/1.0.0/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_0
2 | description: test
3 | version: 1.0.0
4 | homepage: https://example.com
5 | environment:
6 | sdk: ">=2.0.0 <3.0.0"
7 |
--------------------------------------------------------------------------------
/test/fixtures/package_1/0.0.1/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_1
2 | description: test
3 | version: 0.0.1
4 | homepage: https://example.com
5 | environment:
6 | sdk: ">=2.0.0 <3.0.0"
7 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/0.0.3+1/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_0
2 | description: test
3 | version: 0.0.3+1
4 | homepage: https://example.com
5 | environment:
6 | sdk: ">=2.0.0 <3.0.0"
7 |
--------------------------------------------------------------------------------
/test/fixtures/package_0/1.0.0-noreadme/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: package_0
2 | description: test
3 | version: 1.0.0-noreadme
4 | homepage: https://example.com
5 | environment:
6 | sdk: ">=2.0.0 <3.0.0"
7 |
--------------------------------------------------------------------------------
/lib/unpub.dart:
--------------------------------------------------------------------------------
1 | export 'src/meta_store.dart';
2 | export 'src/mongo_store.dart';
3 | export 'src/package_store.dart';
4 | export 'src/file_store.dart';
5 | export 'src/app.dart';
6 | export 'src/models.dart';
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Files and directories created by pub
2 | .dart_tool/
3 | .packages
4 | # Remove the following pattern if you wish to check in your lock file
5 | pubspec.lock
6 |
7 | # Conventional directory for build outputs
8 | build/
9 |
10 | # Directory created by dartdoc
11 | doc/api/
12 | unpub-packages/
13 | .vscode/
14 |
--------------------------------------------------------------------------------
/lib/src/package_store.dart:
--------------------------------------------------------------------------------
1 | abstract class PackageStore {
2 | bool supportsDownloadUrl = false;
3 |
4 | String downloadUrl(String name, String version) {
5 | throw 'downloadUri not implemented';
6 | }
7 |
8 | Stream> download(String name, String version) {
9 | throw 'download not implemented';
10 | }
11 |
12 | Future upload(String name, String version, List content);
13 | }
14 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 | services:
3 | unpub:
4 | build: ./
5 | container_name: unpub
6 | restart: always
7 | ports:
8 | - 4000:4000
9 | volumes:
10 | - ~/.unpub-packages:/app/unpub-packages
11 | depends_on:
12 | - mongo
13 | mongo:
14 | image: mongo:4.2.19
15 | container_name: unpub_mongo
16 | restart: always
17 | volumes:
18 | - ~/.unpub_mongo:/data/db
--------------------------------------------------------------------------------
/tool/pre_publish.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:path/path.dart' as path;
3 |
4 | var files = ['index.html', 'main.dart.js'];
5 |
6 | main(List args) {
7 | for (var file in files) {
8 | var content =
9 | File(path.absolute('unpub_web/build', file)).readAsStringSync();
10 | content = content.replaceAll('\\', '\\\\').replaceAll('\$', '\\\$');
11 | content = 'const content="""$content""";';
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:mongo_dart/mongo_dart.dart';
2 | import 'package:unpub/unpub.dart' as unpub;
3 |
4 | main(List args) async {
5 | final db = Db('mongodb://localhost:27017/dart_pub');
6 | await db.open(); // make sure the MongoDB connection opened
7 |
8 | final app = unpub.App(
9 | metaStore: unpub.MongoStore(db),
10 | packageStore: unpub.FileStore('./unpub-packages'),
11 | );
12 |
13 | final server = await app.serve('0.0.0.0', 4000);
14 | print('Serving at http://${server.address.host}:${server.port}');
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/meta_store.dart:
--------------------------------------------------------------------------------
1 | import 'package:unpub/src/models.dart';
2 |
3 | abstract class MetaStore {
4 | Future queryPackage(String name);
5 |
6 | Future addVersion(String name, UnpubVersion version);
7 |
8 | Future addUploader(String name, String email);
9 |
10 | Future removeUploader(String name, String email);
11 |
12 | void increaseDownloads(String name, String version);
13 |
14 | Future queryPackages({
15 | required int size,
16 | required int page,
17 | required String sort,
18 | String? keyword,
19 | String? uploader,
20 | String? dependency,
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 2.0.0
2 |
3 | - Supports NNBD
4 | - Fixes Web styles
5 |
6 | ## 1.2.1
7 |
8 | ## 1.2.0
9 |
10 | - Supports mongodb pool connection
11 | - Update web page styles
12 |
13 | ## 1.1.0
14 |
15 | - Add badges for version and downloads
16 | - Fix web page styles
17 |
18 | ## 1.0.0
19 |
20 | ## 0.4.0
21 |
22 | ## 0.3.0
23 |
24 | ## 0.2.2
25 |
26 | ## 0.2.1
27 |
28 | ## 0.2.0
29 |
30 | - Refactor
31 | - Semver whitelist
32 |
33 | ## 0.1.1
34 |
35 | - Get email via Google APIs
36 | - Upload validator
37 |
38 | ## 0.1.0
39 |
40 | - `pub get`
41 | - `pub publish` with permission check
42 |
43 | ## 0.0.1
44 |
45 | - Initial version, created by Stagehand
46 |
--------------------------------------------------------------------------------
/lib/src/utils.dart:
--------------------------------------------------------------------------------
1 | import 'package:yaml/yaml.dart';
2 |
3 | convertYaml(dynamic value) {
4 | if (value is YamlMap) {
5 | return value
6 | .cast()
7 | .map((k, v) => MapEntry(k, convertYaml(v)));
8 | }
9 | if (value is YamlList) {
10 | return value.map((e) => convertYaml(e)).toList();
11 | }
12 | return value;
13 | }
14 |
15 | Map? loadYamlAsMap(dynamic value) {
16 | var yamlMap = loadYaml(value) as YamlMap?;
17 | return convertYaml(yamlMap).cast();
18 | }
19 |
20 | List getPackageTags(Map pubspec) {
21 | // TODO: web and other tags
22 | if (pubspec['flutter'] != null) {
23 | return ['flutter'];
24 | } else {
25 | return ['flutter', 'web', 'other'];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: unpub
2 | description: Self-hosted private Dart Pub server for Enterprise, with a simple web interface to search and view packages information.
3 | version: 2.0.0
4 | homepage: https://github.com/bytedance/unpub
5 | environment:
6 | sdk: ">=2.12.0 <3.0.0"
7 | executables:
8 | unpub: unpub
9 | dependencies:
10 | args: ^2.2.0
11 | shelf: ^1.2.0
12 | shelf_router: ^1.1.1
13 | http_parser: ^4.0.0
14 | http: ^0.13.3
15 | archive: ^3.1.2
16 | logging: ^1.0.1
17 | meta: ^1.1.7
18 | googleapis: ^4.0.0
19 | yaml: ^3.1.0
20 | pub_semver: ^2.0.0
21 | json_annotation: ^4.1.0
22 | mongo_dart: ^0.7.1
23 | mime: ^1.0.0
24 | intl: ^0.17.0
25 | path: ^1.6.2
26 | collection: ^1.15.0
27 | shelf_cors_headers: ^0.1.2
28 | dev_dependencies:
29 | test: ^1.6.1
30 | build_runner: ^2.1.1
31 | json_serializable: ^5.0.0
32 | shelf_router_generator: ^1.0.1
33 | chunked_stream: ^1.4.1
34 |
--------------------------------------------------------------------------------
/lib/src/file_store.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:path/path.dart' as path;
3 | import 'package_store.dart';
4 |
5 | class FileStore extends PackageStore {
6 | String baseDir;
7 | String Function(String name, String version)? getFilePath;
8 |
9 | FileStore(this.baseDir, {this.getFilePath});
10 |
11 | File _getTarballFile(String name, String version) {
12 | final filePath =
13 | getFilePath?.call(name, version) ?? '$name-$version.tar.gz';
14 | return File(path.join(baseDir, filePath));
15 | }
16 |
17 | @override
18 | Future upload(String name, String version, List content) async {
19 | var file = _getTarballFile(name, version);
20 | await file.create(recursive: true);
21 | await file.writeAsBytes(content);
22 | }
23 |
24 | @override
25 | Stream> download(String name, String version) {
26 | return _getTarballFile(name, version).openRead();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Rongjian Zhang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/bin/unpub.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:path/path.dart' as path;
3 | import 'package:args/args.dart';
4 | import 'package:mongo_dart/mongo_dart.dart';
5 | import 'package:unpub/unpub.dart' as unpub;
6 |
7 | main(List args) async {
8 | var parser = ArgParser();
9 | parser.addOption('host', abbr: 'h', defaultsTo: '0.0.0.0');
10 | parser.addOption('port', abbr: 'p', defaultsTo: '4000');
11 | parser.addOption('database', abbr: 'd', defaultsTo: 'mongodb://localhost:27017/dart_pub');
12 |
13 | var results = parser.parse(args);
14 |
15 | var host = results['host'] as String?;
16 | var port = int.parse(results['port'] as String);
17 | var dbUri = results['database'] as String;
18 |
19 | if (results.rest.isNotEmpty) {
20 | print('Got unexpected arguments: "${results.rest.join(' ')}".\n\nUsage:\n');
21 | print(parser.usage);
22 | exit(1);
23 | }
24 |
25 | final db = Db(dbUri);
26 | await db.open();
27 |
28 | var baseDir = path.absolute('unpub-packages');
29 |
30 | var app = unpub.App(
31 | metaStore: unpub.MongoStore(db),
32 | packageStore: unpub.FileStore(baseDir),
33 | );
34 |
35 | var server = await app.serve(host, port);
36 | print('Serving at http://${server.address.host}:${server.port}');
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/app.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'app.dart';
4 |
5 | // **************************************************************************
6 | // ShelfRouterGenerator
7 | // **************************************************************************
8 |
9 | Router _$AppRouter(App service) {
10 | final router = Router();
11 | router.add('GET', r'/api/packages/', service.getVersions);
12 | router.add(
13 | 'GET', r'/api/packages//versions/', service.getVersion);
14 | router.add(
15 | 'GET', r'/packages//versions/.tar.gz', service.download);
16 | router.add('GET', r'/api/packages/versions/new', service.getUploadUrl);
17 | router.add('POST', r'/api/packages/versions/newUpload', service.upload);
18 | router.add(
19 | 'GET', r'/api/packages/versions/newUploadFinish', service.uploadFinish);
20 | router.add('POST', r'/api/packages//uploaders', service.addUploader);
21 | router.add('DELETE', r'/api/packages//uploaders/',
22 | service.removeUploader);
23 | router.add('GET', r'/webapi/packages', service.getPackages);
24 | router.add(
25 | 'GET', r'/webapi/package//', service.getPackageDetail);
26 | router.add('GET', r'/', service.indexHtml);
27 | router.add('GET', r'/packages', service.indexHtml);
28 | router.add('GET', r'/packages/', service.indexHtml);
29 | router.add('GET', r'/packages//versions/', service.indexHtml);
30 | router.add('GET', r'/main.dart.js', service.mainDartJs);
31 | router.add('GET', r'/badge//', service.badge);
32 | return router;
33 | }
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 通过 Docker 部署
4 | 为了方便大家移植部署,这边将 `unpub` 打包成 `docker` 镜像环境
5 |
6 | ### 安装镜像
7 | 首先拉取 GitHub 地址 代码,安装 [docker](https://www.docker.com/get-started/) 环境, 然后在项目根目录下执行下面命令即可
8 |
9 | ```sh
10 | # install docker and cd docker-compose.yml file
11 | docker compose up -d
12 | ```
13 |
14 | ### 安装运行成功如下
15 |
16 | 安装完成
17 |
18 | 
19 |
20 | 通过 docker ps -a 命令查看运行中容器
21 |
22 | 
23 |
24 | 
25 |
26 | ## bytedance Unpub
27 | ### Command Line
28 |
29 | ```sh
30 | pub global activate unpub
31 | unpub --database mongodb://localhost:27017/dart_pub # Replace this with production database uri
32 | ```
33 |
34 | ### Dart API
35 |
36 | ```dart
37 | import 'package:mongo_dart/mongo_dart.dart';
38 | import 'package:unpub/unpub.dart' as unpub;
39 |
40 | main(List args) async {
41 | final db = Db('mongodb://localhost:27017/dart_pub');
42 | await db.open(); // make sure the MongoDB connection opened
43 |
44 | final app = unpub.App(
45 | metaStore: unpub.MongoStore(db),
46 | packageStore: unpub.FileStore('./unpub-packages'),
47 | );
48 |
49 | final server = await app.serve('0.0.0.0', 4000);
50 | print('Serving at http://${server.address.host}:${server.port}');
51 | }
52 | ```
53 |
54 | ### 相关文章
55 | [Flutter搭建私有Pub仓库Docker部署](https://zhengzeqin.netlify.app/2022/05/16/flutter%E6%90%AD%E5%BB%BA%E7%A7%81%E6%9C%89pub%E4%BB%93%E5%BA%93docker%E9%83%A8%E7%BD%B2/)
56 |
57 | ### 具体了解地址 [unpub](https://github.com/bytedance/unpub)
58 |
59 |
60 | ## License
61 |
62 | MIT
63 |
--------------------------------------------------------------------------------
/lib/src/models.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'models.g.dart';
4 |
5 | DateTime identity(DateTime x) => x;
6 |
7 | @JsonSerializable(includeIfNull: false)
8 | class UnpubVersion {
9 | final String version;
10 | final Map pubspec;
11 | final String pubspecYaml;
12 | final String? uploader; // TODO: not sure why null. keep it nullable
13 | final String? readme;
14 | final String? changelog;
15 |
16 | @JsonKey(fromJson: identity, toJson: identity)
17 | final DateTime createdAt;
18 |
19 | UnpubVersion(
20 | this.version,
21 | this.pubspec,
22 | this.pubspecYaml,
23 | this.uploader,
24 | this.readme,
25 | this.changelog,
26 | this.createdAt,
27 | );
28 |
29 | factory UnpubVersion.fromJson(Map map) =>
30 | _$UnpubVersionFromJson(map);
31 |
32 | Map toJson() => _$UnpubVersionToJson(this);
33 | }
34 |
35 | @JsonSerializable()
36 | class UnpubPackage {
37 | final String name;
38 | final List versions;
39 | final bool private;
40 | final List uploaders;
41 |
42 | @JsonKey(fromJson: identity, toJson: identity)
43 | final DateTime createdAt;
44 |
45 | @JsonKey(fromJson: identity, toJson: identity)
46 | final DateTime updatedAt;
47 |
48 | final int? download;
49 |
50 | UnpubPackage(
51 | this.name,
52 | this.versions,
53 | this.private,
54 | this.uploaders,
55 | this.createdAt,
56 | this.updatedAt,
57 | this.download,
58 | );
59 |
60 | factory UnpubPackage.fromJson(Map map) =>
61 | _$UnpubPackageFromJson(map);
62 | }
63 |
64 | @JsonSerializable()
65 | class UnpubQueryResult {
66 | int count;
67 | List packages;
68 |
69 | UnpubQueryResult(this.count, this.packages);
70 |
71 | factory UnpubQueryResult.fromJson(Map map) =>
72 | _$UnpubQueryResultFromJson(map);
73 | }
74 |
--------------------------------------------------------------------------------
/test/file_store_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:chunked_stream/chunked_stream.dart';
4 | import 'package:path/path.dart' as path;
5 | import 'package:test/test.dart';
6 | import 'package:unpub/unpub.dart' as unpub;
7 |
8 | //test gzip data
9 | const TEST_PKG_DATA = [
10 | 0x8b, 0x1f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x03, //
11 | 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 //
12 | ];
13 |
14 | main() {
15 | test('upload-download-default-path', () async {
16 | var baseDir = _setup_fixture('upload-download-default-path');
17 | var store = unpub.FileStore(baseDir.path);
18 | await store.upload('test_package', '1.0.0', TEST_PKG_DATA);
19 | var pkg2 = await readByteStream(store.download('test_package', '1.0.0'));
20 | expect(pkg2, TEST_PKG_DATA);
21 | expect(
22 | File(path.join(baseDir.path, 'test_package-1.0.0.tar.gz')).existsSync(),
23 | isTrue);
24 | });
25 |
26 | test('upload-download-custom-path', () async {
27 | var baseDir = _setup_fixture('upload-download-custom-path');
28 | var store = unpub.FileStore(baseDir.path, getFilePath: newFilePathFunc());
29 | await store.upload('test_package', '1.0.0', TEST_PKG_DATA);
30 | var pkg2 = await readByteStream(store.download('test_package', '1.0.0'));
31 | expect(pkg2, TEST_PKG_DATA);
32 | expect(
33 | File(path.join(baseDir.path, 'packages', 't', 'te', 'test_package',
34 | 'versions', 'test_package-1.0.0.tar.gz'))
35 | .existsSync(),
36 | isTrue);
37 | });
38 | }
39 |
40 | String Function(String, String) newFilePathFunc() {
41 | return (String package, String version) {
42 | var grp = package[0];
43 | var subgrp = package.substring(0, 2);
44 | return path.join('packages', grp, subgrp, package, 'versions',
45 | '$package-$version.tar.gz');
46 | };
47 | }
48 |
49 | _setup_fixture(final String name) {
50 | var baseDir =
51 | Directory(path.absolute('test', 'fixtures', 'file_store', name));
52 | if (baseDir.existsSync()) {
53 | baseDir.deleteSync(recursive: true);
54 | }
55 | baseDir.createSync();
56 | return baseDir;
57 | }
58 |
--------------------------------------------------------------------------------
/test/utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:path/path.dart' as path;
3 | import 'package:http/http.dart' as http;
4 | import 'package:unpub/unpub.dart' as unpub;
5 | import 'package:mongo_dart/mongo_dart.dart';
6 |
7 | final notExistingPacakge = 'not_existing_package';
8 | final baseDir = path.absolute('unpub-packages');
9 | final pubHostedUrl = 'http://localhost:4000';
10 | final baseUri = Uri.parse(pubHostedUrl);
11 |
12 | final package0 = 'package_0';
13 | final package1 = 'package_1';
14 | final email0 = 'email0@example.com';
15 | final email1 = 'email1@example.com';
16 | final email2 = 'email2@example.com';
17 | final email3 = 'email3@example.com';
18 |
19 | createServer(String opEmail) async {
20 | final db = Db('mongodb://localhost:27017/dart_pub_test');
21 | await db.open();
22 | var mongoStore = unpub.MongoStore(db);
23 |
24 | var app = unpub.App(
25 | metaStore: mongoStore,
26 | packageStore: unpub.FileStore(baseDir),
27 | overrideUploaderEmail: opEmail,
28 | );
29 |
30 | var server = await app.serve('0.0.0.0', 4000);
31 | return server;
32 | }
33 |
34 | Future getVersions(String package) {
35 | package = Uri.encodeComponent(package);
36 | return http.get(baseUri.resolve('/api/packages/$package'));
37 | }
38 |
39 | Future getSpecificVersion(String package, String version) {
40 | package = Uri.encodeComponent(package);
41 | version = Uri.encodeComponent(version);
42 | return http.get(baseUri.resolve('/api/packages/$package/versions/$version'));
43 | }
44 |
45 | Future pubPublish(String name, String version) {
46 | return Process.run('dart', ['pub', 'publish', '--force'],
47 | workingDirectory: path.absolute('test/fixtures', name, version),
48 | environment: {'PUB_HOSTED_URL': pubHostedUrl});
49 | }
50 |
51 | Future pubUploader(String name, String operation, String email) {
52 | assert(['add', 'remove'].contains(operation), 'operation error');
53 |
54 | return Process.run('dart', ['pub', 'uploader', operation, email],
55 | workingDirectory: path.absolute('test/fixtures', name, '0.0.1'),
56 | environment: {'PUB_HOSTED_URL': pubHostedUrl});
57 | }
58 |
--------------------------------------------------------------------------------
/lib/unpub_api/lib/models.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'models.g.dart';
4 |
5 | @JsonSerializable()
6 | class ListApi {
7 | int count;
8 | List packages;
9 |
10 | ListApi(this.count, this.packages);
11 |
12 | factory ListApi.fromJson(Map map) => _$ListApiFromJson(map);
13 | Map toJson() => _$ListApiToJson(this);
14 | }
15 |
16 | @JsonSerializable()
17 | class ListApiPackage {
18 | String name;
19 | String? description;
20 | List tags;
21 | String latest;
22 | DateTime updatedAt;
23 |
24 | ListApiPackage(
25 | this.name, this.description, this.tags, this.latest, this.updatedAt);
26 |
27 | factory ListApiPackage.fromJson(Map map) =>
28 | _$ListApiPackageFromJson(map);
29 | Map toJson() => _$ListApiPackageToJson(this);
30 | }
31 |
32 | @JsonSerializable()
33 | class DetailViewVersion {
34 | String version;
35 | DateTime createdAt;
36 |
37 | DetailViewVersion(this.version, this.createdAt);
38 |
39 | factory DetailViewVersion.fromJson(Map map) =>
40 | _$DetailViewVersionFromJson(map);
41 |
42 | Map toJson() => _$DetailViewVersionToJson(this);
43 | }
44 |
45 | @JsonSerializable()
46 | class WebapiDetailView {
47 | String name;
48 | String version;
49 | String description;
50 | String homepage;
51 | List uploaders;
52 | DateTime createdAt;
53 | final String? readme;
54 | final String? changelog;
55 | List versions;
56 | List authors;
57 | List? dependencies;
58 | List tags;
59 |
60 | WebapiDetailView(
61 | this.name,
62 | this.version,
63 | this.description,
64 | this.homepage,
65 | this.uploaders,
66 | this.createdAt,
67 | this.readme,
68 | this.changelog,
69 | this.versions,
70 | this.authors,
71 | this.dependencies,
72 | this.tags);
73 |
74 | factory WebapiDetailView.fromJson(Map map) =>
75 | _$WebapiDetailViewFromJson(map);
76 |
77 | Map toJson() => _$WebapiDetailViewToJson(this);
78 | }
79 |
--------------------------------------------------------------------------------
/.idea/unpub-2.0.0.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/lib/src/models.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'models.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | UnpubVersion _$UnpubVersionFromJson(Map json) => UnpubVersion(
10 | json['version'] as String,
11 | json['pubspec'] as Map,
12 | json['pubspecYaml'] as String,
13 | json['uploader'] as String?,
14 | json['readme'] as String?,
15 | json['changelog'] as String?,
16 | identity(json['createdAt'] as DateTime),
17 | );
18 |
19 | Map _$UnpubVersionToJson(UnpubVersion instance) {
20 | final val = {
21 | 'version': instance.version,
22 | 'pubspec': instance.pubspec,
23 | 'pubspecYaml': instance.pubspecYaml,
24 | };
25 |
26 | void writeNotNull(String key, dynamic value) {
27 | if (value != null) {
28 | val[key] = value;
29 | }
30 | }
31 |
32 | writeNotNull('uploader', instance.uploader);
33 | writeNotNull('readme', instance.readme);
34 | writeNotNull('changelog', instance.changelog);
35 | writeNotNull('createdAt', identity(instance.createdAt));
36 | return val;
37 | }
38 |
39 | UnpubPackage _$UnpubPackageFromJson(Map json) => UnpubPackage(
40 | json['name'] as String,
41 | (json['versions'] as List)
42 | .map((e) => UnpubVersion.fromJson(e as Map))
43 | .toList(),
44 | json['private'] as bool,
45 | (json['uploaders'] as List).map((e) => e as String).toList(),
46 | identity(json['createdAt'] as DateTime),
47 | identity(json['updatedAt'] as DateTime),
48 | json['download'] as int?,
49 | );
50 |
51 | Map _$UnpubPackageToJson(UnpubPackage instance) =>
52 | {
53 | 'name': instance.name,
54 | 'versions': instance.versions,
55 | 'private': instance.private,
56 | 'uploaders': instance.uploaders,
57 | 'createdAt': identity(instance.createdAt),
58 | 'updatedAt': identity(instance.updatedAt),
59 | 'download': instance.download,
60 | };
61 |
62 | UnpubQueryResult _$UnpubQueryResultFromJson(Map json) =>
63 | UnpubQueryResult(
64 | json['count'] as int,
65 | (json['packages'] as List)
66 | .map((e) => UnpubPackage.fromJson(e as Map))
67 | .toList(),
68 | );
69 |
70 | Map _$UnpubQueryResultToJson(UnpubQueryResult instance) =>
71 | {
72 | 'count': instance.count,
73 | 'packages': instance.packages,
74 | };
75 |
--------------------------------------------------------------------------------
/lib/src/mongo_store.dart:
--------------------------------------------------------------------------------
1 | import 'package:mongo_dart/mongo_dart.dart';
2 | import 'package:intl/intl.dart';
3 | import 'package:unpub/src/models.dart';
4 | import 'meta_store.dart';
5 |
6 | final packageCollection = 'packages';
7 | final statsCollection = 'stats';
8 |
9 | class MongoStore extends MetaStore {
10 | Db db;
11 |
12 | MongoStore(this.db);
13 |
14 | static SelectorBuilder _selectByName(String? name) => where.eq('name', name);
15 |
16 | Future _queryPackagesBySelector(
17 | SelectorBuilder selector) async {
18 | final count = await db.collection(packageCollection).count(selector);
19 | final packages = await db
20 | .collection(packageCollection)
21 | .find(selector)
22 | .map((item) => UnpubPackage.fromJson(item))
23 | .toList();
24 | return UnpubQueryResult(count, packages);
25 | }
26 |
27 | @override
28 | queryPackage(name) async {
29 | var json =
30 | await db.collection(packageCollection).findOne(_selectByName(name));
31 | if (json == null) return null;
32 | return UnpubPackage.fromJson(json);
33 | }
34 |
35 | @override
36 | addVersion(name, version) async {
37 | await db.collection(packageCollection).update(
38 | _selectByName(name),
39 | modify
40 | .push('versions', version.toJson())
41 | .addToSet('uploaders', version.uploader)
42 | .setOnInsert('createdAt', version.createdAt)
43 | .setOnInsert('private', true)
44 | .setOnInsert('download', 0)
45 | .set('updatedAt', version.createdAt),
46 | upsert: true);
47 | }
48 |
49 | @override
50 | addUploader(name, email) async {
51 | await db
52 | .collection(packageCollection)
53 | .update(_selectByName(name), modify.push('uploaders', email));
54 | }
55 |
56 | @override
57 | removeUploader(name, email) async {
58 | await db
59 | .collection(packageCollection)
60 | .update(_selectByName(name), modify.pull('uploaders', email));
61 | }
62 |
63 | @override
64 | increaseDownloads(name, version) {
65 | var today = DateFormat('yyyyMMdd').format(DateTime.now());
66 | db
67 | .collection(packageCollection)
68 | .update(_selectByName(name), modify.inc('download', 1));
69 | db
70 | .collection(statsCollection)
71 | .update(_selectByName(name), modify.inc('d$today', 1));
72 | }
73 |
74 | @override
75 | Future queryPackages({
76 | required size,
77 | required page,
78 | required sort,
79 | keyword,
80 | uploader,
81 | dependency,
82 | }) {
83 | var selector =
84 | where.sortBy(sort, descending: true).limit(size).skip(page * size);
85 |
86 | if (keyword != null) {
87 | selector = selector.match('name', '.*$keyword.*');
88 | }
89 | if (uploader != null) {
90 | selector = selector.eq('uploaders', uploader);
91 | }
92 | if (dependency != null) {
93 | selector = selector.raw({
94 | 'versions': {
95 | r'$elemMatch': {
96 | 'pubspec.dependencies.$dependency': {r'$exists': true}
97 | }
98 | }
99 | });
100 | }
101 |
102 | return _queryPackagesBySelector(selector);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/lib/unpub_api/lib/models.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'models.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | ListApi _$ListApiFromJson(Map json) => ListApi(
10 | json['count'] as int,
11 | (json['packages'] as List)
12 | .map((e) => ListApiPackage.fromJson(e as Map))
13 | .toList(),
14 | );
15 |
16 | Map _$ListApiToJson(ListApi instance) => {
17 | 'count': instance.count,
18 | 'packages': instance.packages,
19 | };
20 |
21 | ListApiPackage _$ListApiPackageFromJson(Map json) =>
22 | ListApiPackage(
23 | json['name'] as String,
24 | json['description'] as String?,
25 | (json['tags'] as List).map((e) => e as String).toList(),
26 | json['latest'] as String,
27 | DateTime.parse(json['updatedAt'] as String),
28 | );
29 |
30 | Map _$ListApiPackageToJson(ListApiPackage instance) =>
31 | {
32 | 'name': instance.name,
33 | 'description': instance.description,
34 | 'tags': instance.tags,
35 | 'latest': instance.latest,
36 | 'updatedAt': instance.updatedAt.toIso8601String(),
37 | };
38 |
39 | DetailViewVersion _$DetailViewVersionFromJson(Map json) =>
40 | DetailViewVersion(
41 | json['version'] as String,
42 | DateTime.parse(json['createdAt'] as String),
43 | );
44 |
45 | Map _$DetailViewVersionToJson(DetailViewVersion instance) =>
46 | {
47 | 'version': instance.version,
48 | 'createdAt': instance.createdAt.toIso8601String(),
49 | };
50 |
51 | WebapiDetailView _$WebapiDetailViewFromJson(Map json) =>
52 | WebapiDetailView(
53 | json['name'] as String,
54 | json['version'] as String,
55 | json['description'] as String,
56 | json['homepage'] as String,
57 | (json['uploaders'] as List).map((e) => e as String).toList(),
58 | DateTime.parse(json['createdAt'] as String),
59 | json['readme'] as String?,
60 | json['changelog'] as String?,
61 | (json['versions'] as List)
62 | .map((e) => DetailViewVersion.fromJson(e as Map))
63 | .toList(),
64 | (json['authors'] as List).map((e) => e as String?).toList(),
65 | (json['dependencies'] as List?)
66 | ?.map((e) => e as String)
67 | .toList(),
68 | (json['tags'] as List).map((e) => e as String).toList(),
69 | );
70 |
71 | Map _$WebapiDetailViewToJson(WebapiDetailView instance) =>
72 | {
73 | 'name': instance.name,
74 | 'version': instance.version,
75 | 'description': instance.description,
76 | 'homepage': instance.homepage,
77 | 'uploaders': instance.uploaders,
78 | 'createdAt': instance.createdAt.toIso8601String(),
79 | 'readme': instance.readme,
80 | 'changelog': instance.changelog,
81 | 'versions': instance.versions,
82 | 'authors': instance.authors,
83 | 'dependencies': instance.dependencies,
84 | 'tags': instance.tags,
85 | };
86 |
--------------------------------------------------------------------------------
/test/unpub_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:convert';
3 | import 'package:collection/collection.dart';
4 | import 'package:unpub/src/utils.dart';
5 | import 'package:test/test.dart';
6 | import 'package:path/path.dart' as path;
7 | import 'package:http/http.dart' as http;
8 | import 'package:mongo_dart/mongo_dart.dart';
9 | import 'utils.dart';
10 | import 'package:unpub/unpub.dart';
11 |
12 | main() {
13 | Db _db = Db('mongodb://localhost:27017/dart_pub_test');
14 | late HttpServer _server;
15 |
16 | setUpAll(() async {
17 | await _db.open();
18 | });
19 |
20 | Future