ic-mops 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -4,25 +4,21 @@ import fs from 'fs';
4
4
  import path from 'path';
5
5
  import {program} from 'commander';
6
6
  import chalk from 'chalk';
7
- import TOML from '@iarna/toml';
8
7
 
9
8
  import {init} from './commands/init.js';
10
9
  import {install} from './commands/install.js';
11
10
  import {publish} from './commands/publish.js';
12
11
  import {importPem} from './commands/import-identity.js';
13
12
  import {sources} from './commands/sources.js';
14
- import {checkApiCompatibility, getHighestVersion, getNetwork, setNetwork} from './mops.js';
13
+ import {checkApiCompatibility, getHighestVersion, getNetwork, parseGithubURL, readConfig, setNetwork, writeConfig} from './mops.js';
15
14
  import {whoami} from './commands/whoami.js';
16
15
  import {installAll} from './commands/install-all.js';
17
16
  import logUpdate from 'log-update';
17
+ import { installFromGithub } from './vessel.js';
18
18
 
19
19
  let cwd = process.cwd();
20
20
  let configFile = path.join(cwd, 'mops.toml');
21
21
 
22
- function wirteConfig(config) {
23
- fs.writeFileSync(configFile, TOML.stringify(config).trim());
24
- }
25
-
26
22
  program.name('mops');
27
23
 
28
24
  // init
@@ -44,8 +40,7 @@ program
44
40
  let config = {};
45
41
  let exists = fs.existsSync(configFile);
46
42
  if (exists) {
47
- let text = fs.readFileSync(configFile).toString();
48
- config = TOML.parse(text);
43
+ config = readConfig(configFile);
49
44
  }
50
45
  else {
51
46
  console.log(chalk.red('Error: ') + `mops.toml not found. Please run ${chalk.green('mops init')} first`);
@@ -62,22 +57,63 @@ program
62
57
 
63
58
  if (!pkg) {
64
59
  installAll(options);
60
+ return;
65
61
  }
66
- else {
62
+
63
+ let pkgDetails;
64
+ let existingPkg = config.dependencies[pkg];
65
+
66
+ if (pkg.startsWith('https://github.com') || pkg.split('/') > 1){
67
+ const {org, gitName, branch} = parseGithubURL(pkg);
68
+
69
+ pkgDetails = {
70
+ name: parseGithubURL(pkg).gitName,
71
+ repo: `https://github.com/${org}/${gitName}#${branch}`,
72
+ version: ''
73
+ };
74
+
75
+ existingPkg = config.dependencies[pkgDetails.name];
76
+
77
+ }else if (!existingPkg || !existingPkg.repo){
67
78
  let versionRes = await getHighestVersion(pkg);
68
79
  if (versionRes.err) {
69
80
  console.log(chalk.red('Error: ') + versionRes.err);
70
81
  return;
71
82
  }
72
- let version = versionRes.ok;
73
83
 
74
- await install(pkg, version, {verbose: options.verbose});
84
+ pkgDetails = {
85
+ name: pkg,
86
+ repo: '',
87
+ version: versionRes.ok
88
+ };
75
89
 
76
- config.dependencies[pkg] = version;
77
- wirteConfig(config);
78
- logUpdate.clear();
79
- console.log(chalk.green('Package installed ') + `${pkg} = "${version}"`);
90
+ }else{
91
+ options.silent || logUpdate(`Installing ${existingPkg.name}@${existingPkg.version} (cache) from Github`);
92
+ return;
80
93
  }
94
+
95
+ const {name, repo, version} = pkgDetails;
96
+
97
+ if (repo){
98
+ // pkg name conflict with an installed mops pkg
99
+ if (existingPkg && !existingPkg.repo){
100
+ console.log(chalk.red('Error: ') + `Conflicting Package Name '${name}`);
101
+ console.log('Consider entering the repo url and assigning a new name in the \'mops.toml\' file');
102
+ return;
103
+ }
104
+
105
+ await installFromGithub(name, repo, {verbose: options.verbose});
106
+ }else{
107
+ await install(name, version, {verbose: options.verbose});
108
+ }
109
+
110
+ config.dependencies[name] = pkgDetails;
111
+ writeConfig(config);
112
+
113
+ logUpdate.clear();
114
+ console.log(
115
+ chalk.green('Package installed ') + `${name} = "${repo || version}"`
116
+ );
81
117
  });
82
118
 
83
119
  // publish
package/commands/init.js CHANGED
@@ -1,9 +1,9 @@
1
- import TOML from '@iarna/toml';
2
1
  import chalk from 'chalk';
3
2
  import path from 'path';
4
3
  import fs from 'fs';
