ic-mops 0.22.0 → 0.24.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/README.md CHANGED
@@ -17,28 +17,16 @@ npm i -g ic-mops
17
17
 
18
18
  ## Install Packages
19
19
 
20
- ### 1. Configure dfx.json
21
- Add `mops` as a packtool to your `dfx.json`
22
-
23
- ```json
24
- {
25
- "defaults": {
26
- "build": {
27
- "packtool": "mops sources"
28
- }
29
- }
30
- }
31
- ```
32
-
33
- ### 2. Initialize
20
+ ### 1. Initialize
34
21
  Run this command in the root directory of your project (where is `dfx.json` placed)
22
+
35
23
  If there are Vessel config files, mops will migrate packages from `vessel.dhall` to `mops.toml`
36
24
 
37
25
  ```
38
26
  mops init
39
27
  ```
40
28
 
41
- ### 3. Install Motoko Packages
29
+ ### 2. Install Motoko Packages
42
30
  Use `mops add <package_name>` to install a specific package and save it to `mops.toml`
43
31
 
44
32
  ```
@@ -65,7 +53,7 @@ Use `mops install` to install all packages specified in `mops.toml`
65
53
  mops install
66
54
  ```
67
55
 
68
- ### 4. Import Package
56
+ ### 3. Import Package
69
57
  Now you can import installed packages in your Motoko code
70
58
 
71
59
  ```motoko
@@ -92,10 +80,10 @@ mops import-identity -- "$(dfx identity export mops)"
92
80
  ```
93
81
 
94
82
  ### 2. Initialize
95
- Run this command in your package root
83
+ Run this command in your package root and select type "Package"
96
84
 
97
85
  ```
98
- mops init <your_package_name>
86
+ mops init
99
87
  ```
100
88
 
101
89
  Edit `description` and `repository` fields in `mops.toml` file.
package/cli.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'node:fs';
4
- import {program} from 'commander';
4
+ import {program, Argument} from 'commander';
5
5
  import chalk from 'chalk';
6
6
  import {Principal} from '@dfinity/principal';
7
7
 
@@ -21,6 +21,7 @@ import {selfUpdate} from './commands/self-update.js';
21
21
  import {remove} from './commands/remove.js';
22
22
  import {getUserProp, setUserProp} from './commands/user.js';
23
23
  import {bump} from './commands/bump.js';
24
+ import {sync} from './commands/sync.js';
24
25
  // import {docs} from './commands/docs.js';
25
26
 
26
27
  program.name('mops');
@@ -31,10 +32,11 @@ program.version(`CLI ${packageJson.version}\nAPI ${apiVersion}`, '-v --version')
31
32
 
32
33
  // init
33
34
  program
