├── .circleci
└── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── bin
├── run
└── run.cmd
├── nxpm-plugins.gif
├── nxpm-projects.gif
├── nxpm-sandbox.gif
├── package.json
├── src
├── commands
│ ├── config
│ │ ├── delete.ts
│ │ ├── edit.ts
│ │ ├── get.ts
│ │ └── set.ts
│ ├── plugins.ts
│ ├── projects.ts
│ ├── registry
│ │ ├── disable.ts
│ │ ├── enable.ts
│ │ ├── start.ts
│ │ └── status.ts
│ ├── release.ts
│ ├── sandbox.ts
│ └── sandbox
│ │ └── pull.ts
├── global.d.ts
├── index.ts
├── lib
│ ├── config
│ │ ├── config.ts
│ │ └── utils
│ │ │ └── config-utils.ts
│ ├── plugins
│ │ ├── interfaces
│ │ │ └── plugin-config.ts
│ │ ├── plugins.ts
│ │ └── utils
│ │ │ └── plugin-utils.ts
│ ├── projects
│ │ └── projects.ts
│ ├── release
│ │ ├── interfaces
│ │ │ ├── release-config.ts
│ │ │ ├── validated-config.ts
│ │ │ ├── validated-packages.ts
│ │ │ └── validated-workspace.ts
│ │ ├── release-validate.ts
│ │ └── release.ts
│ ├── sandbox
│ │ ├── interfaces
│ │ │ ├── sandbox-config.ts
│ │ │ ├── sandbox-pull-config.ts
│ │ │ └── sandbox.ts
│ │ ├── sandbox-pull.ts
│ │ ├── sandbox.ts
│ │ └── utils
│ │ │ └── sandbox-utils.ts
│ └── verdaccio
│ │ └── index.ts
└── utils
│ ├── base-command.ts
│ ├── base-config.ts
│ ├── constants.ts
│ ├── get-workspace-info.ts
│ ├── index.ts
│ ├── logging.ts
│ ├── parse-version.ts
│ ├── user-config.ts
│ ├── utils.ts
│ └── vendor
│ └── nx-console
│ ├── read-schematic-collections.ts
│ ├── schema.ts
│ └── utils.ts
├── test
├── commands
│ ├── config
│ │ ├── delete.test.ts
│ │ ├── edit.test.ts
│ │ ├── get.test.ts
│ │ └── set.test.ts
│ ├── plugins.test.ts
│ ├── projects.test.ts
│ ├── registry
│ │ ├── disable.test.ts
│ │ ├── enable.test.ts
│ │ ├── start.test.ts
│ │ └── status.test.ts
│ ├── release.test.ts
│ ├── sandbox.test.ts
│ └── sandbox
│ │ └── pull.test.ts
├── mocha.opts
└── tsconfig.json
├── tsconfig.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | jobs:
4 | node-latest: &test
5 | docker:
6 | - image: node:latest
7 | working_directory: ~/cli
8 | steps:
9 | - checkout
10 | - restore_cache: &restore_cache
11 | keys:
12 | - v1-npm-{{checksum ".circleci/config.yml"}}-{{checksum "yarn.lock"}}
13 | - v1-npm-{{checksum ".circleci/config.yml"}}
14 | - run:
15 | name: Install dependencies
16 | command: yarn
17 | - run: ./bin/run --version
18 | - run: ./bin/run --help
19 | - run:
20 | name: Testing
21 | command: yarn test
22 | node-12:
23 | <<: *test
24 | docker:
25 | - image: node:12
26 | node-10:
27 | <<: *test
28 | docker:
29 | - image: node:10
30 | cache:
31 | <<: *test
32 | steps:
33 | - checkout
34 | - run:
35 | name: Install dependencies
36 | command: yarn
37 | - save_cache:
38 | key: v1-npm-{{checksum ".circleci/config.yml"}}-{{checksum "yarn.lock"}}
39 | paths:
40 | - ~/cli/node_modules
41 | - /usr/local/share/.cache/yarn
42 | - /usr/local/share/.config/yarn
43 |
44 | workflows:
45 | version: 2
46 | "nxpm":
47 | jobs:
48 | - node-latest
49 | - node-12
50 | - node-10
51 | - cache:
52 | filters:
53 | tags:
54 | only: /^v.*/
55 | branches:
56 | ignore: /.*/
57 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /lib
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "oclif",
4 | "oclif-typescript"
5 | ],
6 | "rules": {
7 | "object-curly-spacing": "off",
8 | "no-console": "off",
9 | "no-else-return": "off",
10 | "no-implicit-coercion": "off",
11 | "indent": "off",
12 | "arrow-parens": "off",
13 | "operator-linebreak": "off",
14 | "no-process-exit": "off",
15 | "unicorn/catch-error-name": "off",
16 | "unicorn/explicit-length-check": "off",
17 | "unicorn/no-process-exit": "off",
18 | "@typescript-eslint/member-delimiter-style": "off"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *-debug.log
2 | *-error.log
3 | /.nyc_output
4 | /dist
5 | /lib
6 | /package-lock.json
7 | /tmp
8 | node_modules
9 | oclif.manifest.json
10 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | /dist
4 | /coverage
5 | /tmp
6 | apps/api/src/schema.graphql
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 100,
4 | "semi": false,
5 | "trailingComma": "all"
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Bram Borggreve https://github.com/beeman
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nxpm
2 |
3 | ## Looking for the full-stack generator?
4 |
5 | ### ➡️ Check here [github.com/nxpm/stack](https://github.com/nxpm/stack)
6 |
7 |

8 |
9 | CLI to make the world-class nx workspace even more amazing!
10 |
11 | nxpm.dev
12 |
13 | [](https://oclif.io)
14 | [](https://npmjs.org/package/nxpm)
15 | [](https://circleci.com/gh/nxpm/nxpm/tree/master)
16 | [](https://npmjs.org/package/nxpm)
17 | [](https://github.com/nxpm/nxpm/blob/master/package.json)
18 |
19 |
20 | * [nxpm](#nxpm)
21 | * [Usage](#usage)
22 | * [Commands](#commands)
23 |
24 |
25 | ### nxpm plugins
26 |
27 | Interactively install and remove plugins, run schematics from installed plugins.
28 |
29 | 
30 |
31 | ### nxpm projects
32 |
33 | Interactively browse the projects in a workspace and run builders and schematics.
34 |
35 | 
36 |
37 | ### nxpm sandbox
38 |
39 | Quickly spin up Docker based sandboxes with various NX presets installed.
40 |
41 | 
42 |
43 | # Usage
44 |
45 |
46 | ```sh-session
47 | $ npm install -g nxpm
48 | $ nxpm COMMAND
49 | running command...
50 | $ nxpm (-v|--version|version)
51 | nxpm/2.0.0 darwin-x64 node-v16.13.0
52 | $ nxpm --help [COMMAND]
53 | USAGE
54 | $ nxpm COMMAND
55 | ...
56 | ```
57 |
58 |
59 | # Commands
60 |
61 |
62 | * [`nxpm config:delete`](#nxpm-configdelete)
63 | * [`nxpm config:edit`](#nxpm-configedit)
64 | * [`nxpm config:get KEY`](#nxpm-configget-key)
65 | * [`nxpm config:set KEY VALUE`](#nxpm-configset-key-value)
66 | * [`nxpm help [COMMAND]`](#nxpm-help-command)
67 | * [`nxpm plugins`](#nxpm-plugins)
68 | * [`nxpm projects [PROJECTNAME] [TARGET]`](#nxpm-projects-projectname-target)
69 | * [`nxpm registry:disable`](#nxpm-registrydisable)
70 | * [`nxpm registry:enable`](#nxpm-registryenable)
71 | * [`nxpm registry:start`](#nxpm-registrystart)
72 | * [`nxpm registry:status`](#nxpm-registrystatus)
73 | * [`nxpm release [VERSION]`](#nxpm-release-version)
74 | * [`nxpm sandbox [SANDBOXID] [ACTION]`](#nxpm-sandbox-sandboxid-action)
75 | * [`nxpm sandbox:pull`](#nxpm-sandboxpull)
76 |
77 | ## `nxpm config:delete`
78 |
79 | Delete the config file
80 |
81 | ```
82 | USAGE
83 | $ nxpm config:delete
84 |
85 | OPTIONS
86 | -g, --global (required) Global config
87 | -h, --help show CLI help
88 | ```
89 |
90 | _See code: [src/commands/config/delete.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/config/delete.ts)_
91 |
92 | ## `nxpm config:edit`
93 |
94 | Edit the config file
95 |
96 | ```
97 | USAGE
98 | $ nxpm config:edit
99 |
100 | OPTIONS
101 | -g, --global (required) Global config
102 | -h, --help show CLI help
103 | ```
104 |
105 | _See code: [src/commands/config/edit.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/config/edit.ts)_
106 |
107 | ## `nxpm config:get KEY`
108 |
109 | describe the command here
110 |
111 | ```
112 | USAGE
113 | $ nxpm config:get KEY
114 |
115 | OPTIONS
116 | -g, --global (required) Global config
117 | -h, --help show CLI help
118 | ```
119 |
120 | _See code: [src/commands/config/get.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/config/get.ts)_
121 |
122 | ## `nxpm config:set KEY VALUE`
123 |
124 | describe the command here
125 |
126 | ```
127 | USAGE
128 | $ nxpm config:set KEY VALUE
129 |
130 | OPTIONS
131 | -g, --global (required) Global config
132 | -h, --help show CLI help
133 | ```
134 |
135 | _See code: [src/commands/config/set.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/config/set.ts)_
136 |
137 | ## `nxpm help [COMMAND]`
138 |
139 | display help for nxpm
140 |
141 | ```
142 | USAGE
143 | $ nxpm help [COMMAND]
144 |
145 | ARGUMENTS
146 | COMMAND command to show help for
147 |
148 | OPTIONS
149 | --all see all commands in CLI
150 | ```
151 |
152 | _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.0.1/src/commands/help.ts)_
153 |
154 | ## `nxpm plugins`
155 |
156 | Install and remove community plugins
157 |
158 | ```
159 | USAGE
160 | $ nxpm plugins
161 |
162 | OPTIONS
163 | -c, --cwd=cwd [default: /Users/beeman/nxpm/nxpm-cli] Current working directory
164 | -h, --help show CLI help
165 | -r, --refresh Refresh the list of plugins
166 |
167 | ALIASES
168 | $ nxpm pl
169 | ```
170 |
171 | _See code: [src/commands/plugins.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/plugins.ts)_
172 |
173 | ## `nxpm projects [PROJECTNAME] [TARGET]`
174 |
175 | Interactive menu to run builders and schematics for projects
176 |
177 | ```
178 | USAGE
179 | $ nxpm projects [PROJECTNAME] [TARGET]
180 |
181 | ARGUMENTS
182 | PROJECTNAME The name of the project you want to operate on
183 | TARGET The target to run (build, serve, test, etc)
184 |
185 | OPTIONS
186 | -c, --cwd=cwd [default: /Users/beeman/nxpm/nxpm-cli] Current working directory
187 | -h, --help show CLI help
188 |
189 | ALIASES
190 | $ nxpm p
191 | ```
192 |
193 | _See code: [src/commands/projects.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/projects.ts)_
194 |
195 | ## `nxpm registry:disable`
196 |
197 | Disable yarn and npm from using local npm registry
198 |
199 | ```
200 | USAGE
201 | $ nxpm registry:disable
202 | ```
203 |
204 | _See code: [src/commands/registry/disable.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/registry/disable.ts)_
205 |
206 | ## `nxpm registry:enable`
207 |
208 | Configure yarn and npm to use the local registry
209 |
210 | ```
211 | USAGE
212 | $ nxpm registry:enable
213 | ```
214 |
215 | _See code: [src/commands/registry/enable.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/registry/enable.ts)_
216 |
217 | ## `nxpm registry:start`
218 |
219 | Start local npm registry
220 |
221 | ```
222 | USAGE
223 | $ nxpm registry:start
224 | ```
225 |
226 | _See code: [src/commands/registry/start.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/registry/start.ts)_
227 |
228 | ## `nxpm registry:status`
229 |
230 | Show yarn and npm registry configuration
231 |
232 | ```
233 | USAGE
234 | $ nxpm registry:status
235 | ```
236 |
237 | _See code: [src/commands/registry/status.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/registry/status.ts)_
238 |
239 | ## `nxpm release [VERSION]`
240 |
241 | Release publishable packages in an Nx Workspace
242 |
243 | ```
244 | USAGE
245 | $ nxpm release [VERSION]
246 |
247 | ARGUMENTS
248 | VERSION The version you want to release in semver format (eg: 1.2.3-beta.4)
249 |
250 | OPTIONS
251 | -b, --build Build libraries after versioning
252 | -c, --cwd=cwd [default: /Users/beeman/nxpm/nxpm-cli] Current working directory
253 | -d, --dry-run Dry run, don't make permanent changes
254 | -f, --fix Automatically fix known issues
255 | -h, --help show CLI help
256 | -i, --allow-ivy Allow publishing Angular packages built for Ivy
257 | --ci CI mode (fully automatic release)
258 | --local Release package to local registry
259 | --localUrl=localUrl [default: http://localhost:4873/] URL to local registry
260 | ```
261 |
262 | _See code: [src/commands/release.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/release.ts)_
263 |
264 | ## `nxpm sandbox [SANDBOXID] [ACTION]`
265 |
266 | Create a sandbox using Docker
267 |
268 | ```
269 | USAGE
270 | $ nxpm sandbox [SANDBOXID] [ACTION]
271 |
272 | ARGUMENTS
273 | SANDBOXID The ID of the sandbox
274 | ACTION Action to perform on sandbox
275 |
276 | OPTIONS
277 | -c, --cwd=cwd [default: /Users/beeman/nxpm/nxpm-cli] Current working directory
278 | -h, --help show CLI help
279 | -r, --refresh Refresh the list of plugins
280 | --port-api=port-api [default: 3000] Port to open for the API app
281 | --port-web=port-web [default: 4200] Port to open for the Web app
282 | --ports=ports Comma-separated list of additional ports to open (eg: 8080, 10080:80)
283 | ```
284 |
285 | _See code: [src/commands/sandbox.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/sandbox.ts)_
286 |
287 | ## `nxpm sandbox:pull`
288 |
289 | Pull images of sandboxes
290 |
291 | ```
292 | USAGE
293 | $ nxpm sandbox:pull
294 |
295 | OPTIONS
296 | -f, --force Force removal of the sandboxes
297 | -h, --help show CLI help
298 | -m, --remove Remove all of the sandboxes before pulling
299 | -r, --refresh Refresh the list of sandboxes
300 | ```
301 |
302 | _See code: [src/commands/sandbox/pull.ts](https://github.com/nxpm/nxpm-cli/blob/v2.0.0/src/commands/sandbox/pull.ts)_
303 |
304 |
--------------------------------------------------------------------------------
/bin/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require('@oclif/command').run()
4 | .then(require('@oclif/command/flush'))
5 | .catch(require('@oclif/errors/handle'))
6 |
--------------------------------------------------------------------------------
/bin/run.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | node "%~dp0\run" %*
4 |
--------------------------------------------------------------------------------
/nxpm-plugins.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nxpm/nxpm-cli/644e446a2babf32beb25eaf0e14c215a7d7e03cf/nxpm-plugins.gif
--------------------------------------------------------------------------------
/nxpm-projects.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nxpm/nxpm-cli/644e446a2babf32beb25eaf0e14c215a7d7e03cf/nxpm-projects.gif
--------------------------------------------------------------------------------
/nxpm-sandbox.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nxpm/nxpm-cli/644e446a2babf32beb25eaf0e14c215a7d7e03cf/nxpm-sandbox.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nxpm",
3 | "description": "nxpm cli",
4 | "version": "2.0.0",
5 | "author": "Bram Borggreve @beeman",
6 | "bin": {
7 | "nxpm": "./bin/run"
8 | },
9 | "bugs": "https://github.com/nxpm/nxpm-cli/issues",
10 | "dependencies": {
11 | "@angular-devkit/core": "^9.1.6",
12 | "@angular-devkit/schematics": "^9.1.6",
13 | "@angular/cli": "^9.1.6",
14 | "@angular/compiler": "^9.1.7",
15 | "@angular/compiler-cli": "^9.1.7",
16 | "@nrwl/workspace": "^14.1.4",
17 | "@oclif/command": "^1.6.1",
18 | "@oclif/config": "^1.15.1",
19 | "@oclif/plugin-help": "^3.0.1",
20 | "chalk": "^4.0.0",
21 | "cli-ux": "^5.4.5",
22 | "fs-extra": "^9.0.0",
23 | "inquirer": "^7.1.0",
24 | "json5": "^2.1.3",
25 | "lodash": "^4.17.15",
26 | "node-fetch": "^2.6.0",
27 | "release-it": "^13.5.8",
28 | "tslib": "^1.13.0"
29 | },
30 | "devDependencies": {
31 | "@nrwl/devkit": "^14.1.4",
32 | "@nrwl/tao": "^14.1.4",
33 | "@oclif/dev-cli": "^1.22.2",
34 | "@oclif/test": "^1.2.6",
35 | "@types/chai": "^4.2.11",
36 | "@types/fs-extra": "^8.1.0",
37 | "@types/inquirer": "^6.5.0",
38 | "@types/json5": "^0.0.30",
39 | "@types/mocha": "^7.0.2",
40 | "@types/node": "^14.0.1",
41 | "@types/node-fetch": "^2.5.7",
42 | "@types/tmp": "^0.2.0",
43 | "chai": "^4.2.0",
44 | "eslint": "^5.13",
45 | "eslint-config-oclif": "^3.1.0",
46 | "eslint-config-oclif-typescript": "^0.1.0",
47 | "globby": "^11.0.0",
48 | "husky": "^4.2.5",
49 | "lint-staged": "^10.2.2",
50 | "mocha": "^7.1.2",
51 | "nx": "^14.6.5",
52 | "nyc": "^15.0.1",
53 | "ts-node": "^8.10.1",
54 | "typescript": "4.4.4"
55 | },
56 | "engines": {
57 | "node": ">=12.0.0"
58 | },
59 | "files": [
60 | "/bin",
61 | "/lib",
62 | "/npm-shrinkwrap.json",
63 | "/oclif.manifest.json"
64 | ],
65 | "homepage": "https://nxpm.dev/",
66 | "keywords": [
67 | "oclif"
68 | ],
69 | "license": "MIT",
70 | "main": "lib/index.js",
71 | "oclif": {
72 | "commands": "./lib/commands",
73 | "bin": "nxpm",
74 | "plugins": [
75 | "@oclif/plugin-help"
76 | ]
77 | },
78 | "repository": "nxpm/nxpm-cli",
79 | "scripts": {
80 | "build": "npx tsc",
81 | "postpack": "rm -f oclif.manifest.json",
82 | "posttest": "eslint . --ext .ts --config .eslintrc",
83 | "prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
84 | "test": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"",
85 | "version": "oclif-dev readme && git add README.md"
86 | },
87 | "types": "lib/index.d.ts"
88 | }
89 |
--------------------------------------------------------------------------------
/src/commands/config/delete.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { deleteConfig } from '../../lib/config/config'
3 | import { BaseCommand } from '../../utils'
4 |
5 | export default class ConfigDelete extends BaseCommand {
6 | static description = 'Delete the config file'
7 |
8 | static flags = {
9 | help: flags.help({ char: 'h' }),
10 | global: flags.boolean({ char: 'g', description: 'Global config', required: true }),
11 | }
12 |
13 | static args = []
14 |
15 | async run() {
16 | const { flags } = this.parse(ConfigDelete)
17 |
18 | await deleteConfig({
19 | global: flags.global,
20 | userConfig: this.userConfig,
21 | config: this.config,
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/config/edit.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { editConfig } from '../../lib/config/config'
3 | import { BaseCommand } from '../../utils'
4 |
5 | export default class ConfigEdit extends BaseCommand {
6 | static description = 'Edit the config file'
7 |
8 | static flags = {
9 | help: flags.help({ char: 'h' }),
10 | global: flags.boolean({ char: 'g', description: 'Global config', required: true }),
11 | }
12 |
13 | static args = []
14 |
15 | async run() {
16 | const { flags } = this.parse(ConfigEdit)
17 |
18 | await editConfig({
19 | global: flags.global,
20 | userConfig: this.userConfig,
21 | config: this.config,
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/config/get.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { getConfigParam } from '../../lib/config/config'
3 | import { BaseCommand } from '../../utils'
4 |
5 | export default class ConfigGet extends BaseCommand {
6 | static description = 'describe the command here'
7 |
8 | static flags = {
9 | help: flags.help({ char: 'h' }),
10 | global: flags.boolean({ char: 'g', description: 'Global config', required: true }),
11 | }
12 |
13 | static args = [
14 | {
15 | name: 'key',
16 | required: true,
17 | },
18 | ]
19 |
20 | async run() {
21 | const { args, flags } = this.parse(ConfigGet)
22 |
23 | await getConfigParam({
24 | global: flags.global,
25 | key: args.key,
26 | userConfig: this.userConfig,
27 | config: this.config,
28 | })
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/config/set.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { setConfigParam } from '../../lib/config/config'
3 | import { BaseCommand } from '../../utils'
4 |
5 | export default class ConfigGet extends BaseCommand {
6 | static description = 'describe the command here'
7 |
8 | static flags = {
9 | help: flags.help({ char: 'h' }),
10 | // flag with a value (-n, --name=VALUE)
11 | global: flags.boolean({ char: 'g', description: 'Global config', required: true }),
12 | }
13 |
14 | static args = [
15 | {
16 | name: 'key',
17 | required: true,
18 | },
19 | { name: 'value', required: true },
20 | ]
21 |
22 | async run() {
23 | const { args, flags } = this.parse(ConfigGet)
24 |
25 | await setConfigParam({
26 | global: flags.global,
27 | key: args.key,
28 | value: args.value,
29 | userConfig: this.userConfig,
30 | config: this.config,
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/commands/plugins.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { plugins } from '../lib/plugins/plugins'
3 | import { BaseCommand } from '../utils'
4 |
5 | export default class Plugins extends BaseCommand {
6 | static aliases = ['pl']
7 |
8 | static description = 'Install and remove community plugins'
9 |
10 | static flags = {
11 | cwd: flags.string({
12 | char: 'c',
13 | description: 'Current working directory',
14 | default: process.cwd(),
15 | }),
16 | help: flags.help({ char: 'h' }),
17 | refresh: flags.boolean({
18 | char: 'r',
19 | description: 'Refresh the list of plugins',
20 | default: false,
21 | }),
22 | }
23 |
24 | async run() {
25 | const { flags } = this.parse(Plugins)
26 |
27 | await plugins({
28 | cwd: flags.cwd,
29 | userConfig: this.userConfig,
30 | refresh: flags.refresh,
31 | config: this.config,
32 | })
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/commands/projects.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { projects } from '../lib/projects/projects'
3 | import { BaseCommand } from '../utils'
4 |
5 | export default class Projects extends BaseCommand {
6 | static aliases = ['p']
7 |
8 | static description = 'Interactive menu to run builders and schematics for projects'
9 |
10 | static flags = {
11 | cwd: flags.string({
12 | char: 'c',
13 | description: 'Current working directory',
14 | default: process.cwd(),
15 | }),
16 | help: flags.help({ char: 'h' }),
17 | }
18 |
19 | static args = [
20 | {
21 | name: 'projectName',
22 | description: 'The name of the project you want to operate on',
23 | required: false,
24 | },
25 | {
26 | name: 'target',
27 | description: 'The target to run (build, serve, test, etc)',
28 | required: false,
29 | },
30 | ]
31 |
32 | async run() {
33 | const { args, flags } = this.parse(Projects)
34 |
35 | await projects(
36 | { cwd: flags.cwd, dryRun: false, userConfig: this.userConfig, config: this.config },
37 | args.projectName,
38 | args.target,
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/commands/registry/disable.ts:
--------------------------------------------------------------------------------
1 | import { Command } from '@oclif/command'
2 | import { disableRegistry } from '../../lib/verdaccio'
3 |
4 | export default class RegistryDisable extends Command {
5 | static description = 'Disable yarn and npm from using local npm registry'
6 |
7 | async run() {
8 | disableRegistry()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/registry/enable.ts:
--------------------------------------------------------------------------------
1 | import { Command } from '@oclif/command'
2 | import { enableRegistry } from '../../lib/verdaccio'
3 |
4 | export default class RegistryEnable extends Command {
5 | static description = 'Configure yarn and npm to use the local registry'
6 |
7 | async run() {
8 | enableRegistry()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/registry/start.ts:
--------------------------------------------------------------------------------
1 | import { Command } from '@oclif/command'
2 | import { startRegistry } from '../../lib/verdaccio'
3 |
4 | export default class RegistryStart extends Command {
5 | static description = 'Start local npm registry'
6 |
7 | async run() {
8 | startRegistry()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/registry/status.ts:
--------------------------------------------------------------------------------
1 | import { Command } from '@oclif/command'
2 | import { registryStatus } from '../../lib/verdaccio'
3 |
4 | export default class RegistryStatus extends Command {
5 | static description = 'Show yarn and npm registry configuration'
6 |
7 | async run() {
8 | registryStatus()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/release.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import * as inquirer from 'inquirer'
3 | import { release } from '../lib/release/release'
4 | import { BaseCommand, log } from '../utils'
5 | import { parseVersion } from '../utils/parse-version'
6 |
7 | export default class Release extends BaseCommand {
8 | static description = 'Release publishable packages in an Nx Workspace'
9 |
10 | static flags = {
11 | build: flags.boolean({ char: 'b', description: 'Build libraries after versioning' }),
12 | ci: flags.boolean({
13 | description: 'CI mode (fully automatic release)',
14 | default: false,
15 | }),
16 | cwd: flags.string({
17 | char: 'c',
18 | description: 'Current working directory',
19 | default: process.cwd(),
20 | }),
21 | 'dry-run': flags.boolean({ char: 'd', description: "Dry run, don't make permanent changes" }),
22 | help: flags.help({ char: 'h' }),
23 | fix: flags.boolean({ char: 'f', description: 'Automatically fix known issues' }),
24 | local: flags.boolean({
25 | description: 'Release package to local registry',
26 | default: false,
27 | }),
28 | localUrl: flags.string({
29 | description: 'URL to local registry',
30 | default: 'http://localhost:4873/',
31 | }),
32 | }
33 |
34 | static args = [
35 | {
36 | name: 'version',
37 | description: 'The version you want to release in semver format (eg: 1.2.3-beta.4)',
38 | required: false,
39 | },
40 | ]
41 |
42 | async run() {
43 | const { args, flags } = this.parse(Release)
44 |
45 | if (!args.version) {
46 | const response = await inquirer.prompt([
47 | {
48 | name: 'version',
49 | type: 'input',
50 | message: 'What version do you want to release?',
51 | validate(version: string): boolean | string {
52 | if (!parseVersion(version).isValid) {
53 | return 'Please use a valid semver version (eg: 1.2.3-beta.4)'
54 | }
55 | return true
56 | },
57 | },
58 | ])
59 | args.version = response.version
60 | }
61 |
62 | if (this.userConfig?.release?.github?.token) {
63 | log('GITHUB_TOKEN', 'Using token from config file')
64 | process.env.GITHUB_TOKEN = this.userConfig?.release?.github?.token
65 | }
66 |
67 | await release({
68 | build: flags.build,
69 | ci: flags.ci,
70 | config: this.config,
71 | cwd: flags.cwd,
72 | dryRun: flags['dry-run'],
73 | fix: flags.fix,
74 | version: args.version,
75 | local: flags.local,
76 | localUrl: flags.localUrl,
77 | })
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/commands/sandbox.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { sandbox } from '../lib/sandbox/sandbox'
3 | import { BaseCommand } from '../utils'
4 |
5 | export default class Sandbox extends BaseCommand {
6 | static description = 'Create a sandbox using Docker'
7 |
8 | static flags = {
9 | cwd: flags.string({
10 | char: 'c',
11 | description: 'Current working directory',
12 | default: process.cwd(),
13 | }),
14 | help: flags.help({ char: 'h' }),
15 | refresh: flags.boolean({
16 | char: 'r',
17 | description: 'Refresh the list of plugins',
18 | default: false,
19 | }),
20 | 'port-api': flags.string({
21 | description: 'Port to open for the API app',
22 | default: '3000',
23 | }),
24 | 'port-web': flags.string({
25 | description: 'Port to open for the Web app',
26 | default: '4200',
27 | }),
28 | ports: flags.string({
29 | description: 'Comma-separated list of additional ports to open (eg: 8080, 10080:80)',
30 | default: '',
31 | }),
32 | }
33 |
34 | static args = [
35 | {
36 | name: 'sandboxId',
37 | description: 'The ID of the sandbox',
38 | required: false,
39 | },
40 | {
41 | name: 'action',
42 | description: 'Action to perform on sandbox',
43 | required: false,
44 | },
45 | ]
46 |
47 | async run() {
48 | const { args, flags } = this.parse(Sandbox)
49 |
50 | await sandbox({
51 | action: args.action,
52 | config: this.config,
53 | refresh: flags.refresh,
54 | portApi: flags['port-api'],
55 | portWeb: flags['port-web'],
56 | ports: flags.ports,
57 | sandboxId: args.sandboxId,
58 | userConfig: this.userConfig,
59 | })
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/commands/sandbox/pull.ts:
--------------------------------------------------------------------------------
1 | import { flags } from '@oclif/command'
2 | import { sandboxPull } from '../../lib/sandbox/sandbox-pull'
3 | import { BaseCommand } from '../../utils'
4 |
5 | export default class SandboxPull extends BaseCommand {
6 | static description = 'Pull images of sandboxes'
7 |
8 | static flags = {
9 | force: flags.boolean({
10 | char: 'f',
11 | description: 'Force removal of the sandboxes',
12 | default: false,
13 | }),
14 | help: flags.help({ char: 'h' }),
15 | refresh: flags.boolean({
16 | char: 'r',
17 | description: 'Refresh the list of sandboxes',
18 | default: false,
19 | }),
20 | remove: flags.boolean({
21 | char: 'm',
22 | description: 'Remove all of the sandboxes before pulling',
23 | default: false,
24 | }),
25 | }
26 |
27 | async run() {
28 | const { flags } = this.parse(SandboxPull)
29 |
30 | await sandboxPull({
31 | force: flags.force,
32 | refresh: flags.refresh,
33 | remove: flags.remove,
34 | config: this.config,
35 | userConfig: this.userConfig,
36 | })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'release-it'
2 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { run } from '@oclif/command'
2 | export * from './lib/release/release'
3 | export * from './utils'
4 | export { ReleaseConfig } from './lib/release/interfaces/release-config'
5 |
--------------------------------------------------------------------------------
/src/lib/config/config.ts:
--------------------------------------------------------------------------------
1 | import { IConfig } from '@oclif/config'
2 | import { unlink } from 'fs-extra'
3 | import { get, has, set } from 'lodash'
4 | import { error, exec, log, warning } from '../../utils'
5 | import { UserConfig } from '../../utils/user-config'
6 | import { getConfigFile, getConfigFilePath, updateConfigFile } from './utils/config-utils'
7 |
8 | export interface EditConfigParamOptions {
9 | config: IConfig
10 | global: boolean
11 | userConfig: UserConfig
12 | }
13 |
14 | export interface GetConfigParamOptions extends EditConfigParamOptions {
15 | key: string
16 | }
17 | export interface SetConfigParamOptions extends GetConfigParamOptions {
18 | value: string
19 | }
20 |
21 | function validateOptions(
22 | options: EditConfigParamOptions | GetConfigParamOptions | SetConfigParamOptions,
23 | ) {
24 | if (!options.global) {
25 | error(`The cli currently only supports global variables.`)
26 | process.exit(1)
27 | }
28 | }
29 |
30 | export async function deleteConfig(options: EditConfigParamOptions) {
31 | validateOptions(options)
32 | const configFile = await getConfigFilePath(options.config)
33 | await unlink(configFile)
34 | log('DELETE', `Deleted ${configFile}`)
35 | }
36 |
37 | export async function editConfig(options: EditConfigParamOptions) {
38 | validateOptions(options)
39 | const configFile = await getConfigFilePath(options.config)
40 | const editor = process.env.EDITOR || 'vim'
41 | const command = `${editor} ${configFile}`
42 | exec(command)
43 | }
44 |
45 | export async function getConfigParam(options: GetConfigParamOptions) {
46 | validateOptions(options)
47 | const configFile = await getConfigFile(options.config)
48 | const keyExists = has(configFile, options.key)
49 |
50 | if (!keyExists) {
51 | warning(`Option ${options.key} is not set`)
52 | process.exit()
53 | }
54 | console.log(JSON.stringify(get(configFile, options.key), null, 2))
55 | }
56 |
57 | export async function setConfigParam(options: SetConfigParamOptions) {
58 | validateOptions(options)
59 | const configFile = await getConfigFile(options.config)
60 | const keyExists = has(configFile, options.key)
61 |
62 | if (keyExists && get(configFile, options.key) === options.value) {
63 | warning(`Option ${options.key} is already set to ${options.value}`)
64 | process.exit()
65 | }
66 | const updated = set(configFile, options.key, options.value)
67 | await updateConfigFile(options.config, updated)
68 | log(keyExists ? 'UPDATE' : 'CREATE', `Set option ${options.key} to ${options.value}`)
69 | }
70 |
--------------------------------------------------------------------------------
/src/lib/config/utils/config-utils.ts:
--------------------------------------------------------------------------------
1 | import { IConfig } from '@oclif/config'
2 | import { readJSON, writeJSON } from 'fs-extra'
3 | import { join } from 'path'
4 | import { UserConfig } from '../../../utils/user-config'
5 |
6 | export function getConfigFilePath(config: IConfig) {
7 | return join(config.configDir, 'config.json')
8 | }
9 | export async function getConfigFile(config: IConfig) {
10 | return readJSON(getConfigFilePath(config))
11 | }
12 |
13 | export async function updateConfigFile(config: IConfig, content: UserConfig) {
14 | return writeJSON(getConfigFilePath(config), content, { spaces: 2 })
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/plugins/interfaces/plugin-config.ts:
--------------------------------------------------------------------------------
1 | import { BaseConfig } from '../../../utils/base-config'
2 |
3 | export interface PluginConfig extends BaseConfig {
4 | cwd: string
5 | refresh: boolean
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/plugins/plugins.ts:
--------------------------------------------------------------------------------
1 | import { readJSON } from 'fs-extra'
2 | import * as inquirer from 'inquirer'
3 | import { join } from 'path'
4 | import {
5 | error,
6 | exec,
7 | getWorkspaceInfo,
8 | gray,
9 | log,
10 | NX_COMMUNITY_PLUGINS_URL,
11 | NX_PLUGINS_URL,
12 | NXPM_PLUGINS_CACHE,
13 | NXPM_PLUGINS_URL,
14 | selectFromList,
15 | WorkspaceInfo,
16 | } from '../../utils'
17 | import { readAllSchematicCollections } from '../../utils/vendor/nx-console/read-schematic-collections'
18 | import { SchematicCollection } from '../../utils/vendor/nx-console/schema'
19 | import { BACK_OPTION, INSTALL_OPTION, REMOVE_OPTION } from '../projects/projects'
20 | import { PluginConfig } from './interfaces/plugin-config'
21 | import { pluginUrlCache } from './utils/plugin-utils'
22 |
23 | export interface NxPlugin {
24 | name: string
25 | description: string
26 | url: string
27 | }
28 |
29 | export const loadPluginsSchematics = async (info: WorkspaceInfo, config: PluginConfig) => {
30 | const cacheFile = join(config.config.cacheDir, NXPM_PLUGINS_CACHE)
31 | const pluginGroups = await readJSON(cacheFile)
32 | const plugins = Object.values(pluginGroups).flat()
33 | const schematics = await readAllSchematicCollections(info.workspaceJsonPath)
34 | const schematicsNames = schematics.map((s: SchematicCollection) => s.name)
35 | const availablePlugins = plugins.filter((p: NxPlugin) => !schematicsNames.includes(p.name))
36 | const installedPlugins = plugins.filter((p: NxPlugin) => schematicsNames.includes(p.name))
37 | const availablePluginNames = availablePlugins.map((p: NxPlugin) => p.name)
38 | const installedPluginNames = installedPlugins.map((p: NxPlugin) => p.name)
39 | return {
40 | plugins,
41 | schematics,
42 | schematicsNames,
43 | availablePlugins,
44 | availablePluginNames,
45 | installedPlugins,
46 | installedPluginNames,
47 | pluginNames: [...availablePluginNames, ...installedPluginNames],
48 | }
49 | }
50 |
51 | export const selectPlugin = async (
52 | plugins: any[],
53 | message: string,
54 | ): Promise<{ pluginName: string } | false> => {
55 | const pluginName = await selectFromList(plugins, { message, addExit: true })
56 | if (!pluginName) {
57 | return false
58 | }
59 | return { pluginName }
60 | }
61 |
62 | export const selectPluginFlow = async (
63 | info: WorkspaceInfo,
64 | config: PluginConfig,
65 | pluginName?: string,
66 | ): Promise<{ selection: string; pluginName: string; plugin?: NxPlugin } | false> => {
67 | const {
68 | availablePluginNames,
69 | installedPluginNames,
70 | plugins,
71 | schematics,
72 | } = await loadPluginsSchematics(info, config)
73 | const options: any[] = []
74 |
75 | if (!pluginName) {
76 | console.clear()
77 | if (availablePluginNames.length !== 0) {
78 | options.push(new inquirer.Separator('Available Plugins'), ...availablePluginNames.sort())
79 | }
80 | if (installedPluginNames.length !== 0) {
81 | options.push(new inquirer.Separator('Installed Plugins'), ...installedPluginNames.sort())
82 | }
83 | const pluginResult = await selectPlugin(options, 'Plugins')
84 |
85 | if (!pluginResult) {
86 | return Promise.resolve(false)
87 | }
88 | pluginName = pluginResult.pluginName
89 | }
90 |
91 | const plugin: any = plugins.find((p: NxPlugin) => p.name === pluginName)
92 |
93 | if (!plugin) {
94 | error(`Plugin ${pluginName} not found`)
95 | return Promise.resolve(false)
96 | }
97 |
98 | // eslint-disable-next-line no-console
99 | console.log(`
100 | ${plugin.description}
101 | ${gray(plugin.url)}
102 | `)
103 | const isInstalled = installedPluginNames.includes(pluginName)
104 | const availableOptions = [INSTALL_OPTION]
105 | const installedOptions = [REMOVE_OPTION]
106 |
107 | if (isInstalled) {
108 | const found = schematics.find((s: SchematicCollection) => s.name === pluginName)
109 | const schematicNames = found?.schematics.map((s) => s.name).reverse()
110 | schematicNames?.forEach((name: string) => installedOptions.unshift(`${pluginName}:${name}`))
111 | }
112 |
113 | const selection = await selectFromList(isInstalled ? installedOptions : availableOptions, {
114 | addBack: true,
115 | addExit: true,
116 | message: pluginName,
117 | })
118 |
119 | if (!selection) {
120 | return Promise.resolve(false)
121 | }
122 |
123 | return {
124 | selection,
125 | pluginName,
126 | plugin,
127 | }
128 | }
129 |
130 | const loop = async (
131 | info: WorkspaceInfo,
132 | config: PluginConfig,
133 | { pluginName }: { pluginName?: string },
134 | ) => {
135 | const result = await selectPluginFlow(info, config, pluginName)
136 |
137 | if (!result) {
138 | return
139 | }
140 |
141 | if (result.selection === INSTALL_OPTION) {
142 | const command =
143 | info.packageManager === 'yarn'
144 | ? `yarn add ${result.pluginName}`
145 | : `npm install ${result.pluginName}`
146 | log('Installing plugin')
147 | exec(command, { stdio: 'ignore' })
148 | console.clear()
149 | await loop(info, config, { pluginName: result.pluginName })
150 | }
151 |
152 | if (result.selection === REMOVE_OPTION) {
153 | const command =
154 | info.packageManager === 'yarn'
155 | ? `yarn remove ${result.pluginName}`
156 | : `npm uninstall ${result.pluginName}`
157 | log('Removing plugin')
158 | exec(command, { stdio: 'ignore' })
159 | log('Done')
160 | }
161 |
162 | if (result.selection.startsWith(result.pluginName)) {
163 | log('Running schematic', result.selection)
164 | const command = `nx generate ${result.selection}`
165 | exec(command)
166 | log('Done')
167 | }
168 |
169 | if (result.selection === BACK_OPTION) {
170 | await loop(info, config, { pluginName })
171 | }
172 | }
173 |
174 | export const plugins = async (config: PluginConfig): Promise => {
175 | const info = getWorkspaceInfo({ cwd: config.cwd })
176 | await pluginUrlCache(config)
177 | await loop(info, config, {})
178 | }
179 |
--------------------------------------------------------------------------------
/src/lib/plugins/utils/plugin-utils.ts:
--------------------------------------------------------------------------------
1 | import { cli } from 'cli-ux'
2 | import { existsSync } from 'fs'
3 | import { join } from 'path'
4 | import {
5 | cacheUrls,
6 | NX_COMMUNITY_PLUGINS_URL,
7 | NX_PLUGINS_URL,
8 | NXPM_PLUGINS_CACHE,
9 | NXPM_PLUGINS_URL,
10 | } from '../../../utils'
11 | import { PluginConfig } from '../interfaces/plugin-config'
12 |
13 | export async function pluginUrlCache(config: PluginConfig) {
14 | const cacheFile = join(config.config.cacheDir, NXPM_PLUGINS_CACHE)
15 | const urls = [NX_PLUGINS_URL, NX_COMMUNITY_PLUGINS_URL, NXPM_PLUGINS_URL]
16 |
17 | if (config.userConfig?.plugins?.urls) {
18 | urls.push(...config.userConfig?.plugins?.urls)
19 | }
20 |
21 | if (!existsSync(join(cacheFile)) || config.refresh) {
22 | cli.action.start(`Downloading plugins registry from ${urls.length} source(s)`)
23 | await cacheUrls(urls, cacheFile)
24 | cli.action.stop()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/projects/projects.ts:
--------------------------------------------------------------------------------
1 | import { readJSONSync } from 'fs-extra'
2 | import * as inquirer from 'inquirer'
3 | import { get } from 'lodash'
4 | import { join } from 'path'
5 | import {
6 | error,
7 | exec,
8 | getWorkspaceInfo,
9 | gray,
10 | log,
11 | selectFromList,
12 | WorkspaceInfo,
13 | } from '../../utils'
14 | import { BaseConfig } from '../../utils/base-config'
15 |
16 | export const BACK_OPTION = '[ BACK ]'
17 | export const EXIT_OPTION = '[ EXIT ]'
18 | export const INSTALL_OPTION = '[ INSTALL ]'
19 | export const REMOVE_OPTION = '[ REMOVE ]'
20 | export const RUN_OPTION = '[ RUN ]'
21 | export interface ProjectsConfig extends BaseConfig {
22 | cwd: string
23 | }
24 |
25 | export const setType = (type: string) => {
26 | switch (type) {
27 | case 'boolean':
28 | return 'confirm'
29 | default:
30 | return 'input'
31 | }
32 | }
33 | export const getSchematicParams = (cwd: string, param: string): Promise => {
34 | try {
35 | const [pkg, schematic] = param.split(':')
36 | const pkgRootPath = join(cwd, 'node_modules', pkg)
37 |
38 | // Get schematics property form {cwd}/node_modules/{pkg}/package.json
39 | const pkgJsonPath = join(pkgRootPath, 'package.json')
40 | const pkgJson = readJSONSync(pkgJsonPath)
41 | if (!pkgJson) {
42 | return Promise.reject(new Error(`Package ${pkg} not found`))
43 | }
44 | if (!pkgJson.schematics) {
45 | return Promise.reject(new Error(`Package ${pkg} does not have schematics`))
46 | }
47 |
48 | // Get collection.json defined in schematics
49 | const collectionPath = join(pkgRootPath, pkgJson.schematics)
50 | const collection = readJSONSync(collectionPath)
51 | if (!collection || !collection.schematics) {
52 | return Promise.reject(new Error(`Collections for ${pkg} not found`))
53 | }
54 | if (!collection.schematics[schematic]) {
55 | return Promise.reject(new Error(`Collection in ${pkg} does not have schematic ${schematic}`))
56 | }
57 |
58 | // Get schema.json from selected schematic
59 | const schematicDef = collection.schematics[schematic]
60 | if (!schematicDef.schema) {
61 | return Promise.resolve({})
62 | }
63 | const schemaPath = join(pkgRootPath, schematicDef.schema)
64 | const schema = readJSONSync(schemaPath)
65 |
66 | if (!schema || !schema.properties) {
67 | return Promise.reject(new Error(`Properties for ${pkg}:${schematic} not found`))
68 | }
69 |
70 | const schemaProperties = schema.properties
71 | const properties = [
72 | ...Object.keys(schemaProperties).map((property) => ({
73 | name: property,
74 | type: setType(schemaProperties[property].type),
75 | message: schemaProperties[property]?.description,
76 | default: schemaProperties[property]?.default,
77 | })),
78 | {
79 | name: 'dryRun',
80 | type: 'confirm',
81 | message: 'Do you want to do a dry-run?',
82 | default: false,
83 | },
84 | ]
85 |
86 | return Promise.resolve({ properties })
87 | } catch (error) {
88 | error(error)
89 | return Promise.reject(error)
90 | }
91 | }
92 |
93 | const selectProjectName = async (info: WorkspaceInfo): Promise => {
94 | const items: any = info.workspace?.projects || []
95 | if (Object.keys(items).length === 0) {
96 | error("Can't find any projects in this workspace")
97 | return Promise.resolve(false)
98 | }
99 |
100 | const projectList = Object.keys(items).map((item: string) => ({
101 | projectName: item,
102 | type: items[item].projectType,
103 | }))
104 | const apps: string[] = projectList
105 | .filter((t: any) => t.type === 'application')
106 | .map((t: any) => t.projectName)
107 | .sort()
108 | const libs: string[] = projectList
109 | .filter((t: any) => t.type === 'library')
110 | .map((t: any) => t.projectName)
111 | .sort()
112 |
113 | const options = []
114 | if (apps.length !== 0) {
115 | options.push(new inquirer.Separator('Apps'), ...apps)
116 | }
117 | if (libs.length !== 0) {
118 | options.push(new inquirer.Separator('Libraries:'), ...libs)
119 | }
120 | const projectName = await selectFromList(options, {
121 | addExit: true,
122 | message: `Select project (${projectList.length} found)`,
123 | })
124 |
125 | if (projectName === false) {
126 | return Promise.resolve(false)
127 | }
128 |
129 | return projectName
130 | }
131 |
132 | const selectProjectAction = async (
133 | info: WorkspaceInfo,
134 | {
135 | target,
136 | params,
137 | projectName,
138 | project,
139 | }: { target?: string; params: { [key: string]: any }; projectName: string; project: any },
140 | ): Promise<{ action: string; payload: any } | false> => {
141 | const answers: any = { projectName }
142 | const targets = Object.keys(project?.targets ?? project?.architect).sort()
143 | const schematics = ['@nrwl/workspace:move', '@nrwl/workspace:remove']
144 | const projectOptions: any[] = [
145 | new inquirer.Separator('Builders'),
146 | ...targets,
147 | new inquirer.Separator('Schematics'),
148 | ...schematics,
149 | ]
150 | if (!target) {
151 | const found = await selectFromList(projectOptions, {
152 | addExit: true,
153 | message: `Selected ${project.projectType} ${projectName} ${gray(project.root)}`,
154 | })
155 | if (!found) {
156 | error(`Action not found`)
157 | return Promise.resolve(false)
158 | }
159 | target = found
160 | }
161 |
162 | if (targets.includes(target)) {
163 | const architectParams = get(params, `${target}.params`, '')
164 | return {
165 | action: 'exec',
166 | payload: `nx run ${projectName}:${target} ${architectParams}`,
167 | }
168 | }
169 | if (schematics.includes(target)) {
170 | const params = await getSchematicParams(info.cwd, target)
171 | const payload = [`nx generate ${target}`]
172 | if (Object.keys(params.properties).length !== 0) {
173 | Object.keys(answers).forEach((answer) => payload.push(` --${answer} ${answers[answer]}`))
174 |
175 | const res: any = await inquirer.prompt(
176 | params.properties.filter((p: any) => {
177 | return !Object.keys(answers).includes(p.name)
178 | }),
179 | )
180 | Object.keys(res).forEach((answer) => payload.push(` --${answer} ${res[answer]}`))
181 | }
182 |
183 | return { action: 'exec', payload: payload.join(' ') }
184 | }
185 | return Promise.resolve(false)
186 | }
187 |
188 | export const interactive = async (
189 | info: WorkspaceInfo,
190 | config: ProjectsConfig,
191 | { projectName, target }: { projectName?: string; target?: string },
192 | ) => {
193 | if (!projectName) {
194 | const res = await selectProjectName(info)
195 | if (res) {
196 | projectName = res
197 | }
198 | }
199 |
200 | if (typeof projectName === 'undefined') {
201 | return
202 | }
203 |
204 | const project = info?.workspace?.projects[projectName]
205 |
206 | if (!project) {
207 | error(`Project ${projectName} not found`)
208 | return
209 | }
210 |
211 | const params = get(config?.userConfig, 'projects', {})
212 | const projectActionResult = await selectProjectAction(info, {
213 | target,
214 | projectName,
215 | project,
216 | params,
217 | })
218 |
219 | if (projectActionResult === false) {
220 | return
221 | }
222 |
223 | if (projectActionResult.action === 'exec') {
224 | exec(`${projectActionResult.payload}`)
225 | exec(`yarn format`, { stdio: 'ignore' })
226 | } else {
227 | error(`Unknown action ${projectActionResult.action}`)
228 | }
229 | }
230 | export const projects = async (
231 | config: ProjectsConfig,
232 | projectName?: string,
233 | target?: string,
234 | ): Promise => {
235 | log('Projects', gray(`Working directory ${config.cwd}`))
236 | const info = getWorkspaceInfo({ cwd: config.cwd })
237 |
238 | await interactive(info, config, { projectName, target })
239 | }
240 |
--------------------------------------------------------------------------------
/src/lib/release/interfaces/release-config.ts:
--------------------------------------------------------------------------------
1 | import { BaseConfig } from '../../../utils/base-config'
2 |
3 | export interface ReleaseConfig extends BaseConfig {
4 | build: boolean
5 | ci: boolean
6 | cwd: string
7 | dryRun: boolean
8 | fix: boolean
9 | local?: boolean
10 | localUrl?: string
11 | version: string
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/release/interfaces/validated-config.ts:
--------------------------------------------------------------------------------
1 | import { ReleaseConfig } from './release-config'
2 |
3 | export interface ValidatedConfig extends ReleaseConfig {
4 | // Trigger the build
5 | build: boolean
6 | // NPM Scope defined in nx.json
7 | npmScope: string
8 | // Tag we aim to publish
9 | npmTag: 'latest' | 'next'
10 | // Content of the nx.json file
11 | nx: { [key: string]: any }
12 | // Content of the package.json file
13 | package: { [key: string]: any }
14 | // Whether it's a pre release or not
15 | preRelease: boolean
16 | // Content of angular.json or workspace.json
17 | workspace: { [key: string]: any }
18 | // Path to angular.json or workspace.json
19 | workspacePath: string
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/release/interfaces/validated-packages.ts:
--------------------------------------------------------------------------------
1 | export interface ValidatedPackages {
2 | pkgFiles: string[]
3 | }
4 |
--------------------------------------------------------------------------------
/src/lib/release/interfaces/validated-workspace.ts:
--------------------------------------------------------------------------------
1 | export interface ValidatedWorkspace {
2 | packages: any[]
3 | }
4 |
--------------------------------------------------------------------------------
/src/lib/release/release-validate.ts:
--------------------------------------------------------------------------------
1 | import { getProjects } from '@nrwl/devkit'
2 | import { FsTree } from '@nrwl/tao/src/shared/tree'
3 | import { existsSync, writeFileSync } from 'fs'
4 | import { readJSONSync } from 'fs-extra'
5 | import { join, relative, resolve } from 'path'
6 | import {
7 | error,
8 | exec,
9 | getWorkspaceInfo,
10 | gray,
11 | log,
12 | NX_PACKAGE_BUILDERS,
13 | parseVersion,
14 | red,
15 | validatePackageJson,
16 | yellowBright,
17 | } from '../../utils'
18 |
19 | import { ReleaseConfig } from './interfaces/release-config'
20 | import { ValidatedConfig } from './interfaces/validated-config'
21 | import { ValidatedPackages } from './interfaces/validated-packages'
22 | import { ValidatedWorkspace } from './interfaces/validated-workspace'
23 |
24 | export function validateConfig(config: ReleaseConfig): ValidatedConfig | false {
25 | const info = getWorkspaceInfo({ cwd: config.cwd })
26 | const { isPrerelease } = parseVersion(config.version)
27 | const validated: ValidatedConfig = {
28 | ...config,
29 | nx: info.nx,
30 | npmScope: `@${info.nx.npmScope}`,
31 | npmTag: isPrerelease ? 'next' : 'latest',
32 | package: info.package,
33 | preRelease: isPrerelease,
34 | workspacePath: info.path,
35 | workspace: info.workspace,
36 | }
37 |
38 | if (!validated.version) {
39 | log(red('ERROR'), 'Please provide the release version (like: 1.2.3-beta.4)')
40 | return false
41 | }
42 |
43 | if (!parseVersion(config.version).isValid) {
44 | log(red('ERROR'), 'Please provide a valid release version (like: 1.2.3-beta.4)')
45 | return false
46 | }
47 |
48 | log(
49 | 'VALIDATE', `Using Nx workspace: ${gray(relative(config.cwd, validated.workspacePath))}`,
50 | )
51 | return validated
52 | }
53 |
54 | export function validateWorkspace(config: ValidatedConfig): ValidatedWorkspace | false {
55 | const host = new FsTree(process.cwd(), true)
56 | const projects = getProjects(host)
57 |
58 | const libs = [...projects.keys()]
59 | .map((id) => ({ id, ...projects.get(id) }))
60 | .filter((project) => project.projectType === 'library')
61 |
62 | if (!libs.length) {
63 | throw new Error(`No libraries found in nx workspace ${config.workspacePath}`)
64 | }
65 |
66 | log('VALIDATE', `Found ${yellowBright(libs.length)} libraries:`)
67 |
68 | // Find libraries that have a package executor
69 | const packages = libs
70 | .map((lib) => ({
71 | lib,
72 | target: Object.keys(lib.targets)
73 | .map((id) => ({ id, ...lib.targets[id] }))
74 | // We only include targets that are called 'build'
75 | .filter((target) => target.id === 'build')
76 | .find(({ executor }) => NX_PACKAGE_BUILDERS.includes(executor)),
77 | }))
78 | // Only release packages which turned out to have at least one 'publishable' target
79 | .filter((lib) => !!lib.target)
80 |
81 | for (const pkg of packages) {
82 | log('VALIDATE', `Found executor for ${yellowBright(pkg.lib.id)}: ${gray(pkg.target.executor)} `)
83 | }
84 |
85 | return {
86 | packages,
87 | }
88 | }
89 |
90 | export function validatePackages(
91 | config: ValidatedConfig,
92 | workspace: ValidatedWorkspace,
93 | ): ValidatedPackages | false {
94 | // Validate libraries before packaging
95 | const invalid = workspace.packages.filter(({ lib }) => {
96 | const name = `${config.npmScope}/${lib.id}`
97 | return !validatePackageJson(lib.root, {
98 | dryRun: config.dryRun,
99 | fix: config.fix,
100 | version: config.version,
101 | name,
102 | workspacePkgJson: config.package,
103 | })
104 | })
105 |
106 | if (invalid.length) {
107 | const invalidIds = invalid.map((item) => item.lib.id)
108 | log(red('Could not continue because of errors in the following packages:'))
109 | console.log(invalidIds)
110 | if (!config.fix) {
111 | log('Try running this command with the --fix flag to fix some common problems')
112 | }
113 | return false
114 | }
115 |
116 | const pkgFiles: string[] = workspace.packages
117 | .map((pkg) => {
118 | if (!pkg.target?.options?.outputPath && !pkg.target?.options?.project) {
119 | console.log('pkg.target?.options', pkg.target?.options)
120 | throw new Error(`Error determining dist path for ${pkg.lib.id}`)
121 | }
122 |
123 | if (pkg.target?.options?.outputPath) {
124 | // @nrwl/node:package builder
125 | return pkg.target?.options?.outputPath
126 | }
127 | if (pkg?.target?.options?.project) {
128 | // @nrwl/angular:package builder
129 | const ngPackagePath = join(config.cwd, pkg?.target?.options?.project)
130 | const ngPackageJson = readJSONSync(ngPackagePath)
131 | return relative(config.cwd, resolve(pkg.lib.root, ngPackageJson.dest))
132 | }
133 | throw new Error("Can't find pkg file")
134 | })
135 | .map((file) => join(file, 'package.json'))
136 |
137 | if (config.build) {
138 | exec('yarn nx run-many --target build --all')
139 | }
140 |
141 | // Here we check of the expected packages are built
142 | const foundPkgFiles = pkgFiles.map((pkgFile) => {
143 | const pkgPath = join(config.cwd, pkgFile)
144 | const exists = existsSync(pkgPath)
145 | if (!exists) {
146 | error(`Could not find ${pkgFile}. Make sure to build your packages before releasing`)
147 | return false
148 | }
149 |
150 | const pkgJson = readJSONSync(pkgPath)
151 | writeFileSync(
152 | pkgPath,
153 | JSON.stringify(
154 | {
155 | ...pkgJson,
156 | version: config.version,
157 | },
158 | null,
159 | 2,
160 | ),
161 | )
162 |
163 | return pkgFile
164 | })
165 |
166 | if (foundPkgFiles.length !== pkgFiles.length) {
167 | return false
168 | }
169 |
170 | if (!foundPkgFiles.length) {
171 | error('VALIDATE', 'Found no packages to release')
172 | return false
173 | }
174 |
175 | log('VALIDATE', `Found ${foundPkgFiles.length} packages to release`)
176 |
177 | return {
178 | pkgFiles,
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/lib/release/release.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path'
2 | import { error, exec, log, runNpmPublish, runReleaseIt } from '../../utils'
3 | import { ReleaseConfig } from './interfaces/release-config'
4 | import { validateConfig, validatePackages, validateWorkspace } from './release-validate'
5 |
6 | export const release = async (_config: ReleaseConfig): Promise => {
7 | // Validate the config and read required files
8 | const config = validateConfig(_config)
9 |
10 | if (config === false) {
11 | error('Error validating configuration')
12 | process.exit(1)
13 | }
14 |
15 | const workspace = validateWorkspace(config)
16 |
17 | if (workspace === false) {
18 | error('Error validating workspace')
19 | process.exit(1)
20 | }
21 |
22 | const packages = validatePackages(config, workspace)
23 |
24 | if (packages === false) {
25 | error('Error validating packages')
26 | process.exit(1)
27 | }
28 |
29 | log('RUN', 'Fetching git info release')
30 | exec('git fetch --all', { stdio: 'pipe' })
31 |
32 | if (config.local) {
33 | log('DRY-RUN', 'Skipping GitHub release')
34 | } else {
35 | const releaseResult = await runReleaseIt({
36 | dryRun: config.dryRun,
37 | pkgFiles: [join(config.cwd, 'package.json'), ...packages.pkgFiles],
38 | preRelease: config.preRelease,
39 | version: config.version,
40 | ci: config.ci,
41 | })
42 |
43 | if (!releaseResult) {
44 | error("Something went wrong running 'release-it' :( ")
45 | process.exit(1)
46 | }
47 | }
48 |
49 | if (config.dryRun) {
50 | log('DRY-RUN', 'Skipping npm publish')
51 | } else {
52 | const publishResult = runNpmPublish({
53 | dryRun: config.dryRun,
54 | local: config.local,
55 | localUrl: config.localUrl,
56 | pkgFiles: packages.pkgFiles,
57 | version: config.version,
58 | tag: config.npmTag,
59 | })
60 |
61 | if (!publishResult) {
62 | error("Something went wrong running 'npm publish' :( ")
63 | process.exit(1)
64 | }
65 |
66 | if (publishResult) {
67 | log('SUCCESS', "It looks like we're all done here! :)")
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/lib/sandbox/interfaces/sandbox-config.ts:
--------------------------------------------------------------------------------
1 | import { IConfig } from '@oclif/config'
2 | import { BaseConfig } from '../../../utils/base-config'
3 |
4 | export interface SandboxConfig extends BaseConfig {
5 | action?: string
6 | config: IConfig
7 | portApi?: string
8 | portWeb?: string
9 | ports?: string
10 | refresh: boolean
11 | sandboxId?: string
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/sandbox/interfaces/sandbox-pull-config.ts:
--------------------------------------------------------------------------------
1 | import { SandboxConfig } from './sandbox-config'
2 |
3 | export interface SandboxPullConfig extends SandboxConfig {
4 | force: boolean
5 | remove: boolean
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/sandbox/interfaces/sandbox.ts:
--------------------------------------------------------------------------------
1 | export interface Sandbox {
2 | id: string
3 | name: string
4 | description: string
5 | url: string
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/sandbox/sandbox-pull.ts:
--------------------------------------------------------------------------------
1 | import { cli } from 'cli-ux'
2 | import { error, log } from '../../utils'
3 | import { SandboxPullConfig } from './interfaces/sandbox-pull-config'
4 | import {
5 | getDockerImages,
6 | getSandboxUrlCache,
7 | pullDockerImage,
8 | removeDockerImage,
9 | sandboxUrlCache,
10 | } from './utils/sandbox-utils'
11 |
12 | const pull = async (config: SandboxPullConfig) => {
13 | const existing = await getDockerImages()
14 | const sandboxes = await getSandboxUrlCache(config)
15 |
16 | const filtered = config.remove ? sandboxes : sandboxes.filter((s) => !existing.includes(s.name))
17 |
18 | if (config.remove) {
19 | cli.action.start(`Remove ${filtered.length} Docker images ${config.force ? '(FORCED)' : null}`)
20 | for (let sandbox of filtered) {
21 | cli.action.status = sandbox.name
22 | try {
23 | await removeDockerImage(sandbox.name, config.force)
24 | } catch (e) {}
25 | }
26 | cli.action.stop()
27 | }
28 |
29 | if (filtered.length) {
30 | cli.action.start(`Pulling ${filtered.length} Docker images`)
31 | for (let sandbox of filtered) {
32 | cli.action.status = sandbox.name
33 | try {
34 | await pullDockerImage(sandbox.name)
35 | } catch (e) {
36 | error(e.message)
37 | }
38 | }
39 | cli.action.stop()
40 | } else {
41 | log('SANDBOX:PULL', 'Nothing to pull, you are all in sync!')
42 | }
43 | }
44 |
45 | export const sandboxPull = async (config: SandboxPullConfig): Promise => {
46 | await sandboxUrlCache(config)
47 | await pull(config)
48 | }
49 |
--------------------------------------------------------------------------------
/src/lib/sandbox/sandbox.ts:
--------------------------------------------------------------------------------
1 | import * as inquirer from 'inquirer'
2 | import { error, gray, log, selectFromList } from '../../utils'
3 | import { BACK_OPTION, INSTALL_OPTION, REMOVE_OPTION, RUN_OPTION } from '../projects/projects'
4 | import { Sandbox } from './interfaces/sandbox'
5 | import { SandboxConfig } from './interfaces/sandbox-config'
6 | import {
7 | getDockerImages,
8 | getSandboxUrlCache,
9 | pullDockerImage,
10 | removeDockerImage,
11 | runDockerImage,
12 | sandboxUrlCache,
13 | } from './utils/sandbox-utils'
14 |
15 | export interface NxPlugin {
16 | name: string
17 | description: string
18 | url: string
19 | }
20 |
21 | export const selectSandbox = async (
22 | sandboxes: any[],
23 | message: string,
24 | ): Promise<{ sandboxName: string } | false> => {
25 | const sandboxName = await selectFromList(sandboxes, { message, addExit: true })
26 | if (!sandboxName) {
27 | return false
28 | }
29 | return { sandboxName }
30 | }
31 | export const getConfigAction = (config: SandboxConfig) => {
32 | switch (config.action) {
33 | case 'run':
34 | return RUN_OPTION
35 | }
36 | }
37 |
38 | export const selectSandboxFlow = async (
39 | config: SandboxConfig,
40 | sandboxName?: string,
41 | ): Promise<{ selection: string; sandboxName: string; sandbox?: Sandbox } | false> => {
42 | const [sandboxes, images] = await Promise.all([getSandboxUrlCache(config), getDockerImages()])
43 | if (config.sandboxId) {
44 | sandboxName = sandboxes.find((sandbox) => sandbox.id === config.sandboxId)?.name
45 | }
46 | const availableSandboxes: any[] = sandboxes
47 | .map((s) => s.name)
48 | .filter((name) => !images.includes(name))
49 | const installedSandboxes: any[] = sandboxes
50 | .map((s) => s.name)
51 | .filter((name) => images.includes(name))
52 |
53 | const options: any[] = []
54 | if (!sandboxName) {
55 | console.clear()
56 | if (installedSandboxes.length !== 0) {
57 | options.push(new inquirer.Separator('Installed Sandboxes'), ...installedSandboxes.sort())
58 | }
59 | if (availableSandboxes.length !== 0) {
60 | options.push(new inquirer.Separator('Available Sandboxes'), ...availableSandboxes.sort())
61 | }
62 | const sandboxResult = await selectSandbox(options, 'Sandboxes')
63 |
64 | if (!sandboxResult) {
65 | return Promise.resolve(false)
66 | }
67 | sandboxName = sandboxResult.sandboxName
68 | }
69 |
70 | const sandbox = sandboxes.find((p: NxPlugin) => p.name === sandboxName)
71 |
72 | if (!sandbox) {
73 | error(`Plugin ${sandboxName} not found`)
74 | return Promise.resolve(false)
75 | }
76 |
77 | // eslint-disable-next-line no-console
78 | console.log(`
79 | ${sandbox.description}
80 | ${gray(sandbox.url)}
81 | `)
82 | const isInstalled = installedSandboxes.includes(sandboxName)
83 | const availableOptions = [INSTALL_OPTION]
84 | const installedOptions = [RUN_OPTION, REMOVE_OPTION]
85 |
86 | const selection = config.action
87 | ? getConfigAction(config)
88 | : await selectFromList(isInstalled ? installedOptions : availableOptions, {
89 | addBack: true,
90 | addExit: true,
91 | message: sandboxName,
92 | })
93 |
94 | if (!selection) {
95 | return Promise.resolve(false)
96 | }
97 |
98 | return {
99 | selection,
100 | sandboxName,
101 | sandbox,
102 | }
103 | }
104 |
105 | const loop = async (config: SandboxConfig, { sandboxName }: { sandboxName?: string }) => {
106 | const result = await selectSandboxFlow(config, sandboxName)
107 |
108 | if (!result) {
109 | return
110 | }
111 |
112 | if (result.selection === INSTALL_OPTION) {
113 | log('INSTALL', result.sandboxName)
114 | await pullDockerImage(result.sandboxName)
115 | console.clear()
116 | await loop(config, { sandboxName: result.sandboxName })
117 | }
118 |
119 | if (result.selection === REMOVE_OPTION) {
120 | log('REMOVE', result.sandboxName)
121 | try {
122 | await removeDockerImage(result.sandboxName, false)
123 | } catch (e) {
124 | error(e.message)
125 | const res = await inquirer.prompt([
126 | {
127 | name: 'force',
128 | type: 'confirm',
129 | message: 'Do you want to force removal?',
130 | default: false,
131 | },
132 | ])
133 | if (res.force) {
134 | await removeDockerImage(result.sandboxName, true)
135 | }
136 | }
137 | await loop(config, { sandboxName: undefined })
138 | }
139 |
140 | if (result.selection === RUN_OPTION && result.sandbox) {
141 | log('RUN', `${result.sandbox.id} ${gray(result.sandboxName)}`)
142 | try {
143 | const ports: string[] = []
144 | if (config.portApi) {
145 | ports.push(config.portApi)
146 | }
147 | if (config.portWeb) {
148 | ports.push(config.portWeb)
149 | }
150 | if (config.ports) {
151 | ports.push(...(config.ports.includes(',') ? config.ports.split(',') : [config.ports]))
152 | }
153 | await runDockerImage(result.sandboxName, {
154 | options: {
155 | hostname: result.sandbox.id,
156 | name: result.sandbox.id,
157 | params: [],
158 | ports,
159 | },
160 | })
161 | } catch (e) {
162 | error(e.message)
163 | }
164 | // await loop(config, { sandboxName: result.sandboxName })
165 | }
166 |
167 | if (result.selection.startsWith(result.sandboxName)) {
168 | log('Running sandbox', result.selection)
169 | // const command = `nx generate ${result.selection}`
170 | // exec(command)
171 | log('Done')
172 | }
173 |
174 | if (result.selection === BACK_OPTION) {
175 | await loop(config, {})
176 | }
177 | }
178 |
179 | export const sandbox = async (config: SandboxConfig): Promise => {
180 | await sandboxUrlCache(config)
181 | await loop(config, {})
182 | }
183 |
--------------------------------------------------------------------------------
/src/lib/sandbox/utils/sandbox-utils.ts:
--------------------------------------------------------------------------------
1 | import { spawnSync } from 'child_process'
2 | import { cli } from 'cli-ux'
3 | import { existsSync } from 'fs'
4 | import { readJSON } from 'fs-extra'
5 | import { join } from 'path'
6 | import {
7 | cacheUrls,
8 | error,
9 | exec,
10 | gray,
11 | log,
12 | NXPM_SANDBOX_CACHE,
13 | NXPM_SANDBOXES_URL,
14 | } from '../../../utils'
15 | import { Sandbox } from '../interfaces/sandbox'
16 | import { SandboxConfig } from '../interfaces/sandbox-config'
17 |
18 | export async function getDockerContainer(name: string) {
19 | const raw = spawnSync(`docker`, [
20 | 'ps',
21 | '--no-trunc',
22 | '-f',
23 | `name=${name}`,
24 | `--no-trunc`,
25 | `--format`,
26 | `{{ json . }}`,
27 | ])
28 | if (raw.stdout) {
29 | try {
30 | return JSON.parse(raw.stdout.toString())
31 | } catch (e) {}
32 | }
33 | }
34 |
35 | export async function getDockerImages() {
36 | const res: any[] = []
37 | const raw = spawnSync(`docker`, ['images', `--no-trunc`, `--format`, `{{ json . }}`])
38 |
39 | if (raw.stdout) {
40 | const lines = raw.stdout?.toString()
41 | lines
42 | ?.split('\n')
43 | .filter((line) => !!line)
44 | .forEach((line) => {
45 | try {
46 | const parsed = JSON.parse(line)
47 | if (parsed.Repository && parsed.Tag) {
48 | res.push(`${parsed.Repository}:${parsed.Tag}`)
49 | }
50 | } catch (e) {}
51 | })
52 | }
53 | return res
54 | }
55 |
56 | export async function sandboxUrlCache(config: SandboxConfig) {
57 | const cacheFile = join(config.config.cacheDir, NXPM_SANDBOX_CACHE)
58 | const urls = [NXPM_SANDBOXES_URL]
59 |
60 | if (config.userConfig?.sandbox?.urls) {
61 | urls.push(...config.userConfig?.sandbox?.urls)
62 | }
63 |
64 | if (!existsSync(join(cacheFile)) || config.refresh) {
65 | cli.action.start(`Downloading sandbox registry from ${urls.length} source(s)`)
66 | await cacheUrls(urls, cacheFile)
67 | cli.action.stop()
68 | }
69 | }
70 |
71 | export async function getSandboxUrlCache(config: SandboxConfig): Promise {
72 | const cacheFile = join(config.config.cacheDir, NXPM_SANDBOX_CACHE)
73 | const sandboxGroups = await readJSON(cacheFile)
74 | return Object.values(sandboxGroups).flat() as Sandbox[]
75 | }
76 |
77 | export async function removeDockerImage(image: string, force: boolean) {
78 | return exec(`docker rmi ${force ? '-f' : ''} ${image}`, { stdio: [] })
79 | }
80 |
81 | export async function pullDockerImage(image: string) {
82 | return exec(`docker pull ${image}`)
83 | }
84 |
85 | export async function attachDockerImage(image: string, options: { name: string }) {
86 | const existing = await getDockerContainer(options.name)
87 |
88 | if (!existing) {
89 | error(`Can't find container ${options.name}`)
90 | return Promise.reject()
91 | }
92 |
93 | const cmd = 'docker'
94 | const action = 'exec'
95 | const params = ['-it']
96 | const command = [cmd, action, params.join(' '), options.name, 'zsh', '&& true'].join(' ')
97 | log('ATTACH', gray(command))
98 | return exec(command)
99 | }
100 |
101 | export async function runDockerImage(
102 | image: string,
103 | { options }: { options: { hostname?: string; name: string; params?: string[]; ports: string[] } },
104 | ) {
105 | const existing = await getDockerContainer(options.name)
106 |
107 | if (existing) {
108 | return attachDockerImage(image, { name: options.name })
109 | }
110 |
111 | const host = process.env.DOCKER_MACHINE_NAME ? process.env.DOCKER_MACHINE_NAME : 'localhost'
112 | const cmd = 'docker'
113 | const action = 'run'
114 | const ports = options.ports
115 | .map((p) => (p.includes(':') ? p : `${p}:${p}`))
116 | .map((p) => {
117 | log('LISTEN', `http://${host}:${p.split(':')[0]}`)
118 | return p
119 | })
120 | .map((p) => `-p ${p}`)
121 | .join(' ')
122 |
123 | const defaultParams = [
124 | '-it',
125 | '--rm',
126 | options.name ? `--name ${options.name}` : '',
127 | options.hostname ? `--hostname ${options.hostname}` : '',
128 | ports,
129 | ]
130 | const params = options.params || []
131 | const command = [cmd, action, defaultParams.join(' '), params.join(' '), image, '&& true'].join(
132 | ' ',
133 | )
134 | log('RUN', gray(command))
135 | return exec(command)
136 | }
137 |
--------------------------------------------------------------------------------
/src/lib/verdaccio/index.ts:
--------------------------------------------------------------------------------
1 | import { run } from '../../utils'
2 |
3 | export function startRegistry() {
4 | run('npx verdaccio')
5 | }
6 |
7 | export function disableRegistry() {
8 | run('npm config delete registry')
9 | run('yarn config delete registry')
10 | }
11 |
12 | export function enableRegistry() {
13 | run('npm config set registry http://localhost:4873/')
14 | run('yarn config set registry http://localhost:4873/')
15 | }
16 |
17 | export function registryStatus() {
18 | run(`npm config get registry`)
19 | run(`yarn config get registry`)
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/base-command.ts:
--------------------------------------------------------------------------------
1 | import { Command } from '@oclif/command'
2 | import { mkdirp, pathExists, readJSON } from 'fs-extra'
3 | import { join } from 'path'
4 | import { updateConfigFile } from '../lib/config/utils/config-utils'
5 | import { defaultUserConfig, UserConfig } from './user-config'
6 |
7 | export abstract class BaseCommand extends Command {
8 | public readonly configFile = join(this.config.configDir, 'config.json')
9 |
10 | public userConfig: UserConfig = defaultUserConfig
11 |
12 | async init() {
13 | if (!(await pathExists(this.configFile))) {
14 | if (!(await pathExists(this.config.configDir))) {
15 | await mkdirp(this.config.configDir)
16 | }
17 | await updateConfigFile(this.config, defaultUserConfig)
18 | }
19 | this.userConfig = await readJSON(this.configFile)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/base-config.ts:
--------------------------------------------------------------------------------
1 | import { IConfig } from '@oclif/config'
2 | import { UserConfig } from './user-config'
3 |
4 | export interface BaseConfig {
5 | config: IConfig
6 | cwd?: string
7 | dryRun?: boolean
8 | userConfig?: UserConfig
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | // These are the builders that the CLI knows can produce 'publishable' libs
2 | export const NX_PACKAGE_BUILDERS = [
3 | '@nrwl/angular:package',
4 | '@nrwl/nest:package',
5 | '@nrwl/node:package',
6 | '@nrwl/workspace:run-commands',
7 | 'nx:run-commands',
8 | '@nrwl/web:package',
9 | '@nrwl/web:rollup',
10 | '@nrwl/rollup:rollup',
11 | '@nrwl/js:tsc',
12 | '@nrwl/js:swc',
13 | ]
14 | export const NXPM_PLUGINS_CACHE = 'nxpm-plugins.json'
15 | export const NXPM_SANDBOX_CACHE = 'nxpm-sandbox.json'
16 | export const NX_PLUGINS_URL =
17 | 'https://gist.githubusercontent.com/beeman/11c2761fc1b6681182af3271b2badcaa/raw/official-plugins.json'
18 | export const NX_COMMUNITY_PLUGINS_URL =
19 | 'https://raw.githubusercontent.com/nrwl/nx/master/community/approved-plugins.json'
20 | export const NXPM_PLUGINS_URL =
21 | 'https://gist.githubusercontent.com/beeman/11c2761fc1b6681182af3271b2badcaa/raw/nxpm-plugins.json'
22 | export const NXPM_SANDBOXES_URL =
23 | 'https://raw.githubusercontent.com/nxpm/nxpm-docker-images/master/nxpm-sandboxes.json'
24 |
--------------------------------------------------------------------------------
/src/utils/get-workspace-info.ts:
--------------------------------------------------------------------------------
1 | import { existsSync } from 'fs'
2 | import { readJSONSync } from 'fs-extra'
3 | import { join } from 'path'
4 | import { log } from './logging'
5 |
6 | export interface WorkspaceInfo {
7 | cwd: string
8 | package: { [key: string]: any }
9 | nx: { [key: string]: any }
10 | packageManager: 'npm' | 'yarn'
11 | path: string
12 | workspace: { [key: string]: any }
13 | workspaceJsonPath: string
14 | }
15 |
16 | export interface WorkspaceParams {
17 | cwd: string
18 | }
19 |
20 | export function getWorkspaceInfo({ cwd }: WorkspaceParams): WorkspaceInfo {
21 | const nxJsonPath = join(cwd, 'nx.json')
22 | const packageJsonPath = join(cwd, 'package.json')
23 | const packageLockJsonPath = join(cwd, 'package-lock.json')
24 | const yarnLockPath = join(cwd, 'yarn.lock')
25 |
26 | const packageLockJsonExists = existsSync(packageLockJsonPath)
27 | const yarnLockExists = existsSync(yarnLockPath)
28 |
29 | if (packageLockJsonExists && yarnLockExists) {
30 | log('WARNING', 'Found package-lock.json AND yarn.lock - defaulting to yarn.')
31 | }
32 |
33 | const workspacePath = join(process.cwd(), 'nx.json')
34 | if (!existsSync(nxJsonPath)) {
35 | throw new Error(`Can't find nx.json in ${nxJsonPath}`)
36 | }
37 |
38 | const packageManager = yarnLockExists ? 'yarn' : 'npm'
39 |
40 | return {
41 | cwd,
42 | package: readJSONSync(packageJsonPath),
43 | nx: existsSync(nxJsonPath) ? readJSONSync(nxJsonPath) : {},
44 | path: workspacePath,
45 | workspace: readJSONSync(workspacePath),
46 | workspaceJsonPath: workspacePath,
47 | packageManager,
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './base-command'
2 | export * from './constants'
3 | export * from './logging'
4 | export * from './utils'
5 | export * from './get-workspace-info'
6 | export * from './parse-version'
7 |
--------------------------------------------------------------------------------
/src/utils/logging.ts:
--------------------------------------------------------------------------------
1 | import * as chalk from 'chalk'
2 |
3 | export const {
4 | whiteBright,
5 | greenBright,
6 | bold,
7 | inverse,
8 | redBright,
9 | red,
10 | yellowBright,
11 | magentaBright,
12 | gray,
13 | } = chalk
14 |
15 | const info = (label = 'NXPM'): string => inverse(magentaBright(bold(` ${label} `)))
16 | const err = (label = 'ERROR'): string => inverse(redBright(bold(` ${label} `)))
17 | const warn = (label = 'WARN'): string => inverse(yellowBright(bold(` ${label} `)))
18 | export const log = (msg: string, ...params: unknown[]): void =>
19 | console.log(`${info()}`, `${greenBright(msg)}`, ...params)
20 |
21 | export const error = (msg: string, ...params: unknown[]): void =>
22 | log(`${err()}`, `${redBright(msg)}`, ...params)
23 |
24 | export const warning = (msg: string, ...params: unknown[]): void =>
25 | log(`${warn()}`, `${yellowBright(msg)}`, ...params)
26 |
--------------------------------------------------------------------------------
/src/utils/parse-version.ts:
--------------------------------------------------------------------------------
1 | export interface ParsedVersion {
2 | version: string
3 | isValid: boolean
4 | isPrerelease: boolean
5 | }
6 |
7 | export const parseVersion = (version: string): ParsedVersion => {
8 | if (!version || !version.length) {
9 | return {
10 | version,
11 | isValid: false,
12 | isPrerelease: false,
13 | }
14 | }
15 | const sections = version.split('-')
16 | if (sections.length === 1) {
17 | /**
18 | * Not a prerelease version, validate matches exactly the
19 | * standard {number}.{number}.{number} format
20 | */
21 | return {
22 | version,
23 | isValid: !!sections[0].match(/\d+\.\d+\.\d+$/),
24 | isPrerelease: false,
25 | }
26 | }
27 | /**
28 | * Is a prerelease version, validate each section
29 | * 1. {number}.{number}.{number} format
30 | * 2. {alpha|beta|rc}.{number}
31 | */
32 | return {
33 | version,
34 | isValid: !!(sections[0].match(/\d+\.\d+\.\d+$/) && sections[1].match(/(alpha|beta|rc)\.\d+$/)),
35 | isPrerelease: true,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils/user-config.ts:
--------------------------------------------------------------------------------
1 | export interface UserConfig {
2 | plugins?: {
3 | urls?: string[]
4 | }
5 | release?: {
6 | github?: {
7 | token?: string | null
8 | }
9 | }
10 | projects?: {
11 | serve?: {
12 | params?: string
13 | }
14 | }
15 | sandbox?: {
16 | urls?: string[]
17 | }
18 | }
19 |
20 | export const defaultUserConfig: UserConfig = {
21 | plugins: {
22 | urls: [],
23 | },
24 | release: {
25 | github: {
26 | token: null,
27 | },
28 | },
29 | sandbox: {
30 | urls: [],
31 | },
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import { JsonObject } from '@angular-devkit/core'
2 | import { execSync, ExecSyncOptions } from 'child_process'
3 | import { existsSync } from 'fs'
4 | import { mkdirpSync, readJSONSync, writeFileSync, writeJSONSync } from 'fs-extra'
5 | import * as inquirer from 'inquirer'
6 | import fetch from 'node-fetch'
7 | import { dirname, join, relative } from 'path'
8 |
9 | import * as releaseIt from 'release-it'
10 | import { BACK_OPTION, EXIT_OPTION } from '../lib/projects/projects'
11 |
12 | import { gray, greenBright, log, red, yellowBright } from './logging'
13 |
14 | export const exec = (command: string, options?: ExecSyncOptions): Buffer =>
15 | execSync(command, { stdio: [0, 1, 2], ...options })
16 |
17 | export const run = (command: string) => {
18 | log('RUNNING', command)
19 | exec(command)
20 | }
21 |
22 | export const getPackageJson = (root: string): { [key: string]: any } | null => {
23 | const pkgPath = join(process.cwd(), root, 'package.json')
24 | if (!existsSync(pkgPath)) {
25 | log(red(`Could not find package.json in ${root}`))
26 | return null
27 | }
28 | return readJSONSync(pkgPath)
29 | }
30 |
31 | export const updatePackageJson = (root: string, obj: { [key: string]: any }): any => {
32 | const pkgPath = join(process.cwd(), root, 'package.json')
33 | const pkgJson = getPackageJson(root)
34 | writeFileSync(
35 | pkgPath,
36 | JSON.stringify(
37 | {
38 | ...pkgJson,
39 | ...obj,
40 | },
41 | null,
42 | 2,
43 | ) + '\n',
44 | )
45 | return getPackageJson(root)
46 | }
47 |
48 | export const validatePackageJsonLicense = (
49 | root: string,
50 | { pkgJson, license }: { pkgJson: any; license: string },
51 | ): boolean => {
52 | // Verify that the name in package.json is correct
53 | if (!pkgJson.license) {
54 | log(red('ERROR'), `License not defined in in ${join(root, 'package.json')}`)
55 | return false
56 | }
57 | if (pkgJson.license !== license) {
58 | log(
59 | red('ERROR'),
60 | `License not valid in ${join(root, 'package.json')}, should be "${license}", not "${
61 | pkgJson.license
62 | }"`,
63 | )
64 | return false
65 | }
66 | return true
67 | }
68 |
69 | export const validatePackageJsonName = (
70 | root: string,
71 | { pkgJson, name }: { pkgJson: any; name: string },
72 | ): boolean => {
73 | if (pkgJson?.nxpm?.allowPackageName) {
74 | return true
75 | }
76 | // Verify that the name in package.json is correct
77 | if (pkgJson.name !== name) {
78 | log(
79 | red('ERROR'),
80 | `Name not valid in ${join(root, 'package.json')}, should be "${name}", not "${
81 | pkgJson.name
82 | }" `,
83 | )
84 | return false
85 | }
86 | return true
87 | }
88 |
89 | export const validatePackageJsonVersion = (
90 | root: string,
91 | { pkgJson, version }: { pkgJson: any; version: string },
92 | ): boolean => {
93 | // Verify that the version is set correctly
94 | if (!pkgJson.version || pkgJson.version !== version) {
95 | log(
96 | red('ERROR'),
97 | `Version "${pkgJson.version}" should be "${version}" in ${join(root, 'package.json')} `,
98 | )
99 | return false
100 | }
101 | return true
102 | }
103 |
104 | export const updatePackageJsonLicense = (
105 | root: string,
106 | { license }: { license: string },
107 | ): boolean => {
108 | updatePackageJson(root, { license })
109 |
110 | log(greenBright('FIXED'), `License set to ${license} in ${join(root, 'package.json')}`)
111 | return validatePackageJsonLicense(root, { pkgJson: getPackageJson(root), license })
112 | }
113 |
114 | export const updatePackageJsonVersion = (
115 | root: string,
116 | { version }: { version: string },
117 | ): boolean => {
118 | updatePackageJson(root, { version })
119 |
120 | log(greenBright('FIXED'), `Version set to "${version}" in ${join(root, 'package.json')}`)
121 | return validatePackageJsonVersion(root, { pkgJson: getPackageJson(root), version })
122 | }
123 |
124 | export const updatePackageJsonName = (root: string, { name }: { name: string }): boolean => {
125 | updatePackageJson(root, { name })
126 |
127 | log(greenBright('FIXED'), `Name set to "${name}" in ${join(root, 'package.json')}`)
128 | return validatePackageJsonName(root, { pkgJson: getPackageJson(root), name })
129 | }
130 |
131 | export const validatePackageJson = (
132 | root: string,
133 | {
134 | fix,
135 | name,
136 | version,
137 | workspacePkgJson,
138 | }: { dryRun: boolean; fix: boolean; name: string; version: string; workspacePkgJson: any },
139 | ): boolean => {
140 | // Read the libs package.json
141 | const pkgJson = getPackageJson(root)
142 |
143 | if (!pkgJson) {
144 | return false
145 | }
146 |
147 | let hasErrors = false
148 |
149 | // Verify that the version is set correctly
150 | if (!validatePackageJsonVersion(root, { pkgJson, version })) {
151 | if (fix) {
152 | hasErrors = !updatePackageJsonVersion(root, { version })
153 | } else {
154 | hasErrors = true
155 | }
156 | }
157 |
158 | // Verify License
159 | if (!validatePackageJsonLicense(root, { pkgJson, license: workspacePkgJson.license })) {
160 | if (fix) {
161 | hasErrors = !updatePackageJsonLicense(root, { license: workspacePkgJson.license })
162 | } else {
163 | hasErrors = true
164 | }
165 | }
166 |
167 | // Verify name
168 | if (!validatePackageJsonName(root, { pkgJson, name })) {
169 | if (fix) {
170 | hasErrors = !updatePackageJsonName(root, { name })
171 | } else {
172 | hasErrors = true
173 | }
174 | }
175 |
176 | if (!hasErrors) {
177 | log('VALIDATE', `Package ${yellowBright(pkgJson.name)} is valid.`)
178 | }
179 | return !hasErrors
180 | }
181 |
182 | export interface NpmPublishOptions {
183 | dryRun: boolean
184 | pkgFiles: string[]
185 | version: string
186 | local?: boolean
187 | localUrl?: string
188 | tag: 'next' | 'latest'
189 | }
190 | export const runNpmPublish = ({
191 | dryRun,
192 | pkgFiles,
193 | version,
194 | tag,
195 | local,
196 | localUrl,
197 | }: NpmPublishOptions): boolean => {
198 | const registryUrl = local ? localUrl : 'https://registry.npmjs.org/'
199 |
200 | let hasErrors = false
201 | for (const pkgFile of pkgFiles) {
202 | const filePath = relative(process.cwd(), pkgFile)
203 | // Skip the root package.json file
204 | if (filePath !== 'package.json') {
205 | const baseDir = dirname(filePath)
206 | const pkgInfo = readJSONSync(join(process.cwd(), pkgFile))
207 | const name = `${pkgInfo.name}@${version}`
208 | const command = `npm publish --tag ${tag} --access public --registry=${registryUrl}`
209 | if (dryRun) {
210 | log('[dry-run]', 'Skipping command', gray(command))
211 | } else {
212 | try {
213 | exec(command, { cwd: baseDir })
214 | } catch (error) {
215 | error(`Failed to publish ${name} to npm:`)
216 | console.log(error)
217 | hasErrors = true
218 | }
219 | }
220 | }
221 | }
222 | return !hasErrors
223 | }
224 |
225 | export interface ReleaseItOptions {
226 | ci: boolean
227 | dryRun: boolean
228 | pkgFiles: string[]
229 | preRelease: boolean
230 | version: string
231 | }
232 | export const runReleaseIt = ({
233 | ci = false,
234 | dryRun = false,
235 | preRelease,
236 | version,
237 | }: ReleaseItOptions): Promise => {
238 | const options = {
239 | ci,
240 | 'dry-run': dryRun,
241 | changelogCommand: 'conventional-changelog -p angular | tail -n +3',
242 | /**
243 | * Needed so that we can leverage conventional-changelog to generate
244 | * the changelog
245 | */
246 | safeBump: false,
247 | /**
248 | * All the package.json files that will have their version updated
249 | * by release-it
250 | */
251 | increment: version,
252 | requireUpstream: false,
253 | github: {
254 | preRelease: preRelease,
255 | release: true,
256 | token: process.env.GITHUB_TOKEN,
257 | },
258 | npm: {
259 | /**
260 | * We don't use release-it to do the npm publish, because it is not
261 | * able to understand our multi-package setup.
262 | */
263 | release: false,
264 | publish: false,
265 | },
266 | git: {
267 | requireCleanWorkingDir: false,
268 | },
269 | }
270 | return releaseIt(options)
271 | .then(() => {
272 | return true
273 | })
274 | .catch((error: any) => {
275 | error(error.message)
276 | return false
277 | })
278 | }
279 | export const selectFromList = async (
280 | choices: any[],
281 | {
282 | addBack = false,
283 | addExit = false,
284 | message,
285 | }: { addBack?: boolean; addExit?: boolean; message?: string },
286 | ): Promise => {
287 | const options = [...choices]
288 | const extraOptions: string[] = []
289 |
290 | if (addBack) {
291 | extraOptions.push(BACK_OPTION)
292 | }
293 |
294 | if (addExit) {
295 | extraOptions.push(EXIT_OPTION)
296 | }
297 | const response = await inquirer.prompt([
298 | {
299 | name: 'select',
300 | type: 'list',
301 | message,
302 | choices:
303 | extraOptions.length === 0
304 | ? [...options]
305 | : [...options, new inquirer.Separator(), ...extraOptions, new inquirer.Separator()],
306 | },
307 | ])
308 | if (response.select === EXIT_OPTION) {
309 | console.clear()
310 | return false
311 | }
312 | return response.select
313 | }
314 |
315 | export async function fetchJson(url: string): Promise {
316 | return fetch(url).then((data: any) => data.json())
317 | }
318 |
319 | export async function cacheUrls(urls: string[], cachePath: string) {
320 | mkdirpSync(dirname(cachePath))
321 | const results = await Promise.all(urls.map(fetchJson))
322 | const cache = urls.reduce((acc, curr, i) => ({ ...acc, [curr]: results[i] }), {})
323 | writeJSONSync(cachePath, cache, { spaces: 2 })
324 | }
325 |
--------------------------------------------------------------------------------
/src/utils/vendor/nx-console/read-schematic-collections.ts:
--------------------------------------------------------------------------------
1 | import { basename, dirname, join } from 'path'
2 | import { Schematic, SchematicCollection } from './schema'
3 |
4 | import {
5 | directoryExists,
6 | fileExistsSync,
7 | listFiles,
8 | listOfUnnestedNpmPackages,
9 | normalizeSchema,
10 | readAndCacheJsonFile,
11 | readAndParseJson,
12 | } from './utils'
13 |
14 | function readWorkspaceJsonDefaults(workspaceJsonPath: string): any {
15 | const defaults = readAndParseJson(workspaceJsonPath).schematics || {}
16 | const collectionDefaults = Object.keys(defaults).reduce((collectionDefaultsMap: any, key) => {
17 | if (key.includes(':')) {
18 | const [collectionName, schematicName] = key.split(':')
19 | if (!collectionDefaultsMap[collectionName]) {
20 | collectionDefaultsMap[collectionName] = {}
21 | }
22 | collectionDefaultsMap[collectionName][schematicName] = defaults[key]
23 | } else {
24 | const collectionName = key
25 | if (!collectionDefaultsMap[collectionName]) {
26 | collectionDefaultsMap[collectionName] = {}
27 | }
28 | Object.keys(defaults[collectionName]).forEach((schematicName) => {
29 | collectionDefaultsMap[collectionName][schematicName] =
30 | defaults[collectionName][schematicName]
31 | })
32 | }
33 | return collectionDefaultsMap
34 | }, {})
35 | return collectionDefaults
36 | }
37 |
38 | export function canAdd(
39 | name: string,
40 | s: { hidden: boolean; private: boolean; schema: string; extends: boolean },
41 | ): boolean {
42 | return !s.hidden && !s.private && !s.extends && name !== 'ng-add'
43 | }
44 |
45 | async function readCollectionSchematics(
46 | collectionName: string,
47 | collectionPath: string,
48 | collectionJson: any,
49 | defaults?: any,
50 | ) {
51 | const schematicCollection = {
52 | name: collectionName,
53 | schematics: [] as Schematic[],
54 | }
55 | Object.entries(collectionJson.schematics).forEach(async ([k, v]: [any, any]) => {
56 | try {
57 | if (canAdd(k, v)) {
58 | const schematicSchema = readAndCacheJsonFile(v.schema, dirname(collectionPath))
59 | const projectDefaults = defaults && defaults[collectionName] && defaults[collectionName][k]
60 |
61 | schematicCollection.schematics.push({
62 | name: k,
63 | collection: collectionName,
64 | options: await normalizeSchema(schematicSchema.json, projectDefaults),
65 | description: v.description || '',
66 | })
67 | }
68 | } catch (e) {
69 | // console.error(e)
70 | console.error(`Invalid package.json for schematic ${collectionName}:${k}`)
71 | }
72 | })
73 | return schematicCollection
74 | }
75 |
76 | async function readCollection(
77 | basedir: string,
78 | collectionName: string,
79 | defaults?: any,
80 | ): Promise {
81 | try {
82 | const packageJson = readAndCacheJsonFile(join(collectionName, 'package.json'), basedir)
83 | const collection = readAndCacheJsonFile(packageJson.json.schematics, dirname(packageJson.path))
84 | return readCollectionSchematics(collectionName, collection.path, collection.json, defaults)
85 | } catch (e) {
86 | // this happens when package is misconfigured. We decided to ignore such a case.
87 | return null
88 | }
89 | }
90 |
91 | async function readSchematicCollectionsFromNodeModules(
92 | workspaceJsonPath: string,
93 | ): Promise {
94 | const basedir = join(workspaceJsonPath, '..')
95 | const nodeModulesDir = join(basedir, 'node_modules')
96 | const packages = listOfUnnestedNpmPackages(nodeModulesDir)
97 | const schematicCollections = packages.filter((p) => {
98 | try {
99 | return !!readAndCacheJsonFile(join(p, 'package.json'), nodeModulesDir).json.schematics
100 | } catch (e) {
101 | if (
102 | e.message &&
103 | (e.message.indexOf('no such file') > -1 || e.message.indexOf('not a directory') > -1)
104 | ) {
105 | return false
106 | }
107 | throw e
108 | }
109 | })
110 | const defaults = readWorkspaceJsonDefaults(workspaceJsonPath)
111 |
112 | return (
113 | await Promise.all(schematicCollections.map((c) => readCollection(nodeModulesDir, c, defaults)))
114 | ).filter((c): c is SchematicCollection => Boolean(c))
115 | }
116 |
117 | async function readWorkspaceSchematicsCollection(
118 | basedir: string,
119 | workspaceSchematicsPath: string,
120 | ): Promise<{
121 | name: string
122 | schematics: Schematic[]
123 | }> {
124 | const collectionDir = join(basedir, workspaceSchematicsPath)
125 | const collectionName = 'workspace-schematic'
126 | if (fileExistsSync(join(collectionDir, 'collection.json'))) {
127 | const collection = readAndCacheJsonFile('collection.json', collectionDir)
128 |
129 | return readCollectionSchematics(collectionName, collection.path, collection.json)
130 | }
131 | const schematics: Schematic[] = await Promise.all(
132 | listFiles(collectionDir)
133 | .filter((f) => basename(f) === 'schema.json')
134 | .map(async (f) => {
135 | const schemaJson = readAndCacheJsonFile(f, '')
136 | return {
137 | name: schemaJson.json.id,
138 | collection: collectionName,
139 | options: await normalizeSchema(schemaJson.json),
140 | description: '',
141 | }
142 | }),
143 | )
144 | return { name: collectionName, schematics }
145 | }
146 |
147 | export async function readAllSchematicCollections(
148 | workspaceJsonPath: string,
149 | workspaceSchematicsPath: string = join('tools', 'schematics'),
150 | ): Promise {
151 | const basedir = join(workspaceJsonPath, '..')
152 | let collections = await readSchematicCollectionsFromNodeModules(workspaceJsonPath)
153 | if (directoryExists(join(basedir, workspaceSchematicsPath))) {
154 | collections = [
155 | await readWorkspaceSchematicsCollection(basedir, workspaceSchematicsPath),
156 | ...collections,
157 | ]
158 | }
159 | return collections.filter(
160 | (collection): collection is SchematicCollection =>
161 | !!collection && collection!.schematics!.length > 0,
162 | )
163 | }
164 |
--------------------------------------------------------------------------------
/src/utils/vendor/nx-console/schema.ts:
--------------------------------------------------------------------------------
1 | export interface Option {
2 | [key: string]: any
3 | }
4 |
5 | export interface SchematicCollection {
6 | name: string
7 | schematics: Schematic[]
8 | }
9 |
10 | export interface Schematic {
11 | collection: string
12 | name: string
13 | description: string
14 | options: Option[]
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/vendor/nx-console/utils.ts:
--------------------------------------------------------------------------------
1 | import { schema } from '@angular-devkit/core'
2 | import { standardFormats } from '@angular-devkit/schematics/src/formats'
3 | import { Option } from '@angular/cli/models/interface'
4 | import { parseJsonSchemaToOptions } from '@angular/cli/utilities/json-schema'
5 | import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
6 | import * as JSON5 from 'json5'
7 | import * as path from 'path'
8 |
9 | export interface SchematicDefaults {
10 | [name: string]: string
11 | }
12 |
13 | export const files: { [path: string]: string[] } = {}
14 | export const fileContents: { [path: string]: any } = {}
15 |
16 | const IMPORTANT_FIELD_NAMES = ['name', 'project', 'module', 'watch', 'style', 'directory', 'port']
17 | const IMPORTANT_FIELDS_SET = new Set(IMPORTANT_FIELD_NAMES)
18 |
19 | export function listOfUnnestedNpmPackages(nodeModulesDir: string): string[] {
20 | const res: string[] = []
21 | if (!existsSync(nodeModulesDir)) {
22 | return res
23 | }
24 |
25 | readdirSync(nodeModulesDir).forEach((npmPackageOrScope) => {
26 | if (npmPackageOrScope.startsWith('@')) {
27 | readdirSync(path.join(nodeModulesDir, npmPackageOrScope)).forEach((p) => {
28 | res.push(`${npmPackageOrScope}/${p}`)
29 | })
30 | } else {
31 | res.push(npmPackageOrScope)
32 | }
33 | })
34 | return res
35 | }
36 |
37 | export function listFiles(dirName: string): string[] {
38 | // TODO use .gitignore to skip files
39 | if (dirName.indexOf('node_modules') > -1) return []
40 | if (dirName.indexOf('dist') > -1) return []
41 |
42 | const res = [dirName]
43 | // the try-catch here is intentional. It's only used in auto-completion.
44 | // If it doesn't work, we don't want the process to exit
45 | try {
46 | readdirSync(dirName).forEach((c) => {
47 | const child = path.join(dirName, c)
48 | try {
49 | if (!statSync(child).isDirectory()) {
50 | res.push(child)
51 | } else if (statSync(child).isDirectory()) {
52 | res.push(...listFiles(child))
53 | }
54 | } catch (e) {}
55 | })
56 | } catch (e) {}
57 | return res
58 | }
59 |
60 | export function directoryExists(filePath: string): boolean {
61 | try {
62 | return statSync(filePath).isDirectory()
63 | } catch (err) {
64 | return false
65 | }
66 | }
67 |
68 | export function fileExistsSync(filePath: string): boolean {
69 | try {
70 | return statSync(filePath).isFile()
71 | } catch (err) {
72 | return false
73 | }
74 | }
75 |
76 | export function readAndParseJson(fullFilePath: string): any {
77 | return JSON5.parse(readFileSync(fullFilePath).toString())
78 | }
79 |
80 | export function readAndCacheJsonFile(
81 | filePath: string,
82 | basedir: string,
83 | ): { path: string; json: any } {
84 | const fullFilePath = path.join(basedir, filePath)
85 |
86 | if (fileContents[fullFilePath] || existsSync(fullFilePath)) {
87 | fileContents[fullFilePath] = fileContents[fullFilePath] || readAndParseJson(fullFilePath)
88 |
89 | return {
90 | path: fullFilePath,
91 | json: fileContents[fullFilePath],
92 | }
93 | }
94 | return {
95 | path: fullFilePath,
96 | json: {},
97 | }
98 | }
99 |
100 | const registry = new schema.CoreSchemaRegistry(standardFormats)
101 | export async function normalizeSchema(
102 | s: {
103 | properties: { [k: string]: any }
104 | required: string[]
105 | },
106 | projectDefaults?: SchematicDefaults,
107 | ): Promise