5
- import {checkApiCompatibility, mainActor, readDfxJson} from '../mops.js';
4
+ import {checkApiCompatibility, mainActor, readDfxJson, writeConfig} from '../mops.js';
6
5
  import {installAll} from './install-all.js';
6
+ import { readVesselConfig } from '../vessel.js';
7
7
 
8
8
  export async function init(name = '') {
9
9
  let configFile = path.join(process.cwd(), 'mops.toml');
@@ -16,18 +16,37 @@ export async function init(name = '') {
16
16
  console.log('Initializing...');
17
17
 
18
18
  let config = {};
19
+ let vesselConfig = {};
20
+
21
+ const vesselFile = path.join(process.cwd(), 'vessel.dhall');
22
+
23
+ if (fs.existsSync(vesselFile)){
24
+ console.log('Reading vessel.dhall file');
25
+ const res = await readVesselConfig(process.cwd(), { cache: false });
26
+ vesselConfig = {...res};
27
+ }
28
+
29
+ if (vesselConfig.dependencies){
30
+ config.dependencies = {};
31
+
32
+ for (const dep of (vesselConfig.dependencies || [])){
33
+ config.dependencies[dep.name] = dep;
34
+ }
35
+ }
19
36
 
20
37
  // lib mode
21
38
  if (name) {
22
- config = {
23
- package: {
24
- name: name,
25
- version: '0.1.0',
26
- description: '',
27
- repository: '',
28
- }
39
+ config.package = {
40
+ name,
41
+ version: '0.1.0',
42
+ description: '',
43
+ repository: '',
29
44
  };
30
- fs.writeFileSync(configFile, TOML.stringify(config).trim());
45
+
46
+ writeConfig(config);
47
+
48
+ if (Object.keys(config.dependencies || {}).length)
49
+ await installAll({verbose: true});
31
50
  }
32
51
 
33
52
  // project mode
@@ -43,14 +62,15 @@ export async function init(name = '') {
43
62
  let actor = await mainActor();
44
63
  let defaultPackages = await actor.getDefaultPackages(dfxVersion);
45
64
 
65
+ if (!config.dependencies)
66
+ config.dependencies = {};
67
+
46
68
  defaultPackages.forEach(([name, version]) => {
47
- config.dependencies = {
48
- ...config.dependencies,
49
- [name]: version,
50
- };
69
+ config.dependencies[name] = version;
51
70
  });
52
71
 
53
- fs.writeFileSync(configFile, TOML.stringify(config).trim());
72
+ writeConfig(config);
73
+
54
74
  await installAll({verbose: true});
55
75
  }
56
76
 
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import logUpdate from 'log-update';
3
3
  import {checkConfigFile, readConfig} from '../mops.js';
4
4
  import {install} from './install.js';
5
+ import {installFromGithub} from '../vessel.js';
5
6
 
6
7
  export async function installAll({verbose} = {}) {
7
8
  if (!checkConfigFile()) {
@@ -9,10 +10,14 @@ export async function installAll({verbose} = {}) {
9
10
  }
10
11
 
11
12
  let config = readConfig();
12
- let deps = Object.entries(config.dependencies || {});
13
+ const deps = Object.values(config.dependencies || {});
13
14
 
14
- for (let [pkg, ver] of deps) {
15
- await install(pkg, ver, {verbose});
15
+ for (let {name, repo, version} of deps) {
16
+ if (repo){
17
+ await installFromGithub(name, repo, {verbose});
18
+ }else{
19
+ await install(name, version, {verbose});
20
+ }
16
21
  }
17
22
 
18
23
  logUpdate.clear();
@@ -1,9 +1,10 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
3
  import logUpdate from 'log-update';
4
- import {checkConfigFile, getHighestVersion, mainActor, progressBar, readConfig, storageActor} from '../mops.js';
4
+ import {checkConfigFile, formatDir, getHighestVersion, mainActor, progressBar, readConfig, storageActor} from '../mops.js';
5
5
  import {parallel} from '../parallel.js';
6
6
  import chalk from 'chalk';
7
+ import { installFromGithub } from '../vessel.js';
7
8
 
8
9
  export async function install(pkg, version = '', {verbose, silent, dep} = {}) {
9
10
  if (!checkConfigFile()) {
@@ -19,7 +20,7 @@ export async function install(pkg, version = '', {verbose, silent, dep} = {}) {
19
20
  version = versionRes.ok;
20
21
  }
21
22
 
22
- let dir = path.join(process.cwd(), '.mops', `${pkg}@${version}`);
23
+ let dir = formatDir(pkg, version);
23
24
  let actor = await mainActor();
24
25
 
25
26
  // cache
@@ -30,9 +31,7 @@ export async function install(pkg, version = '', {verbose, silent, dep} = {}) {
30
31
  else {
31
32
  fs.mkdirSync(dir, {recursive: true});
32
33
 
33
- if (!dep) {
34
- actor.notifyInstall(pkg, version);
35
- }
34
+ actor.notifyInstall(pkg, version);
36
35
 
37
36
  let packageDetailsRes = await actor.getPackageDetails(pkg, version);
38
37
  if (packageDetailsRes.err) {
@@ -91,7 +90,11 @@ export async function install(pkg, version = '', {verbose, silent, dep} = {}) {
91
90
 
92
91
  // install dependencies
93
92
  let config = readConfig(path.join(dir, 'mops.toml'));
94
- for (let [name, version] of Object.entries(config.dependencies || {})) {
95
- await install(name, version, {verbose, silent, dep: true});
93
+ for (const {name, repo, version} of Object.values(config.dependencies || {})) {
94
+ if (repo){
95
+ await installFromGithub(name, repo, {verbose});
96
+ }else{
97
+ await install(name, version, {verbose});
98
+ }
96
99
  }
97
100
  }
@@ -136,9 +136,7 @@ export async function publish() {
136
136
  dfx: config.package.dfx || '',
137
137
  moc: config.package.moc || '',
138
138
  donation: config.package.donation || '',
139
- dependencies: (Object.entries(config.dependencies || {})).map(([name, version]) => {
140
- return {name, version};
141
- }),
139
+ dependencies: Object.values(config.dependencies || {}),
142
140
  devDependencies: [],
143
141
  scripts: [],
144
142
  };
@@ -2,7 +2,8 @@ import path from 'path';
2
2
  import fs from 'fs';
3
3
  import chalk from 'chalk';
4
4
  import {install} from './install.js';
5
- import {getHighestVersion, readConfig} from '../mops.js';
5
+ import {formatDir, formatGithubDir, getHighestVersion, parseGithubURL, readConfig} from '../mops.js';
6
+ import { readVesselConfig } from '../vessel.js';
6
7
 
7
8
  function rootDir(cwd = process.cwd()) {
8
9
  let configFile = path.join(cwd, 'mops.toml');
@@ -16,8 +17,7 @@ function rootDir(cwd = process.cwd()) {
16
17
  return rootDir(path.join(cwd, '..'));
17
18
  }
18
19
 
19
- // TODO: resolve deps for a specific file to avoid conflicts
20
- // TODO: remove base-unofficial
20
+ // TODO: resolve conflicts
21
21
  export async function sources({verbose} = {}) {
22
22
  let root = rootDir();
23
23
  if (!root) {
@@ -42,48 +42,63 @@ export async function sources({verbose} = {}) {
42
42
  return 0;
43
43
  };
44
44
 
45
- let collectDeps = (config, isRoot = false) => {
46
- for (let [dep, ver] of Object.entries(config.dependencies || {})) {
47
- // take root dep ver or bigger one
48
- if (isRoot || !packages[dep] || compareVersions(packages[dep], ver) === -1) {
49
- packages[dep] = ver;
50
- }
45
+ const gitVerRegex = new RegExp(/v(\d{1,2}\.\d{1,2}\.\d{1,2})(-.*)?$/);
51
46
 
52
- let config = readConfig(path.join(root, `.mops/${dep}@${ver}/mops.toml`));
53
- collectDeps(config);
47
+ const compareGitVersions = (repoA, repoB) => {
48
+ const {branch: a} = parseGithubURL(repoA);
49
+ const {branch: b} = parseGithubURL(repoB);
54
50
 
55
- if (!versions[dep]) {
56
- versions[dep] = [];
57
- }
58
- versions[dep].push(ver);
51
+ if (gitVerRegex.test(a) && gitVerRegex.test(b)){
52
+ return compareVersions(a.substring(1) , b.substring(1));
53
+ }else if (!gitVerRegex.test(a)){
54
+ return -1;
55
+ }else{
56
+ return 1;
59
57
  }
60
58
  };
61
59
 
62
- let config = readConfig(path.join(root, 'mops.toml'));
63
- collectDeps(config, true);
60
+ let collectDeps = async (config, isRoot = false) => {
61
+ for (const pkgDetails of Object.values(config.dependencies || {})) {
62
+ const {name, repo, version} = pkgDetails;
64
63
 
65
- // install base-unofficial
66
- if (!packages['base-unofficial']) {
67
- let dirs = fs.readdirSync(path.join(root, '.mops'));
68
- let downloadedPackages = Object.fromEntries(dirs.map((dir) => {
69
- return dir.split('@');
70
- }));
64
+ // take root dep version or bigger one
65
+ if (
66
+ isRoot ||
67
+ !packages[name] ||
68
+ repo && compareGitVersions(packages[name].repo, repo) === -1 ||
69
+ compareVersions(packages[name].version, version) === -1
70
+ ) {
71
+ packages[name] = pkgDetails;
72
+ }
71
73
 
72
- if (downloadedPackages['base-unofficial']) {
73
- packages['base-unofficial'] = downloadedPackages['base-unofficial'];
74
- }
75
- else {
76
- let versionRes = await getHighestVersion('base-unofficial');
77
- if (versionRes.err) {
78
- console.log(chalk.red('Error: ') + versionRes.err);
79
- return;
74
+ let nestedConfig;
75
+
76
+ if (repo){
77
+ const dir = formatGithubDir(name, repo);
78
+ nestedConfig = await readVesselConfig(dir) || {};
79
+ }
80
+ else{
81
+ const dir = formatDir(name, version) + '/mops.toml';
82
+ nestedConfig = readConfig(dir);
83
+ }
84
+
85
+ await collectDeps(nestedConfig);
86
+
87
+ if (!versions[name]) {
88
+ versions[name] = [];
80
89
  }
81
- let version = versionRes.ok;
82
90
 
83
- await install('base-unofficial', version, {silent: true, dep: true});
84
- packages['base-unofficial'] = version;
91
+ if (repo){
92
+ const {branch} = parseGithubURL(repo);
93
+ versions[name].push(branch);
94
+ }else{
95
+ versions[name].push(version);
96
+ }
85
97
  }
86
- }
98
+ };
99
+
100
+ let config = readConfig(path.join(root, 'mops.toml'));
101
+ await collectDeps(config, true);
87
102
 
88
103
  // show conflicts
89
104
  if (verbose) {
@@ -95,13 +110,14 @@ export async function sources({verbose} = {}) {
95
110
  }
96
111
 
97
112
  // sources
98
- for (let [name, ver] of Object.entries(packages)) {
99
- let pkgDir = path.relative(process.cwd(), path.join(root, `.mops/${name}@${ver}/src`));
100
- console.log(`--package ${name} ${pkgDir}`);
101
-
102
- // fallback base to base-unofficial
103
- if (name == 'base-unofficial' && !packages.base) {
104
- console.log(`--package base ${pkgDir}`);
113
+ for (let [name, {repo, version}] of Object.entries(packages)) {
114
+ let pkgDir;
115
+ if (repo){
116
+ pkgDir = path.relative(process.cwd(), formatGithubDir(name, repo)) + '/src';
117
+ }else{
118
+ pkgDir = path.relative(process.cwd(), formatDir(name, version)) + '/src';
105
119
  }
120
+
121
+ console.log(`--package ${name} ${pkgDir}`);
106
122
  }
107
123
  }
@@ -1,8 +1,8 @@
1
- import {checkConfigFile} from '../mops.js';
2
- import path from 'path';
1
+ import {checkConfigFile, readConfig} from '../mops.js';
3
2
  import fs from 'fs';
4
3
  import del from 'del';
5
4
  import chalk from 'chalk';
5
+ import { formatDir, formatGithubDir } from '../mops.js';
6
6
 
7
7
  export async function uninstall(pkg, version) {
8
8
  if (!checkConfigFile()) {
@@ -10,7 +10,23 @@ export async function uninstall(pkg, version) {
10
10
  }
11
11
 
12
12
  // TODO: check if deps relate on this package
13
- let pkgDir = path.join(process.cwd(), '.mops', `${pkg}@${version}`);
13
+ const config = readConfig();
14
+
15
+ const pkgDetails = config.dependencies[pkg];
16
+
17
+ if (!pkgDetails){
18
+ console.log(`No dependency to remove ${pkg} = "${version}"`);
19
+ return;
20
+ }
21
+
22
+ const {repo} = pkgDetails;
23
+ let pkgDir;
24
+
25
+ if (repo){
26
+ pkgDir = formatGithubDir(pkg, repo);
27
+ }else{
28
+ pkgDir = formatDir(pkg, version);
29
+ }
14
30
 
15
31
  if (!fs.existsSync(pkgDir)) {
16
32
  console.log(`No cache to remove ${pkg} = "${version}"`);
@@ -0,0 +1,16 @@
1
+ import { ActorSubclass, HttpAgentOptions, ActorConfig } from '@dfinity/agent';
2
+ import { Principal } from '@dfinity/principal';
3
+
4
+ import { _SERVICE } from './main.did';
5
+
6
+ export declare interface CreateActorOptions {
7
+ agentOptions?: HttpAgentOptions;
8
+ actorOptions?: ActorConfig;
9
+ }
10
+
11
+ export declare const createActor: (
12
+ canisterId: string | Principal,
13
+ options: CreateActorOptions
14
+ ) => ActorSubclass<_SERVICE>;
15
+
16
+ export declare const main: ActorSubclass<_SERVICE>;
@@ -56,18 +56,18 @@ type PackageName__1 = text;
56
56
  type PackageName = text;
57
57
  type PackageDetails =
58
58
  record {
59
- config: PackageConfig__1;
59
+ config: PackageConfigV2__1;
60
60
  downloadsInLast30Days: nat;
61
61
  downloadsTotal: nat;
62
62
  owner: principal;
63
63
  publication: PackagePublication;
64
64
  };
65
- type PackageConfig__1 =
65
+ type PackageConfigV2__1 =
66
66
  record {
67
67
  baseDir: text;
68
- dependencies: vec Dependency;
68
+ dependencies: vec DependencyV2;
69
69
  description: text;
70
- devDependencies: vec Dependency;
70
+ devDependencies: vec DependencyV2;
71
71
  dfx: text;
72
72
  documentation: text;
73
73
  donation: text;
@@ -81,12 +81,12 @@ type PackageConfig__1 =
81
81
  scripts: vec Script;
82
82
  version: text;
83
83
  };
84
- type PackageConfig =
84
+ type PackageConfigV2 =
85
85
  record {
86
86
  baseDir: text;
87
- dependencies: vec Dependency;
87
+ dependencies: vec DependencyV2;
88
88
  description: text;
89
- devDependencies: vec Dependency;
89
+ devDependencies: vec DependencyV2;
90
90
  dfx: text;
91
91
  documentation: text;
92
92
  donation: text;
@@ -102,9 +102,10 @@ type PackageConfig =
102
102
  };
103
103
  type FileId = text;
104
104
  type Err = text;
105
- type Dependency =
105
+ type DependencyV2 =
106
106
  record {
107
107
  name: PackageName;
108
+ repo: text;
108
109
  version: text;
109
110
  };
110
111
  service : {
@@ -128,6 +129,6 @@ service : {
128
129
  notifyInstall: (PackageName__1, Ver) -> () oneway;
129
130
  search: (Text) -> (vec PackageDetails) query;
130
131
  startFileUpload: (PublishingId, Text, nat, blob) -> (Result_2);
131
- startPublish: (PackageConfig) -> (Result_1);
132
+ startPublish: (PackageConfigV2) -> (Result_1);
132
133
  uploadFileChunk: (PublishingId, FileId, nat, blob) -> (Result);
133
134
  }
@@ -1,10 +1,14 @@
1
1
  import type { Principal } from '@dfinity/principal';
2
2
  import type { ActorMethod } from '@dfinity/agent';
3
3
 
4
- export interface Dependency { 'name' : PackageName, 'version' : string }
4
+ export interface DependencyV2 {
5
+ 'name' : PackageName,
6
+ 'repo' : string,
7
+ 'version' : string,
8
+ }
5
9
  export type Err = string;
6
10
  export type FileId = string;
7
- export interface PackageConfig {
11
+ export interface PackageConfigV2 {
8
12
  'dfx' : string,
9
13
  'moc' : string,
10
14
  'scripts' : Array<Script>,
@@ -16,13 +20,13 @@ export interface PackageConfig {
16
20
  'version' : string,
17
21
  'keywords' : Array<string>,
18
22
  'donation' : string,
19
- 'devDependencies' : Array<Dependency>,
23
+ 'devDependencies' : Array<DependencyV2>,
20
24
  'repository' : string,
21
- 'dependencies' : Array<Dependency>,
25
+ 'dependencies' : Array<DependencyV2>,
22
26
  'license' : string,
23
27
  'readme' : string,
24
28
  }
25
- export interface PackageConfig__1 {
29
+ export interface PackageConfigV2__1 {
26
30
  'dfx' : string,
27
31
  'moc' : string,
28
32
  'scripts' : Array<Script>,
@@ -34,9 +38,9 @@ export interface PackageConfig__1 {
34
38
  'version' : string,
35
39
  'keywords' : Array<string>,
36
40
  'donation' : string,
37
- 'devDependencies' : Array<Dependency>,
41
+ 'devDependencies' : Array<DependencyV2>,
38
42
  'repository' : string,
39
- 'dependencies' : Array<Dependency>,
43
+ 'dependencies' : Array<DependencyV2>,
40
44
  'license' : string,
41
45
  'readme' : string,
42
46
  }
@@ -44,7 +48,7 @@ export interface PackageDetails {
44
48
  'owner' : Principal,
45
49
  'downloadsTotal' : bigint,
46
50
  'downloadsInLast30Days' : bigint,
47
- 'config' : PackageConfig__1,
51
+ 'config' : PackageConfigV2__1,
48
52
  'publication' : PackagePublication,
49
53
  }
50
54
  export type PackageName = string;
@@ -100,7 +104,7 @@ export interface _SERVICE {
100
104
  [PublishingId, Text, bigint, Uint8Array],
101
105
  Result_2,
102
106
  >,
103
- 'startPublish' : ActorMethod<[PackageConfig], Result_1>,
107
+ 'startPublish' : ActorMethod<[PackageConfigV2], Result_1>,
104
108
  'uploadFileChunk' : ActorMethod<
105
109
  [PublishingId, FileId, bigint, Uint8Array],
106
110
  Result,
@@ -11,8 +11,12 @@ export const idlFactory = ({ IDL }) => {
11
11
  const Result_4 = IDL.Variant({ 'ok' : Ver, 'err' : Err });
12
12
  const Script = IDL.Record({ 'value' : IDL.Text, 'name' : IDL.Text });
13
13
  const PackageName = IDL.Text;
14
- const Dependency = IDL.Record({ 'name' : PackageName, 'version' : IDL.Text });
15
- const PackageConfig__1 = IDL.Record({
14
+ const DependencyV2 = IDL.Record({
15
+ 'name' : PackageName,
16
+ 'repo' : IDL.Text,
17
+ 'version' : IDL.Text,
18
+ });
19
+ const PackageConfigV2__1 = IDL.Record({
16
20
  'dfx' : IDL.Text,
17
21
  'moc' : IDL.Text,
18
22
  'scripts' : IDL.Vec(Script),
@@ -24,9 +28,9 @@ export const idlFactory = ({ IDL }) => {
24
28
  'version' : IDL.Text,
25
29
  'keywords' : IDL.Vec(IDL.Text),
26
30
  'donation' : IDL.Text,
27
- 'devDependencies' : IDL.Vec(Dependency),
31
+ 'devDependencies' : IDL.Vec(DependencyV2),
28
32
  'repository' : IDL.Text,
29
- 'dependencies' : IDL.Vec(Dependency),
33
+ 'dependencies' : IDL.Vec(DependencyV2),
30
34
  'license' : IDL.Text,
31
35
  'readme' : IDL.Text,
32
36
  });
@@ -40,7 +44,7 @@ export const idlFactory = ({ IDL }) => {
40
44
  'owner' : IDL.Principal,
41
45
  'downloadsTotal' : IDL.Nat,
42
46
  'downloadsInLast30Days' : IDL.Nat,
43
- 'config' : PackageConfig__1,
47
+ 'config' : PackageConfigV2__1,
44
48
  'publication' : PackagePublication,
45
49
  });
46
50
  const Result_3 = IDL.Variant({ 'ok' : PackageDetails, 'err' : Err });
@@ -51,7 +55,7 @@ export const idlFactory = ({ IDL }) => {
51
55
  'memorySize' : IDL.Nat,
52
56
  });
53
57
  const Result_2 = IDL.Variant({ 'ok' : FileId, 'err' : Err });
54
- const PackageConfig = IDL.Record({
58
+ const PackageConfigV2 = IDL.Record({
55
59
  'dfx' : IDL.Text,
56
60
  'moc' : IDL.Text,
57
61
  'scripts' : IDL.Vec(Script),
@@ -63,9 +67,9 @@ export const idlFactory = ({ IDL }) => {
63
67
  'version' : IDL.Text,
64
68
  'keywords' : IDL.Vec(IDL.Text),
65
69
  'donation' : IDL.Text,
66
- 'devDependencies' : IDL.Vec(Dependency),
70
+ 'devDependencies' : IDL.Vec(DependencyV2),
67
71
  'repository' : IDL.Text,
68
- 'dependencies' : IDL.Vec(Dependency),
72
+ 'dependencies' : IDL.Vec(DependencyV2),
69
73
  'license' : IDL.Text,
70
74
  'readme' : IDL.Text,
71
75
  });
@@ -110,7 +114,7 @@ export const idlFactory = ({ IDL }) => {
110
114
  [Result_2],
111
115
  [],
112
116
  ),
113
- 'startPublish' : IDL.Func([PackageConfig], [Result_1], []),
117
+ 'startPublish' : IDL.Func([PackageConfigV2], [Result_1], []),
114
118
  'uploadFileChunk' : IDL.Func(
115
119
  [PublishingId, FileId, IDL.Nat, IDL.Vec(IDL.Nat8)],
116
120
  [Result],
package/mops.js CHANGED
@@ -13,7 +13,7 @@ import {decodeFile} from './pem.js';
13
13
  global.fetch = fetch;
14
14
 
15
15
  // (!) make changes in pair with backend
16
- let apiVersion = '0.2';
16
+ let apiVersion = '1.2';
17
17
 
18
18
  let networkFile = new URL('./network.txt', import.meta.url);
19
19
 
@@ -107,9 +107,57 @@ export async function getHighestVersion(pkgName) {
107
107
  return actor.getHighestVersion(pkgName);
108
108
  }
109
109
 
110
+ export function parseGithubURL(href){
111
+ const url = new URL(href);
112
+ const branch = url.hash?.substring(1) || 'master';
113
+
114
+ let [org, gitName] = url.pathname.split('/').filter(path => !!path);
115
+
116
+ if (gitName.endsWith('.git')){
117
+ gitName = gitName.substring(0, gitName.length - 4);
118
+ }
119
+
120
+ return { org, gitName, branch };
121
+ }
122
+
110
123
  export function readConfig(configFile = path.join(process.cwd(), 'mops.toml')) {
111
124
  let text = fs.readFileSync(configFile).toString();
112
- return TOML.parse(text);
125
+ let toml = TOML.parse(text);
126
+
127
+ const deps = toml.dependencies || {};
128
+
129
+ Object.entries(deps).forEach(([name, data])=>{
130
+ if (data.startsWith('https://github.com/')){
131
+ deps[name] = {name, repo: data, version: ''};
132
+ }else{
133
+ deps[name] = {name, repo: '', version: data};
134
+ }
135
+ });
136
+
137
+ return toml;
138
+ }
139
+
140
+ export function writeConfig(config, configFile = path.join(process.cwd(), 'mops.toml')) {
141
+ const deps = config.dependencies || {};
142
+
143
+ Object.entries(deps).forEach(([name, {repo, version}])=>{
144
+ if (repo){
145
+ deps[name] = repo;
146
+ }else{
147
+ deps[name] = version;
148
+ }
149
+ });
150
+
151
+ fs.writeFileSync(configFile, TOML.stringify(config).trim());
152
+ }
153
+
154
+ export function formatDir(name, version){
155
+ return path.join(process.cwd(), '.mops', `${name}@${version}`);
156
+ }
157
+
158
+ export function formatGithubDir(name, repo){
159
+ const { branch } = parseGithubURL(repo);
160
+ return path.join(process.cwd(), '.mops/_github', `${name}@${branch}`);
113
161
  }
114
162
 
115
163
  export function readDfxJson() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "cli.js"
@@ -13,9 +13,13 @@
13
13
  "@iarna/toml": "^2.2.5",
14
14
  "chalk": "^4.1.2",
15
15
  "commander": "^9.2.0",
16
+ "decompress": "^4.2.1",
16
17
  "del": "^6.0.0",
18
+ "dhall-to-json-cli": "^1.7.6",
17
19
  "eslint": "^8.15.0",
20
+ "execa": "^6.1.0",
18
21
  "globby": "^13.1.1",
22
+ "got": "^12.5.3",
19
23
  "log-update": "^5.0.1",
20
24
  "minimatch": "^5.0.1",
21
25
  "node-fetch": "^2.6.7",
package/vessel.js ADDED
@@ -0,0 +1,162 @@
1
+ import {
2
+ existsSync, mkdirSync, createWriteStream, readFileSync, writeFileSync
3
+ } from 'fs';
4
+ import del from 'del';
5
+ import { execaCommand} from 'execa';
6
+ import chalk from 'chalk';
7
+ import logUpdate from 'log-update';
8
+ import { formatGithubDir, parseGithubURL, progressBar } from './mops.js';
9
+ import path from 'path';
10
+ import got from 'got';
11
+ import decompress from 'decompress';
12
+ import {pipeline} from 'stream/promises';
13
+
14
+ const dhallFileToJson = async (filePath) => {
15
+ if (existsSync(filePath)) {
16
+ let cwd = new URL(path.dirname(import.meta.url)).pathname;
17
+ const res = await execaCommand(`dhall-to-json --file ${filePath}`, {preferLocal:true, cwd});
18
+
19
+ if (res.exitCode === 0){
20
+ return JSON.parse(res.stdout);
21
+ }
22
+ else {
23
+ return res;
24
+ }
25
+ }
26
+
27
+ return null;
28
+ };
29
+
30
+ export const readVesselConfig = async (
31
+ configFile,
32
+ { cache = true } = { cache: true }
33
+ ) => {
34
+ const cachedFile = (configFile || process.cwd()) + '/vessel.json';
35
+
36
+ if (existsSync(cachedFile)) {
37
+ let cachedConfig = readFileSync(cachedFile);
38
+ return JSON.parse(cachedConfig);
39
+ }
40
+
41
+ const [vessel, packageSetArray] = await Promise.all([
42
+ dhallFileToJson((configFile || process.cwd()) + '/vessel.dhall'),
43
+ dhallFileToJson((configFile || process.cwd()) + '/package-set.dhall')
44
+ ]);
45
+
46
+ if (!vessel || !packageSetArray) return null;
47
+
48
+ let repos = {};
49
+ for (const { name, repo, version } of packageSetArray) {
50
+ const { org, gitName } = parseGithubURL(repo);
51
+ repos[name] = `https://github.com/${org}/${gitName}#${version}`;
52
+ }
53
+
54
+ let config = {
55
+ compiler: vessel.compiler,
56
+ dependencies: vessel.dependencies.map((name) => {
57
+ return { name, repo: repos[name], version: '' };
58
+ }),
59
+ };
60
+
61
+ if (cache === true) {
62
+ writeFileSync(cachedFile, JSON.stringify(config), 'utf-8');
63
+ }
64
+
65
+ return config;
66
+ };
67
+
68
+ export const downloadFromGithub = async (repo, dest, onProgress = null) => {
69
+ const {branch, org, gitName} = parseGithubURL(repo);
70
+
71
+ const zipFile = `https://github.com/${org}/${gitName}/archive/${branch}.zip`;
72
+ const readStream = got.stream(zipFile);
73
+
74
+ const promise = new Promise((resolve, reject) => {
75
+
76
+ readStream.on('downloadProgress', ({ transferred, total}) => {
77
+ onProgress?.(transferred, total || 2 * (1024 ** 2) );
78
+ });
79
+
80
+ readStream.on('response', (response) => {
81
+ if (response.headers.age > 3600) {
82
+ console.log(chalk.red('Error: ') + 'Failure - response too old');
83
+ readStream.destroy(); // Destroy the stream to prevent hanging resources.
84
+ reject();
85
+ return;
86
+ }
87
+
88
+ // Prevent `onError` being called twice.
89
+ readStream.off('error', reject);
90
+ const tmpDir = process.cwd() + '/.mops/_tmp/';
91
+ const tmpFile = tmpDir + `/${gitName}@${branch}.zip`;
92
+
93
+ try {
94
+ mkdirSync(tmpDir, {recursive: true});
95
+
96
+ pipeline(readStream, createWriteStream(tmpFile))
97
+ .then(() => {
98
+ let options = {
99
+ extract: true,
100
+ strip: 1,
101
+ headers: {
102
+ accept: 'application/zip'
103
+ }
104
+ };
105
+
106
+ return decompress(tmpFile, dest, options);
107
+
108
+ }).then((unzippedFiles) => {
109
+ del.sync([tmpDir]);
110
+ resolve(unzippedFiles);
111
+
112
+ }).catch(err => {
113
+ del.sync([tmpDir]);
114
+ reject(err);
115
+ });
116
+
117
+ } catch (err) {
118
+ del.sync([tmpDir]);
119
+ reject(err);
120
+ }
121
+ });
122
+ });
123
+
124
+ return promise;
125
+ };
126
+
127
+ export const installFromGithub = async (name, repo, options = {})=>{
128
+
129
+ const {verbose, dep, silent} = options;
130
+
131
+ const {branch} = parseGithubURL(repo);
132
+ const dir = formatGithubDir(name, repo);
133
+
134
+ if (existsSync(dir)){
135
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} (cache) from Github`);
136
+ }
137
+ else {
138
+ mkdirSync(dir, {recursive: true});
139
+
140
+ let progress = (step, total) => {
141
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} ${progressBar(step, total)}`);
142
+ };
143
+
144
+ progress(0, 2 * (1024 ** 2));
145
+ await downloadFromGithub(repo, dir, progress).catch((err)=> {
146
+ del.sync([dir]);
147
+ console.log(chalk.red('Error: ') + err);
148
+ });
149
+ }
150
+
151
+ if (verbose) {
152
+ silent || logUpdate.done();
153
+ }
154
+
155
+ const config = await readVesselConfig(dir);
156
+
157
+ if (config){
158
+ for (const {name, repo} of config.dependencies){
159
+ await installFromGithub(name, repo, {verbose, silent, dep: true });
160
+ }
161
+ }
162
+ };