34
- .command('init [name]')
35
- .description('Create mops.toml')
36
- .action(async (name: string) => {
37
- await init(name);
35
+ .command('init')
36
+ .description('Initialize a new project or package in the current directory')
37
+ .option('-y, --yes', 'Accept all defaults')
38
+ .action(async (options) => {
39
+ await init(options);
38
40
  });
39
41
 
40
42
  // add
@@ -110,7 +112,7 @@ program
110
112
  program
111
113
  .command('set-network <network>')
112
114
  .alias('sn')
113
- .description('Set network local|dev|ic')
115
+ .description('Set network local|staging|ic')
114
116
  .action(async (network) => {
115
117
  await setNetwork(network);
116
118
  console.log(`Selected '${network}' network`);
@@ -166,8 +168,9 @@ program
166
168
 
167
169
  // cache
168
170
  program
169
- .command('cache [sub-command]')
171
+ .command('cache')
170
172
  .description('Manage cache')
173
+ .addArgument(new Argument('<sub>').choices(['size', 'clean']))
171
174
  .action(async (sub) => {
172
175
  if (sub == 'clean') {
173
176
  await cleanCache();
@@ -177,9 +180,6 @@ program
177
180
  let size = await cacheSize();
178
181
  console.log('Cache size is ' + size);
179
182
  }
180
- else {
181
- console.log('Unknown sub command. Available sub commands: clean, size');
182
- }
183
183
  });
184
184
 
185
185
  // test
@@ -236,7 +236,10 @@ program
236
236
 
237
237
  // user
238
238
  program
239
- .command('user set|get <prop> [value]')
239
+ .command('user')
240
+ .addArgument(new Argument('<sub>').choices(['set', 'get']))
241
+ .addArgument(new Argument('<prop>').choices(['name', 'site', 'email', 'github', 'twitter']))
242
+ .addArgument(new Argument('[value]'))
240
243
  .description('User settings')
241
244
  .action(async (sub, prop, value) => {
242
245
  if (sub == 'get') {
@@ -249,9 +252,6 @@ program
249
252
  }
250
253
  await setUserProp(prop, value);
251
254
  }
252
- else {
253
- console.log('Unknown sub command. Available sub commands: set, get');
254
- }
255
255
  });
256
256
 
257
257
  // airdrop
@@ -294,4 +294,12 @@ program
294
294
  bump(part);
295
295
  });
296
296
 
297
+ // sync
298
+ program
299
+ .command('sync')
300
+ .description('Add missing packages and remove unused packages')
301
+ .action(async () => {
302
+ await sync();
303
+ });
304
+
297
305
  program.parse();
package/commands/init.ts CHANGED
@@ -1,15 +1,18 @@
1
1
  import {execSync} from 'node:child_process';
2
2
  import path from 'node:path';
3
- import fs from 'node:fs';
3
+ import {existsSync, readFileSync, writeFileSync} from 'node:fs';
4
4
  import chalk from 'chalk';
5
- import {checkApiCompatibility, mainActor, readDfxJson, writeConfig} from '../mops.js';
5
+ import prompts from 'prompts';
6
+
7
+ import {checkApiCompatibility, mainActor, writeConfig} from '../mops.js';
6
8
  import {installAll} from './install-all.js';
7
9
  import {VesselConfig, readVesselConfig} from '../vessel.js';
8
10
  import {Config, Dependencies} from '../types.js';
11
+ import {template} from './template.js';
9
12
 
10
- export async function init(name = '') {
13
+ export async function init({yes = false} = {}) {
11
14
  let configFile = path.join(process.cwd(), 'mops.toml');
12
- let exists = fs.existsSync(configFile);
15
+ let exists = existsSync(configFile);
13
16
  if (exists) {
14
17
  console.log(chalk.yellow('mops.toml already exists'));
15
18
  return;
@@ -18,56 +21,179 @@ export async function init(name = '') {
18
21
  console.log('Initializing...');
19
22
 
20
23
  let config: Config = {};
21
- let vesselConfig: VesselConfig = {dependencies: [], 'dev-dependencies': []};
22
- let deps: Dependencies = {};
23
24
 
24
- const vesselFile = path.join(process.cwd(), 'vessel.dhall');
25
+ if (yes) {
26
+ await applyInit({
27
+ type: 'project',
28
+ config,
29
+ setupWorkflow: true,
30
+ addTest: false,
31
+ copyrightOwner: '',
32
+ });
33
+ return;
34
+ }
35
+
36
+ // migrate from vessel
37
+ let vesselFile = path.join(process.cwd(), 'vessel.dhall');
38
+ let vesselConfig: VesselConfig = {dependencies: [], 'dev-dependencies': []};
25
39
 
26
- if (fs.existsSync(vesselFile)) {
40
+ if (existsSync(vesselFile)) {
27
41
  console.log('Reading vessel.dhall file');
28
- const res = await readVesselConfig(process.cwd(), {cache: false});
42
+ let res = await readVesselConfig(process.cwd(), {cache: false});
29
43
  if (res) {
30
44
  vesselConfig = {...res};
31
45
  }
32
46
  }
33
47
 
34
48
  if (vesselConfig.dependencies) {
49
+ let deps: Dependencies = {};
35
50
  deps = {};
36
51
 
37
52
  for (const dep of (vesselConfig.dependencies || [])) {
38
53
  deps[dep.name] = dep;
39
54
  }
55
+
56
+ if (Object.keys(deps).length) {
57
+ config.dependencies = deps;
58
+ }
40
59
  }
41
60
 
42
- // lib mode
43
- if (name) {
61
+ let promptsConfig = {
62
+ onCancel() {
63
+ console.log('aborted');
64
+ process.exit(0);
65
+ }
66
+ };
67
+
68
+ // type
69
+ let {type} = await prompts({
70
+ type: 'select',
71
+ name: 'type',
72
+ message: 'Select type:',
73
+ choices: [
74
+ {title: `Project ${chalk.dim('(I just want to use mops packages in my project)')}`, value: 'project'},
75
+ {title: `Package ${chalk.dim('(I plan to publish this package on mops)')}`, value: 'package'},
76
+ ],
77
+ }, promptsConfig);
78
+
79
+ let addTest = false;
80
+ let copyrightOwner = '';
81
+
82
+ // package details
83
+ if (type === 'package') {
84
+ let res = await prompts([
85
+ {
86
+ type: 'text',
87
+ name: 'name',
88
+ message: 'Enter package name:',
89
+ initial: '',
90
+ },
91
+ {
92
+ type: 'text',
93
+ name: 'description',
94
+ message: 'Enter package description:',
95
+ initial: '',
96
+ },
97
+ {
98
+ type: 'text',
99
+ name: 'repository',
100
+ message: 'Enter package repository url:',
101
+ initial: '',
102
+ },
103
+ {
104
+ type: 'text',
105
+ name: 'keywords',
106
+ message: 'Enter keywords separated by spaces:',
107
+ initial: '',
108
+ },
109
+ {
110
+ type: 'select',
111
+ name: 'license',
112
+ message: 'Choose a license:',
113
+ choices: [
114
+ {title: 'MIT', value: 'MIT'},
115
+ {title: 'Apache-2.0', value: 'Apache-2.0'},
116
+ ],
117
+ initial: 0,
118
+ },
119
+ {
120
+ type: 'text',
121
+ name: 'copyrightOwner',
122
+ message: 'Enter license copyright owner:',
123
+ initial: '',
124
+ },
125
+ {
126
+ type: 'confirm',
127
+ name: 'addTest',
128
+ message: `Add example test file? ${chalk.dim('(test/lib.test.mo)')}`,
129
+ initial: true,
130
+ },
131
+ ], promptsConfig);
132
+
44
133
  config.package = {
45
- name,
46
- version: '0.1.0',
47
- description: '',
48
- repository: '',
134
+ name: (res.name || '').trim(),
135
+ version: '1.0.0',
136
+ description: (res.description || '').trim(),
137
+ repository: (res.repository || '').trim(),
138
+ keywords: [...new Set(res.keywords.split(' ').filter(Boolean))] as string[],
139
+ license: (res.license || '').trim(),
49
140
  };
50
141
 
51
- if (deps) {
52
- config.dependencies = deps;
53
- }
142
+ addTest = res.addTest;
143
+ copyrightOwner = res.copyrightOwner;
144
+ }
145
+
146
+ // GitHub workflow
147
+ let {setupWorkflow} = await prompts({
148
+ type: 'confirm',
149
+ name: 'setupWorkflow',
150
+ message: `Setup GitHub workflow? ${chalk.dim('(run `mops test` on push)')}`,
151
+ initial: true,
152
+ }, promptsConfig);
153
+
154
+ await applyInit({
155
+ type,
156
+ config,
157
+ setupWorkflow,
158
+ addTest,
159
+ copyrightOwner,
160
+ });
161
+ }
54
162
 
55
- writeConfig(config, configFile);
163
+ type ApplyInitOptions = {
164
+ type: 'project' | 'package';
165
+ config: Config;
166
+ setupWorkflow: boolean;
167
+ addTest: boolean;
168
+ copyrightOwner: string;
169
+ }
56
170
 
57
- if (Object.keys(config.dependencies || {}).length) {
58
- await installAll({verbose: true});
171
+ async function applyInit({type, config, setupWorkflow, addTest, copyrightOwner} : ApplyInitOptions) {
172
+ // set packtool in dfx.json
173
+ let dfxJson = path.resolve(process.cwd(), 'dfx.json');
174
+ let dfxJsonData;
175
+ if (existsSync(dfxJson)) {
176
+ let dfxJsonText = readFileSync(dfxJson).toString();
177
+ dfxJsonData = JSON.parse(dfxJsonText);
178
+ console.log('Setting packtool in dfx.json...');
179
+ dfxJsonData.defaults = dfxJsonData.defaults || {};
180
+ dfxJsonData.defaults.build = dfxJsonData.defaults.build || {};
181
+ if (dfxJsonData.defaults.build.packtool !== 'mops sources') {
182
+ dfxJsonData.defaults.build.packtool = 'mops sources';
183
+ let indent = dfxJsonText.match(/([ \t]+)"/)?.[1] || ' ';
184
+ writeFileSync(path.join(process.cwd(), 'dfx.json'), JSON.stringify(dfxJsonData, null, indent));
185
+ console.log(chalk.green('packtool set to "mops sources"'));
59
186
  }
60
187
  }
61
188
 
62
- // project mode
63
- if (!name) {
189
+ // get default packages
190
+ if (type === 'project') {
64
191
  let compatible = await checkApiCompatibility();
65
192
  if (!compatible) {
66
193
  return;
67
194
  }
68
195
 
69
- let dfxJson = readDfxJson();
70
- let dfxVersion = dfxJson?.dfx || '';
196
+ let dfxVersion = dfxJsonData?.dfx || '';
71
197
  if (!dfxVersion) {
72
198
  try {
73
199
  let res = execSync('dfx --version').toString();
@@ -79,6 +205,7 @@ export async function init(name = '') {
79
205
  catch {}
80
206
  }
81
207
 
208
+ console.log(`Fetching default packages for dfx ${dfxVersion}...`);
82
209
  let actor = await mainActor();
83
210
  let defaultPackages = await actor.getDefaultPackages(dfxVersion);
84
211
 
@@ -86,18 +213,57 @@ export async function init(name = '') {
86
213
  config.dependencies = {};
87
214
  }
88
215
 
89
- if (deps) {
90
- config.dependencies = deps;
91
- }
92
-
93
216
  for (let [name, version] of defaultPackages) {
94
217
  config.dependencies[name] = {name, version};
95
218
  }
219
+ }
220
+
221
+ // save config
222
+ let configFile = path.join(process.cwd(), 'mops.toml');
223
+ writeConfig(config, configFile);
224
+ console.log(chalk.green('Created'), 'mops.toml');
225
+
226
+ // add src/lib.mo
227
+ if (type === 'package' && !existsSync(path.join(process.cwd(), 'src'))) {
228
+ await template('lib.mo');
229
+ }
230
+
231
+ // add src/lib.test.mo
232
+ if (addTest && !existsSync(path.join(process.cwd(), 'test'))) {
233
+ await template('lib.test.mo');
234
+ }
235
+
236
+ // add license
237
+ if (config.package?.license) {
238
+ await template(`license:${config.package.license}`, {copyrightOwner});
239
+ }
240
+
241
+ // add readme
242
+ if (type === 'package') {
243
+ await template('readme');
244
+ }
96
245
 
97
- writeConfig(config, configFile);
246
+ // add GitHub workflow
247
+ if (setupWorkflow) {
248
+ await template('github-workflow:mops-test');
249
+ }
250
+
251
+ // add .mops to .gitignore
252
+ {
253
+ let gitignore = path.join(process.cwd(), '.gitignore');
254
+ let gitignoreData = existsSync(gitignore) ? readFileSync(gitignore).toString() : '';
255
+ let lf = gitignoreData.endsWith('\n') ? '\n' : '';
256
+ if (!gitignoreData.includes('.mops')) {
257
+ writeFileSync(gitignore, `${gitignoreData}\n.mops${lf}`.trimStart());
258
+ console.log(chalk.green('Added'), '.mops to .gitignore');
259
+ }
260
+ }
98
261
 
262
+ // install deps
263
+ if (Object.keys(config.dependencies || {}).length) {
264
+ console.log('Installing dependencies...');
99
265
  await installAll({verbose: true});
100
266
  }
101
267
 
102
- console.log(chalk.green('mops.toml has been created'));
268
+ console.log(chalk.green('Done!'));
103
269
  }
@@ -77,7 +77,7 @@ export async function remove(name: string, {dev = false, verbose = false, dryRun
77
77
  pkgDir = formatDir(dep.name, dep.version);
78
78
  }
79
79
  if (pkgDir && fs.existsSync(pkgDir)) {
80
- dryRun || deleteSync([`${pkgDir}`]);
80
+ dryRun || deleteSync([`${pkgDir}`], {force: true});
81
81
  verbose && console.log(`Removed local cache ${pkgDir}`);
82
82
  }
83
83
  }
@@ -0,0 +1,98 @@
1
+ import path from 'node:path';
2
+ import {execSync} from 'node:child_process';
3
+ import {globSync} from 'glob';
4
+ import chalk from 'chalk';
5
+ import {checkConfigFile, getRootDir, readConfig} from '../mops.js';
6
+ import {add} from './add.js';
7
+ import {remove} from './remove.js';
8
+
9
+ let ignore = [
10
+ '**/node_modules/**',
11
+ '**/.vessel/**',
12
+ '**/.git/**',
13
+ '**/.mops/**',
14
+ '**/test/**',
15
+ '**/*.test.mo',
16
+ ];
17
+
18
+ let mocPath = '';
19
+ function getMocPath(): string {
20
+ if (!mocPath) {
21
+ mocPath = process.env.DFX_MOC_PATH || '';
22
+ }
23
+ if (!mocPath) {
24
+ try {
25
+ mocPath = execSync('dfx cache show').toString().trim() + '/moc';
26
+ }
27
+ catch {}
28
+ }
29
+ if (!mocPath) {
30
+ mocPath = 'moc';
31
+ }
32
+ return mocPath;
33
+ }
34
+
35
+ async function getUsedPackages(): Promise<string[]> {
36
+ let rootDir = getRootDir();
37
+ let files = globSync('**/*.mo', {
38
+ cwd: rootDir,
39
+ nocase: true,
40
+ ignore: ignore,
41
+ });
42
+
43
+ let packages: Set<string> = new Set;
44
+
45
+ for (let file of files) {
46
+ let deps: string[] = execSync(`${getMocPath()} --print-deps ${path.join(rootDir, file)}`).toString().trim().split('\n');
47
+
48
+ for (let dep of deps) {
49
+ if (dep.startsWith('mo:') && !dep.startsWith('mo:prim') && !dep.startsWith('mo:⛔')) {
50
+ packages.add(dep.replace(/^mo:([^/]+).*$/, '$1'));
51
+ }
52
+ }
53
+ }
54
+
55
+ return [...packages];
56
+ }
57
+
58
+ async function getMissingPackages(): Promise<string[]> {
59
+ let config = readConfig();
60
+ let allDeps = [...Object.keys(config.dependencies || {}), ...Object.keys(config['dev-dependencies'] || {})];
61
+ let missing = new Set(await getUsedPackages());
62
+ for (let pkg of allDeps) {
63
+ missing.delete(pkg);
64
+ }
65
+ return [...missing];
66
+ }
67
+
68
+ async function getUnusedPackages(): Promise<string[]> {
69
+ let config = readConfig();
70
+ let allDeps = new Set([...Object.keys(config.dependencies || {})]);
71
+ let used = await getUsedPackages();
72
+ for (let pkg of used) {
73
+ allDeps.delete(pkg);
74
+ }
75
+ return [...allDeps];
76
+ }
77
+
78
+ export async function sync() {
79
+ if (!checkConfigFile()) {
80
+ return;
81
+ }
82
+
83
+ let missing = await getMissingPackages();
84
+ let unused = await getUnusedPackages();
85
+
86
+ missing.length && console.log(`${chalk.yellow('Missing packages:')} ${missing.join(', ')}`);
87
+ unused.length && console.log(`${chalk.yellow('Unused packages:')} ${unused.join(', ')}`);
88
+
89
+ // add missing packages
90
+ for (let pkg of missing) {
91
+ await add(pkg);
92
+ }
93
+
94
+ // remove unused packages
95
+ for (let pkg of unused) {
96
+ await remove(pkg);
97
+ }
98
+ }
@@ -2,29 +2,97 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import chalk from 'chalk';
4
4
  import prompts from 'prompts';
5
- import {getRootDir} from '../mops.js';
5
+ import camelCase from 'camelcase';
6
+ import {getRootDir, readConfig} from '../mops.js';
6
7
 
7
- export async function template() {
8
- let res = await prompts({
9
- type: 'select',
10
- name: 'value',
11
- message: 'Select template:',
12
- choices: [
13
- {title: 'GitHub Workflow to run \'mops test\'', value: 'github-workflow:mops-test'},
14
- {title: '× Cancel', value: ''},
15
- ],
16
- initial: 0,
17
- });
8
+ export async function template(templateName?: string, options: any = {}) {
9
+ if (!templateName) {
10
+ let res = await prompts({
11
+ type: 'select',
12
+ name: 'value',
13
+ message: 'Select template:',
14
+ choices: [
15
+ {title: 'README.md', value: 'readme'},
16
+ {title: 'src/lib.mo', value: 'lib.mo'},
17
+ {title: 'test/lib.test.mo', value: 'lib.test.mo'},
18
+ {title: 'License MIT', value: 'license:MIT'},
19
+ {title: 'License Apache-2.0', value: 'license:Apache-2.0'},
20
+ {title: 'GitHub Workflow to run \'mops test\'', value: 'github-workflow:mops-test'},
21
+ {title: '× Cancel', value: ''},
22
+ ],
23
+ initial: 0,
24
+ });
25
+ templateName = res.value;
26
+ }
18
27
 
19
- if (res.value === 'github-workflow:mops-test') {
28
+ if (templateName === 'github-workflow:mops-test') {
20
29
  let dest = path.resolve(getRootDir(), '.github/workflows/mops-test.yml');
21
30
  if (fs.existsSync(dest)) {
22
- console.log(chalk.yellow('Workflow already exists:'), dest);
31
+ console.log(chalk.yellow('Workflow already exists:'), path.relative(getRootDir(), dest));
23
32
  return;
24
33
  }
25
34
  let mopsTestYml = new URL('../templates/mops-test.yml', import.meta.url);
26
35
  fs.mkdirSync(path.resolve(getRootDir(), '.github/workflows'), {recursive: true});
27
36
  fs.copyFileSync(mopsTestYml, dest);
28
- console.log(chalk.green('Workflow created:'), dest);
37
+ console.log(chalk.green('Created'), path.relative(getRootDir(), dest));
38
+ }
39
+ else if (templateName?.startsWith('license:')) {
40
+ let dest = path.resolve(getRootDir(), 'LICENSE');
41
+ if (fs.existsSync(dest)) {
42
+ console.log(chalk.yellow('LICENSE already exists'));
43
+ return;
44
+ }
45
+
46
+ let setYearAndOwner = (file: string) => {
47
+ let license = fs.readFileSync(file).toString();
48
+ license = license.replace(/<year>/g, new Date().getFullYear().toString());
49
+ if (options.copyrightOwner) {
50
+ license = license.replace(/<copyright-owner>/g, options.copyrightOwner);
51
+ }
52
+ fs.writeFileSync(file, license);
53
+ };
54
+
55
+ if (templateName === 'license:MIT') {
56
+ fs.copyFileSync(new URL('../templates/licenses/MIT', import.meta.url), path.resolve(getRootDir(), 'LICENSE'));
57
+ setYearAndOwner(path.resolve(getRootDir(), 'LICENSE'));
58
+ console.log(chalk.green('Created'), path.relative(getRootDir(), 'LICENSE'));
59
+ }
60
+ else if (templateName === 'license:Apache-2.0') {
61
+ fs.copyFileSync(new URL('../templates/licenses/Apache-2.0', import.meta.url), path.resolve(getRootDir(), 'LICENSE'));
62
+ fs.copyFileSync(new URL('../templates/licenses/Apache-2.0-NOTICE', import.meta.url), path.resolve(getRootDir(), 'NOTICE'));
63
+ setYearAndOwner(path.resolve(getRootDir(), 'NOTICE'));
64
+ console.log(chalk.green('Created'), path.relative(getRootDir(), 'LICENSE'));
65
+ console.log(chalk.green('Created'), path.relative(getRootDir(), 'NOTICE'));
66
+ }
67
+ }
68
+ else if (templateName === 'lib.mo') {
69
+ fs.mkdirSync(path.join(getRootDir(), 'src'), {recursive: true});
70
+ fs.copyFileSync(new URL('../templates/src/lib.mo', import.meta.url), path.resolve(getRootDir(), 'src/lib.mo'));
71
+ console.log(chalk.green('Created'), path.relative(getRootDir(), 'src/lib.mo'));
72
+ }
73
+ else if (templateName === 'lib.test.mo') {
74
+ fs.mkdirSync(path.join(getRootDir(), 'test'), {recursive: true});
75
+ fs.copyFileSync(new URL('../templates/test/lib.test.mo', import.meta.url), path.resolve(getRootDir(), 'test/lib.test.mo'));
76
+ console.log(chalk.green('Created'), path.relative(getRootDir(), 'test/lib.test.mo'));
77
+ }
78
+ else if (templateName === 'readme') {
79
+ let dest = path.resolve(getRootDir(), 'README.md');
80
+ if (fs.existsSync(dest)) {
81
+ console.log(chalk.yellow('README.md already exists'));
82
+ return;
83
+ }
84
+ fs.copyFileSync(new URL('../templates/README.md', import.meta.url), dest);
85
+
86
+ let config = readConfig();
87
+
88
+ let data = fs.readFileSync(dest).toString();
89
+ data = data.replace(/<year>/g, new Date().getFullYear().toString());
90
+ if (config.package?.name) {
91
+ data = data.replace(/<name>/g, config.package.name);
92
+ data = data.replace(/<import-name>/g, camelCase(config.package.name, {pascalCase: true}));
93
+ }
94
+ fs.writeFileSync(dest, data);
95
+
96
+ console.log(chalk.green('Created'), path.relative(getRootDir(), 'README.md'));
29
97
  }
30
98
  }