ic-mops 0.27.3 → 0.28.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/commands/add.ts CHANGED
@@ -4,6 +4,7 @@ import logUpdate from 'log-update';
4
4
  import {checkConfigFile, getHighestVersion, parseGithubURL, readConfig, writeConfig} from '../mops.js';
5
5
  import {installFromGithub} from '../vessel.js';
6
6
  import {install} from './install.js';
7
+ import {notifyInstalls} from '../notify-installs.js';
7
8
 
8
9
  export async function add(name: string, {verbose = false, dev = false} = {}) {
9
10
  if (!checkConfigFile()) {
@@ -66,14 +67,17 @@ export async function add(name: string, {verbose = false, dev = false} = {}) {
66
67
  };
67
68
  }
68
69
 
70
+ let installedPackages = {};
71
+
69
72
  if (pkgDetails.repo) {
70
73
  await installFromGithub(pkgDetails.name, pkgDetails.repo, {verbose: verbose});
71
74
  }
72
75
  else if (!pkgDetails.path) {
73
- let ok = await install(pkgDetails.name, pkgDetails.version, {verbose: verbose});
74
- if (!ok) {
76
+ let res = await install(pkgDetails.name, pkgDetails.version, {verbose: verbose});
77
+ if (res === false) {
75
78
  return;
76
79
  }
80
+ installedPackages = {...installedPackages, ...res};
77
81
  }
78
82
 
79
83
  const depsProp = dev ? 'dev-dependencies' : 'dependencies';
@@ -84,7 +88,9 @@ export async function add(name: string, {verbose = false, dev = false} = {}) {
84
88
  else {
85
89
  throw Error(`Invalid config file: [${depsProp}] not found`);
86
90
  }
91
+
87
92
  writeConfig(config);
93
+ await notifyInstalls(Object.keys(installedPackages));
88
94
 
89
95
  logUpdate.clear();
90
96
  console.log(chalk.green('Package installed ') + `${pkgDetails.name} = "${pkgDetails.repo || pkgDetails.path || pkgDetails.version}"`);
@@ -3,6 +3,7 @@ import logUpdate from 'log-update';
3
3
  import {checkConfigFile, readConfig} from '../mops.js';
4
4
  import {install} from './install.js';
5
5
  import {installFromGithub} from '../vessel.js';
6
+ import {notifyInstalls} from '../notify-installs.js';
6
7
 
7
8
  export async function installAll({verbose = false, silent = false} = {}) {
8
9
  if (!checkConfigFile()) {
@@ -13,19 +14,23 @@ export async function installAll({verbose = false, silent = false} = {}) {
13
14
  let deps = Object.values(config.dependencies || {});
14
15
  let devDeps = Object.values(config['dev-dependencies'] || {});
15
16
  let allDeps = [...deps, ...devDeps];
17
+ let installedPackages = {};
16
18
 
17
19
  for (let {name, repo, path, version} of allDeps) {
18
20
  if (repo) {
19
21
  await installFromGithub(name, repo, {verbose, silent});
20
22
  }
21
23
  else if (!path) {
22
- let ok = await install(name, version, {verbose, silent});
23
- if (!ok) {
24
+ let res = await install(name, version, {verbose, silent});
25
+ if (res === false) {
24
26
  return;
25
27
  }
28
+ installedPackages = {...installedPackages, ...res};
26
29
  }
27
30
  }
28
31
 
32
+ await notifyInstalls(Object.keys(installedPackages));
33
+
29
34
  if (!silent) {
30
35
  logUpdate.clear();
31
36
  console.log(chalk.green('All packages installed'));
@@ -7,7 +7,7 @@ import {parallel} from '../parallel.js';
7
7
  import {installFromGithub} from '../vessel.js';
8
8
  import {addCache, copyCache, isCached} from '../cache.js';
9
9
 
10
- export async function install(pkg: string, version = '', {verbose = false, silent = false, dep = false} = {}) {
10
+ export async function install(pkg: string, version = '', {verbose = false, silent = false, dep = false} = {}): Promise<Record<string, string> | false> {
11
11
  if (!checkConfigFile()) {
12
12
  return false;
13
13
  }
@@ -32,14 +32,15 @@ export async function install(pkg: string, version = '', {verbose = false, silen
32
32
 
33
33
  let dir = formatDir(pkg, version);
34
34
  let actor = await mainActor();
35
+ let alreadyInstalled = false;
35
36
 
36
37
  // already installed
37
38
  if (fs.existsSync(dir)) {
38
39
  silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (already installed)`);
40
+ alreadyInstalled = true;
39
41
  }
40
42
  // copy from cache
41
43
  else if (isCached(`${pkg}@${version}`)) {
42
- actor.notifyInstall(pkg, version);
43
44
  await copyCache(`${pkg}@${version}`, dir);
44
45
  silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (cache)`);
45
46
  }
@@ -65,8 +66,6 @@ export async function install(pkg: string, version = '', {verbose = false, silen
65
66
 
66
67
  let storage = await storageActor(packageDetails.publication.storage);
67
68
 
68
- actor.notifyInstall(pkg, version);
69
-
70
69
  // download files
71
70
  let filesData = new Map;
72
71
  let threads = 16;
@@ -118,17 +117,28 @@ export async function install(pkg: string, version = '', {verbose = false, silen
118
117
  let ok = true;
119
118
  let config = readConfig(path.join(dir, 'mops.toml'));
120
119
  let deps = Object.values(config.dependencies || {});
120
+ let installedDeps = {};
121
121
  for (const {name, repo, version} of deps) {
122
122
  if (repo) {
123
123
  await installFromGithub(name, repo, {silent, verbose});
124
124
  }
125
125
  else {
126
126
  let res = await install(name, version, {silent, verbose});
127
- if (!res) {
127
+ if (res) {
128
+ installedDeps = {...installedDeps, ...res};
129
+ }
130
+ else {
128
131
  ok = false;
129
132
  }
130
133
  }
131
134
  }
132
135
 
133
- return ok;
136
+ if (!alreadyInstalled) {
137
+ installedDeps = {...installedDeps, [pkg]: version};
138
+ }
139
+
140
+ if (ok) {
141
+ return installedDeps;
142
+ }
143
+ return false;
134
144
  }
@@ -233,6 +233,14 @@ export async function publish(options: {docs?: boolean, test?: boolean} = {}) {
233
233
  // parse changelog
234
234
  console.log('Parsing CHANGELOG.md...');
235
235
  let changelog = parseChangelog(config.package.version);
236
+ if (!changelog && config.package.repository) {
237
+ console.log('Fetching release notes from GitHub...');
238
+ changelog = await fetchGitHubReleaseNotes(config.package.repository, config.package.version);
239
+ }
240
+ if (changelog) {
241
+ console.log('Changelog:');
242
+ console.log(chalk.gray(changelog));
243
+ }
236
244
 
237
245
  // test
238
246
  let reporter = new SilentReporter;
@@ -342,17 +350,31 @@ function parseChangelog(version: string): string {
342
350
  let str = fs.readFileSync(changelogFile, 'utf-8');
343
351
  let changelog = findChangelogEntry(str, version);
344
352
 
345
- if (changelog) {
346
- console.log('Changelog:');
347
- console.log(chalk.gray(changelog));
348
- }
349
- else {
353
+ if (!changelog) {
350
354
  console.log(chalk.yellow('No changelog entry found'));
351
355
  }
352
356
 
353
357
  return changelog || '';
354
358
  }
355
359
 
360
+ async function fetchGitHubReleaseNotes(repo: string, version: string): Promise<string> {
361
+ let repoPath = new URL(repo).pathname;
362
+ let res = await fetch(`https://api.github.com/repos${repoPath}/releases/tags/${version}`);
363
+ let release = await res.json();
364
+
365
+ if (release.message === 'Not Found') {
366
+ res = await fetch(`https://api.github.com/repos${repoPath}/releases/tags/v${version}`);
367
+ release = await res.json();
368
+
369
+ if (release.message === 'Not Found') {
370
+ console.log(chalk.yellow(`No GitHub release found with name ${version} or v${version}`));
371
+ return '';
372
+ }
373
+ }
374
+
375
+ return release.body;
376
+ }
377
+
356
378
  function findChangelogEntry(changelog: string, version: string): string {
357
379
  let tree = fromMarkdown(changelog);
358
380
  let found = false;
@@ -1,9 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs';
3
- import chalk from 'chalk';
4
- import {checkConfigFile, formatDir, formatGithubDir, getRootDir, parseGithubURL, readConfig} from '../mops.js';
5
- import {VesselConfig, readVesselConfig} from '../vessel.js';
6
- import {Config, Dependency} from '../types.js';
3
+ import {checkConfigFile, formatDir, formatGithubDir, getDependencyType, readConfig} from '../mops.js';
4
+ import {resolvePackages} from '../resolve-packages.js';
7
5
 
8
6
  // TODO: resolve conflicts
9
7
  export async function sources({verbose = false} = {}) {
@@ -11,118 +9,21 @@ export async function sources({verbose = false} = {}) {
11
9
  return [];
12
10
  }
13
11
 
14
- let packages: Record<string, Dependency & {isRoot: boolean}> = {};
15
- let versions: Record<string, string[]> = {};
16
-
17
- let compareVersions = (a: string = '0.0.0', b: string = '0.0.0') => {
18
- let ap = a.split('.').map((x: string) => parseInt(x)) as [number, number, number];
19
- let bp = b.split('.').map((x: string) => parseInt(x)) as [number, number, number];
20
- if (ap[0] - bp[0]) {
21
- return Math.sign(ap[0] - bp[0]);
22
- }
23
- if (ap[0] === bp[0] && ap[1] - bp[1]) {
24
- return Math.sign(ap[1] - bp[1]);
25
- }
26
- if (ap[0] === bp[0] && ap[1] === bp[1] && ap[2] - bp[2]) {
27
- return Math.sign(ap[2] - bp[2]);
28
- }
29
- return 0;
30
- };
31
-
32
- const gitVerRegex = new RegExp(/v(\d{1,2}\.\d{1,2}\.\d{1,2})(-.*)?$/);
33
-
34
- const compareGitVersions = (repoA: string, repoB: string) => {
35
- const {branch: a} = parseGithubURL(repoA);
36
- const {branch: b} = parseGithubURL(repoB);
37
-
38
- if (gitVerRegex.test(a) && gitVerRegex.test(b)) {
39
- return compareVersions(a.substring(1) , b.substring(1));
40
- }
41
- else if (!gitVerRegex.test(a)) {
42
- return -1;
43
- }
44
- else {
45
- return 1;
46
- }
47
- };
48
-
49
- let collectDeps = async (config: Config | VesselConfig, isRoot = false) => {
50
- let allDeps = [...Object.values(config.dependencies || {})];
51
- if (isRoot) {
52
- allDeps = [...allDeps, ...Object.values(config['dev-dependencies'] || {})];
53
- }
54
- for (const pkgDetails of allDeps) {
55
- const {name, repo, version} = pkgDetails;
56
-
57
- // take root dep version or bigger one
58
- if (
59
- isRoot
60
- || !packages[name]
61
- || !packages[name]?.isRoot
62
- && (
63
- repo && packages[name]?.repo && compareGitVersions(packages[name]?.repo || '', repo) === -1
64
- || compareVersions(packages[name]?.version, version) === -1)
65
- ) {
66
- packages[name] = {
67
- ...pkgDetails,
68
- isRoot,
69
- };
70
- }
71
-
72
- let nestedConfig;
73
-
74
- if (repo) {
75
- const dir = formatGithubDir(name, repo);
76
- nestedConfig = await readVesselConfig(dir) || {};
77
- }
78
- else if (!pkgDetails.path && version) {
79
- const file = formatDir(name, version) + '/mops.toml';
80
- nestedConfig = readConfig(file);
81
- }
82
-
83
- if (nestedConfig && !pkgDetails.path) {
84
- await collectDeps(nestedConfig);
85
- }
86
-
87
- if (!versions[name]) {
88
- versions[name] = [];
89
- }
90
-
91
- if (repo) {
92
- const {branch} = parseGithubURL(repo);
93
- versions[name]?.push(branch);
94
- }
95
- else if (version) {
96
- versions[name]?.push(version);
97
- }
98
- }
99
- };
100
-
101
- let config = readConfig();
102
- await collectDeps(config, true);
103
-
104
- // show conflicts
105
- if (verbose) {
106
- for (let [dep, vers] of Object.entries(versions)) {
107
- if (vers.length > 1) {
108
- console.log(chalk.yellow('WARN:'), `Conflicting package versions "${dep}" - ${vers.join(', ')}`);
109
- }
110
- }
111
- }
12
+ let resolvedPackages = await resolvePackages({verbose});
112
13
 
113
14
  // sources
114
- let rootDir = getRootDir();
115
- return Object.entries(packages).map(([name, pkg]) => {
15
+ return Object.entries(resolvedPackages).map(([name, version]) => {
16
+ let depType = getDependencyType(version);
17
+
116
18
  let pkgDir;
117
- if (pkg.path) {
118
- pkgDir = path.relative(process.cwd(), path.resolve(rootDir, pkg.path));
119
- pkgDir = pkgDir.replaceAll('{MOPS_ENV}', process.env.MOPS_ENV || 'local');
19
+ if (depType === 'local') {
20
+ pkgDir = path.relative(process.cwd(), version);
120
21
  }
121
- else if (pkg.repo) {
122
- pkgDir = path.relative(process.cwd(), formatGithubDir(name, pkg.repo));
22
+ else if (depType === 'github') {
23
+ pkgDir = path.relative(process.cwd(), formatGithubDir(name, version));
123
24
  }
124
- else if (pkg.version) {
125
- pkgDir = path.relative(process.cwd(), formatDir(name, pkg.version));
25
+ else if (depType === 'mops') {
26
+ pkgDir = path.relative(process.cwd(), formatDir(name, version));
126
27
  }
127
28
  else {
128
29
  return;
@@ -139,7 +40,7 @@ export async function sources({verbose = false} = {}) {
139
40
  }
140
41
 
141
42
  // use pkgDir if baseDir doesn't exist for local packages
142
- if (pkg.path && !fs.existsSync(pkgBaseDir)) {
43
+ if (depType === 'local' && !fs.existsSync(pkgBaseDir)) {
143
44
  pkgBaseDir = pkgDir;
144
45
  }
145
46
 
@@ -301,6 +301,10 @@ service : {
301
301
  getTotalPackages: () -> (nat) query;
302
302
  getUser: (principal) -> (opt User__1) query;
303
303
  notifyInstall: (PackageName__1, PackageVersion) -> () oneway;
304
+ notifyInstalls: (vec record {
305
+ PackageName__1;
306
+ PackageVersion;
307
+ }) -> () oneway;
304
308
  restore: (nat, nat) -> ();
305
309
  search: (Text, opt nat, opt nat) -> (vec PackageSummary, PageCount) query;
306
310
  setUserProp: (text, text) -> (Result_3);
@@ -247,6 +247,10 @@ export interface _SERVICE {
247
247
  'getTotalPackages' : ActorMethod<[], bigint>,
248
248
  'getUser' : ActorMethod<[Principal], [] | [User__1]>,
249
249
  'notifyInstall' : ActorMethod<[PackageName__1, PackageVersion], undefined>,
250
+ 'notifyInstalls' : ActorMethod<
251
+ [Array<[PackageName__1, PackageVersion]>],
252
+ undefined
253
+ >,
250
254
  'restore' : ActorMethod<[bigint, bigint], undefined>,
251
255
  'search' : ActorMethod<
252
256
  [Text, [] | [bigint], [] | [bigint]],
@@ -275,6 +275,11 @@ export const idlFactory = ({ IDL }) => {
275
275
  [],
276
276
  ['oneway'],
277
277
  ),
278
+ 'notifyInstalls' : IDL.Func(
279
+ [IDL.Vec(IDL.Tuple(PackageName__1, PackageVersion))],
280
+ [],
281
+ ['oneway'],
282
+ ),
278
283
  'restore' : IDL.Func([IDL.Nat, IDL.Nat], [], []),
279
284
  'search' : IDL.Func(
280
285
  [Text, IDL.Opt(IDL.Nat), IDL.Opt(IDL.Nat)],
@@ -4,6 +4,7 @@ import logUpdate from 'log-update';
4
4
  import { checkConfigFile, getHighestVersion, parseGithubURL, readConfig, writeConfig } from '../mops.js';
5
5
  import { installFromGithub } from '../vessel.js';
6
6
  import { install } from './install.js';
7
+ import { notifyInstalls } from '../notify-installs.js';
7
8
  export async function add(name, { verbose = false, dev = false } = {}) {
8
9
  if (!checkConfigFile()) {
9
10
  return;
@@ -59,14 +60,16 @@ export async function add(name, { verbose = false, dev = false } = {}) {
59
60
  version: ver,
60
61
  };
61
62
  }
63
+ let installedPackages = {};
62
64
  if (pkgDetails.repo) {
63
65
  await installFromGithub(pkgDetails.name, pkgDetails.repo, { verbose: verbose });
64
66
  }
65
67
  else if (!pkgDetails.path) {
66
- let ok = await install(pkgDetails.name, pkgDetails.version, { verbose: verbose });
67
- if (!ok) {
68
+ let res = await install(pkgDetails.name, pkgDetails.version, { verbose: verbose });
69
+ if (res === false) {
68
70
  return;
69
71
  }
72
+ installedPackages = { ...installedPackages, ...res };
70
73
  }
71
74
  const depsProp = dev ? 'dev-dependencies' : 'dependencies';
72
75
  let deps = config[depsProp];
@@ -77,6 +80,7 @@ export async function add(name, { verbose = false, dev = false } = {}) {
77
80
  throw Error(`Invalid config file: [${depsProp}] not found`);
78
81
  }
79
82
  writeConfig(config);
83
+ await notifyInstalls(Object.keys(installedPackages));
80
84
  logUpdate.clear();
81
85
  console.log(chalk.green('Package installed ') + `${pkgDetails.name} = "${pkgDetails.repo || pkgDetails.path || pkgDetails.version}"`);
82
86
  }
@@ -3,6 +3,7 @@ import logUpdate from 'log-update';
3
3
  import { checkConfigFile, readConfig } from '../mops.js';
4
4
  import { install } from './install.js';
5
5
  import { installFromGithub } from '../vessel.js';
6
+ import { notifyInstalls } from '../notify-installs.js';
6
7
  export async function installAll({ verbose = false, silent = false } = {}) {
7
8
  if (!checkConfigFile()) {
8
9
  return;
@@ -11,17 +12,20 @@ export async function installAll({ verbose = false, silent = false } = {}) {
11
12
  let deps = Object.values(config.dependencies || {});
12
13
  let devDeps = Object.values(config['dev-dependencies'] || {});
13
14
  let allDeps = [...deps, ...devDeps];
15
+ let installedPackages = {};
14
16
  for (let { name, repo, path, version } of allDeps) {
15
17
  if (repo) {
16
18
  await installFromGithub(name, repo, { verbose, silent });
17
19
  }
18
20
  else if (!path) {
19
- let ok = await install(name, version, { verbose, silent });
20
- if (!ok) {
21
+ let res = await install(name, version, { verbose, silent });
22
+ if (res === false) {
21
23
  return;
22
24
  }
25
+ installedPackages = { ...installedPackages, ...res };
23
26
  }
24
27
  }
28
+ await notifyInstalls(Object.keys(installedPackages));
25
29
  if (!silent) {
26
30
  logUpdate.clear();
27
31
  console.log(chalk.green('All packages installed'));
@@ -2,4 +2,4 @@ export declare function install(pkg: string, version?: string, { verbose, silent
2
2
  verbose?: boolean | undefined;
3
3
  silent?: boolean | undefined;
4
4
  dep?: boolean | undefined;
5
- }): Promise<boolean>;
5
+ }): Promise<Record<string, string> | false>;
@@ -28,13 +28,14 @@ export async function install(pkg, version = '', { verbose = false, silent = fal
28
28
  }
29
29
  let dir = formatDir(pkg, version);
30
30
  let actor = await mainActor();
31
+ let alreadyInstalled = false;
31
32
  // already installed
32
33
  if (fs.existsSync(dir)) {
33
34
  silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (already installed)`);
35
+ alreadyInstalled = true;
34
36
  }
35
37
  // copy from cache
36
38
  else if (isCached(`${pkg}@${version}`)) {
37
- actor.notifyInstall(pkg, version);
38
39
  await copyCache(`${pkg}@${version}`, dir);
39
40
  silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (cache)`);
40
41
  }
@@ -56,7 +57,6 @@ export async function install(pkg, version = '', { verbose = false, silent = fal
56
57
  let filesIds = filesIdsRes.ok;
57
58
  total = filesIds.length + 2;
58
59
  let storage = await storageActor(packageDetails.publication.storage);
59
- actor.notifyInstall(pkg, version);
60
60
  // download files
61
61
  let filesData = new Map;
62
62
  let threads = 16;
@@ -100,16 +100,26 @@ export async function install(pkg, version = '', { verbose = false, silent = fal
100
100
  let ok = true;
101
101
  let config = readConfig(path.join(dir, 'mops.toml'));
102
102
  let deps = Object.values(config.dependencies || {});
103
+ let installedDeps = {};
103
104
  for (const { name, repo, version } of deps) {
104
105
  if (repo) {
105
106
  await installFromGithub(name, repo, { silent, verbose });
106
107
  }
107
108
  else {
108
109
  let res = await install(name, version, { silent, verbose });
109
- if (!res) {
110
+ if (res) {
111
+ installedDeps = { ...installedDeps, ...res };
112
+ }
113
+ else {
110
114
  ok = false;
111
115
  }
112
116
  }
113
117
  }
114
- return ok;
118
+ if (!alreadyInstalled) {
119
+ installedDeps = { ...installedDeps, [pkg]: version };
120
+ }
121
+ if (ok) {
122
+ return installedDeps;
123
+ }
124
+ return false;
115
125
  }
@@ -208,6 +208,14 @@ export async function publish(options = {}) {
208
208
  // parse changelog
209
209
  console.log('Parsing CHANGELOG.md...');
210
210
  let changelog = parseChangelog(config.package.version);
211
+ if (!changelog && config.package.repository) {
212
+ console.log('Fetching release notes from GitHub...');
213
+ changelog = await fetchGitHubReleaseNotes(config.package.repository, config.package.version);
214
+ }
215
+ if (changelog) {
216
+ console.log('Changelog:');
217
+ console.log(chalk.gray(changelog));
218
+ }
211
219
  // test
212
220
  let reporter = new SilentReporter;
213
221
  if (options.test) {
@@ -298,15 +306,25 @@ function parseChangelog(version) {
298
306
  }
299
307
  let str = fs.readFileSync(changelogFile, 'utf-8');
300
308
  let changelog = findChangelogEntry(str, version);
301
- if (changelog) {
302
- console.log('Changelog:');
303
- console.log(chalk.gray(changelog));
304
- }
305
- else {
309
+ if (!changelog) {
306
310
  console.log(chalk.yellow('No changelog entry found'));
307
311
  }
308
312
  return changelog || '';
309
313
  }
314
+ async function fetchGitHubReleaseNotes(repo, version) {
315
+ let repoPath = new URL(repo).pathname;
316
+ let res = await fetch(`https://api.github.com/repos${repoPath}/releases/tags/${version}`);
317
+ let release = await res.json();
318
+ if (release.message === 'Not Found') {
319
+ res = await fetch(`https://api.github.com/repos${repoPath}/releases/tags/v${version}`);
320
+ release = await res.json();
321
+ if (release.message === 'Not Found') {
322
+ console.log(chalk.yellow(`No GitHub release found with name ${version} or v${version}`));
323
+ return '';
324
+ }
325
+ }
326
+ return release.body;
327
+ }
310
328
  function findChangelogEntry(changelog, version) {
311
329
  let tree = fromMarkdown(changelog);
312
330
  let found = false;
@@ -1,108 +1,25 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs';
3
- import chalk from 'chalk';
4
- import { checkConfigFile, formatDir, formatGithubDir, getRootDir, parseGithubURL, readConfig } from '../mops.js';
5
- import { readVesselConfig } from '../vessel.js';
3
+ import { checkConfigFile, formatDir, formatGithubDir, getDependencyType, readConfig } from '../mops.js';
4
+ import { resolvePackages } from '../resolve-packages.js';
6
5
  // TODO: resolve conflicts
7
6
  export async function sources({ verbose = false } = {}) {
8
7
  if (!checkConfigFile()) {
9
8
  return [];
10
9
  }
11
- let packages = {};
12
- let versions = {};
13
- let compareVersions = (a = '0.0.0', b = '0.0.0') => {
14
- let ap = a.split('.').map((x) => parseInt(x));
15
- let bp = b.split('.').map((x) => parseInt(x));
16
- if (ap[0] - bp[0]) {
17
- return Math.sign(ap[0] - bp[0]);
18
- }
19
- if (ap[0] === bp[0] && ap[1] - bp[1]) {
20
- return Math.sign(ap[1] - bp[1]);
21
- }
22
- if (ap[0] === bp[0] && ap[1] === bp[1] && ap[2] - bp[2]) {
23
- return Math.sign(ap[2] - bp[2]);
24
- }
25
- return 0;
26
- };
27
- const gitVerRegex = new RegExp(/v(\d{1,2}\.\d{1,2}\.\d{1,2})(-.*)?$/);
28
- const compareGitVersions = (repoA, repoB) => {
29
- const { branch: a } = parseGithubURL(repoA);
30
- const { branch: b } = parseGithubURL(repoB);
31
- if (gitVerRegex.test(a) && gitVerRegex.test(b)) {
32
- return compareVersions(a.substring(1), b.substring(1));
33
- }
34
- else if (!gitVerRegex.test(a)) {
35
- return -1;
36
- }
37
- else {
38
- return 1;
39
- }
40
- };
41
- let collectDeps = async (config, isRoot = false) => {
42
- let allDeps = [...Object.values(config.dependencies || {})];
43
- if (isRoot) {
44
- allDeps = [...allDeps, ...Object.values(config['dev-dependencies'] || {})];
45
- }
46
- for (const pkgDetails of allDeps) {
47
- const { name, repo, version } = pkgDetails;
48
- // take root dep version or bigger one
49
- if (isRoot
50
- || !packages[name]
51
- || !packages[name]?.isRoot
52
- && (repo && packages[name]?.repo && compareGitVersions(packages[name]?.repo || '', repo) === -1
53
- || compareVersions(packages[name]?.version, version) === -1)) {
54
- packages[name] = {
55
- ...pkgDetails,
56
- isRoot,
57
- };
58
- }
59
- let nestedConfig;
60
- if (repo) {
61
- const dir = formatGithubDir(name, repo);
62
- nestedConfig = await readVesselConfig(dir) || {};
63
- }
64
- else if (!pkgDetails.path && version) {
65
- const file = formatDir(name, version) + '/mops.toml';
66
- nestedConfig = readConfig(file);
67
- }
68
- if (nestedConfig && !pkgDetails.path) {
69
- await collectDeps(nestedConfig);
70
- }
71
- if (!versions[name]) {
72
- versions[name] = [];
73
- }
74
- if (repo) {
75
- const { branch } = parseGithubURL(repo);
76
- versions[name]?.push(branch);
77
- }
78
- else if (version) {
79
- versions[name]?.push(version);
80
- }
81
- }
82
- };
83
- let config = readConfig();
84
- await collectDeps(config, true);
85
- // show conflicts
86
- if (verbose) {
87
- for (let [dep, vers] of Object.entries(versions)) {
88
- if (vers.length > 1) {
89
- console.log(chalk.yellow('WARN:'), `Conflicting package versions "${dep}" - ${vers.join(', ')}`);
90
- }
91
- }
92
- }
10
+ let resolvedPackages = await resolvePackages({ verbose });
93
11
  // sources
94
- let rootDir = getRootDir();
95
- return Object.entries(packages).map(([name, pkg]) => {
12
+ return Object.entries(resolvedPackages).map(([name, version]) => {
13
+ let depType = getDependencyType(version);
96
14
  let pkgDir;
97
- if (pkg.path) {
98
- pkgDir = path.relative(process.cwd(), path.resolve(rootDir, pkg.path));
99
- pkgDir = pkgDir.replaceAll('{MOPS_ENV}', process.env.MOPS_ENV || 'local');
15
+ if (depType === 'local') {
16
+ pkgDir = path.relative(process.cwd(), version);
100
17
  }
101
- else if (pkg.repo) {
102
- pkgDir = path.relative(process.cwd(), formatGithubDir(name, pkg.repo));
18
+ else if (depType === 'github') {
19
+ pkgDir = path.relative(process.cwd(), formatGithubDir(name, version));
103
20
  }
104
- else if (pkg.version) {
105
- pkgDir = path.relative(process.cwd(), formatDir(name, pkg.version));
21
+ else if (depType === 'mops') {
22
+ pkgDir = path.relative(process.cwd(), formatDir(name, version));
106
23
  }
107
24
  else {
108
25
  return;
@@ -117,7 +34,7 @@ export async function sources({ verbose = false } = {}) {
117
34
  pkgBaseDir = path.join(pkgDir, 'src');
118
35
  }
119
36
  // use pkgDir if baseDir doesn't exist for local packages
120
- if (pkg.path && !fs.existsSync(pkgBaseDir)) {
37
+ if (depType === 'local' && !fs.existsSync(pkgBaseDir)) {
121
38
  pkgBaseDir = pkgDir;
122
39
  }
123
40
  return `--package ${name} ${pkgBaseDir}`;
@@ -301,6 +301,10 @@ service : {
301
301
  getTotalPackages: () -> (nat) query;
302
302
  getUser: (principal) -> (opt User__1) query;
303
303
  notifyInstall: (PackageName__1, PackageVersion) -> () oneway;
304
+ notifyInstalls: (vec record {
305
+ PackageName__1;
306
+ PackageVersion;
307
+ }) -> () oneway;
304
308
  restore: (nat, nat) -> ();
305
309
  search: (Text, opt nat, opt nat) -> (vec PackageSummary, PageCount) query;
306
310
  setUserProp: (text, text) -> (Result_3);
@@ -247,6 +247,10 @@ export interface _SERVICE {
247
247
  'getTotalPackages' : ActorMethod<[], bigint>,
248
248
  'getUser' : ActorMethod<[Principal], [] | [User__1]>,
249
249
  'notifyInstall' : ActorMethod<[PackageName__1, PackageVersion], undefined>,
250
+ 'notifyInstalls' : ActorMethod<
251
+ [Array<[PackageName__1, PackageVersion]>],
252
+ undefined
253
+ >,
250
254
  'restore' : ActorMethod<[bigint, bigint], undefined>,
251
255
  'search' : ActorMethod<
252
256
  [Text, [] | [bigint], [] | [bigint]],
@@ -275,6 +275,11 @@ export const idlFactory = ({ IDL }) => {
275
275
  [],
276
276
  ['oneway'],
277
277
  ),
278
+ 'notifyInstalls' : IDL.Func(
279
+ [IDL.Vec(IDL.Tuple(PackageName__1, PackageVersion))],
280
+ [],
281
+ ['oneway'],
282
+ ),
278
283
  'restore' : IDL.Func([IDL.Nat, IDL.Nat], [], []),
279
284
  'search' : IDL.Func(
280
285
  [Text, IDL.Opt(IDL.Nat), IDL.Opt(IDL.Nat)],
package/dist/mops.d.ts CHANGED
@@ -25,6 +25,7 @@ export declare function parseGithubURL(href: string): {
25
25
  gitName: string | undefined;
26
26
  branch: string;
27
27
  };
28
+ export declare function getDependencyType(version: string): "mops" | "github" | "local";
28
29
  export declare function readConfig(configFile?: string): Config;
29
30
  export declare function writeConfig(config: Config, configFile?: string): void;
30
31
  export declare function formatDir(name: string, version: string): string;
package/dist/mops.js CHANGED
@@ -186,6 +186,20 @@ export function parseGithubURL(href) {
186
186
  }
187
187
  return { org, gitName, branch };
188
188
  }
189
+ export function getDependencyType(version) {
190
+ if (!version || typeof version !== 'string') {
191
+ throw Error(`Invalid dependency value "${version}"`);
192
+ }
193
+ if (version.startsWith('https://github.com/')) {
194
+ return 'github';
195
+ }
196
+ else if (version.match(/^(\.?\.)?\//)) {
197
+ return 'local';
198
+ }
199
+ else {
200
+ return 'mops';
201
+ }
202
+ }
189
203
  export function readConfig(configFile = getClosestConfigFile()) {
190
204
  let text = fs.readFileSync(configFile).toString();
191
205
  let toml = TOML.parse(text);
@@ -194,10 +208,11 @@ export function readConfig(configFile = getClosestConfigFile()) {
194
208
  if (!data || typeof data !== 'string') {
195
209
  throw Error(`Invalid dependency value ${name} = "${data}"`);
196
210
  }
197
- if (data.startsWith('https://github.com/')) {
211
+ let depType = getDependencyType(data);
212
+ if (depType === 'github') {
198
213
  deps[name] = { name, repo: data, version: '' };
199
214
  }
200
- else if (data.match(/^(\.?\.)?\//)) {
215
+ else if (depType === 'local') {
201
216
  deps[name] = { name, repo: '', path: data, version: '' };
202
217
  }
203
218
  else {
@@ -0,0 +1 @@
1
+ export declare function notifyInstalls(names: string[]): Promise<void>;
@@ -0,0 +1,10 @@
1
+ import { mainActor } from './mops.js';
2
+ import { resolvePackages } from './resolve-packages.js';
3
+ export async function notifyInstalls(names) {
4
+ let resolvedPackages = await resolvePackages();
5
+ let packages = names.map(name => [name, resolvedPackages[name]]);
6
+ if (packages.length) {
7
+ let actor = await mainActor();
8
+ await actor.notifyInstalls(packages);
9
+ }
10
+ }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.27.3",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/cli.js"
@@ -0,0 +1,3 @@
1
+ export declare function resolvePackages({ verbose }?: {
2
+ verbose?: boolean | undefined;
3
+ }): Promise<Record<string, string>>;
@@ -0,0 +1,108 @@
1
+ import path from 'node:path';
2
+ import chalk from 'chalk';
3
+ import { checkConfigFile, formatDir, formatGithubDir, getRootDir, parseGithubURL, readConfig } from './mops.js';
4
+ import { readVesselConfig } from './vessel.js';
5
+ export async function resolvePackages({ verbose = false } = {}) {
6
+ if (!checkConfigFile()) {
7
+ return {};
8
+ }
9
+ let packages = {};
10
+ let versions = {};
11
+ let compareVersions = (a = '0.0.0', b = '0.0.0') => {
12
+ let ap = a.split('.').map((x) => parseInt(x));
13
+ let bp = b.split('.').map((x) => parseInt(x));
14
+ if (ap[0] - bp[0]) {
15
+ return Math.sign(ap[0] - bp[0]);
16
+ }
17
+ if (ap[0] === bp[0] && ap[1] - bp[1]) {
18
+ return Math.sign(ap[1] - bp[1]);
19
+ }
20
+ if (ap[0] === bp[0] && ap[1] === bp[1] && ap[2] - bp[2]) {
21
+ return Math.sign(ap[2] - bp[2]);
22
+ }
23
+ return 0;
24
+ };
25
+ const gitVerRegex = new RegExp(/v(\d{1,2}\.\d{1,2}\.\d{1,2})(-.*)?$/);
26
+ const compareGitVersions = (repoA, repoB) => {
27
+ const { branch: a } = parseGithubURL(repoA);
28
+ const { branch: b } = parseGithubURL(repoB);
29
+ if (gitVerRegex.test(a) && gitVerRegex.test(b)) {
30
+ return compareVersions(a.substring(1), b.substring(1));
31
+ }
32
+ else if (!gitVerRegex.test(a)) {
33
+ return -1;
34
+ }
35
+ else {
36
+ return 1;
37
+ }
38
+ };
39
+ let collectDeps = async (config, isRoot = false) => {
40
+ let allDeps = [...Object.values(config.dependencies || {})];
41
+ if (isRoot) {
42
+ allDeps = [...allDeps, ...Object.values(config['dev-dependencies'] || {})];
43
+ }
44
+ for (const pkgDetails of allDeps) {
45
+ const { name, repo, version } = pkgDetails;
46
+ // take root dep version or bigger one
47
+ if (isRoot
48
+ || !packages[name]
49
+ || !packages[name]?.isRoot
50
+ && (repo && packages[name]?.repo && compareGitVersions(packages[name]?.repo || '', repo) === -1
51
+ || compareVersions(packages[name]?.version, version) === -1)) {
52
+ packages[name] = {
53
+ ...pkgDetails,
54
+ isRoot,
55
+ };
56
+ }
57
+ let nestedConfig;
58
+ if (repo) {
59
+ const dir = formatGithubDir(name, repo);
60
+ nestedConfig = await readVesselConfig(dir) || {};
61
+ }
62
+ else if (!pkgDetails.path && version) {
63
+ const file = formatDir(name, version) + '/mops.toml';
64
+ nestedConfig = readConfig(file);
65
+ }
66
+ if (nestedConfig && !pkgDetails.path) {
67
+ await collectDeps(nestedConfig);
68
+ }
69
+ if (!versions[name]) {
70
+ versions[name] = [];
71
+ }
72
+ if (repo) {
73
+ const { branch } = parseGithubURL(repo);
74
+ versions[name]?.push(branch);
75
+ }
76
+ else if (version) {
77
+ versions[name]?.push(version);
78
+ }
79
+ }
80
+ };
81
+ let config = readConfig();
82
+ await collectDeps(config, true);
83
+ // show conflicts
84
+ if (verbose) {
85
+ for (let [dep, vers] of Object.entries(versions)) {
86
+ if (vers.length > 1) {
87
+ console.log(chalk.yellow('WARN:'), `Conflicting package versions "${dep}" - ${vers.join(', ')}`);
88
+ }
89
+ }
90
+ }
91
+ let rootDir = getRootDir();
92
+ return Object.fromEntries(Object.entries(packages).map(([name, pkg]) => {
93
+ let version;
94
+ if (pkg.path) {
95
+ version = path.resolve(rootDir, pkg.path).replaceAll('{MOPS_ENV}', process.env.MOPS_ENV || 'local');
96
+ }
97
+ else if (pkg.repo) {
98
+ version = pkg.repo;
99
+ }
100
+ else if (pkg.version) {
101
+ version = pkg.version;
102
+ }
103
+ else {
104
+ return [name, ''];
105
+ }
106
+ return [name, version];
107
+ }).filter(([, version]) => version !== ''));
108
+ }
package/mops.ts CHANGED
@@ -221,6 +221,21 @@ export function parseGithubURL(href: string) {
221
221
  return {org, gitName, branch};
222
222
  }
223
223
 
224
+ export function getDependencyType(version: string) {
225
+ if (!version || typeof version !== 'string') {
226
+ throw Error(`Invalid dependency value "${version}"`);
227
+ }
228
+ if (version.startsWith('https://github.com/')) {
229
+ return 'github';
230
+ }
231
+ else if (version.match(/^(\.?\.)?\//)) {
232
+ return 'local';
233
+ }
234
+ else {
235
+ return 'mops';
236
+ }
237
+ }
238
+
224
239
  export function readConfig(configFile = getClosestConfigFile()): Config {
225
240
  let text = fs.readFileSync(configFile).toString();
226
241
  let toml = TOML.parse(text);
@@ -230,10 +245,11 @@ export function readConfig(configFile = getClosestConfigFile()): Config {
230
245
  if (!data || typeof data !== 'string') {
231
246
  throw Error(`Invalid dependency value ${name} = "${data}"`);
232
247
  }
233
- if (data.startsWith('https://github.com/')) {
248
+ let depType = getDependencyType(data);
249
+ if (depType === 'github') {
234
250
  deps[name] = {name, repo: data, version: ''};
235
251
  }
236
- else if (data.match(/^(\.?\.)?\//)) {
252
+ else if (depType === 'local') {
237
253
  deps[name] = {name, repo: '', path: data, version: ''};
238
254
  }
239
255
  else {
@@ -0,0 +1,11 @@
1
+ import {mainActor} from './mops.js';
2
+ import {resolvePackages} from './resolve-packages.js';
3
+
4
+ export async function notifyInstalls(names: string[]) {
5
+ let resolvedPackages = await resolvePackages();
6
+ let packages: [string, string][] = names.map(name => [name, resolvedPackages[name] as string]);
7
+ if (packages.length) {
8
+ let actor = await mainActor();
9
+ await actor.notifyInstalls(packages);
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.27.3",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/cli.js"
@@ -0,0 +1,131 @@
1
+ import path from 'node:path';
2
+ import chalk from 'chalk';
3
+ import {checkConfigFile, formatDir, formatGithubDir, getRootDir, parseGithubURL, readConfig} from './mops.js';
4
+ import {VesselConfig, readVesselConfig} from './vessel.js';
5
+ import {Config, Dependency} from './types.js';
6
+
7
+ export async function resolvePackages({verbose = false} = {}): Promise<Record<string, string>> {
8
+ if (!checkConfigFile()) {
9
+ return {};
10
+ }
11
+
12
+ let packages: Record<string, Dependency & {isRoot: boolean;}> = {};
13
+ let versions: Record<string, string[]> = {};
14
+
15
+ let compareVersions = (a: string = '0.0.0', b: string = '0.0.0') => {
16
+ let ap = a.split('.').map((x: string) => parseInt(x)) as [number, number, number];
17
+ let bp = b.split('.').map((x: string) => parseInt(x)) as [number, number, number];
18
+ if (ap[0] - bp[0]) {
19
+ return Math.sign(ap[0] - bp[0]);
20
+ }
21
+ if (ap[0] === bp[0] && ap[1] - bp[1]) {
22
+ return Math.sign(ap[1] - bp[1]);
23
+ }
24
+ if (ap[0] === bp[0] && ap[1] === bp[1] && ap[2] - bp[2]) {
25
+ return Math.sign(ap[2] - bp[2]);
26
+ }
27
+ return 0;
28
+ };
29
+
30
+ const gitVerRegex = new RegExp(/v(\d{1,2}\.\d{1,2}\.\d{1,2})(-.*)?$/);
31
+
32
+ const compareGitVersions = (repoA: string, repoB: string) => {
33
+ const {branch: a} = parseGithubURL(repoA);
34
+ const {branch: b} = parseGithubURL(repoB);
35
+
36
+ if (gitVerRegex.test(a) && gitVerRegex.test(b)) {
37
+ return compareVersions(a.substring(1), b.substring(1));
38
+ }
39
+ else if (!gitVerRegex.test(a)) {
40
+ return -1;
41
+ }
42
+ else {
43
+ return 1;
44
+ }
45
+ };
46
+
47
+ let collectDeps = async (config: Config | VesselConfig, isRoot = false) => {
48
+ let allDeps = [...Object.values(config.dependencies || {})];
49
+ if (isRoot) {
50
+ allDeps = [...allDeps, ...Object.values(config['dev-dependencies'] || {})];
51
+ }
52
+ for (const pkgDetails of allDeps) {
53
+ const {name, repo, version} = pkgDetails;
54
+
55
+ // take root dep version or bigger one
56
+ if (
57
+ isRoot
58
+ || !packages[name]
59
+ || !packages[name]?.isRoot
60
+ && (
61
+ repo && packages[name]?.repo && compareGitVersions(packages[name]?.repo || '', repo) === -1
62
+ || compareVersions(packages[name]?.version, version) === -1)
63
+ ) {
64
+ packages[name] = {
65
+ ...pkgDetails,
66
+ isRoot,
67
+ };
68
+ }
69
+
70
+ let nestedConfig;
71
+
72
+ if (repo) {
73
+ const dir = formatGithubDir(name, repo);
74
+ nestedConfig = await readVesselConfig(dir) || {};
75
+ }
76
+ else if (!pkgDetails.path && version) {
77
+ const file = formatDir(name, version) + '/mops.toml';
78
+ nestedConfig = readConfig(file);
79
+ }
80
+
81
+ if (nestedConfig && !pkgDetails.path) {
82
+ await collectDeps(nestedConfig);
83
+ }
84
+
85
+ if (!versions[name]) {
86
+ versions[name] = [];
87
+ }
88
+
89
+ if (repo) {
90
+ const {branch} = parseGithubURL(repo);
91
+ versions[name]?.push(branch);
92
+ }
93
+ else if (version) {
94
+ versions[name]?.push(version);
95
+ }
96
+ }
97
+ };
98
+
99
+ let config = readConfig();
100
+ await collectDeps(config, true);
101
+
102
+ // show conflicts
103
+ if (verbose) {
104
+ for (let [dep, vers] of Object.entries(versions)) {
105
+ if (vers.length > 1) {
106
+ console.log(chalk.yellow('WARN:'), `Conflicting package versions "${dep}" - ${vers.join(', ')}`);
107
+ }
108
+ }
109
+ }
110
+
111
+ let rootDir = getRootDir();
112
+
113
+ return Object.fromEntries(
114
+ Object.entries(packages).map(([name, pkg]) => {
115
+ let version: string;
116
+ if (pkg.path) {
117
+ version = path.resolve(rootDir, pkg.path).replaceAll('{MOPS_ENV}', process.env.MOPS_ENV || 'local');
118
+ }
119
+ else if (pkg.repo) {
120
+ version = pkg.repo;
121
+ }
122
+ else if (pkg.version) {
123
+ version = pkg.version;
124
+ }
125
+ else {
126
+ return [name, ''];
127
+ }
128
+ return [name, version];
129
+ }).filter(([, version]) => version !== '')
130
+ );
131
+ }