mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
572 Commits
v14.4.4
...
example-mo
Author | SHA1 | Date | |
---|---|---|---|
357866e345 | |||
629ac9e5e9 | |||
2f03b24bcf | |||
233ee990f9 | |||
facc66e9f9 | |||
6efd24489f | |||
0339160a0b | |||
0591f5edbd | |||
c30dd323f1 | |||
1640bd6457 | |||
da2ffde483 | |||
5c9e3ad8f6 | |||
7515d4b710 | |||
fae5af6b75 | |||
45aa1adacb | |||
b34ea14413 | |||
90eae06017 | |||
41da8f6f6f | |||
8d706a7d81 | |||
d67952024c | |||
8895fc485c | |||
c2dbcaaaf4 | |||
52cb951e49 | |||
2a357a438f | |||
a9a202281d | |||
b74979fb9e | |||
4760866c77 | |||
2b044348e0 | |||
c9fa10b9c6 | |||
63674c8201 | |||
a08ac447a3 | |||
12a338fb21 | |||
1d70e6b4b4 | |||
d3458379e6 | |||
544f615ca0 | |||
245c51d974 | |||
03f0f11f8b | |||
2c0c1f8fd1 | |||
d4d7fce2c1 | |||
0f23318367 | |||
003d537433 | |||
46d3497663 | |||
a39a772c9e | |||
efa0d67f0a | |||
232b9678bc | |||
a8ce14b0e8 | |||
838a36758a | |||
4e101e2fd9 | |||
9f9fd97795 | |||
1b36dc84fc | |||
5d6ee707ff | |||
3c64e13fb3 | |||
7e41fda8d4 | |||
5df316e9cb | |||
79fcd95491 | |||
33199acbe8 | |||
4633c2456d | |||
f8bc081228 | |||
1702f8ba59 | |||
60b0c7e346 | |||
e95ef8b3b4 | |||
1bc0f7447f | |||
f65215e144 | |||
97abc5cf1c | |||
e64a09d2f4 | |||
b1073ca549 | |||
e659e3577a | |||
f7233c5d42 | |||
4ae2ff1740 | |||
19a60bb0ab | |||
d1a6f7560c | |||
4619ce7daa | |||
7624240d5e | |||
7273656d07 | |||
00bd4d5415 | |||
c2d3c9fc71 | |||
1749937373 | |||
bcb7fb8902 | |||
81e9601d6b | |||
6c89ba4b22 | |||
57d3d6d537 | |||
6330574c01 | |||
b6d1afac2d | |||
f2d0da0837 | |||
068cd887c8 | |||
93e597a596 | |||
5b1d6a3190 | |||
dba102f347 | |||
c30a1dc1ed | |||
78368c8a51 | |||
d7250ccc4e | |||
2d47eb53cd | |||
b5fc97bdf9 | |||
3472df2c04 | |||
6b5657625a | |||
dad6b23202 | |||
6b59c06978 | |||
b518067058 | |||
bd4bdb805f | |||
32e59eccc5 | |||
f05e49915d | |||
92146429c4 | |||
40f5214317 | |||
14e1255b5f | |||
15e91e95b4 | |||
1814fe7581 | |||
7325e8d9d5 | |||
5358f92590 | |||
fe6a7cfdba | |||
a29bd8d0ef | |||
049e1da53e | |||
2c0b4072ae | |||
15c0c32a01 | |||
8f2c7f9dbf | |||
90982256c7 | |||
73220206a2 | |||
8b453aae89 | |||
d85d5933fb | |||
2cd455ff81 | |||
066cbaf35f | |||
17fa888fea | |||
f50287873a | |||
edff14fa72 | |||
9de753d9d3 | |||
75d2d7d375 | |||
d9b193acc1 | |||
2e42999642 | |||
5a3f0ea453 | |||
e1cd30060c | |||
7959e23cd3 | |||
9c4d788d6d | |||
181f5a6a2f | |||
163dcf596e | |||
1724187466 | |||
b27dcdd582 | |||
c28039a3f2 | |||
233bc705de | |||
71518678e1 | |||
88a705c935 | |||
55d06aced2 | |||
aa9a148c46 | |||
10ca5b4f59 | |||
47e11d5f9b | |||
6fb65bcf22 | |||
954de13b10 | |||
f81a27e931 | |||
e8815d0275 | |||
766e6d4e5e | |||
7b46f65a01 | |||
db8df0ac35 | |||
7c7f46fe2b | |||
b29aae1821 | |||
0b10701015 | |||
1dbe08d7e0 | |||
d01461ff3e | |||
2a970478bd | |||
ffd44d3fec | |||
df51f87fbc | |||
6178f34f88 | |||
c5ecf692bb | |||
87f5f18721 | |||
e33810b448 | |||
3caf54aa16 | |||
9d3ee9eb49 | |||
3dac94db70 | |||
04b4444fc2 | |||
98514cef09 | |||
4811031172 | |||
be682c7426 | |||
c6827ee51d | |||
2cba3bbc22 | |||
933eacf275 | |||
e7869f4c6d | |||
1a246a9ba5 | |||
e26895085d | |||
71345a8cc1 | |||
619f605eb2 | |||
bb4713ab9a | |||
168bddf7db | |||
24076e4f8d | |||
634ad156ce | |||
6ebeb97917 | |||
cb444998cd | |||
742c015f21 | |||
556e50c87c | |||
3294f78b00 | |||
7f11805a7f | |||
42dd732f68 | |||
aed50480c3 | |||
6515d6ae10 | |||
7903c82821 | |||
eee8a0ecca | |||
38a2817587 | |||
2bd0641d5f | |||
122a763f82 | |||
756f6b328b | |||
eb9db6f7b4 | |||
6f9e5a697c | |||
f9f41eef4b | |||
5371fea588 | |||
bacb55a1ea | |||
ecfd4a260e | |||
1525822239 | |||
1614d9b2c8 | |||
2e061845ae | |||
9e4dd3fce2 | |||
b2590136fc | |||
bf5e61a61c | |||
f550d0c596 | |||
54302669b8 | |||
a4a4e33d7b | |||
8d6a621bfb | |||
4b2602676b | |||
b0810c0f85 | |||
97a6013537 | |||
1ba8db1459 | |||
cdada0aec8 | |||
1166533482 | |||
01538728cd | |||
3a7f6d78b0 | |||
dce48c90e9 | |||
fe70d164c1 | |||
09e2550b32 | |||
07854c3d42 | |||
858a455501 | |||
4e5eb4bcee | |||
696bad3ed6 | |||
9a9d0f02ef | |||
f46d00640b | |||
e369bd3599 | |||
75b29112a7 | |||
b7b01ecd53 | |||
801a25995c | |||
8296dea78c | |||
1da5a75c14 | |||
166de57179 | |||
85dece9e95 | |||
bfbc71215c | |||
d243c14d74 | |||
804eb27551 | |||
4266dc6951 | |||
0ba3522584 | |||
19b0e9489d | |||
d9fed9c34c | |||
81ee9f397f | |||
b9722c6796 | |||
29ade0f696 | |||
d5ae612513 | |||
65ba63d1a8 | |||
f5ffa7d84f | |||
dac3ace61d | |||
72459a04d1 | |||
1e83fcf1e3 | |||
b8769bb9e9 | |||
9f52ee8b21 | |||
90b65cd06b | |||
72a924f00e | |||
e4624eda10 | |||
4173cd82e6 | |||
b393f27e1b | |||
1a4a0e2439 | |||
4cd8f4c16e | |||
2de9d526e5 | |||
d9427c3c59 | |||
fc0cfac475 | |||
99094dbfda | |||
0711eefb7c | |||
dc40b0d969 | |||
4b5def0a8a | |||
f44fa38113 | |||
167dfeb269 | |||
a816548bb5 | |||
94001efc81 | |||
8bfafe8ecc | |||
d78045b6ab | |||
11eabc4b96 | |||
bfaa91c752 | |||
1b615e4690 | |||
7954e13154 | |||
45d8872a82 | |||
56cff46408 | |||
47a1a9c6af | |||
a434a5e657 | |||
221c213791 | |||
d2150c5cb7 | |||
70e113152d | |||
4a64102d67 | |||
9bf267166e | |||
f12249bc81 | |||
80d6c71b02 | |||
9ef4117fb8 | |||
25f3bf1fbe | |||
fe65351666 | |||
7d13946c3e | |||
1ffa8d38f1 | |||
6914d39370 | |||
04db9c7a91 | |||
c785d01a1c | |||
87ba364f89 | |||
b7e5915c7a | |||
0cef6b8f87 | |||
71f1dbd80a | |||
36edcf0cb8 | |||
8204dcad93 | |||
03bcb6cff7 | |||
66b6eed57c | |||
99b0f2c022 | |||
947a9bade7 | |||
15ea601d68 | |||
ce888966dd | |||
3fc6eb7a47 | |||
73aebf10bf | |||
292c372eb4 | |||
dc3261d9c7 | |||
301d0ab3a0 | |||
efe0a018d7 | |||
fa2a232e5f | |||
da1f022df9 | |||
8e6a6b81d9 | |||
77906c4152 | |||
26bc68753b | |||
af6b263f7a | |||
a27e216e44 | |||
50e1efa448 | |||
519ac0383a | |||
3d0ef9bc4f | |||
49e23464f9 | |||
a1c9b4b80e | |||
2b1be3e5d9 | |||
e46378ec51 | |||
27ee9c85e7 | |||
21b6ec46e3 | |||
817ce5dc96 | |||
d9af28bca7 | |||
8646be7979 | |||
14ba287e0d | |||
1671e46d99 | |||
507333c463 | |||
8b320d3e9e | |||
e1be268749 | |||
1a0019e6d0 | |||
e79cdb671f | |||
f38e643cf0 | |||
b8e190cd1d | |||
9cca654bd5 | |||
35177e2d2f | |||
1a24b193e7 | |||
272915192b | |||
96774f4c52 | |||
a034f585ba | |||
365d95c36b | |||
c6313c08ae | |||
f5764c4659 | |||
aff094575b | |||
4aaaf64f8d | |||
7b88ce273f | |||
b011af89ad | |||
1bf8c1bfe7 | |||
2b39d5d111 | |||
98663af7f6 | |||
5628824bee | |||
d12d7996bc | |||
0dcf4cbff6 | |||
884e37d242 | |||
f4a24e26c3 | |||
122eccf3dc | |||
bd598788dc | |||
406482b4da | |||
a381c97ca9 | |||
8ce78ba33c | |||
f53f148c89 | |||
0086feb645 | |||
4ee55b049f | |||
90c6f121cc | |||
d3c27ae859 | |||
8f39c1de6c | |||
4df1831187 | |||
2bce761ace | |||
d78b76aceb | |||
f07f6b84d4 | |||
d297a10570 | |||
9d0b82122a | |||
338477463a | |||
d1275760fa | |||
0f4054fa4d | |||
7545fc5d6e | |||
a1f25809cb | |||
e0a3c4bd95 | |||
d843e75512 | |||
72c57608d5 | |||
d9de7636db | |||
10b5af6967 | |||
51c050c725 | |||
eb52c47de5 | |||
4b1378dfbc | |||
1a77d86347 | |||
bd5188f4b9 | |||
034f459bfa | |||
bc405d997e | |||
af27cf2cbe | |||
83b9bf67c2 | |||
abd73b805b | |||
37bfd4db98 | |||
be74143d5f | |||
9975e5d9ac | |||
1341413966 | |||
1a5b914a6f | |||
c5e8f0d6ea | |||
3a143fe413 | |||
3445e4a08e | |||
166130c3df | |||
c3a8a905f7 | |||
2b878e87d8 | |||
063e9d40f0 | |||
2b58143164 | |||
861d4f33b7 | |||
81f4aae7d2 | |||
46ab335407 | |||
07cb0cbfcd | |||
a2392dc580 | |||
8b3235ab2b | |||
15dac6f194 | |||
3c93db8449 | |||
9d8df0b781 | |||
bcadbdbed8 | |||
05a96fa60e | |||
2e37536e7a | |||
025c4ef7f2 | |||
ecbc660bf5 | |||
ba1f17d537 | |||
3ab8f7500e | |||
0a25bec010 | |||
01e765e670 | |||
61844f2386 | |||
46aa08c953 | |||
b6c7fb82c3 | |||
fcda09009a | |||
1a6fe1f3de | |||
98e91c0607 | |||
bed2387d83 | |||
50e852acee | |||
da30623e4e | |||
7a46b367a7 | |||
d9651c7393 | |||
e371b1e759 | |||
77cf4af166 | |||
9d197317ca | |||
9a8b0b4a0d | |||
0c62b9ef08 | |||
83a5e7392a | |||
f0c8c37022 | |||
ba26d3204d | |||
d53542975e | |||
632296a271 | |||
3e089fcdb2 | |||
d61c300750 | |||
a0a97c5f40 | |||
165f3b83ca | |||
5bf95300ee | |||
adb460b270 | |||
ca80bd52fe | |||
281f8abb9a | |||
2cf2918d73 | |||
7dfb7474f5 | |||
6ee0b48c9a | |||
bd01fbf90c | |||
cd19845b6b | |||
5545883c3f | |||
75a380b0ba | |||
35fe7c6a58 | |||
69249b3139 | |||
bf897fd56d | |||
150c6e75f5 | |||
e8bc43dc64 | |||
1213689de2 | |||
c1017e8e27 | |||
7ad9e685f6 | |||
c778aaffaf | |||
b98047cacf | |||
03ace6e4b2 | |||
9b4701bcb7 | |||
174312977a | |||
963d9af817 | |||
af5ec51232 | |||
1cd9fbf6a0 | |||
72639e9e59 | |||
447dcc1480 | |||
564716faa7 | |||
3e5b4457c2 | |||
793e70d909 | |||
5761a306be | |||
adff0f2a0a | |||
4ec45a0c43 | |||
ecf4b046b5 | |||
b0cae93ac9 | |||
53b66678d4 | |||
0b9b65ef88 | |||
8a84d9d792 | |||
c535b8e1ea | |||
234fb6cd39 | |||
8714830b48 | |||
0e07b36691 | |||
ba80d3c38c | |||
e65dc82cfe | |||
bc727521c6 | |||
a8c0c884d3 | |||
b11c7157d3 | |||
578de7bcd4 | |||
cfc6b3ce9e | |||
1c7a354fe7 | |||
40a0941ca3 | |||
0ab4760272 | |||
42b2269e81 | |||
c818d846b3 | |||
3328f40416 | |||
58d10c1908 | |||
2fd0ca6a02 | |||
173028fd0d | |||
62d5bf4436 | |||
63a0d19770 | |||
8244636bf2 | |||
6a01fb361c | |||
ca637b3fb6 | |||
006293bd01 | |||
338b5d79d3 | |||
60dd0daae5 | |||
662b8283a6 | |||
cfc866cf41 | |||
e566badfff | |||
69834c417e | |||
8aa9c62afd | |||
4f29e37fe7 | |||
99e8a36bb5 | |||
669cbe227f | |||
e9156d77f1 | |||
767216c842 | |||
d3018f9061 | |||
37c6ad855b | |||
ca97678358 | |||
3bb0036ba8 | |||
ac9e2a9e7e | |||
52e95e6d0a | |||
c5d2aa7eec | |||
683220e303 | |||
44f09b32fa | |||
d1a0660a3d | |||
ee1987f188 | |||
39e9997d9e | |||
97b8c75043 | |||
7cb8349f29 | |||
6063f4c776 | |||
4899d545f1 | |||
115bf6433d | |||
e5ce1ade89 | |||
9c4174ea8a | |||
cf16957195 | |||
4de369ff95 | |||
ac3ebff8ee | |||
76b01d92d3 | |||
19144163ee | |||
535ffccbad | |||
6f5ada9692 | |||
1c7d9255ae | |||
807e6ea2ad | |||
c76f019fd0 | |||
3c2c925eed | |||
14b54be15e | |||
7fb82f7447 | |||
4a5d44a0f1 | |||
d1e3bdf29a | |||
bdf7fedd7a | |||
c163662f4a |
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
/completion/*
|
||||
/bin/*
|
21
.eslintrc.js
Normal file
21
.eslintrc.js
Normal file
@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@balena/lint/config/.eslintrc.js'],
|
||||
parserOptions: {
|
||||
project: 'tsconfig.dev.json',
|
||||
},
|
||||
root: true,
|
||||
rules: {
|
||||
ignoreDefinitionFiles: 0,
|
||||
// to avoid the `warning Forbidden non-null assertion @typescript-eslint/no-non-null-assertion`
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: ['resin-cli-visuals', 'chalk', 'common-tags', 'resin-cli-form'],
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
},
|
||||
};
|
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -4,6 +4,10 @@
|
||||
*.* -eol
|
||||
|
||||
*.sh text eol=lf
|
||||
.dockerignore eol=lf
|
||||
Dockerfile eol=lf
|
||||
Dockerfile.* eol=lf
|
||||
* text=auto eol=lf
|
||||
|
||||
# lf for the docs as it's auto-generated and will otherwise trigger an uncommited error on windows
|
||||
docs/balena-cli.md text eol=lf
|
||||
|
143
.github/actions/publish/action.yml
vendored
Normal file
143
.github/actions/publish/action.yml
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
---
|
||||
name: package and draft GitHub release
|
||||
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
||||
inputs:
|
||||
json:
|
||||
description: 'JSON stringified object containing all the inputs from the calling workflow'
|
||||
required: true
|
||||
secrets:
|
||||
description: 'JSON stringified object containing all the secrets from the calling workflow'
|
||||
required: true
|
||||
variables:
|
||||
description: 'JSON stringified object containing all the variables from the calling workflow'
|
||||
required: true
|
||||
|
||||
# --- custom environment
|
||||
XCODE_APP_LOADER_EMAIL:
|
||||
type: string
|
||||
default: 'accounts+apple@balena.io'
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
default: '20.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: 'true'
|
||||
|
||||
runs:
|
||||
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Download custom source artifact
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ runner.temp }}
|
||||
|
||||
- name: Extract custom source artifact
|
||||
shell: pwsh
|
||||
working-directory: .
|
||||
run: tar -xf ${{ runner.temp }}/custom.tgz
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
|
||||
with:
|
||||
node-version: ${{ inputs.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Set up Python 3.11
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install additional tools
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
choco install yq
|
||||
|
||||
- name: Install additional tools
|
||||
if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
brew install coreutils
|
||||
|
||||
# https://www.electron.build/code-signing.html
|
||||
# https://github.com/Apple-Actions/import-codesign-certs
|
||||
- name: Import Apple code signing certificate
|
||||
if: runner.os == 'macOS'
|
||||
uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 # v1
|
||||
with:
|
||||
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||
|
||||
- name: Import Windows code signing certificate
|
||||
if: runner.os == 'Windows'
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:SM_CLIENT_CERT_FILE_B64
|
||||
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/Certificate_pkcs12.p12
|
||||
Remove-Item -path ${{ runner.temp }} -include certificate.base64
|
||||
env:
|
||||
SM_CLIENT_CERT_FILE_B64: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_FILE_B64 }}
|
||||
|
||||
# https://github.com/product-os/scripts/tree/master/shared
|
||||
# https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml
|
||||
- name: Package release
|
||||
shell: bash
|
||||
run: |
|
||||
set -ea
|
||||
|
||||
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||
|
||||
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
|
||||
runner_arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [[ $runner_os =~ darwin|macos|osx ]]; then
|
||||
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||
CSC_KEYCHAIN=signing_temp
|
||||
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||
|
||||
elif [[ $runner_os =~ windows|win ]]; then
|
||||
SM_HOST=${{ fromJSON(inputs.secrets).SM_HOST }}
|
||||
SM_API_KEY=${{ fromJSON(inputs.secrets).SM_API_KEY }}
|
||||
SM_CLIENT_CERT_FILE='${{ runner.temp }}\Certificate_pkcs12.p12'
|
||||
SM_CLIENT_CERT_PASSWORD=${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_PASSWORD }}
|
||||
SM_CODE_SIGNING_CERT_SHA1_HASH=${{ fromJSON(inputs.secrets).SM_CODE_SIGNING_CERT_SHA1_HASH }}
|
||||
|
||||
curl --silent --retry 3 --fail https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download \
|
||||
-H "x-api-key:$SM_API_KEY" \
|
||||
-o smtools-windows-x64.msi
|
||||
msiexec -i smtools-windows-x64.msi -qn
|
||||
PATH="/c/Program Files/DigiCert/DigiCert One Signing Manager Tools:${PATH}"
|
||||
smksp_registrar.exe list
|
||||
smctl.exe keypair ls
|
||||
/c/Windows/System32/certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||
smksp_cert_sync.exe
|
||||
|
||||
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks
|
||||
PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
|
||||
fi
|
||||
|
||||
npm run package
|
||||
|
||||
find dist -type f -maxdepth 1
|
||||
|
||||
env:
|
||||
# https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks
|
||||
# https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
# https://docs.digicert.com/es/software-trust-manager/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html
|
||||
TIMESTAMP_SERVER: http://timestamp.digicert.com
|
||||
# Apple notarization (automation/build-bin.ts)
|
||||
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
|
||||
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
|
||||
XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
|
||||
with:
|
||||
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ strategy.job-index }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
65
.github/actions/test/action.yml
vendored
Normal file
65
.github/actions/test/action.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
---
|
||||
name: test release
|
||||
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
||||
inputs:
|
||||
json:
|
||||
description: "JSON stringified object containing all the inputs from the calling workflow"
|
||||
required: true
|
||||
secrets:
|
||||
description: "JSON stringified object containing all the secrets from the calling workflow"
|
||||
required: true
|
||||
variables:
|
||||
description: "JSON stringified object containing all the variables from the calling workflow"
|
||||
required: true
|
||||
|
||||
# --- custom environment
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
default: '20.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: "true"
|
||||
|
||||
runs:
|
||||
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||
using: "composite"
|
||||
steps:
|
||||
# https://github.com/actions/setup-node#caching-global-packages-data
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
|
||||
with:
|
||||
node-version: ${{ inputs.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Set up Python 3.11
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Test release
|
||||
shell: bash
|
||||
run: |
|
||||
set -ea
|
||||
|
||||
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||
|
||||
if [[ -e package-lock.json ]] || [[ -e npm-shrinkwrap.json ]]; then
|
||||
npm ci
|
||||
else
|
||||
npm i
|
||||
fi
|
||||
|
||||
npm run build
|
||||
npm run test:core
|
||||
|
||||
- name: Compress custom source
|
||||
shell: pwsh
|
||||
run: tar --exclude-vcs -acf ${{ runner.temp }}/custom.tgz .
|
||||
|
||||
- name: Upload custom artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ runner.temp }}/custom.tgz
|
||||
retention-days: 1
|
4
.github/renovate.json
vendored
Normal file
4
.github/renovate.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": ["github>balena-io/renovate-config"],
|
||||
"postUpdateOptions": ["npmDedupe"]
|
||||
}
|
45
.github/workflows/flowzone.yml
vendored
Normal file
45
.github/workflows/flowzone.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Flowzone
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, closed]
|
||||
branches: [main, master]
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, closed]
|
||||
branches: [main, master]
|
||||
jobs:
|
||||
flowzone:
|
||||
name: Flowzone
|
||||
uses: product-os/flowzone/.github/workflows/flowzone.yml@master
|
||||
# prevent duplicate workflow executions for pull_request and pull_request_target
|
||||
if: |
|
||||
(
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event_name == 'pull_request'
|
||||
) || (
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
github.event_name == 'pull_request_target'
|
||||
)
|
||||
secrets: inherit
|
||||
with:
|
||||
custom_test_matrix: >
|
||||
{
|
||||
"os": [
|
||||
["self-hosted", "X64"],
|
||||
["self-hosted", "ARM64"],
|
||||
["macos-12"],
|
||||
["windows-2019"],
|
||||
["macos-latest-xlarge"]
|
||||
]
|
||||
}
|
||||
custom_publish_matrix: >
|
||||
{
|
||||
"os": [
|
||||
["self-hosted", "X64"],
|
||||
["self-hosted", "ARM64"],
|
||||
["macos-12"],
|
||||
["windows-2019"],
|
||||
["macos-latest-xlarge"]
|
||||
]
|
||||
}
|
||||
github_prerelease: false
|
||||
restrict_custom_actions: false
|
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@ -0,0 +1 @@
|
||||
node automation/check-npm-version.js && ts-node automation/check-doc.ts
|
20
.resinci.yml
20
.resinci.yml
@ -1,20 +0,0 @@
|
||||
---
|
||||
npm:
|
||||
platforms:
|
||||
- name: linux
|
||||
os: ubuntu
|
||||
architecture: x86_64
|
||||
node_versions:
|
||||
- "12"
|
||||
- "14"
|
||||
##
|
||||
## Temporarily skip Alpine tests until the following issues are resolved:
|
||||
## * https://github.com/concourse/concourse/issues/7905
|
||||
## * https://github.com/product-os/balena-concourse/issues/631
|
||||
##
|
||||
# - name: linux
|
||||
# os: alpine
|
||||
# architecture: x86_64
|
||||
# node_versions:
|
||||
# - "12"
|
||||
# - "14"
|
File diff suppressed because it is too large
Load Diff
2889
CHANGELOG.md
2889
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -115,14 +115,28 @@ The content sources for the auto generation of `docs/balena-cli.md` are:
|
||||
* [Selected
|
||||
sections](https://github.com/balena-io/balena-cli/blob/v12.23.0/automation/capitanodoc/capitanodoc.ts#L199-L204)
|
||||
of the README file.
|
||||
* The CLI's command documentation in source code (`lib/commands/` folder), for example:
|
||||
* `lib/commands/push.ts`
|
||||
* `lib/commands/env/add.ts`
|
||||
* The CLI's command documentation in source code (`src/commands/` folder), for example:
|
||||
* `src/commands/push.ts`
|
||||
* `src/commands/env/add.ts`
|
||||
|
||||
The README file is manually edited, but subsections are automatically extracted for inclusion in
|
||||
`docs/balena-cli.md` by the `getCapitanoDoc()` function in
|
||||
[`automation/capitanodoc/capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts).
|
||||
|
||||
**IMPORTANT**
|
||||
|
||||
The file [`capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts) lists
|
||||
commands to generate documentation from. At the moment, it's manually updated and maintained alphabetically.
|
||||
|
||||
To add a new command to be documented,
|
||||
|
||||
1. Find the resource which it is part of or create a new one.
|
||||
2. List the location of the build file
|
||||
3. Make sure to add your files in alphabetical order
|
||||
4. Resources with plural names needs to have 2 sections if they have commands like: "fleet, fleets" or "device, devices" or "tag, tags"
|
||||
|
||||
Once added, run the command `npm run build` to generate the documentation
|
||||
|
||||
The `INSTALL*.md` and `TROUBLESHOOTING.md` files are also manually edited.
|
||||
|
||||
## Patches folder
|
||||
@ -210,7 +224,7 @@ command, or by manually editing or copying files to the `node_modules` folder.
|
||||
|
||||
Unexpected behavior may then be observed because of the CLI's use of the
|
||||
[fast-boot2](https://www.npmjs.com/package/fast-boot2) package that caches module resolution.
|
||||
`fast-boot2` is configured in `lib/fast-boot.ts` to automatically invalidate the cache if
|
||||
`fast-boot2` is configured in `src/fast-boot.ts` to automatically invalidate the cache if
|
||||
changes are made to the `package.json` or `npm-shrinkwrap.json` files, but the cache won't
|
||||
be automatically invalidated if `npm link` is used or if manual modifications are made to the
|
||||
`node_modules` folder. In this situation:
|
||||
|
@ -40,7 +40,7 @@ By default, the CLI is installed to the following folders:
|
||||
OS | Folders
|
||||
--- | ---
|
||||
Windows: | `C:\Program Files\balena-cli\`
|
||||
macOS: | `/usr/local/lib/balena-cli/` <br> `/usr/local/bin/balena`
|
||||
macOS: | `/usr/local/src/balena-cli/` <br> `/usr/local/bin/balena`
|
||||
|
||||
## Standalone Zip Package
|
||||
|
||||
@ -78,8 +78,8 @@ If you are a Node.js developer, you may wish to install the balena CLI via [npm]
|
||||
The npm installation involves building native (platform-specific) binary modules, which require
|
||||
some development tools to be installed first, as follows.
|
||||
|
||||
> **The balena CLI currently requires Node.js version 12 (min 12.8.0).**
|
||||
> **Versions 13 and later are not yet fully supported.**
|
||||
> **The balena CLI currently requires Node.js version ^20.6.0**
|
||||
> **Versions 21 and later are not yet fully supported.**
|
||||
|
||||
### Install development tools
|
||||
|
||||
@ -89,7 +89,7 @@ some development tools to be installed first, as follows.
|
||||
$ sudo apt-get update && sudo apt-get -y install curl python3 git make g++
|
||||
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
||||
$ . ~/.bashrc
|
||||
$ nvm install 12
|
||||
$ nvm install 20
|
||||
```
|
||||
|
||||
The `curl` command line above uses
|
||||
@ -106,15 +106,15 @@ recommended.
|
||||
```sh
|
||||
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
||||
$ . ~/.bashrc
|
||||
$ nvm install 12
|
||||
$ nvm install 20
|
||||
```
|
||||
|
||||
#### **Windows** (not WSL)
|
||||
|
||||
Install:
|
||||
|
||||
* Node.js v12 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
|
||||
* If you'd like the ability to switch between Node.js versions, install
|
||||
- Node.js v20 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
|
||||
[nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows)
|
||||
instead.
|
||||
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:
|
||||
|
@ -27,7 +27,7 @@ To update the balena CLI, repeat the steps above for the new version.
|
||||
To uninstall it, run the following command on a terminal prompt:
|
||||
|
||||
```text
|
||||
sudo /usr/local/lib/balena-cli/bin/uninstall
|
||||
sudo /usr/local/src/balena-cli/bin/uninstall
|
||||
```
|
||||
|
||||
## Additional Dependencies
|
||||
|
@ -15,21 +15,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { JsonVersions } from '../lib/commands/version';
|
||||
import type { JsonVersions } from '../src/commands/version/index';
|
||||
|
||||
import { run as oclifRun } from 'oclif';
|
||||
import { run as oclifRun } from '@oclif/core';
|
||||
import * as archiver from 'archiver';
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { execFile } from 'child_process';
|
||||
import { exec, execFile } from 'child_process';
|
||||
import * as filehound from 'filehound';
|
||||
import { Stats } from 'fs';
|
||||
import type { Stats } from 'fs';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as klaw from 'klaw';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
import * as semver from 'semver';
|
||||
import { promisify } from 'util';
|
||||
import { notarize } from '@electron/notarize';
|
||||
|
||||
import { stripIndent } from '../build/utils/lazy';
|
||||
import {
|
||||
@ -41,12 +41,11 @@ import {
|
||||
} from './utils';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export const packageJSON = loadPackageJson();
|
||||
export const version = 'v' + packageJSON.version;
|
||||
const arch = process.arch;
|
||||
const MSYS2_BASH =
|
||||
process.env.MSYSSHELLPATH || 'C:\\msys64\\usr\\bin\\bash.exe';
|
||||
|
||||
function dPath(...paths: string[]) {
|
||||
return path.join(ROOT, 'dist', ...paths);
|
||||
@ -62,9 +61,13 @@ const standaloneZips: PathByPlatform = {
|
||||
win32: dPath(`balena-cli-${version}-windows-${arch}-standalone.zip`),
|
||||
};
|
||||
|
||||
const oclifInstallers: PathByPlatform = {
|
||||
darwin: dPath('macos', `balena-${version}.pkg`),
|
||||
win32: dPath('win32', `balena-${version}-${arch}.exe`),
|
||||
const getOclifInstallersOriginalNames = async (): Promise<PathByPlatform> => {
|
||||
const { stdout } = await execAsync('git rev-parse --short HEAD');
|
||||
const sha = stdout.trim();
|
||||
return {
|
||||
darwin: dPath('macos', `balena-${version}-${sha}-${arch}.pkg`),
|
||||
win32: dPath('win32', `balena-${version}-${sha}-${arch}.exe`),
|
||||
};
|
||||
};
|
||||
|
||||
const renamedOclifInstallers: PathByPlatform = {
|
||||
@ -89,7 +92,7 @@ async function diffPkgOutput(pkgOut: string) {
|
||||
'tests',
|
||||
'test-data',
|
||||
'pkg',
|
||||
`expected-warnings-${process.platform}.txt`,
|
||||
`expected-warnings-${process.platform}-${arch}.txt`,
|
||||
);
|
||||
const absSavedPath = path.join(ROOT, relSavedPath);
|
||||
const ignoreStartsWith = [
|
||||
@ -157,7 +160,7 @@ ${sep}
|
||||
* messages (stdout and stderr) in order to call diffPkgOutput().
|
||||
*/
|
||||
async function execPkg(...args: any[]) {
|
||||
const { exec: pkgExec } = await import('pkg');
|
||||
const { exec: pkgExec } = await import('@yao-pkg/pkg');
|
||||
const outTap = new StdOutTap(true);
|
||||
try {
|
||||
outTap.tap();
|
||||
@ -182,9 +185,18 @@ async function execPkg(...args: any[]) {
|
||||
* to be directly executed from inside another binary executable.)
|
||||
*/
|
||||
async function buildPkg() {
|
||||
// https://github.com/vercel/pkg#targets
|
||||
let targets = `linux-${arch}`;
|
||||
if (process.platform === 'darwin') {
|
||||
targets = `macos-${arch}`;
|
||||
}
|
||||
// TBC: not yet possible to build for Windows arm64 on x64 nodes
|
||||
if (process.platform === 'win32') {
|
||||
targets = `win-x64`;
|
||||
}
|
||||
const args = [
|
||||
'--target',
|
||||
'host',
|
||||
'--targets',
|
||||
targets,
|
||||
'--output',
|
||||
'build-bin/balena',
|
||||
'package.json',
|
||||
@ -199,7 +211,6 @@ async function buildPkg() {
|
||||
const paths: Array<[string, string[], string[]]> = [
|
||||
// [platform, [source path], [destination path]]
|
||||
['*', ['open', 'xdg-open'], ['xdg-open']],
|
||||
['*', ['opn', 'xdg-open'], ['xdg-open-402']],
|
||||
['darwin', ['denymount', 'bin', 'denymount'], ['denymount']],
|
||||
];
|
||||
await Promise.all(
|
||||
@ -315,7 +326,11 @@ async function zipPkg() {
|
||||
});
|
||||
}
|
||||
|
||||
async function signFilesForNotarization() {
|
||||
export async function signFilesForNotarization() {
|
||||
console.log('Signing files for notarization');
|
||||
if (process.platform !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
console.log('Deleting unneeded zip files...');
|
||||
await new Promise((resolve, reject) => {
|
||||
klaw('node_modules/')
|
||||
@ -415,6 +430,7 @@ export async function buildStandaloneZip() {
|
||||
}
|
||||
|
||||
async function renameInstallerFiles() {
|
||||
const oclifInstallers = await getOclifInstallersOriginalNames();
|
||||
if (await fs.pathExists(oclifInstallers[process.platform])) {
|
||||
await fs.rename(
|
||||
oclifInstallers[process.platform],
|
||||
@ -425,20 +441,30 @@ async function renameInstallerFiles() {
|
||||
|
||||
/**
|
||||
* If the CSC_LINK and CSC_KEY_PASSWORD env vars are set, digitally sign the
|
||||
* executable installer by running the balena-io/scripts/shared/sign-exe.sh
|
||||
* script (which must be in the PATH) using a MSYS2 bash shell.
|
||||
* executable installer using Microsoft SignTool.exe (Sign Tool)
|
||||
* https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
|
||||
*/
|
||||
async function signWindowsInstaller() {
|
||||
if (process.env.CSC_LINK && process.env.CSC_KEY_PASSWORD) {
|
||||
if (process.env.SM_CODE_SIGNING_CERT_SHA1_HASH) {
|
||||
const exeName = renamedOclifInstallers[process.platform];
|
||||
console.log(`Signing installer "${exeName}"`);
|
||||
await execFileAsync(MSYS2_BASH, [
|
||||
'sign-exe.sh',
|
||||
'-f',
|
||||
exeName,
|
||||
// trust ...
|
||||
await execFileAsync('signtool.exe', [
|
||||
'sign',
|
||||
'-sha1',
|
||||
process.env.SM_CODE_SIGNING_CERT_SHA1_HASH,
|
||||
'-tr',
|
||||
process.env.TIMESTAMP_SERVER || 'http://timestamp.comodoca.com',
|
||||
'-td',
|
||||
'SHA256',
|
||||
'-fd',
|
||||
'SHA256',
|
||||
'-d',
|
||||
`balena-cli ${version}`,
|
||||
exeName,
|
||||
]);
|
||||
// ... but verify
|
||||
await execFileAsync('signtool.exe', ['verify', '-pa', '-v', exeName]);
|
||||
} else {
|
||||
console.log(
|
||||
'Skipping installer signing step because CSC_* env vars are not set',
|
||||
@ -450,14 +476,20 @@ async function signWindowsInstaller() {
|
||||
* Wait for Apple Installer Notarization to continue
|
||||
*/
|
||||
async function notarizeMacInstaller(): Promise<void> {
|
||||
const appleId = 'accounts+apple@balena.io';
|
||||
const { notarize } = await import('electron-notarize');
|
||||
await notarize({
|
||||
appBundleId: 'io.balena.etcher',
|
||||
appPath: renamedOclifInstallers.darwin,
|
||||
appleId,
|
||||
appleIdPassword: '@keychain:CLI_PASSWORD',
|
||||
});
|
||||
const teamId = process.env.XCODE_APP_LOADER_TEAM_ID || '66H43P8FRG';
|
||||
const appleId =
|
||||
process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io';
|
||||
const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD;
|
||||
|
||||
if (appleIdPassword && teamId) {
|
||||
await notarize({
|
||||
tool: 'notarytool',
|
||||
teamId,
|
||||
appPath: renamedOclifInstallers.darwin,
|
||||
appleId,
|
||||
appleIdPassword,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -471,9 +503,10 @@ export async function buildOclifInstaller() {
|
||||
let packOpts = ['-r', ROOT];
|
||||
if (process.platform === 'darwin') {
|
||||
packOS = 'macos';
|
||||
packOpts = packOpts.concat('--targets', `darwin-${arch}`);
|
||||
} else if (process.platform === 'win32') {
|
||||
packOS = 'win';
|
||||
packOpts = packOpts.concat('-t', 'win32-x64');
|
||||
packOpts = packOpts.concat('--targets', 'win32-x64');
|
||||
}
|
||||
if (packOS) {
|
||||
console.log(`Building oclif installer for CLI ${version}`);
|
||||
@ -486,15 +519,12 @@ export async function buildOclifInstaller() {
|
||||
console.log(`rimraf(${dir})`);
|
||||
await Bluebird.fromCallback((cb) => rimraf(dir, cb));
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
console.log('Signing files for notarization...');
|
||||
await signFilesForNotarization();
|
||||
}
|
||||
console.log('=======================================================');
|
||||
console.log(`oclif "${packCmd}" "${packOpts.join('" "')}"`);
|
||||
console.log(`oclif ${packCmd} ${packOpts.join(' ')}`);
|
||||
console.log(`cwd="${process.cwd()}" ROOT="${ROOT}"`);
|
||||
console.log('=======================================================');
|
||||
await oclifRun([packCmd].concat(...packOpts));
|
||||
const oclifPath = path.join(ROOT, 'node_modules', 'oclif');
|
||||
await oclifRun([packCmd].concat(...packOpts), oclifPath);
|
||||
await renameInstallerFiles();
|
||||
// The Windows installer is explicitly signed here (oclif doesn't do it).
|
||||
// The macOS installer is automatically signed by oclif (which runs the
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
import * as path from 'path';
|
||||
import { MarkdownFileParser } from './utils';
|
||||
import { GlobSync } from 'glob';
|
||||
|
||||
/**
|
||||
* This is the skeleton of CLI documentation/reference web page at:
|
||||
@ -24,173 +25,120 @@ import { MarkdownFileParser } from './utils';
|
||||
*
|
||||
* The `getCapitanoDoc` function in this module parses README.md and adds
|
||||
* some content to this object.
|
||||
*
|
||||
* IMPORTANT
|
||||
*
|
||||
* All commands need to be stored under a folder in src/commands to maintain uniformity
|
||||
* Generating docs will error out if directive not followed
|
||||
* To add a custom heading for command docs, add the heading next to the folder name
|
||||
* in the `commandHeadings` dictionary.
|
||||
*
|
||||
* This dictionary is the source of truth that creates the docs config which is used
|
||||
* to generate the CLI documentation. By default, the folder name will be used.
|
||||
*
|
||||
* Resources with plural names needs to have 2 sections if they have commands like:
|
||||
* "fleet, fleets" or "device, devices" or "tag, tags"
|
||||
*
|
||||
*/
|
||||
const capitanoDoc = {
|
||||
title: 'balena CLI Documentation',
|
||||
introduction: '',
|
||||
categories: [
|
||||
{
|
||||
title: 'API keys',
|
||||
files: ['build/commands/api-key/generate.js'],
|
||||
},
|
||||
{
|
||||
title: 'Fleet',
|
||||
files: [
|
||||
'build/commands/fleets.js',
|
||||
'build/commands/fleet/index.js',
|
||||
'build/commands/fleet/create.js',
|
||||
'build/commands/fleet/purge.js',
|
||||
'build/commands/fleet/rename.js',
|
||||
'build/commands/fleet/restart.js',
|
||||
'build/commands/fleet/rm.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
files: [
|
||||
'build/commands/login.js',
|
||||
'build/commands/logout.js',
|
||||
'build/commands/whoami.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Device',
|
||||
files: [
|
||||
'build/commands/devices/index.js',
|
||||
'build/commands/devices/supported.js',
|
||||
'build/commands/device/index.js',
|
||||
'build/commands/device/deactivate.js',
|
||||
'build/commands/device/identify.js',
|
||||
'build/commands/device/init.js',
|
||||
'build/commands/device/local-mode.js',
|
||||
'build/commands/device/move.js',
|
||||
'build/commands/device/os-update.js',
|
||||
'build/commands/device/public-url.js',
|
||||
'build/commands/device/purge.js',
|
||||
'build/commands/device/reboot.js',
|
||||
'build/commands/device/register.js',
|
||||
'build/commands/device/rename.js',
|
||||
'build/commands/device/restart.js',
|
||||
'build/commands/device/rm.js',
|
||||
'build/commands/device/shutdown.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Releases',
|
||||
files: [
|
||||
'build/commands/releases.js',
|
||||
'build/commands/release/index.js',
|
||||
'build/commands/release/finalize.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Environment Variables',
|
||||
files: [
|
||||
'build/commands/envs.js',
|
||||
'build/commands/env/add.js',
|
||||
'build/commands/env/rename.js',
|
||||
'build/commands/env/rm.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
files: [
|
||||
'build/commands/tags.js',
|
||||
'build/commands/tag/rm.js',
|
||||
'build/commands/tag/set.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Help and Version',
|
||||
files: ['help', 'build/commands/version.js'],
|
||||
},
|
||||
{
|
||||
title: 'Keys',
|
||||
files: [
|
||||
'build/commands/keys.js',
|
||||
'build/commands/key/index.js',
|
||||
'build/commands/key/add.js',
|
||||
'build/commands/key/rm.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Logs',
|
||||
files: ['build/commands/logs.js'],
|
||||
},
|
||||
{
|
||||
title: 'Network',
|
||||
files: [
|
||||
'build/commands/scan.js',
|
||||
'build/commands/ssh.js',
|
||||
'build/commands/tunnel.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Notes',
|
||||
files: ['build/commands/note.js'],
|
||||
},
|
||||
{
|
||||
title: 'OS',
|
||||
files: [
|
||||
'build/commands/os/build-config.js',
|
||||
'build/commands/os/configure.js',
|
||||
'build/commands/os/versions.js',
|
||||
'build/commands/os/download.js',
|
||||
'build/commands/os/initialize.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Config',
|
||||
files: [
|
||||
'build/commands/config/generate.js',
|
||||
'build/commands/config/inject.js',
|
||||
'build/commands/config/read.js',
|
||||
'build/commands/config/reconfigure.js',
|
||||
'build/commands/config/write.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Preload',
|
||||
files: ['build/commands/preload.js'],
|
||||
},
|
||||
{
|
||||
title: 'Push',
|
||||
files: ['build/commands/push.js'],
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
files: ['build/commands/settings.js'],
|
||||
},
|
||||
{
|
||||
title: 'Local',
|
||||
files: [
|
||||
'build/commands/local/configure.js',
|
||||
'build/commands/local/flash.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Deploy',
|
||||
files: ['build/commands/build.js', 'build/commands/deploy.js'],
|
||||
},
|
||||
{
|
||||
title: 'Platform',
|
||||
files: ['build/commands/join.js', 'build/commands/leave.js'],
|
||||
},
|
||||
{
|
||||
title: 'Utilities',
|
||||
files: ['build/commands/util/available-drives.js'],
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
files: ['build/commands/support.js'],
|
||||
},
|
||||
],
|
||||
|
||||
interface Category {
|
||||
title: string;
|
||||
files: string[];
|
||||
}
|
||||
|
||||
interface Documentation {
|
||||
title: string;
|
||||
introduction: string;
|
||||
categories: Category[];
|
||||
}
|
||||
|
||||
// Mapping folders names to custom headings in the docs
|
||||
const commandHeadings: { [key: string]: string } = {
|
||||
'api-key': 'API Keys',
|
||||
'api-keys': 'API Keys',
|
||||
login: 'Authentication',
|
||||
whoami: 'Authentication',
|
||||
logout: 'Authentication',
|
||||
env: 'Environment Variables',
|
||||
envs: 'Environment Variables',
|
||||
help: 'Help and Version',
|
||||
key: 'SSH Keys',
|
||||
keys: 'SSH Keys',
|
||||
orgs: 'Organizations',
|
||||
os: 'OS',
|
||||
util: 'Utilities',
|
||||
ssh: 'Network',
|
||||
scan: 'Network',
|
||||
tunnel: 'Network',
|
||||
build: 'Deploy',
|
||||
join: 'Platform',
|
||||
leave: 'Platform',
|
||||
app: 'Apps',
|
||||
block: 'Blocks',
|
||||
device: 'Devices',
|
||||
fleet: 'Fleets',
|
||||
release: 'Releases',
|
||||
tag: 'Tags',
|
||||
};
|
||||
|
||||
// Fetch all available commands
|
||||
const allCommandsPaths = new GlobSync('build/commands/**/*.js', {
|
||||
ignore: 'build/commands/internal/**',
|
||||
}).found;
|
||||
|
||||
// Throw error if any commands found outside of command directories
|
||||
const illegalCommandPaths = allCommandsPaths.filter((commandPath: string) =>
|
||||
/^build\/commands\/[^/]+\.js$/.test(commandPath),
|
||||
);
|
||||
|
||||
if (illegalCommandPaths.length !== 0) {
|
||||
throw new Error(
|
||||
`Found the following commands without a command directory: ${illegalCommandPaths}\n
|
||||
To resolve this error, move the respective commands to their resource directories or create new ones.\n
|
||||
Refer to the automation/capitanodoc/capitanodoc.ts file for more information.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Docs config template
|
||||
const capitanoDoc: Documentation = {
|
||||
title: 'balena CLI Documentation',
|
||||
introduction: '',
|
||||
categories: [],
|
||||
};
|
||||
|
||||
// Helper function to capitalize each word of directory name
|
||||
function formatTitle(dir: string): string {
|
||||
return dir.replace(/(^\w|\s\w)/g, (word) => word.toUpperCase());
|
||||
}
|
||||
|
||||
// Create a map to track the categories for faster lookup
|
||||
const categoriesMap: { [key: string]: Category } = {};
|
||||
|
||||
for (const commandPath of allCommandsPaths) {
|
||||
const commandDir = path.basename(path.dirname(commandPath));
|
||||
const heading = commandHeadings[commandDir] || formatTitle(commandDir);
|
||||
|
||||
if (!categoriesMap[heading]) {
|
||||
categoriesMap[heading] = { title: heading, files: [] };
|
||||
capitanoDoc.categories.push(categoriesMap[heading]);
|
||||
}
|
||||
|
||||
categoriesMap[heading].files.push(commandPath);
|
||||
}
|
||||
|
||||
// Sort Category titles alphabetically
|
||||
capitanoDoc.categories = capitanoDoc.categories.sort((a, b) =>
|
||||
a.title.localeCompare(b.title),
|
||||
);
|
||||
|
||||
// Sort Category file paths alphabetically
|
||||
capitanoDoc.categories.forEach((category) => {
|
||||
category.files.sort((a, b) => a.localeCompare(b));
|
||||
});
|
||||
|
||||
/**
|
||||
* Modify and return the `capitanoDoc` object above in order to render the
|
||||
* CLI documentation/reference web page at:
|
||||
* https://www.balena.io/docs/reference/cli/
|
||||
* Modify and return the `capitanoDoc` object above in order to generate the
|
||||
* CLI documentation at docs/balena-cli.md
|
||||
*
|
||||
* This function parses the README.md file to extract relevant sections
|
||||
* for the documentation web page.
|
||||
|
2
automation/capitanodoc/doc-types.d.ts
vendored
2
automation/capitanodoc/doc-types.d.ts
vendored
@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Command as OclifCommandClass } from '@oclif/command';
|
||||
import type { Command as OclifCommandClass } from '@oclif/core';
|
||||
|
||||
type OclifCommand = typeof OclifCommandClass;
|
||||
|
||||
|
@ -16,9 +16,9 @@
|
||||
*/
|
||||
import * as path from 'path';
|
||||
import { getCapitanoDoc } from './capitanodoc';
|
||||
import { Category, Document, OclifCommand } from './doc-types';
|
||||
import type { Category, Document, OclifCommand } from './doc-types';
|
||||
import * as markdown from './markdown';
|
||||
import { stripIndent } from '../../lib/utils/lazy';
|
||||
import { stripIndent } from '../../src/utils/lazy';
|
||||
|
||||
/**
|
||||
* Generates the markdown document (as a string) for the CLI documentation
|
||||
@ -39,7 +39,7 @@ export async function renderMarkdown(): Promise<string> {
|
||||
};
|
||||
|
||||
for (const jsFilename of commandCategory.files) {
|
||||
category.commands.push(...importOclifCommands(jsFilename));
|
||||
category.commands.push(...(await importOclifCommands(jsFilename)));
|
||||
}
|
||||
result.categories.push(category);
|
||||
}
|
||||
@ -62,12 +62,11 @@ class FakeHelpCommand {
|
||||
'$ balena help os download',
|
||||
];
|
||||
|
||||
args = [
|
||||
{
|
||||
name: 'command',
|
||||
args = {
|
||||
command: {
|
||||
description: 'command to show help for',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
usage = 'help [command]';
|
||||
|
||||
@ -79,7 +78,9 @@ class FakeHelpCommand {
|
||||
};
|
||||
}
|
||||
|
||||
function importOclifCommands(jsFilename: string): OclifCommand[] {
|
||||
async function importOclifCommands(
|
||||
jsFilename: string,
|
||||
): Promise<OclifCommand[]> {
|
||||
// TODO: Currently oclif commands with no `usage` overridden will cause
|
||||
// an error when parsed. This should be improved so that `usage` does not have
|
||||
// to be overridden if not necessary.
|
||||
@ -87,7 +88,8 @@ function importOclifCommands(jsFilename: string): OclifCommand[] {
|
||||
const command: OclifCommand =
|
||||
jsFilename === 'help'
|
||||
? (new FakeHelpCommand() as unknown as OclifCommand)
|
||||
: (require(path.join(process.cwd(), jsFilename)).default as OclifCommand);
|
||||
: ((await import(path.join(process.cwd(), jsFilename)))
|
||||
.default as OclifCommand);
|
||||
|
||||
return [command];
|
||||
}
|
||||
@ -105,5 +107,5 @@ async function printMarkdown() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
printMarkdown();
|
||||
|
@ -14,13 +14,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { flagUsages } from '@oclif/parser';
|
||||
import { Parser } from '@oclif/core';
|
||||
import * as ent from 'ent';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { getManualSortCompareFunction } from '../../lib/utils/helpers';
|
||||
import { capitanoizeOclifUsage } from '../../lib/utils/oclif-utils';
|
||||
import { Category, Document, OclifCommand } from './doc-types';
|
||||
import { getManualSortCompareFunction } from '../../src/utils/helpers';
|
||||
import { capitanoizeOclifUsage } from '../../src/utils/oclif-utils';
|
||||
import type { Category, Document, OclifCommand } from './doc-types';
|
||||
|
||||
function renderOclifCommand(command: OclifCommand): string[] {
|
||||
const result = [`## ${ent.encode(command.usage || '')}`];
|
||||
@ -37,8 +37,8 @@ function renderOclifCommand(command: OclifCommand): string[] {
|
||||
|
||||
if (!_.isEmpty(command.args)) {
|
||||
result.push('### Arguments');
|
||||
for (const arg of command.args!) {
|
||||
result.push(`#### ${arg.name.toUpperCase()}`, arg.description || '');
|
||||
for (const [name, arg] of Object.entries(command.args!)) {
|
||||
result.push(`#### ${name.toUpperCase()}`, arg.description || '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ function renderOclifCommand(command: OclifCommand): string[] {
|
||||
continue;
|
||||
}
|
||||
flag.name = name;
|
||||
const flagUsage = flagUsages([flag])
|
||||
const flagUsage = Parser.flagUsages([flag])
|
||||
.map(([usage, _description]) => usage)
|
||||
.join()
|
||||
.trim();
|
||||
|
@ -15,41 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { OptionDefinition } from 'capitano';
|
||||
import * as ent from 'ent';
|
||||
import * as fs from 'fs';
|
||||
import * as readline from 'readline';
|
||||
|
||||
export function getOptionPrefix(signature: string) {
|
||||
if (signature.length > 1) {
|
||||
return '--';
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
|
||||
export function getOptionSignature(signature: string) {
|
||||
return `${getOptionPrefix(signature)}${signature}`;
|
||||
}
|
||||
|
||||
export function parseCapitanoOption(option: OptionDefinition): string {
|
||||
let result = getOptionSignature(option.signature);
|
||||
|
||||
if (Array.isArray(option.alias)) {
|
||||
for (const alias of option.alias) {
|
||||
result += `, ${getOptionSignature(alias)}`;
|
||||
}
|
||||
} else if (typeof option.alias === 'string') {
|
||||
result += `, ${getOptionSignature(option.alias)}`;
|
||||
}
|
||||
|
||||
if (option.parameter) {
|
||||
result += ` <${option.parameter}>`;
|
||||
}
|
||||
|
||||
return ent.encode(result);
|
||||
}
|
||||
|
||||
export class MarkdownFileParser {
|
||||
constructor(public mdFilePath: string) {}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// tslint:disable-next-line:import-blacklist
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { stripIndent } from 'common-tags';
|
||||
import * as _ from 'lodash';
|
||||
import { promises as fs } from 'fs';
|
||||
@ -43,8 +43,8 @@ async function checkBuildTimestamps() {
|
||||
...gitStatus.staged,
|
||||
...gitStatus.renamed.map((o) => o.to),
|
||||
])
|
||||
// select only staged files that start with lib/ or typings/
|
||||
.filter((f) => f.match(/^(lib|typings)[/\\]/))
|
||||
// select only staged files that start with src/ or typings/
|
||||
.filter((f) => f.match(/^(src|typings)[/\\]/))
|
||||
.map((f) => path.join(ROOT, f));
|
||||
|
||||
const fStats = await Promise.all(stagedFiles.map((f) => fs.stat(f)));
|
||||
@ -82,5 +82,5 @@ async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
run();
|
||||
|
@ -23,8 +23,8 @@ function parseSemver(version) {
|
||||
* @param {string} v2
|
||||
*/
|
||||
function semverGte(v1, v2) {
|
||||
let v1Array = parseSemver(v1);
|
||||
let v2Array = parseSemver(v2);
|
||||
const v1Array = parseSemver(v1);
|
||||
const v2Array = parseSemver(v2);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (v1Array[i] < v2Array[i]) {
|
||||
return false;
|
||||
|
@ -15,59 +15,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { finalReleaseAssets, version } from './build-bin';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { throttling } from '@octokit/plugin-throttling';
|
||||
|
||||
const { GITHUB_TOKEN } = process.env;
|
||||
|
||||
/**
|
||||
* Create or update a release in GitHub's releases page, uploading the
|
||||
* installer files (standalone zip + native oclif installers).
|
||||
*/
|
||||
export async function createGitHubRelease() {
|
||||
console.log(`Publishing release ${version} to GitHub`);
|
||||
const publishRelease = await import('publish-release');
|
||||
const ghRelease = await Bluebird.fromCallback(
|
||||
publishRelease.bind(null, {
|
||||
token: GITHUB_TOKEN || '',
|
||||
owner: 'balena-io',
|
||||
repo: 'balena-cli',
|
||||
tag: version,
|
||||
name: `balena-CLI ${version}`,
|
||||
reuseRelease: true,
|
||||
assets: finalReleaseAssets[process.platform],
|
||||
}),
|
||||
);
|
||||
console.log(`Release ${version} successful: ${ghRelease.html_url}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level function to create a CLI release in GitHub's releases page:
|
||||
* call zipStandaloneInstaller(), rename the files as we'd like them to
|
||||
* display on the releases page, and call createGitHubRelease() to upload
|
||||
* the files.
|
||||
*/
|
||||
export async function release() {
|
||||
try {
|
||||
await createGitHubRelease();
|
||||
} catch (err) {
|
||||
throw new Error(`Error creating GitHub release:\n${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return a cached Octokit instance, creating a new one as needed. */
|
||||
const getOctokit = _.once(function () {
|
||||
const Octokit = (
|
||||
require('@octokit/rest') as typeof import('@octokit/rest')
|
||||
).Octokit.plugin(
|
||||
(
|
||||
require('@octokit/plugin-throttling') as typeof import('@octokit/plugin-throttling')
|
||||
).throttling,
|
||||
);
|
||||
return new Octokit({
|
||||
const OctokitConstructor = Octokit.plugin(throttling);
|
||||
return new OctokitConstructor({
|
||||
auth: GITHUB_TOKEN,
|
||||
throttle: {
|
||||
onRateLimit: (retryAfter: number, options: any) => {
|
||||
@ -103,16 +61,16 @@ const getOctokit = _.once(function () {
|
||||
* 'pages' is the total number of pages, and 'ordinal' is the ordinal number
|
||||
* (3rd, 4th, 5th...) of the first item in the current page.
|
||||
*/
|
||||
function getPageNumbers(
|
||||
async function getPageNumbers(
|
||||
response: any,
|
||||
perPageDefault: number,
|
||||
): { page: number; pages: number; ordinal: number } {
|
||||
): Promise<{ page: number; pages: number; ordinal: number }> {
|
||||
const res = { page: 1, pages: 1, ordinal: 1 };
|
||||
if (!response.headers.link) {
|
||||
return res;
|
||||
}
|
||||
const parse =
|
||||
require('parse-link-header') as typeof import('parse-link-header');
|
||||
const parse = await import('parse-link-header');
|
||||
|
||||
const parsed = parse(response.headers.link);
|
||||
if (parsed == null) {
|
||||
throw new Error(`Failed to parse link header: '${response.headers.link}'`);
|
||||
@ -122,13 +80,13 @@ function getPageNumbers(
|
||||
if (parsed.next.per_page) {
|
||||
perPage = parseInt(parsed.next.per_page, 10);
|
||||
}
|
||||
res.page = parseInt(parsed.next.page, 10) - 1;
|
||||
res.pages = parseInt(parsed.last.page, 10);
|
||||
res.page = parseInt(parsed.next.page!, 10) - 1;
|
||||
res.pages = parseInt(parsed.last!.page!, 10);
|
||||
} else {
|
||||
if (parsed.prev.per_page) {
|
||||
perPage = parseInt(parsed.prev.per_page, 10);
|
||||
if (parsed.prev!.per_page) {
|
||||
perPage = parseInt(parsed.prev!.per_page, 10);
|
||||
}
|
||||
res.page = res.pages = parseInt(parsed.prev.page, 10) + 1;
|
||||
res.page = res.pages = parseInt(parsed.prev!.page!, 10) + 1;
|
||||
}
|
||||
res.ordinal = (res.page - 1) * perPage + 1;
|
||||
return res;
|
||||
@ -154,7 +112,7 @@ async function updateGitHubReleaseDescriptions(
|
||||
) {
|
||||
const perPage = 30;
|
||||
const octokit = getOctokit();
|
||||
const options = await octokit.repos.listReleases.endpoint.merge({
|
||||
const options = octokit.repos.listReleases.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
per_page: perPage,
|
||||
@ -167,7 +125,7 @@ async function updateGitHubReleaseDescriptions(
|
||||
page: thisPage,
|
||||
pages: totalPages,
|
||||
ordinal,
|
||||
} = getPageNumbers(response, perPage);
|
||||
} = await getPageNumbers(response, perPage);
|
||||
let i = 0;
|
||||
for (const cliRelease of response.data) {
|
||||
const prefix = `[#${ordinal + i++} pg ${thisPage}/${totalPages}]`;
|
||||
|
@ -21,12 +21,10 @@ import {
|
||||
buildOclifInstaller,
|
||||
buildStandaloneZip,
|
||||
catchUncommitted,
|
||||
signFilesForNotarization,
|
||||
testShrinkwrap,
|
||||
} from './build-bin';
|
||||
import {
|
||||
release,
|
||||
updateDescriptionOfReleasesAffectedByIssue1359,
|
||||
} from './deploy-bin';
|
||||
import { updateDescriptionOfReleasesAffectedByIssue1359 } from './deploy-bin';
|
||||
|
||||
// DEBUG set to falsy for negative values else is truthy
|
||||
process.env.DEBUG = ['0', 'no', 'false', '', undefined].includes(
|
||||
@ -40,7 +38,6 @@ process.env.DEBUG = ['0', 'no', 'false', '', undefined].includes(
|
||||
* of the following strings, then call the appropriate functions:
|
||||
* 'build:installer' (to build a native oclif installer)
|
||||
* 'build:standalone' (to build a standalone pkg package)
|
||||
* 'release' (to create/update a GitHub release)
|
||||
*
|
||||
* @param args Arguments to parse (default is process.argv.slice(2))
|
||||
*/
|
||||
@ -54,32 +51,17 @@ async function parse(args?: string[]) {
|
||||
const commands: { [cmd: string]: () => void | Promise<void> } = {
|
||||
'build:installer': buildOclifInstaller,
|
||||
'build:standalone': buildStandaloneZip,
|
||||
'sign:binaries': signFilesForNotarization,
|
||||
'catch-uncommitted': catchUncommitted,
|
||||
'test-shrinkwrap': testShrinkwrap,
|
||||
fix1359: updateDescriptionOfReleasesAffectedByIssue1359,
|
||||
release,
|
||||
};
|
||||
for (const arg of args) {
|
||||
if (!commands.hasOwnProperty(arg)) {
|
||||
if (!Object.hasOwn(commands, arg)) {
|
||||
throw new Error(`command unknown: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
// The BUILD_TMP env var is used as an alternative location for oclif
|
||||
// (patched) to copy/extract the CLI files, run npm install and then
|
||||
// create the NSIS executable installer for Windows. This was necessary
|
||||
// to avoid issues with a 260-char limit on Windows paths (possibly a
|
||||
// limitation of some library used by NSIS), as the "current working dir"
|
||||
// provided by balena CI is a rather long path to start with.
|
||||
if (process.platform === 'win32' && !process.env.BUILD_TMP) {
|
||||
const randID = (await import('crypto'))
|
||||
.randomBytes(6)
|
||||
.toString('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_'); // base64url (RFC 4648)
|
||||
process.env.BUILD_TMP = `C:\\tmp\\${randID}`;
|
||||
}
|
||||
|
||||
for (const arg of args) {
|
||||
try {
|
||||
const cmdFunc = commands[arg];
|
||||
@ -103,5 +85,5 @@ export async function run(args?: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
run();
|
||||
|
@ -107,11 +107,11 @@ async function $main() {
|
||||
|
||||
const changeType = process.argv[4]
|
||||
? // if the caller specified a change type, use that one
|
||||
validateChangeType(process.argv[4])
|
||||
validateChangeType(process.argv[4])
|
||||
: // use the same change type as in the dependency, but avoid major bumps
|
||||
semverChangeType && semverChangeType !== 'major'
|
||||
? semverChangeType
|
||||
: 'minor';
|
||||
semverChangeType && semverChangeType !== 'major'
|
||||
? semverChangeType
|
||||
: 'minor';
|
||||
console.log(`Using Change-type: ${changeType}`);
|
||||
|
||||
let { stdout: currentBranch } = await run('git rev-parse --abbrev-ref HEAD');
|
||||
@ -136,5 +136,5 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
main();
|
||||
|
@ -16,8 +16,9 @@
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { diffTrimmedLines } from 'diff';
|
||||
|
||||
export const ROOT = path.join(__dirname, '..');
|
||||
|
||||
@ -65,7 +66,6 @@ export class StdOutTap {
|
||||
* https://www.npmjs.com/package/diff
|
||||
*/
|
||||
export function diffLines(str1: string, str2: string): string {
|
||||
const { diffTrimmedLines } = require('diff');
|
||||
const diffObjs = diffTrimmedLines(str1, str2);
|
||||
const prefix = (chunk: string, char: string) =>
|
||||
chunk
|
||||
@ -77,15 +77,18 @@ export function diffLines(str1: string, str2: string): string {
|
||||
return part.added
|
||||
? prefix(part.value, '+')
|
||||
: part.removed
|
||||
? prefix(part.value, '-')
|
||||
: prefix(part.value, ' ');
|
||||
? prefix(part.value, '-')
|
||||
: prefix(part.value, ' ');
|
||||
})
|
||||
.join('\n');
|
||||
return diffStr;
|
||||
}
|
||||
|
||||
export function loadPackageJson() {
|
||||
return require(path.join(ROOT, 'package.json'));
|
||||
const packageJsonPath = path.join(ROOT, 'package.json');
|
||||
|
||||
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
return JSON.parse(packageJson);
|
||||
}
|
||||
|
||||
/**
|
||||
|
23
bin/balena
23
bin/balena
@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// tslint:disable:no-var-requires
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
|
||||
// Disable oclif registering ts-node
|
||||
process.env.OCLIF_TS_NODE = 0;
|
||||
|
||||
async function run() {
|
||||
// Use fast-boot to cache require lookups, speeding up startup
|
||||
await require('../build/fast-boot').start();
|
||||
|
||||
// Set the desired es version for downstream modules that support it
|
||||
require('@balena/es-version').set('es2018');
|
||||
|
||||
// Run the CLI
|
||||
await require('../build/app').run();
|
||||
}
|
||||
|
||||
run();
|
1
bin/balena
Symbolic link
1
bin/balena
Symbolic link
@ -0,0 +1 @@
|
||||
run.js
|
@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// ****************************************************************************
|
||||
// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE
|
||||
// Before opening a PR you should build and test your changes using bin/balena
|
||||
// ****************************************************************************
|
||||
|
||||
// tslint:disable:no-var-requires
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
|
||||
// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because:
|
||||
// * fast-boot2's cacheKiller option is configured to include the timestamps of
|
||||
// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI
|
||||
// behavior when changes are made to dependencies during development. This is
|
||||
// generally a good thing, however, `balena-dev` (a few lines below) edits
|
||||
// `package.json` to modify oclif paths, and this results in cache
|
||||
// invalidation and a performance hit rather than speedup.
|
||||
// * Even if the timestamps are removed from cacheKiller, so that there is no
|
||||
// cache invalidation, fast-boot's speedup is barely noticeable when ts-node
|
||||
// is used, e.g. 1.43s vs 1.4s when running `balena version`.
|
||||
// * `fast-boot` causes unexpected behavior when used with `npm link` or
|
||||
// when the `node_modules` folder is manually modified (affecting transitive
|
||||
// dependencies) during development (e.g. bug investigations). A workaround
|
||||
// is to use `balena-dev` without `fast-boot`. See also notes in
|
||||
// `CONTRIBUTING.md`.
|
||||
|
||||
const path = require('path');
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
|
||||
// Allow balena-dev to work with oclif by temporarily
|
||||
// pointing oclif config options to lib/ instead of build/
|
||||
modifyOclifPaths();
|
||||
// Undo changes on exit
|
||||
process.on('exit', function () {
|
||||
modifyOclifPaths(true);
|
||||
});
|
||||
// Undo changes in case of ctrl-c
|
||||
process.on('SIGINT', function () {
|
||||
modifyOclifPaths(true);
|
||||
// Note process exit here will interfere with commands that do their own SIGINT handling,
|
||||
// but without it commands can not be exited.
|
||||
// So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c.
|
||||
// Ideally a better solution is needed.
|
||||
process.exit();
|
||||
});
|
||||
|
||||
// Set the desired es version for downstream modules that support it
|
||||
require('@balena/es-version').set('es2018');
|
||||
|
||||
// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the
|
||||
// default option. We upgraded ts-node and found that adding 'transpile-only'
|
||||
// was necessary to avoid a mysterious 'null' error message. On the plus side,
|
||||
// it is supposed to run faster. We still benefit from type checking when
|
||||
// running 'npm run build'.
|
||||
require('ts-node').register({
|
||||
project: path.join(rootDir, 'tsconfig.json'),
|
||||
transpileOnly: true,
|
||||
});
|
||||
require('../lib/app').run();
|
||||
|
||||
// Modify package.json oclif paths from build/ -> lib/, or vice versa
|
||||
function modifyOclifPaths(revert) {
|
||||
const fs = require('fs');
|
||||
const packageJsonPath = path.join(rootDir, 'package.json');
|
||||
|
||||
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
const packageObj = JSON.parse(packageJson);
|
||||
|
||||
if (!packageObj.oclif) {
|
||||
return;
|
||||
}
|
||||
|
||||
let oclifSectionText = JSON.stringify(packageObj.oclif);
|
||||
if (!revert) {
|
||||
oclifSectionText = oclifSectionText.replace(/\/build\//g, '/lib/');
|
||||
} else {
|
||||
oclifSectionText = oclifSectionText.replace(/\/lib\//g, '/build/');
|
||||
}
|
||||
|
||||
packageObj.oclif = JSON.parse(oclifSectionText);
|
||||
fs.writeFileSync(
|
||||
packageJsonPath,
|
||||
`${JSON.stringify(packageObj, null, 2)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
}
|
1
bin/balena-dev
Symbolic link
1
bin/balena-dev
Symbolic link
@ -0,0 +1 @@
|
||||
dev.js
|
3
bin/dev.cmd
Normal file
3
bin/dev.cmd
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
node "%~dp0\run" %*
|
87
bin/dev.js
Executable file
87
bin/dev.js
Executable file
@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// ****************************************************************************
|
||||
// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE
|
||||
// Before opening a PR you should build and test your changes using bin/balena
|
||||
// ****************************************************************************
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
|
||||
// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because:
|
||||
// * fast-boot2's cacheKiller option is configured to include the timestamps of
|
||||
// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI
|
||||
// behavior when changes are made to dependencies during development. This is
|
||||
// generally a good thing, however, `balena-dev` (a few lines below) edits
|
||||
// `package.json` to modify oclif paths, and this results in cache
|
||||
// invalidation and a performance hit rather than speedup.
|
||||
// * Even if the timestamps are removed from cacheKiller, so that there is no
|
||||
// cache invalidation, fast-boot's speedup is barely noticeable when ts-node
|
||||
// is used, e.g. 1.43s vs 1.4s when running `balena version`.
|
||||
// * `fast-boot` causes unexpected behavior when used with `npm link` or
|
||||
// when the `node_modules` folder is manually modified (affecting transitive
|
||||
// dependencies) during development (e.g. bug investigations). A workaround
|
||||
// is to use `balena-dev` without `fast-boot`. See also notes in
|
||||
// `CONTRIBUTING.md`.
|
||||
|
||||
const path = require('path');
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
|
||||
// Allow balena-dev to work with oclif by temporarily
|
||||
// pointing oclif config options to src/ instead of build/
|
||||
modifyOclifPaths();
|
||||
// Undo changes on exit
|
||||
process.on('exit', function () {
|
||||
modifyOclifPaths(true);
|
||||
});
|
||||
// Undo changes in case of ctrl-c
|
||||
process.on('SIGINT', function () {
|
||||
modifyOclifPaths(true);
|
||||
// Note process exit here will interfere with commands that do their own SIGINT handling,
|
||||
// but without it commands can not be exited.
|
||||
// So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c.
|
||||
// Ideally a better solution is needed.
|
||||
process.exit();
|
||||
});
|
||||
|
||||
// Set the desired es version for downstream modules that support it
|
||||
require('@balena/es-version').set('es2018');
|
||||
|
||||
// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the
|
||||
// default option. We upgraded ts-node and found that adding 'transpile-only'
|
||||
// was necessary to avoid a mysterious 'null' error message. On the plus side,
|
||||
// it is supposed to run faster. We still benefit from type checking when
|
||||
// running 'npm run build'.
|
||||
require('ts-node').register({
|
||||
project: path.join(rootDir, 'tsconfig.json'),
|
||||
transpileOnly: true,
|
||||
});
|
||||
require('../src/app').run(undefined, { dir: __dirname, development: true });
|
||||
|
||||
// Modify package.json oclif paths from build/ -> src/, or vice versa
|
||||
function modifyOclifPaths(revert) {
|
||||
const fs = require('fs');
|
||||
const packageJsonPath = path.join(rootDir, 'package.json');
|
||||
|
||||
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
const packageObj = JSON.parse(packageJson);
|
||||
|
||||
if (!packageObj.oclif) {
|
||||
return;
|
||||
}
|
||||
|
||||
let oclifSectionText = JSON.stringify(packageObj.oclif);
|
||||
if (!revert) {
|
||||
oclifSectionText = oclifSectionText.replace(/\/build\//g, '/src/');
|
||||
} else {
|
||||
oclifSectionText = oclifSectionText.replace(/\/src\//g, '/build/');
|
||||
}
|
||||
|
||||
packageObj.oclif = JSON.parse(oclifSectionText);
|
||||
fs.writeFileSync(
|
||||
packageJsonPath,
|
||||
`${JSON.stringify(packageObj, null, 2)}\n`,
|
||||
'utf8',
|
||||
);
|
||||
}
|
3
bin/run.cmd
Normal file
3
bin/run.cmd
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
node "%~dp0\run" %*
|
21
bin/run.js
Executable file
21
bin/run.js
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
|
||||
// Disable oclif registering ts-node
|
||||
process.env.OCLIF_TS_NODE = 0;
|
||||
|
||||
async function run() {
|
||||
// Use fast-boot to cache require lookups, speeding up startup
|
||||
await require('../build/fast-boot').start();
|
||||
|
||||
// Set the desired es version for downstream modules that support it
|
||||
require('@balena/es-version').set('es2018');
|
||||
|
||||
// Run the CLI
|
||||
await require('../build/app').run(undefined, { dir: __dirname });
|
||||
}
|
||||
|
||||
run();
|
@ -8,11 +8,13 @@ _balena() {
|
||||
local context state line curcontext="$curcontext"
|
||||
|
||||
# Valid top-level completions
|
||||
main_commands=( build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key config device device devices env fleet fleet internal key key local os release release tag util )
|
||||
main_commands=( api-key api-keys app block build config deploy device devices env envs fleet fleets internal join key keys leave local login logout logs notes orgs os preload push release releases scan settings ssh support tag tags tunnel util version whoami )
|
||||
# Sub-completions
|
||||
api_key_cmds=( generate )
|
||||
api_key_cmds=( generate revoke )
|
||||
app_cmds=( create )
|
||||
block_cmds=( create )
|
||||
config_cmds=( generate inject read reconfigure write )
|
||||
device_cmds=( deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown track-fleet )
|
||||
device_cmds=( deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet )
|
||||
devices_cmds=( supported )
|
||||
env_cmds=( add rename rm )
|
||||
fleet_cmds=( create pin purge rename restart rm track-latest )
|
||||
@ -20,7 +22,7 @@ _balena() {
|
||||
key_cmds=( add rm )
|
||||
local_cmds=( configure flash )
|
||||
os_cmds=( build-config configure download initialize versions )
|
||||
release_cmds=( finalize invalidate validate )
|
||||
release_cmds=( export finalize import invalidate validate )
|
||||
tag_cmds=( rm set )
|
||||
|
||||
|
||||
@ -43,6 +45,12 @@ _balena_sec_cmds() {
|
||||
"api-key")
|
||||
_describe -t api_key_cmds 'api-key_cmd' api_key_cmds "$@" && ret=0
|
||||
;;
|
||||
"app")
|
||||
_describe -t app_cmds 'app_cmd' app_cmds "$@" && ret=0
|
||||
;;
|
||||
"block")
|
||||
_describe -t block_cmds 'block_cmd' block_cmds "$@" && ret=0
|
||||
;;
|
||||
"config")
|
||||
_describe -t config_cmds 'config_cmd' config_cmds "$@" && ret=0
|
||||
;;
|
||||
|
@ -7,11 +7,13 @@ _balena_complete()
|
||||
local cur prev
|
||||
|
||||
# Valid top-level completions
|
||||
main_commands="build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key config device device devices env fleet fleet internal key key local os release release tag util"
|
||||
main_commands="api-key api-keys app block build config deploy device devices env envs fleet fleets internal join key keys leave local login logout logs notes orgs os preload push release releases scan settings ssh support tag tags tunnel util version whoami"
|
||||
# Sub-completions
|
||||
api_key_cmds="generate"
|
||||
api_key_cmds="generate revoke"
|
||||
app_cmds="create"
|
||||
block_cmds="create"
|
||||
config_cmds="generate inject read reconfigure write"
|
||||
device_cmds="deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown track-fleet"
|
||||
device_cmds="deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet"
|
||||
devices_cmds="supported"
|
||||
env_cmds="add rename rm"
|
||||
fleet_cmds="create pin purge rename restart rm track-latest"
|
||||
@ -19,7 +21,7 @@ _balena_complete()
|
||||
key_cmds="add rm"
|
||||
local_cmds="configure flash"
|
||||
os_cmds="build-config configure download initialize versions"
|
||||
release_cmds="finalize invalidate validate"
|
||||
release_cmds="export finalize import invalidate validate"
|
||||
tag_cmds="rm set"
|
||||
|
||||
|
||||
@ -37,6 +39,12 @@ _balena_complete()
|
||||
api-key)
|
||||
COMPREPLY=( $(compgen -W "$api_key_cmds" -- $cur) )
|
||||
;;
|
||||
app)
|
||||
COMPREPLY=( $(compgen -W "$app_cmds" -- $cur) )
|
||||
;;
|
||||
block)
|
||||
COMPREPLY=( $(compgen -W "$block_cmds" -- $cur) )
|
||||
;;
|
||||
config)
|
||||
COMPREPLY=( $(compgen -W "$config_cmds" -- $cur) )
|
||||
;;
|
||||
|
@ -31,9 +31,9 @@ if (fs.existsSync(commandsFilePath)) {
|
||||
|
||||
const commandsJson = JSON.parse(fs.readFileSync(commandsFilePath, 'utf8'));
|
||||
|
||||
var mainCommands = [];
|
||||
var additionalCommands = [];
|
||||
for (const key of Object.keys(commandsJson.commands)) {
|
||||
const mainCommands = [];
|
||||
const additionalCommands = [];
|
||||
for (const key of Object.keys(commandsJson.commands).sort()) {
|
||||
const cmd = key.split(':');
|
||||
if (cmd.length > 1) {
|
||||
additionalCommands.push(cmd);
|
||||
@ -72,8 +72,8 @@ fs.readFile(bashFilePathIn, 'utf8', function (err, data) {
|
||||
/\$main_commands\$/g,
|
||||
'main_commands="' + mainCommandsStr + '"',
|
||||
);
|
||||
var subCommands = [];
|
||||
var prevElement = additionalCommands[0][0];
|
||||
let subCommands = [];
|
||||
let prevElement = additionalCommands[0][0];
|
||||
additionalCommands.forEach(function (element) {
|
||||
if (element[0] === prevElement) {
|
||||
subCommands.push(element[1]);
|
||||
@ -134,8 +134,8 @@ fs.readFile(zshFilePathIn, 'utf8', function (err, data) {
|
||||
/\$main_commands\$/g,
|
||||
'main_commands=( ' + mainCommandsStr + ' )',
|
||||
);
|
||||
var subCommands = [];
|
||||
var prevElement = additionalCommands[0][0];
|
||||
let subCommands = [];
|
||||
let prevElement = additionalCommands[0][0];
|
||||
additionalCommands.forEach(function (element) {
|
||||
if (element[0] === prevElement) {
|
||||
subCommands.push(element[1]);
|
||||
|
4984
docs/balena-cli.md
4984
docs/balena-cli.md
File diff suppressed because it is too large
Load Diff
@ -1,149 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { Application } from 'balena-sdk';
|
||||
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
organization?: string;
|
||||
type?: string; // application device type
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default class FleetCreateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Create a fleet.
|
||||
|
||||
Create a new balena fleet.
|
||||
|
||||
You can specify the organization the fleet should belong to using
|
||||
the \`--organization\` option. The organization's handle, not its name,
|
||||
should be provided. Organization handles can be listed with the
|
||||
\`balena orgs\` command.
|
||||
|
||||
The fleet's default device type is specified with the \`--type\` option.
|
||||
The \`balena devices supported\` command can be used to list the available
|
||||
device types.
|
||||
|
||||
Interactive dropdowns will be shown for selection if no device type or
|
||||
organization is specified and there are multiple options to choose from.
|
||||
If there is a single option to choose from, it will be chosen automatically.
|
||||
This interactive behavior can be disabled by explicitly specifying a device
|
||||
type and organization.
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena fleet create MyFleet',
|
||||
'$ balena fleet create MyFleet --organization mmyorg',
|
||||
'$ balena fleet create MyFleet -o myorg --type raspberry-pi',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'fleet name',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
public static usage = 'fleet create <name>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
organization: flags.string({
|
||||
char: 'o',
|
||||
description: 'handle of the organization the fleet should belong to',
|
||||
}),
|
||||
type: flags.string({
|
||||
char: 't',
|
||||
description:
|
||||
'fleet device type (Check available types with `balena devices supported`)',
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
FleetCreateCmd,
|
||||
);
|
||||
|
||||
// Ascertain device type
|
||||
const deviceType =
|
||||
options.type ||
|
||||
(await (await import('../../utils/patterns')).selectDeviceType());
|
||||
|
||||
// Ascertain organization
|
||||
const organization =
|
||||
options.organization?.toLowerCase() || (await this.getOrganization());
|
||||
|
||||
// Create application
|
||||
let application: Application;
|
||||
try {
|
||||
application = await getBalenaSdk().models.application.create({
|
||||
name: params.name,
|
||||
deviceType,
|
||||
organization,
|
||||
});
|
||||
} catch (err) {
|
||||
if ((err.message || '').toLowerCase().includes('unique')) {
|
||||
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
|
||||
throw new ExpectedError(
|
||||
`Error: fleet "${params.name}" already exists in organization "${organization}".`,
|
||||
);
|
||||
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
|
||||
// BalenaRequestError: Request error: Unauthorized
|
||||
throw new ExpectedError(
|
||||
`Error: You are not authorized to create fleets in organization "${organization}".`,
|
||||
);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Output
|
||||
console.log(
|
||||
`Fleet created: slug "${application.slug}", device type "${deviceType}"`,
|
||||
);
|
||||
}
|
||||
|
||||
async getOrganization() {
|
||||
const { getOwnOrganizations } = await import('../../utils/sdk');
|
||||
const organizations = await getOwnOrganizations(getBalenaSdk());
|
||||
|
||||
if (organizations.length === 0) {
|
||||
// User is not a member of any organizations (should not happen).
|
||||
throw new Error('This account is not a member of any organizations');
|
||||
} else if (organizations.length === 1) {
|
||||
// User is a member of only one organization - use this.
|
||||
return organizations[0].handle;
|
||||
} else {
|
||||
// User is a member of multiple organizations -
|
||||
const { selectOrganization } = await import('../../utils/patterns');
|
||||
return selectOrganization(organizations);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Balena
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type { DataOutputOptions, DataSetOutputOptions } from './output';
|
||||
|
||||
export { DataOutputOptions, DataSetOutputOptions };
|
@ -1,158 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Balena
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getCliUx, getChalk } from '../utils/lazy';
|
||||
|
||||
export interface DataOutputOptions {
|
||||
fields?: string;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export interface DataSetOutputOptions extends DataOutputOptions {
|
||||
filter?: string;
|
||||
'no-header'?: boolean;
|
||||
'no-truncate'?: boolean;
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output message to STDERR
|
||||
*/
|
||||
export function outputMessage(msg: string) {
|
||||
// Messages go to STDERR
|
||||
console.error(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output result data to STDOUT
|
||||
* Supports:
|
||||
* - arrays of items (displayed in a tabular way),
|
||||
* - single items (displayed in a field per row format).
|
||||
*
|
||||
* @param data Array of data objects to output
|
||||
* @param fields Array of fieldnames, specifying the fields and display order
|
||||
* @param options Output options
|
||||
*/
|
||||
export async function outputData(
|
||||
data: any[] | {},
|
||||
fields: string[],
|
||||
options: DataOutputOptions | DataSetOutputOptions,
|
||||
) {
|
||||
if (Array.isArray(data)) {
|
||||
await outputDataSet(data, fields, options as DataSetOutputOptions);
|
||||
} else {
|
||||
await outputDataItem(data, fields, options as DataOutputOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the cli.ux table implementation, to output tabular data
|
||||
*
|
||||
* @param data Array of data objects to output
|
||||
* @param fields Array of fieldnames, specifying the fields and display order
|
||||
* @param options Output options
|
||||
*/
|
||||
async function outputDataSet(
|
||||
data: any[],
|
||||
fields: string[],
|
||||
options: DataSetOutputOptions,
|
||||
) {
|
||||
// Oclif expects fields to be specified in the format used in table headers (though lowercase)
|
||||
// By replacing underscores with spaces here, we can support both header format and actual field name
|
||||
// (e.g. as seen in json output).
|
||||
options.fields = options.fields?.replace(/_/g, ' ');
|
||||
options.filter = options.filter?.replace(/_/g, ' ');
|
||||
options.sort = options.sort?.replace(/_/g, ' ');
|
||||
|
||||
getCliUx().table(
|
||||
data,
|
||||
// Convert fields array to column object keys
|
||||
// that cli.ux expects. We can later add support
|
||||
// for both formats if beneficial
|
||||
fields.reduce((ac, a) => ({ ...ac, [a]: {} }), {}),
|
||||
{
|
||||
...options,
|
||||
...(options.json
|
||||
? {
|
||||
output: 'json',
|
||||
}
|
||||
: {}),
|
||||
columns: options.fields,
|
||||
printLine,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a single data object (like `resin-cli-visuals table.vertical`),
|
||||
* but supporting a subset of options from `cli-ux table` (--json and --fields)
|
||||
*
|
||||
* @param data Array of data objects to output
|
||||
* @param fields Array of fieldnames, specifying the fields and display order
|
||||
* @param options Output options
|
||||
*/
|
||||
async function outputDataItem(
|
||||
data: any,
|
||||
fields: string[],
|
||||
options: DataOutputOptions,
|
||||
) {
|
||||
const outData: typeof data = {};
|
||||
|
||||
// Convert comma separated list of fields in `options.fields` to array of correct format.
|
||||
// Note, user may have specified the true field name (e.g. `some_field`),
|
||||
// or the format displayed in headers (e.g. `Some field`, case insensitive).
|
||||
const userSelectedFields = options.fields?.split(',').map((f) => {
|
||||
return f.toLowerCase().trim().replace(/ /g, '_');
|
||||
});
|
||||
|
||||
// Order and filter the fields based on `fields` parameter and `options.fields`
|
||||
(userSelectedFields || fields).forEach((fieldName) => {
|
||||
if (fields.includes(fieldName)) {
|
||||
outData[fieldName] = data[fieldName];
|
||||
}
|
||||
});
|
||||
|
||||
if (options.json) {
|
||||
printLine(JSON.stringify(outData, undefined, 2));
|
||||
} else {
|
||||
const chalk = getChalk();
|
||||
const { capitalize } = await import('lodash');
|
||||
|
||||
// Find longest key, so we can align results
|
||||
const longestKeyLength = getLongestObjectKeyLength(outData);
|
||||
|
||||
// Output one field per line
|
||||
for (const [k, v] of Object.entries(outData)) {
|
||||
const shim = ' '.repeat(longestKeyLength - k.length);
|
||||
const kDisplay = capitalize(k.replace(/_/g, ' '));
|
||||
printLine(`${chalk.bold(kDisplay) + shim} : ${v}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLongestObjectKeyLength(o: any): number {
|
||||
return Object.keys(o).length >= 1
|
||||
? Object.keys(o).reduce((a, b) => {
|
||||
return a.length > b.length ? a : b;
|
||||
}).length
|
||||
: 0;
|
||||
}
|
||||
|
||||
function printLine(s: any) {
|
||||
// Duplicating oclif cli-ux's default implementation here,
|
||||
// but using this one explicitly for ease of testing
|
||||
process.stdout.write(s + '\n');
|
||||
}
|
28221
npm-shrinkwrap.json
generated
28221
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
156
package.json
156
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "14.4.4",
|
||||
"version": "19.0.3",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -14,33 +14,32 @@
|
||||
"bin/",
|
||||
"build/",
|
||||
"doc/",
|
||||
"lib/",
|
||||
"src/",
|
||||
"patches/",
|
||||
"!patches/**/**.dev.patch",
|
||||
"*.md",
|
||||
"npm-shrinkwrap.json",
|
||||
"oclif.manifest.json"
|
||||
],
|
||||
"bin": {
|
||||
"balena": "./bin/balena"
|
||||
"balena": "./bin/run.js"
|
||||
},
|
||||
"pkg": {
|
||||
"scripts": [
|
||||
"build/**/*.js",
|
||||
"node_modules/balena-sdk/es2018/index.js",
|
||||
"node_modules/balena-sync/build/**/*.js",
|
||||
"node_modules/pinejs-client-request/node_modules/pinejs-client-core/es2018/index.js",
|
||||
"node_modules/@balena/compose/dist/parse/schemas/*.json"
|
||||
],
|
||||
"assets": [
|
||||
"build/auth/pages/*.ejs",
|
||||
"node_modules/resin-discoverable-services/services/**/*",
|
||||
"node_modules/balena-sdk/node_modules/balena-pine/**/*",
|
||||
"node_modules/balena-pine/**/*",
|
||||
"node_modules/pinejs-client-core/**/*",
|
||||
"node_modules/opn/xdg-open",
|
||||
"node_modules/open/xdg-open",
|
||||
"node_modules/windosu/*.bat",
|
||||
"node_modules/windosu/*.cmd",
|
||||
"node_modules/axios/**/*",
|
||||
"npm-shrinkwrap.json",
|
||||
"oclif.manifest.json"
|
||||
]
|
||||
@ -48,10 +47,11 @@
|
||||
"scripts": {
|
||||
"postinstall": "node patches/apply-patches.js",
|
||||
"prebuild": "rimraf build/ build-bin/",
|
||||
"pretarball": "ts-node --transpile-only ../../automation/run.ts sign:binaries",
|
||||
"build": "npm run build:src && npm run catch-uncommitted",
|
||||
"build:t": "npm run lint && npm run build:fast && npm run build:test",
|
||||
"build:src": "npm run lint && npm run build:fast && npm run build:test && npm run build:doc && npm run build:completion",
|
||||
"build:pages": "mkdirp ./build/auth/pages/&& inline-source --compress ./lib/auth/pages/error.ejs ./build/auth/pages/error.ejs && inline-source --compress ./lib/auth/pages/success.ejs ./build/auth/pages/success.ejs",
|
||||
"build:pages": "mkdirp ./build/auth/pages/&& inline-source --compress ./src/auth/pages/error.ejs ./build/auth/pages/error.ejs && inline-source --compress ./src/auth/pages/success.ejs ./build/auth/pages/success.ejs",
|
||||
"build:fast": "npm run build:pages && tsc && npx oclif manifest",
|
||||
"build:test": "tsc -P ./tsconfig.dev.json --noEmit",
|
||||
"build:doc": "ts-node --transpile-only automation/capitanodoc/index.ts > docs/balena-cli.md",
|
||||
@ -59,23 +59,24 @@
|
||||
"build:standalone": "ts-node --transpile-only automation/run.ts build:standalone",
|
||||
"build:installer": "ts-node --transpile-only automation/run.ts build:installer",
|
||||
"package": "npm run build:fast && npm run build:standalone && npm run build:installer",
|
||||
"release": "ts-node --transpile-only automation/run.ts release",
|
||||
"pretest": "npm run build",
|
||||
"test": "npm run test:shrinkwrap && npm run test:source && npm run test:standalone",
|
||||
"test": "npm run test:shrinkwrap && npm run test:core",
|
||||
"test:core": "npm run test:source && npm run test:standalone",
|
||||
"test:shrinkwrap": "ts-node --transpile-only automation/run.ts test-shrinkwrap",
|
||||
"test:source": "cross-env BALENA_CLI_TEST_TYPE=source mocha",
|
||||
"test:standalone": "npm run build:standalone && npm run test:standalone:fast",
|
||||
"test:standalone:fast": "cross-env BALENA_CLI_TEST_TYPE=standalone mocha --config .mocharc-standalone.js",
|
||||
"test:fast": "npm run build:fast && npm run test:source",
|
||||
"test:fast-profile": "npm run test:fast -- -- --inspect-brk=0.0.0.0",
|
||||
"test:debug": "cross-env BALENA_CLI_TEST_TYPE=source mocha --inspect-brk=0.0.0.0",
|
||||
"test:only": "npm run build:fast && cross-env BALENA_CLI_TEST_TYPE=source mocha \"tests/**/${npm_config_test}.spec.ts\"",
|
||||
"catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted",
|
||||
"ci": "npm run test && npm run catch-uncommitted",
|
||||
"lint": "npm run lint-tsconfig && npm run lint-other",
|
||||
"lint-tsconfig": "balena-lint -e ts -e js -t tsconfig.dev.json --fix automation/ lib/ tests/ typings/",
|
||||
"lint-other": "balena-lint -e ts -e js --fix bin/balena bin/balena-dev completion/ .mocharc.js .mocharc-standalone.js",
|
||||
"lint-tsconfig": "balena-lint -e ts -e js -t tsconfig.dev.json --fix automation/ src/ tests/ typings/",
|
||||
"lint-other": "balena-lint -e ts -e js --fix bin/run.js bin/dev.js completion/ .mocharc.js .mocharc-standalone.js",
|
||||
"update": "ts-node --transpile-only ./automation/update-module.ts",
|
||||
"prepare": "echo {} > bin/.fast-boot.json",
|
||||
"prepare": "echo {} > bin/.fast-boot.json && husky",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
@ -90,146 +91,139 @@
|
||||
"author": "Balena Inc. (https://balena.io/)",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.8.0 <13.0.0",
|
||||
"npm": "<7.0.0"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "node automation/check-npm-version.js && ts-node automation/check-doc.ts"
|
||||
}
|
||||
"node": "^20.6.0"
|
||||
},
|
||||
"oclif": {
|
||||
"bin": "balena",
|
||||
"commands": "./build/commands",
|
||||
"helpClass": "./build/help",
|
||||
"topicSeparator": " ",
|
||||
"hooks": {
|
||||
"prerun": "./build/hooks/prerun/track",
|
||||
"command_not_found": "./build/hooks/command-not-found/suggest"
|
||||
},
|
||||
"additionalHelpFlags": [
|
||||
"help"
|
||||
],
|
||||
"macos": {
|
||||
"identifier": "io.balena.cli",
|
||||
"sign": "Developer ID Installer: Balena Ltd (66H43P8FRG)"
|
||||
},
|
||||
"plugins": [
|
||||
"@oclif/plugin-help"
|
||||
]
|
||||
"sign": "\"Developer ID Installer: Balena Ltd (66H43P8FRG)\""
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/lint": "^6.2.0",
|
||||
"@oclif/config": "^1.18.2",
|
||||
"@oclif/parser": "^3.8.6",
|
||||
"@balena/lint": "^8.0.0",
|
||||
"@electron/notarize": "^2.0.0",
|
||||
"@octokit/plugin-throttling": "^3.5.1",
|
||||
"@octokit/rest": "^18.6.7",
|
||||
"@types/archiver": "^5.1.1",
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/bluebird": "^3.5.36",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/chai-as-promised": "^7.1.4",
|
||||
"@types/cli-truncate": "^2.0.0",
|
||||
"@types/common-tags": "^1.8.1",
|
||||
"@types/dockerode": "^3.3.9",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/dockerode": "3.3.23",
|
||||
"@types/ejs": "^3.1.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/fast-levenshtein": "^0.0.4",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/global-agent": "^2.1.1",
|
||||
"@types/global-tunnel-ng": "^2.1.1",
|
||||
"@types/http-proxy": "^1.17.8",
|
||||
"@types/inquirer": "^7.3.3",
|
||||
"@types/intercept-stdout": "^0.1.0",
|
||||
"@types/is-root": "^2.1.2",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsonwebtoken": "^8.5.6",
|
||||
"@types/klaw": "^3.0.3",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/klaw": "^3.0.6",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/mixpanel": "^2.14.3",
|
||||
"@types/mocha": "^8.2.3",
|
||||
"@types/mocha": "^10.0.7",
|
||||
"@types/mock-require": "^2.0.1",
|
||||
"@types/moment-duration-format": "^2.2.3",
|
||||
"@types/ndjson": "^2.0.1",
|
||||
"@types/net-keepalive": "^0.4.1",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^12.20.42",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/node-cleanup": "^2.1.2",
|
||||
"@types/parse-link-header": "^1.0.1",
|
||||
"@types/prettyjson": "^0.0.30",
|
||||
"@types/parse-link-header": "^2.0.3",
|
||||
"@types/prettyjson": "^0.0.33",
|
||||
"@types/progress-stream": "^2.0.2",
|
||||
"@types/request": "^2.48.7",
|
||||
"@types/rewire": "^2.5.28",
|
||||
"@types/rewire": "^2.5.30",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/semver": "^7.3.9",
|
||||
"@types/shell-escape": "^0.2.0",
|
||||
"@types/sinon": "^10.0.6",
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@types/split": "^1.0.0",
|
||||
"@types/stream-to-promise": "^2.2.1",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/through2": "^2.0.36",
|
||||
"@types/tmp": "^0.2.3",
|
||||
"@types/update-notifier": "^4.1.1",
|
||||
"@types/which": "^2.0.1",
|
||||
"archiver": "^5.3.0",
|
||||
"@types/window-size": "^1.1.1",
|
||||
"@yao-pkg/pkg": "^5.11.1",
|
||||
"archiver": "^7.0.1",
|
||||
"catch-uncommitted": "^2.0.0",
|
||||
"chai": "^4.3.4",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"deep-object-diff": "^1.1.0",
|
||||
"diff": "^5.0.0",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"ent": "^2.2.0",
|
||||
"filehound": "^1.17.5",
|
||||
"fs-extra": "^9.1.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"husky": "^4.3.8",
|
||||
"husky": "^9.1.5",
|
||||
"inline-source-cli": "^2.0.0",
|
||||
"intercept-stdout": "^0.1.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mocha": "^8.4.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"klaw": "^4.1.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
"mocha": "^10.6.0",
|
||||
"mock-require": "^3.0.3",
|
||||
"nock": "^13.2.1",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"pkg": "^5.5.1",
|
||||
"publish-release": "^1.6.1",
|
||||
"rewire": "^5.0.0",
|
||||
"oclif": "^4.14.0",
|
||||
"parse-link-header": "^2.0.0",
|
||||
"rewire": "^7.0.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"sinon": "^11.1.2",
|
||||
"sinon": "^18.0.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.6.4"
|
||||
"typescript": "^5.5.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@balena/compose": "^2.1.1",
|
||||
"@balena/compose": "^4.0.1",
|
||||
"@balena/dockerignore": "^1.0.2",
|
||||
"@balena/env-parsing": "^1.1.8",
|
||||
"@balena/es-version": "^1.0.1",
|
||||
"@oclif/command": "^1.8.16",
|
||||
"@balena/release-bundle": "^0.5.2",
|
||||
"@oclif/core": "^4.0.8",
|
||||
"@resin.io/valid-email": "^0.1.0",
|
||||
"@sentry/node": "^6.16.1",
|
||||
"@types/fast-levenshtein": "0.0.1",
|
||||
"@types/update-notifier": "^4.1.1",
|
||||
"JSONStream": "^1.0.3",
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-device-init": "^6.0.0",
|
||||
"balena-errors": "^4.7.1",
|
||||
"balena-device-init": "^7.0.1",
|
||||
"balena-errors": "^4.7.3",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-image-manager": "^8.0.0",
|
||||
"balena-preload": "^12.1.0",
|
||||
"balena-sdk": "^16.28.0",
|
||||
"balena-image-manager": "^10.0.1",
|
||||
"balena-preload": "^15.0.6",
|
||||
"balena-sdk": "^19.7.3",
|
||||
"balena-semver": "^2.3.0",
|
||||
"balena-settings-client": "^4.0.7",
|
||||
"balena-settings-storage": "^7.0.0",
|
||||
"balena-sync": "^11.0.2",
|
||||
"balena-settings-client": "^5.0.2",
|
||||
"balena-settings-storage": "^8.1.0",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.19.1",
|
||||
"bonjour-service": "^1.2.1",
|
||||
"chalk": "^3.0.0",
|
||||
"chokidar": "^3.5.2",
|
||||
"cli-truncate": "^2.1.0",
|
||||
"cli-ux": "^5.5.1",
|
||||
"color-hash": "^1.1.1",
|
||||
"columnify": "^1.5.2",
|
||||
"common-tags": "^1.7.2",
|
||||
"denymount": "^2.3.0",
|
||||
"docker-modem": "3.0.0",
|
||||
"docker-modem": "^5.0.3",
|
||||
"docker-progress": "^5.1.3",
|
||||
"dockerode": "^3.3.1",
|
||||
"dockerode": "^4.0.2",
|
||||
"ejs": "^3.1.6",
|
||||
"etcher-sdk": "^6.2.1",
|
||||
"event-stream": "3.3.4",
|
||||
"etcher-sdk": "9.1.0",
|
||||
"express": "^4.17.2",
|
||||
"fast-boot2": "^1.1.0",
|
||||
"fast-levenshtein": "^3.0.0",
|
||||
@ -244,25 +238,22 @@
|
||||
"is-elevated": "^3.0.0",
|
||||
"is-root": "^2.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"klaw": "^3.0.0",
|
||||
"JSONStream": "^1.0.3",
|
||||
"livepush": "^3.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^3.0.4",
|
||||
"moment": "^2.29.1",
|
||||
"moment-duration-format": "^2.3.2",
|
||||
"ndjson": "^2.0.0",
|
||||
"net-keepalive": "^3.0.0",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"node-unzip-2": "^0.2.8",
|
||||
"oclif": "^1.18.4",
|
||||
"open": "^7.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettyjson": "^1.2.5",
|
||||
"progress-stream": "^2.0.0",
|
||||
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
||||
"request": "^2.88.2",
|
||||
"resin-cli-form": "^2.0.2",
|
||||
"resin-cli-visuals": "^1.8.0",
|
||||
"resin-cli-form": "^3.0.0",
|
||||
"resin-cli-visuals": "^2.0.0",
|
||||
"resin-doodles": "^0.2.0",
|
||||
"resin-stream-logger": "^0.1.2",
|
||||
"rimraf": "^3.0.2",
|
||||
@ -284,7 +275,12 @@
|
||||
"optionalDependencies": {
|
||||
"windosu": "^0.3.0"
|
||||
},
|
||||
"overrides": {
|
||||
"inline-source-cli": {
|
||||
"inline-source": "^8.0.3"
|
||||
}
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2022-10-18T10:00:36.262Z"
|
||||
"publishedAt": "2024-09-05T12:34:09.871Z"
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
diff --git a/node_modules/@oclif/config/lib/config.js b/node_modules/@oclif/config/lib/config.js
|
||||
index aba1da9..830f800 100644
|
||||
--- a/node_modules/@oclif/config/lib/config.js
|
||||
+++ b/node_modules/@oclif/config/lib/config.js
|
||||
@@ -165,7 +165,9 @@ class Config {
|
||||
debug('runCommand %s %o', id, argv);
|
||||
const c = this.findCommand(id);
|
||||
if (!c) {
|
||||
- await this.runHook('command_not_found', { id });
|
||||
+ // argv added to command_not_found hook
|
||||
+ // We should try to upstream this change
|
||||
+ await this.runHook('command_not_found', { id, argv });
|
||||
throw new errors_1.CLIError(`command ${id} not found`);
|
||||
}
|
||||
const command = c.load();
|
79
patches/all/@oclif+core+4.0.18.patch
Normal file
79
patches/all/@oclif+core+4.0.18.patch
Normal file
@ -0,0 +1,79 @@
|
||||
diff --git a/node_modules/@oclif/core/lib/help/command.js b/node_modules/@oclif/core/lib/help/command.js
|
||||
index 90922c8..6b7f417 100644
|
||||
--- a/node_modules/@oclif/core/lib/help/command.js
|
||||
+++ b/node_modules/@oclif/core/lib/help/command.js
|
||||
@@ -58,7 +58,8 @@ class CommandHelp extends formatter_1.HelpFormatter {
|
||||
return;
|
||||
return args.map((a) => {
|
||||
// Add ellipsis to indicate that the argument takes multiple values if strict is false
|
||||
- const name = this.command.strict === false ? `${a.name.toUpperCase()}...` : a.name.toUpperCase();
|
||||
+ let name = this.command.strict === false ? `${a.name.toUpperCase()}...` : a.name.toUpperCase();
|
||||
+ name = a.required ? `<${name}>` : `[${name}]`;
|
||||
let description = a.description || '';
|
||||
if (a.default)
|
||||
description = `${(0, theme_1.colorize)(this.config?.theme?.flagDefaultValue, `[default: ${a.default}]`)} ${description}`;
|
||||
diff --git a/node_modules/@oclif/core/lib/help/index.js b/node_modules/@oclif/core/lib/help/index.js
|
||||
index 4a34b89..d7eb6ac 100644
|
||||
--- a/node_modules/@oclif/core/lib/help/index.js
|
||||
+++ b/node_modules/@oclif/core/lib/help/index.js
|
||||
@@ -172,11 +172,12 @@ class Help extends HelpBase {
|
||||
}
|
||||
this.log(this.formatCommand(command));
|
||||
this.log('');
|
||||
- if (subTopics.length > 0) {
|
||||
+ const SUPPRESS_SUBTOPICS = true;
|
||||
+ if (subTopics.length > 0 && !SUPPRESS_SUBTOPICS) {
|
||||
this.log(this.formatTopics(subTopics));
|
||||
this.log('');
|
||||
}
|
||||
- if (subCommands.length > 0) {
|
||||
+ if (subCommands.length > 0 && !SUPPRESS_SUBTOPICS) {
|
||||
const aliases = [];
|
||||
const uniqueSubCommands = subCommands.filter((p) => {
|
||||
aliases.push(...p.aliases);
|
||||
diff --git a/node_modules/@oclif/core/lib/parser/errors.js b/node_modules/@oclif/core/lib/parser/errors.js
|
||||
index 168da99..538a880 100644
|
||||
--- a/node_modules/@oclif/core/lib/parser/errors.js
|
||||
+++ b/node_modules/@oclif/core/lib/parser/errors.js
|
||||
@@ -15,7 +15,8 @@ class CLIParseError extends errors_1.CLIError {
|
||||
parse;
|
||||
showHelp = false;
|
||||
constructor(options) {
|
||||
- options.message += '\nSee more help with --help';
|
||||
+ const help = options.command ? `\`${options.command} --help\`` : '--help';
|
||||
+ options.message += `\nSee more help with ${help}`;
|
||||
super(options.message, { exit: options.exit });
|
||||
this.parse = options.parse;
|
||||
}
|
||||
@@ -38,7 +39,8 @@ exports.InvalidArgsSpecError = InvalidArgsSpecError;
|
||||
class RequiredArgsError extends CLIParseError {
|
||||
args;
|
||||
constructor({ args, exit, flagsWithMultiple, parse, }) {
|
||||
- let message = `Missing ${args.length} required arg${args.length === 1 ? '' : 's'}`;
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
+ let message = `Missing ${args.length} required argument${args.length === 1 ? '' : 's'}`;
|
||||
const namedArgs = args.filter((a) => a.name);
|
||||
if (namedArgs.length > 0) {
|
||||
const list = (0, list_1.default)(namedArgs.map((a) => {
|
||||
@@ -52,7 +54,7 @@ class RequiredArgsError extends CLIParseError {
|
||||
message += `\n\nNote: ${flags} allow${flagsWithMultiple.length === 1 ? 's' : ''} multiple values. Because of this you need to provide all arguments before providing ${flagsWithMultiple.length === 1 ? 'that flag' : 'those flags'}.`;
|
||||
message += '\nAlternatively, you can use "--" to signify the end of the flags and the beginning of arguments.';
|
||||
}
|
||||
- super({ exit: cache_1.default.getInstance().get('exitCodes')?.requiredArgs ?? exit, message, parse });
|
||||
+ super({ exit: cache_1.default.getInstance().get('exitCodes')?.requiredArgs ?? exit, message, parse, command });
|
||||
this.args = args;
|
||||
this.showHelp = true;
|
||||
}
|
||||
diff --git a/node_modules/@oclif/core/lib/ux/list.js b/node_modules/@oclif/core/lib/ux/list.js
|
||||
index 954954c..0e507c7 100644
|
||||
--- a/node_modules/@oclif/core/lib/ux/list.js
|
||||
+++ b/node_modules/@oclif/core/lib/ux/list.js
|
||||
@@ -22,7 +22,7 @@ function renderList(items) {
|
||||
}
|
||||
left = left.padEnd(maxLength);
|
||||
right = linewrap(maxLength + 2, right);
|
||||
- return `${left} ${right}`;
|
||||
+ return `${left} : ${right}`;
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
diff --git a/node_modules/@oclif/parser/lib/errors.js b/node_modules/@oclif/parser/lib/errors.js
|
||||
index 0c93a81..95d06be 100644
|
||||
--- a/node_modules/@oclif/parser/lib/errors.js
|
||||
+++ b/node_modules/@oclif/parser/lib/errors.js
|
||||
@@ -13,7 +13,8 @@ const m = deps_1.default()
|
||||
.add('list', () => require('./list'));
|
||||
class CLIParseError extends errors_1.CLIError {
|
||||
constructor(options) {
|
||||
- options.message += '\nSee more help with --help';
|
||||
+ const help = options.command ? `\`${options.command} --help\`` : '--help';
|
||||
+ options.message += `\nSee more help with ${help}`;
|
||||
super(options.message);
|
||||
this.parse = options.parse;
|
||||
}
|
||||
@@ -34,22 +35,24 @@ class InvalidArgsSpecError extends CLIParseError {
|
||||
exports.InvalidArgsSpecError = InvalidArgsSpecError;
|
||||
class RequiredArgsError extends CLIParseError {
|
||||
constructor({ args, parse }) {
|
||||
- let message = `Missing ${args.length} required arg${args.length === 1 ? '' : 's'}`;
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
+ let message = `Missing ${args.length} required argument${args.length === 1 ? '' : 's'}`;
|
||||
const namedArgs = args.filter(a => a.name);
|
||||
if (namedArgs.length > 0) {
|
||||
const list = m.list.renderList(namedArgs.map(a => [a.name, a.description]));
|
||||
message += `:\n${list}`;
|
||||
}
|
||||
- super({ parse, message });
|
||||
+ super({ parse, message, command });
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
exports.RequiredArgsError = RequiredArgsError;
|
||||
class RequiredFlagError extends CLIParseError {
|
||||
constructor({ flag, parse }) {
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
const usage = m.list.renderList(m.help.flagUsages([flag], { displayRequired: false }));
|
||||
const message = `Missing required flag:\n${usage}`;
|
||||
- super({ parse, message });
|
||||
+ super({ parse, message, command });
|
||||
this.flag = flag;
|
||||
}
|
||||
}
|
||||
diff --git a/node_modules/@oclif/parser/lib/list.js b/node_modules/@oclif/parser/lib/list.js
|
||||
index 3907cc0..b689ca1 100644
|
||||
--- a/node_modules/@oclif/parser/lib/list.js
|
||||
+++ b/node_modules/@oclif/parser/lib/list.js
|
||||
@@ -21,7 +21,7 @@ function renderList(items) {
|
||||
}
|
||||
left = left.padEnd(maxLength);
|
||||
right = linewrap(maxLength + 2, right);
|
||||
- return `${left} ${right}`;
|
||||
+ return `${left} : ${right}`;
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
diff --git a/node_modules/@oclif/plugin-help/lib/command.js b/node_modules/@oclif/plugin-help/lib/command.js
|
||||
index b3b9010..788e5c6 100644
|
||||
--- a/node_modules/@oclif/plugin-help/lib/command.js
|
||||
+++ b/node_modules/@oclif/plugin-help/lib/command.js
|
||||
@@ -88,7 +88,7 @@ class CommandHelp {
|
||||
return;
|
||||
const body = list_1.renderList(args.map(a => {
|
||||
var _a;
|
||||
- const name = a.name.toUpperCase();
|
||||
+ const name = a.required ? `<${a.name}>` : `[${a.name}]`;
|
||||
let description = a.description || '';
|
||||
// `a.default` is actually not always a string (typing bug), hence `toString()`
|
||||
if (a.default || ((_a = a.default) === null || _a === void 0 ? void 0 : _a.toString()) === '0')
|
||||
@@ -133,9 +133,7 @@ class CommandHelp {
|
||||
if (!flag.helpValue && flag.options) {
|
||||
value = flag.options.join('|');
|
||||
}
|
||||
- if (!value.includes('|'))
|
||||
- value = underline(value);
|
||||
- left += `=${value}`;
|
||||
+ left += ` <${value}>`;
|
||||
}
|
||||
let right = flag.description || '';
|
||||
// `flag.default` is not always a string (typing bug), hence `toString()`
|
||||
diff --git a/node_modules/@oclif/plugin-help/lib/index.js b/node_modules/@oclif/plugin-help/lib/index.js
|
||||
index 04d7861..c2fb591 100644
|
||||
--- a/node_modules/@oclif/plugin-help/lib/index.js
|
||||
+++ b/node_modules/@oclif/plugin-help/lib/index.js
|
||||
@@ -98,11 +98,12 @@ class Help extends HelpBase {
|
||||
console.log(title + '\n');
|
||||
console.log(this.formatCommand(command));
|
||||
console.log('');
|
||||
- if (subTopics.length > 0) {
|
||||
+ const SUPPRESS_SUBTOPICS = true;
|
||||
+ if (subTopics.length > 0 && !SUPPRESS_SUBTOPICS) {
|
||||
console.log(this.formatTopics(subTopics));
|
||||
console.log('');
|
||||
}
|
||||
- if (subCommands.length > 0) {
|
||||
+ if (subCommands.length > 0 && !SUPPRESS_SUBTOPICS) {
|
||||
console.log(this.formatCommands(subCommands));
|
||||
console.log('');
|
||||
}
|
@ -1,278 +0,0 @@
|
||||
diff --git a/node_modules/oclif/lib/commands/pack/macos.js b/node_modules/oclif/lib/commands/pack/macos.js
|
||||
index 924f092..a69e60b 100644
|
||||
--- a/node_modules/oclif/lib/commands/pack/macos.js
|
||||
+++ b/node_modules/oclif/lib/commands/pack/macos.js
|
||||
@@ -133,6 +133,7 @@ class PackMacos extends command_1.Command {
|
||||
if (process.env.OSX_KEYCHAIN)
|
||||
args.push('--keychain', process.env.OSX_KEYCHAIN);
|
||||
args.push(dist);
|
||||
+ console.error(`[debug] oclif pkgbuild "${args.join('" "')}"`);
|
||||
await qq.x('pkgbuild', args);
|
||||
}
|
||||
}
|
||||
diff --git a/node_modules/oclif/lib/commands/pack/win.js b/node_modules/oclif/lib/commands/pack/win.js
|
||||
index bf4657e..fd58c7d 100644
|
||||
--- a/node_modules/oclif/lib/commands/pack/win.js
|
||||
+++ b/node_modules/oclif/lib/commands/pack/win.js
|
||||
@@ -52,6 +52,13 @@ VIAddVersionKey /LANG=\${LANG_ENGLISH} "ProductVersion" "\${VERSION}.0"
|
||||
InstallDir "\$PROGRAMFILES${arch === 'x64' ? '64' : ''}\\${config.dirname}"
|
||||
|
||||
Section "${config.name} CLI \${VERSION}"
|
||||
+ ; First remove any old client files.
|
||||
+ ; (Remnants of old versions were causing CLI errors)
|
||||
+ ; Initially tried running the Uninstall.exe, but was
|
||||
+ ; unable to make script wait for completion (despite using _?)
|
||||
+ DetailPrint "Removing files from previous version."
|
||||
+ RMDir /r "$INSTDIR\\client"
|
||||
+
|
||||
SetOutPath $INSTDIR
|
||||
File /r bin
|
||||
File /r client
|
||||
@@ -61,6 +68,8 @@ Section "${config.name} CLI \${VERSION}"
|
||||
WriteUninstaller "$INSTDIR\\Uninstall.exe"
|
||||
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||
"DisplayName" "${config.name}"
|
||||
+ WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||
+ "DisplayVersion" "\${VERSION}"
|
||||
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||
"UninstallString" "$\\"$INSTDIR\\uninstall.exe$\\""
|
||||
WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${config.dirname}" \\
|
||||
@@ -193,7 +202,8 @@ class PackWin extends command_1.Command {
|
||||
async run() {
|
||||
await this.checkForNSIS();
|
||||
const { flags } = this.parse(PackWin);
|
||||
- const buildConfig = await Tarballs.buildConfig(flags.root);
|
||||
+ const $targets = flags.targets ? flags.targets.split(',') : undefined;
|
||||
+ const buildConfig = await Tarballs.buildConfig(flags.root, { targets: $targets });
|
||||
const { config, version, gitSha, targets, tmp } = buildConfig;
|
||||
await Tarballs.build(buildConfig, { platform: 'win32', pack: false });
|
||||
const arches = targets.filter(t => t.platform === 'win32').map(t => t.arch);
|
||||
@@ -208,7 +218,8 @@ class PackWin extends command_1.Command {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await qq.mv(buildConfig.workspace({ platform: 'win32', arch }), [installerBase, 'client']);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
- await qq.x(`makensis ${installerBase}/${config.bin}.nsi | grep -v "\\[compress\\]" | grep -v "^File: Descending to"`);
|
||||
+ const { msysExec, toMsysPath } = require("../../util");
|
||||
+ await msysExec(`makensis ${toMsysPath(installerBase)}/${config.bin}.nsi | grep -v "\\[compress\\]" | grep -v "^File: Descending to"`);
|
||||
const templateKey = upload_util_1.templateShortKey('win32', { bin: config.bin, version: version, sha: gitSha, arch });
|
||||
const o = buildConfig.dist(`win32/${templateKey}`);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
@@ -255,4 +266,5 @@ PackWin.hidden = true;
|
||||
PackWin.description = 'create windows installer from oclif CLI';
|
||||
PackWin.flags = {
|
||||
root: command_1.flags.string({ char: 'r', description: 'path to oclif CLI root', default: '.', required: true }),
|
||||
+ targets: command_1.flags.string({char: 't', description: 'comma-separated targets to pack (e.g.: win32-x86,win32-x64)'}),
|
||||
};
|
||||
diff --git a/node_modules/oclif/lib/tarballs/build.js b/node_modules/oclif/lib/tarballs/build.js
|
||||
index d3e8e89..a5d29e2 100644
|
||||
--- a/node_modules/oclif/lib/tarballs/build.js
|
||||
+++ b/node_modules/oclif/lib/tarballs/build.js
|
||||
@@ -18,8 +18,9 @@ const pack = async (from, to) => {
|
||||
qq.cd(prevCwd);
|
||||
};
|
||||
async function build(c, options = {}) {
|
||||
- const { xz, config, version, s3Config, gitSha, nodeVersion, targets, updateConfig } = c;
|
||||
+ const { xz, config, version, s3Config, gitSha, nodeVersion, targets, updateConfig, tmp } = c;
|
||||
const prevCwd = qq.cwd();
|
||||
+ console.error(`[debug] oclif cwd="${prevCwd}"\n c.root="${c.root}" c.workspace()="${c.workspace()}"`);
|
||||
const packCLI = async () => {
|
||||
const stdout = await qq.x.stdout('npm', ['pack', '--unsafe-perm'], { cwd: c.root });
|
||||
return path.join(c.root, stdout.split('\n').pop());
|
||||
@@ -30,11 +31,19 @@ async function build(c, options = {}) {
|
||||
tarball = path.basename(tarball);
|
||||
tarball = qq.join([c.workspace(), tarball]);
|
||||
qq.cd(c.workspace());
|
||||
- await qq.x(`tar -xzf ${tarball}`);
|
||||
+ const { msysExec, toMsysPath } = require("../util");
|
||||
+ await msysExec(`tar -xzf ${toMsysPath(tarball)}`);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
for (const f of await qq.ls('package', { fullpath: true }))
|
||||
await qq.mv(f, '.');
|
||||
await qq.rm('package', tarball, 'bin/run.cmd');
|
||||
+ // rename the original balena-cli ./bin/balena entry point for oclif compatibility
|
||||
+ await qq.mv('bin/balena', 'bin/run');
|
||||
+ // The oclif installers are a production installation, while the source
|
||||
+ // `bin` folder may contain a `.fast-boot.json` file of a dev installation.
|
||||
+ // This has previously led to issues preventing the CLI from starting, so
|
||||
+ // delete `.fast-boot.json` (if any) from the destination folder.
|
||||
+ await qq.rm('bin/.fast-boot.json');
|
||||
};
|
||||
const updatePJSON = async () => {
|
||||
qq.cd(c.workspace());
|
||||
@@ -46,21 +55,21 @@ async function build(c, options = {}) {
|
||||
await qq.writeJSON('package.json', pjson);
|
||||
};
|
||||
const addDependencies = async () => {
|
||||
- qq.cd(c.workspace());
|
||||
- const yarnRoot = findYarnWorkspaceRoot(c.root) || c.root;
|
||||
- const yarn = await qq.exists([yarnRoot, 'yarn.lock']);
|
||||
- if (yarn) {
|
||||
- await qq.cp([yarnRoot, 'yarn.lock'], '.');
|
||||
- await qq.x('yarn --no-progress --production --non-interactive');
|
||||
- }
|
||||
- else {
|
||||
- let lockpath = qq.join(c.root, 'package-lock.json');
|
||||
- if (!await qq.exists(lockpath)) {
|
||||
- lockpath = qq.join(c.root, 'npm-shrinkwrap.json');
|
||||
- }
|
||||
- await qq.cp(lockpath, '.');
|
||||
- await qq.x('npm install --production');
|
||||
+ const ws = c.workspace();
|
||||
+ qq.cd(ws);
|
||||
+ console.error(`[debug] oclif copying node_modules to "${ws}"`)
|
||||
+ const source = path.join(c.root, 'node_modules');
|
||||
+ if (process.platform === 'win32') {
|
||||
+ // xcopy is much faster than `qq.cp(source, ws)`
|
||||
+ await qq.x(`xcopy "${source}" "${ws}\\node_modules" /S /E /B /I /K /Q /Y`);
|
||||
+ } else {
|
||||
+ // use the shell's `cp` on macOS in order to preserve extended
|
||||
+ // file attributes containing `codesign` digital signatures
|
||||
+ await qq.x(`cp -pR "${source}" "${ws}"`);
|
||||
}
|
||||
+ console.error(`[debug] oclif running "npm prune --production" in "${ws}"`);
|
||||
+ await qq.x('npm prune --production');
|
||||
+ console.error(`[debug] oclif done`);
|
||||
};
|
||||
const pretarball = async () => {
|
||||
qq.cd(c.workspace());
|
||||
@@ -99,7 +108,8 @@ async function build(c, options = {}) {
|
||||
output: path.join(workspace, 'bin', 'node'),
|
||||
platform: target.platform,
|
||||
arch: target.arch,
|
||||
- tmp: qq.join(config.root, 'tmp'),
|
||||
+ tmp,
|
||||
+ projectRootPath: c.root,
|
||||
});
|
||||
if (options.pack === false)
|
||||
return;
|
||||
diff --git a/node_modules/oclif/lib/tarballs/config.js b/node_modules/oclif/lib/tarballs/config.js
|
||||
index 0dc3cd7..1336219 100644
|
||||
--- a/node_modules/oclif/lib/tarballs/config.js
|
||||
+++ b/node_modules/oclif/lib/tarballs/config.js
|
||||
@@ -18,7 +18,10 @@ function gitSha(cwd, options = {}) {
|
||||
}
|
||||
exports.gitSha = gitSha;
|
||||
async function Tmp(config) {
|
||||
- const tmp = path.join(config.root, 'tmp');
|
||||
+ const tmp = process.env.BUILD_TMP
|
||||
+ ? path.join(process.env.BUILD_TMP, 'oclif')
|
||||
+ : path.join(config.root, 'tmp');
|
||||
+ console.error(`[debug] oclif tmp="${tmp}"`);
|
||||
await qq.mkdirp(tmp);
|
||||
return tmp;
|
||||
}
|
||||
@@ -43,7 +46,7 @@ async function buildConfig(root, options = {}) {
|
||||
s3Config: updateConfig.s3,
|
||||
nodeVersion: updateConfig.node.version || process.versions.node,
|
||||
workspace(target) {
|
||||
- const base = qq.join(config.root, 'tmp');
|
||||
+ const base = tmp;
|
||||
if (target && target.platform)
|
||||
return qq.join(base, [target.platform, target.arch].join('-'), upload_util_1.templateShortKey('baseDir', { bin: config.bin }));
|
||||
return qq.join(base, upload_util_1.templateShortKey('baseDir', { bin: config.bin }));
|
||||
diff --git a/node_modules/oclif/lib/tarballs/node.js b/node_modules/oclif/lib/tarballs/node.js
|
||||
index fabe5c4..e32dd76 100644
|
||||
--- a/node_modules/oclif/lib/tarballs/node.js
|
||||
+++ b/node_modules/oclif/lib/tarballs/node.js
|
||||
@@ -4,9 +4,10 @@ const errors_1 = require("@oclif/errors");
|
||||
const path = require("path");
|
||||
const qq = require("qqjs");
|
||||
const log_1 = require("../log");
|
||||
+const { isMSYS2, msysExec, toMsysPath } = require("../util");
|
||||
async function checkFor7Zip() {
|
||||
try {
|
||||
- await qq.x('7z', { stdio: [0, null, 2] });
|
||||
+ await msysExec('7z', { stdio: [0, null, 2] });
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 127)
|
||||
@@ -41,7 +42,8 @@ async function fetchNodeBinary({ nodeVersion, output, platform, arch, tmp }) {
|
||||
const basedir = path.dirname(tarball);
|
||||
await qq.mkdirp(basedir);
|
||||
await qq.download(url, tarball);
|
||||
- await qq.x(`grep ${path.basename(tarball)} ${shasums} | shasum -a 256 -c -`, { cwd: basedir });
|
||||
+ const shaCmd = isMSYS2 ? 'sha256sum -c -' : 'shasum -a 256 -c -';
|
||||
+ await msysExec(`grep ${path.basename(tarball)} ${toMsysPath(shasums)} | ${shaCmd}`, { cwd: basedir });
|
||||
};
|
||||
const extract = async () => {
|
||||
log_1.log(`extracting ${nodeBase}`);
|
||||
@@ -51,7 +53,7 @@ async function fetchNodeBinary({ nodeVersion, output, platform, arch, tmp }) {
|
||||
await qq.mkdirp(path.dirname(cache));
|
||||
if (platform === 'win32') {
|
||||
qq.pushd(nodeTmp);
|
||||
- await qq.x(`7z x -bd -y ${tarball} > /dev/null`);
|
||||
+ await msysExec(`7z x -bd -y ${toMsysPath(tarball)} > /dev/null`);
|
||||
await qq.mv([nodeBase, 'node.exe'], cache);
|
||||
qq.popd();
|
||||
}
|
||||
diff --git a/node_modules/oclif/lib/upload-util.js b/node_modules/oclif/lib/upload-util.js
|
||||
index 45392cb..3c806c7 100644
|
||||
--- a/node_modules/oclif/lib/upload-util.js
|
||||
+++ b/node_modules/oclif/lib/upload-util.js
|
||||
@@ -28,10 +28,10 @@ function templateShortKey(type, ext, options = { root: '.' }) {
|
||||
const templates = {
|
||||
baseDir: '<%- bin %>',
|
||||
unversioned: '<%- bin %>-<%- platform %>-<%- arch %><%- ext %>',
|
||||
- versioned: '<%- bin %>-v<%- version %>-<%- sha %>-<%- platform %>-<%- arch %><%- ext %>',
|
||||
- manifest: '<%- bin %>-v<%- version %>-<%- sha %>-<%- platform %>-<%- arch %>-buildmanifest',
|
||||
- macos: '<%- bin %>-v<%- version %>-<%- sha %>.pkg',
|
||||
- win32: '<%- bin %>-v<%- version %>-<%- sha %>-<%- arch %>.exe',
|
||||
+ versioned: '<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %><%- ext %>',
|
||||
+ manifest: '<%- bin %>-v<%- version %>-<%- platform %>-<%- arch %>-buildmanifest',
|
||||
+ macos: '<%- bin %>-v<%- version %>.pkg',
|
||||
+ win32: '<%- bin %>-v<%- version %>-<%- arch %>.exe',
|
||||
deb: '<%- bin %>_<%- versionShaRevision %>_<%- arch %>.deb',
|
||||
};
|
||||
return _.template(templates[type])(Object.assign({}, options));
|
||||
diff --git a/node_modules/oclif/lib/util.js b/node_modules/oclif/lib/util.js
|
||||
index 17748ad..4928fc9 100644
|
||||
--- a/node_modules/oclif/lib/util.js
|
||||
+++ b/node_modules/oclif/lib/util.js
|
||||
@@ -67,3 +67,47 @@ exports.sortVersionsObjectByKeysDesc = (input) => {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
+
|
||||
+// OSTYPE is 'msys' for MSYS 1.0 and for MSYS2, or 'cygwin' for Cygwin
|
||||
+// but note that OSTYPE is not "exported" by default, so run: export OSTYPE=$OSTYPE
|
||||
+// MSYSTEM is 'MINGW32' for MSYS 1.0, 'MSYS' for MSYS2, and undefined for Cygwin
|
||||
+const isCygwin = process.env.OSTYPE === 'cygwin';
|
||||
+const isMinGW = process.env.MSYSTEM && process.env.MSYSTEM.startsWith('MINGW');
|
||||
+const isMSYS2 = process.env.MSYSTEM && process.env.MSYSTEM.startsWith('MSYS');
|
||||
+const MSYSSHELLPATH = process.env.MSYSSHELLPATH ||
|
||||
+ (isMSYS2 ? 'C:\\msys64\\usr\\bin\\bash.exe' :
|
||||
+ (isMinGW ? 'C:\\MinGW\\msys\\1.0\\bin\\bash.exe' :
|
||||
+ (isCygwin ? 'C:\\cygwin64\\bin\\bash.exe' : '/bin/sh')));
|
||||
+
|
||||
+exports.isCygwin = isCygwin;
|
||||
+exports.isMinGW = isMinGW;
|
||||
+exports.isMSYS2 = isMSYS2;
|
||||
+console.error(`[debug] oclif MSYSSHELLPATH=${MSYSSHELLPATH} MSYSTEM=${process.env.MSYSTEM} OSTYPE=${process.env.OSTYPE} isMSYS2=${isMSYS2} isMingGW=${isMinGW} isCygwin=${isCygwin}`);
|
||||
+
|
||||
+const qq = require("qqjs");
|
||||
+
|
||||
+/* Convert a Windows path like 'C:\tmp' to a MSYS path like '/c/tmp' */
|
||||
+function toMsysPath(windowsPath) {
|
||||
+ // 'c:\myfolder' -> '/c/myfolder' or '/cygdrive/c/myfolder'
|
||||
+ let msysPath = windowsPath.replace(/\\/g, '/');
|
||||
+ if (isMSYS2 || isMinGW) {
|
||||
+ msysPath = msysPath.replace(/^([a-zA-Z]):/, '/$1');
|
||||
+ } else if (isCygwin) {
|
||||
+ msysPath = msysPath.replace(/^([a-zA-Z]):/, '/cygdrive/$1');
|
||||
+ }
|
||||
+ console.error(`[debug] oclif toMsysPath before="${windowsPath}" after="${msysPath}"`);
|
||||
+ return msysPath;
|
||||
+}
|
||||
+exports.toMsysPath = toMsysPath;
|
||||
+
|
||||
+/* Like qqjs qq.x(), but using MSYS bash on Windows instead of cmd.exe */
|
||||
+async function msysExec(cmd, options = {}) {
|
||||
+ if (process.platform !== 'win32') {
|
||||
+ return qq.x(cmd, options);
|
||||
+ }
|
||||
+ const sh = MSYSSHELLPATH;
|
||||
+ const args = ['-c', cmd];
|
||||
+ console.error(`[debug] oclif msysExec sh="${sh}" args=${JSON.stringify(args)} options=${JSON.stringify(options)}`);
|
||||
+ return qq.x(sh, args, options);
|
||||
+}
|
||||
+exports.msysExec = msysExec;
|
35
patches/all/oclif+4.14.0.dev.patch
Normal file
35
patches/all/oclif+4.14.0.dev.patch
Normal file
@ -0,0 +1,35 @@
|
||||
diff --git a/node_modules/oclif/lib/commands/pack/win.js b/node_modules/oclif/lib/commands/pack/win.js
|
||||
index ef7f90e..8264b7c 100644
|
||||
--- a/node_modules/oclif/lib/commands/pack/win.js
|
||||
+++ b/node_modules/oclif/lib/commands/pack/win.js
|
||||
@@ -76,6 +76,12 @@ InstallDir "\$PROGRAMFILES${arch === 'x64' ? '64' : ''}\\${config.dirname}"
|
||||
${customization}
|
||||
|
||||
Section "${config.name} CLI \${VERSION}"
|
||||
+ ; First remove any old client files.
|
||||
+ ; (Remnants of old versions were causing CLI errors)
|
||||
+ ; Initially tried running the Uninstall.exe, but was
|
||||
+ ; unable to make script wait for completion (despite using _?)
|
||||
+ DetailPrint "Removing files from previous version."
|
||||
+ RMDir /r "$INSTDIR\\client"
|
||||
SetOutPath $INSTDIR
|
||||
File /r bin
|
||||
File /r client
|
||||
diff --git a/node_modules/oclif/lib/tarballs/build.js b/node_modules/oclif/lib/tarballs/build.js
|
||||
index 14d5a6e..7b42a6f 100644
|
||||
--- a/node_modules/oclif/lib/tarballs/build.js
|
||||
+++ b/node_modules/oclif/lib/tarballs/build.js
|
||||
@@ -200,6 +200,13 @@ const extractCLI = async (tarball, c) => {
|
||||
(0, promises_1.rm)(path.join(workspace, path.basename(tarball)), { recursive: true }),
|
||||
(0, fs_extra_1.remove)(path.join(workspace, 'bin', 'run.cmd')),
|
||||
]);
|
||||
+
|
||||
+ // The oclif installers are a production installation, while the source
|
||||
+ // `bin` folder may contain a `.fast-boot.json` file of a dev installation.
|
||||
+ // This has previously led to issues preventing the CLI from starting, so
|
||||
+ // delete `.fast-boot.json` (if any) from the destination folder.
|
||||
+ await (0, fs_extra_1.remove)(path.join(workspace, 'bin', '.fast-boot.json'));
|
||||
+
|
||||
};
|
||||
const buildTarget = async (target, c, options) => {
|
||||
const workspace = c.workspace(target);
|
@ -1,15 +1,16 @@
|
||||
diff --git a/node_modules/open/index.js b/node_modules/open/index.js
|
||||
index 3bf5373..e042b64 100644
|
||||
index 13147d0..ff161dd 100644
|
||||
--- a/node_modules/open/index.js
|
||||
+++ b/node_modules/open/index.js
|
||||
@@ -11,7 +11,9 @@ const pAccess = promisify(fs.access);
|
||||
const pExecFile = promisify(childProcess.execFile);
|
||||
@@ -10,7 +10,10 @@ const pAccess = promisify(fs.access);
|
||||
const pReadFile = promisify(fs.readFile);
|
||||
|
||||
// Path to included `xdg-open`.
|
||||
-const localXdgOpenPath = path.join(__dirname, 'xdg-open');
|
||||
+const localXdgOpenPath = process.pkg
|
||||
+ ? path.join(path.dirname(process.execPath), 'xdg-open')
|
||||
+ : path.join(__dirname, 'xdg-open');
|
||||
+
|
||||
|
||||
// Convert a path from WSL format to Windows format:
|
||||
// `/mnt/c/Program Files/Example/MyApp.exe` → `C:\Program Files\Example\MyApp.exe`
|
||||
/**
|
||||
Get the mount point for fixed drives in WSL.
|
@ -1,15 +0,0 @@
|
||||
diff --git a/node_modules/opn/index.js b/node_modules/opn/index.js
|
||||
index 13dcb66..0f0c1df 100644
|
||||
--- a/node_modules/opn/index.js
|
||||
+++ b/node_modules/opn/index.js
|
||||
@@ -51,7 +51,9 @@ module.exports = function (target, opts) {
|
||||
if (opts.app) {
|
||||
cmd = opts.app;
|
||||
} else {
|
||||
- cmd = path.join(__dirname, 'xdg-open');
|
||||
+ cmd = process.pkg
|
||||
+ ? path.join(path.dirname(process.execPath), 'xdg-open-402')
|
||||
+ : path.join(__dirname, 'xdg-open');
|
||||
}
|
||||
|
||||
if (appArgs.length > 0) {
|
@ -1,8 +1,8 @@
|
||||
diff --git a/node_modules/node-gyp-build/index.js b/node_modules/node-gyp-build/index.js
|
||||
index b5096ed..7cd451a 100644
|
||||
--- a/node_modules/node-gyp-build/index.js
|
||||
+++ b/node_modules/node-gyp-build/index.js
|
||||
@@ -29,6 +29,9 @@ load.path = function (dir) {
|
||||
diff --git a/node_modules/node-gyp-build/node-gyp-build.js b/node_modules/node-gyp-build/node-gyp-build.js
|
||||
index 61b398e..3cc3be8 100644
|
||||
--- a/node_modules/node-gyp-build/node-gyp-build.js
|
||||
+++ b/node_modules/node-gyp-build/node-gyp-build.js
|
||||
@@ -30,6 +30,9 @@ load.resolve = load.path = function (dir) {
|
||||
if (process.env[name + '_PREBUILD']) dir = process.env[name + '_PREBUILD']
|
||||
} catch (err) {}
|
||||
|
4
repo.yml
4
repo.yml
@ -10,11 +10,9 @@ upstream:
|
||||
url: 'https://github.com/balena-io-modules/balena-image-manager'
|
||||
- repo: 'balena-preload'
|
||||
url: 'https://github.com/balena-io-modules/balena-preload'
|
||||
- repo: 'balena-sync'
|
||||
url: 'https://github.com/balena-io-modules/balena-sync'
|
||||
- repo: 'etcher-sdk'
|
||||
url: 'https://github.com/balena-io-modules/etcher-sdk/'
|
||||
- repo: 'balena-compose'
|
||||
- repo: '@balena/compose'
|
||||
url: 'https://github.com/balena-io-modules/balena-compose'
|
||||
- repo: 'docker-progress'
|
||||
url: 'https://github.com/balena-io-modules/docker-progress'
|
||||
|
@ -16,14 +16,15 @@
|
||||
*/
|
||||
|
||||
import * as packageJSON from '../package.json';
|
||||
import type { AppOptions } from './preparser';
|
||||
import {
|
||||
AppOptions,
|
||||
checkDeletedCommand,
|
||||
preparseArgs,
|
||||
unsupportedFlag,
|
||||
} from './preparser';
|
||||
import { CliSettings } from './utils/bootstrap';
|
||||
import { onceAsync } from './utils/lazy';
|
||||
import { run as mainRun, settings } from '@oclif/core';
|
||||
|
||||
/**
|
||||
* Sentry.io setup
|
||||
@ -114,10 +115,16 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
}
|
||||
|
||||
const runPromise = (async function (shouldFlush: boolean) {
|
||||
const { CustomMain } = await import('./utils/oclif-utils');
|
||||
let isEEXIT = false;
|
||||
try {
|
||||
await CustomMain.run(command);
|
||||
if (options.development) {
|
||||
// In dev mode -> use ts-node and dev plugins
|
||||
process.env.NODE_ENV = 'development';
|
||||
settings.debug = true;
|
||||
}
|
||||
// For posteriority: We can't use default oclif 'execute' as
|
||||
// We customize error handling and flushing
|
||||
await mainRun(command, options.loadOptions ?? options.dir);
|
||||
} catch (error) {
|
||||
// oclif sometimes exits with ExitError code EEXIT 0 (not an error),
|
||||
// for example the `balena help` command.
|
||||
@ -130,7 +137,8 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
}
|
||||
}
|
||||
if (shouldFlush) {
|
||||
await import('@oclif/command/flush');
|
||||
const { flush } = await import('@oclif/core');
|
||||
await flush();
|
||||
}
|
||||
// TODO: figure out why we need to call fast-boot stop() here, in
|
||||
// addition to calling it in the main `run()` function in this file.
|
||||
@ -150,8 +158,8 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
await Promise.all([trackPromise, deprecationPromise, runPromise]);
|
||||
}
|
||||
|
||||
/** CLI entrypoint. Called by the `bin/balena` and `bin/balena-dev` scripts. */
|
||||
export async function run(cliArgs = process.argv, options: AppOptions = {}) {
|
||||
/** CLI entrypoint. Called by the `bin/run.js` and `bin/dev.js` scripts. */
|
||||
export async function run(cliArgs = process.argv, options: AppOptions) {
|
||||
try {
|
||||
const { setOfflineModeEnvVars, normalizeEnvVars, pkgExec } = await import(
|
||||
'./utils/bootstrap'
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@ -15,13 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '@oclif/command';
|
||||
import { Command } from '@oclif/core';
|
||||
import {
|
||||
InsufficientPrivilegesError,
|
||||
NotAvailableInOfflineModeError,
|
||||
} from './errors';
|
||||
import { stripIndent } from './utils/lazy';
|
||||
import * as output from './framework/output';
|
||||
|
||||
export default abstract class BalenaCommand extends Command {
|
||||
/**
|
||||
@ -168,7 +167,4 @@ export default abstract class BalenaCommand extends Command {
|
||||
await this.getStdin();
|
||||
}
|
||||
}
|
||||
|
||||
protected outputMessage = output.outputMessage;
|
||||
protected outputData = output.outputData;
|
||||
}
|
@ -15,20 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default class GenerateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Generate a new balenaCloud API key.
|
||||
@ -41,24 +33,23 @@ export default class GenerateCmd extends Command {
|
||||
`;
|
||||
public static examples = ['$ balena api-key generate "Jenkins Key"'];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'name',
|
||||
public static args = {
|
||||
name: Args.string({
|
||||
description: 'the API key name',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'api-key generate <name>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(GenerateCmd);
|
||||
const { args: params } = await this.parse(GenerateCmd);
|
||||
|
||||
let key;
|
||||
try {
|
67
src/commands/api-key/revoke.ts
Normal file
67
src/commands/api-key/revoke.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class RevokeCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Revoke balenaCloud API keys.
|
||||
|
||||
Revoke balenaCloud API keys with the given
|
||||
comma-separated list of ids.
|
||||
|
||||
The given balenaCloud API keys will no longer be usable.
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena api-key revoke 123',
|
||||
'$ balena api-key revoke 123,124,456',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
ids: Args.string({
|
||||
description: 'the API key ids',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'api-key revoke <ids>';
|
||||
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(RevokeCmd);
|
||||
|
||||
const apiKeyIds = params.ids.split(',');
|
||||
if (apiKeyIds.filter((apiKeyId) => !apiKeyId.match(/^\d+$/)).length > 0) {
|
||||
console.log('API key ids must be positive integers');
|
||||
return;
|
||||
}
|
||||
await Promise.all(
|
||||
apiKeyIds.map(
|
||||
async (id) => await getBalenaSdk().models.apiKey.revoke(Number(id)),
|
||||
),
|
||||
);
|
||||
console.log('Successfully revoked the given API keys');
|
||||
}
|
||||
}
|
83
src/commands/api-keys/index.ts
Normal file
83
src/commands/api-keys/index.ts
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class ApiKeysCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Print a list of balenaCloud API keys.
|
||||
|
||||
Print a list of balenaCloud API keys.
|
||||
|
||||
Print a list of balenaCloud API keys for the current user or for a specific fleet with the \`--fleet\` option.
|
||||
`;
|
||||
public static examples = ['$ balena api-keys'];
|
||||
|
||||
public static usage = 'api-keys';
|
||||
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
user: Flags.boolean({
|
||||
char: 'u',
|
||||
description: 'show API keys for your user',
|
||||
}),
|
||||
fleet: cf.fleet,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = await this.parse(ApiKeysCmd);
|
||||
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const actorId = options.fleet
|
||||
? (
|
||||
await getApplication(getBalenaSdk(), options.fleet, {
|
||||
$select: 'actor',
|
||||
})
|
||||
).actor
|
||||
: await getBalenaSdk().auth.getActorId();
|
||||
const keys = await getBalenaSdk().pine.get({
|
||||
resource: 'api_key',
|
||||
options: {
|
||||
$select: ['id', 'created_at', 'name', 'description', 'expiry_date'],
|
||||
$filter: {
|
||||
is_of__actor: actorId,
|
||||
...(options.user
|
||||
? {
|
||||
name: {
|
||||
$ne: null,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
$orderby: 'name asc',
|
||||
},
|
||||
});
|
||||
const fields = ['id', 'name', 'created_at', 'description', 'expiry_date'];
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
keys.map((key) => _.mapValues(key, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
83
src/commands/app/create.ts
Normal file
83
src/commands/app/create.ts
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class AppCreateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Create an app.
|
||||
|
||||
Create a new balena app.
|
||||
|
||||
You can specify the organization the app should belong to using
|
||||
the \`--organization\` option. The organization's handle, not its name,
|
||||
should be provided. Organization handles can be listed with the
|
||||
\`balena orgs\` command.
|
||||
|
||||
The app's default device type is specified with the \`--type\` option.
|
||||
The \`balena devices supported\` command can be used to list the available
|
||||
device types.
|
||||
|
||||
Interactive dropdowns will be shown for selection if no device type or
|
||||
organization is specified and there are multiple options to choose from.
|
||||
If there is a single option to choose from, it will be chosen automatically.
|
||||
This interactive behavior can be disabled by explicitly specifying a device
|
||||
type and organization.
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena app create MyApp',
|
||||
'$ balena app create MyApp --organization mmyorg',
|
||||
'$ balena app create MyApp -o myorg --type raspberry-pi',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
name: Args.string({
|
||||
description: 'app name',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'app create <name>';
|
||||
|
||||
public static flags = {
|
||||
organization: Flags.string({
|
||||
char: 'o',
|
||||
description: 'handle of the organization the app should belong to',
|
||||
}),
|
||||
type: Flags.string({
|
||||
char: 't',
|
||||
description:
|
||||
'app device type (Check available types with `balena devices supported`)',
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(AppCreateCmd);
|
||||
|
||||
await (
|
||||
await import('../../utils/application-create')
|
||||
).applicationCreateBase('app', options, params);
|
||||
}
|
||||
}
|
83
src/commands/block/create.ts
Normal file
83
src/commands/block/create.ts
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class BlockCreateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Create an block.
|
||||
|
||||
Create a new balena block.
|
||||
|
||||
You can specify the organization the block should belong to using
|
||||
the \`--organization\` option. The organization's handle, not its name,
|
||||
should be provided. Organization handles can be listed with the
|
||||
\`balena orgs\` command.
|
||||
|
||||
The block's default device type is specified with the \`--type\` option.
|
||||
The \`balena devices supported\` command can be used to list the available
|
||||
device types.
|
||||
|
||||
Interactive dropdowns will be shown for selection if no device type or
|
||||
organization is specified and there are multiple options to choose from.
|
||||
If there is a single option to choose from, it will be chosen automatically.
|
||||
This interactive behavior can be disabled by explicitly specifying a device
|
||||
type and organization.
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena block create MyBlock',
|
||||
'$ balena block create MyBlock --organization mmyorg',
|
||||
'$ balena block create MyBlock -o myorg --type raspberry-pi',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
name: Args.string({
|
||||
description: 'block name',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'block create <name>';
|
||||
|
||||
public static flags = {
|
||||
organization: Flags.string({
|
||||
char: 'o',
|
||||
description: 'handle of the organization the block should belong to',
|
||||
}),
|
||||
type: Flags.string({
|
||||
char: 't',
|
||||
description:
|
||||
'block device type (Check available types with `balena devices supported`)',
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(BlockCreateCmd);
|
||||
|
||||
await (
|
||||
await import('../../utils/application-create')
|
||||
).applicationCreateBase('block', options, params);
|
||||
}
|
||||
}
|
@ -15,22 +15,31 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import Command from '../command';
|
||||
import { getBalenaSdk } from '../utils/lazy';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import * as compose from '../utils/compose';
|
||||
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
|
||||
import { Args, Flags } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import { getBalenaSdk } from '../../utils/lazy';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import * as compose from '../../utils/compose';
|
||||
import type {
|
||||
ApplicationType,
|
||||
BalenaSDK,
|
||||
DeviceType,
|
||||
PineOptions,
|
||||
PineTypedResult,
|
||||
} from 'balena-sdk';
|
||||
import {
|
||||
buildArgDeprecation,
|
||||
dockerignoreHelp,
|
||||
registrySecretsHelp,
|
||||
} from '../utils/messages';
|
||||
import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types';
|
||||
import { buildProject, composeCliFlags } from '../utils/compose_ts';
|
||||
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
|
||||
import { dockerCliFlags } from '../utils/docker';
|
||||
} from '../../utils/messages';
|
||||
import type { ComposeCliFlags, ComposeOpts } from '../../utils/compose-types';
|
||||
import { buildProject, composeCliFlags } from '../../utils/compose_ts';
|
||||
import type { BuildOpts, DockerCliFlags } from '../../utils/docker';
|
||||
import { dockerCliFlags } from '../../utils/docker';
|
||||
|
||||
// TODO: For this special one we can't use Interfaces.InferredFlags/InferredArgs
|
||||
// because of the 'registry-secrets' type which is defined in the actual code
|
||||
// as a path (string | undefined) but then the cli turns it into an object
|
||||
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
|
||||
arch?: string;
|
||||
deviceType?: string;
|
||||
@ -39,10 +48,6 @@ interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export default class BuildCmd extends Command {
|
||||
public static description = `\
|
||||
Build a project locally.
|
||||
@ -68,27 +73,25 @@ ${dockerignoreHelp}
|
||||
public static examples = [
|
||||
'$ balena build --fleet myFleet',
|
||||
'$ balena build ./source/ --fleet myorg/myfleet',
|
||||
'$ balena build --deviceType raspberrypi3 --emulated',
|
||||
'$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated',
|
||||
'$ balena build --docker /var/run/docker.sock --fleet myFleet # Linux, Mac',
|
||||
'$ balena build --docker //./pipe/docker_engine --fleet myFleet # Windows',
|
||||
'$ balena build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem -f myFleet',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'source',
|
||||
description: 'path of project source directory',
|
||||
},
|
||||
];
|
||||
public static args = {
|
||||
source: Args.string({ description: 'path of project source directory' }),
|
||||
};
|
||||
|
||||
public static usage = 'build [source]';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
arch: flags.string({
|
||||
public static flags = {
|
||||
arch: Flags.string({
|
||||
description: 'the architecture to build for',
|
||||
char: 'A',
|
||||
}),
|
||||
deviceType: flags.string({
|
||||
deviceType: Flags.string({
|
||||
description: 'the type of device this build is for',
|
||||
char: 'd',
|
||||
}),
|
||||
@ -97,15 +100,13 @@ ${dockerignoreHelp}
|
||||
...dockerCliFlags,
|
||||
// NOTE: Not supporting -h for help, because of clash with -h in DockerCliFlags
|
||||
// Revisit this in future release.
|
||||
help: flags.help({}),
|
||||
help: Flags.help({}),
|
||||
};
|
||||
|
||||
public static primary = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
BuildCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(BuildCmd);
|
||||
|
||||
await Command.checkLoggedInIf(!!options.fleet);
|
||||
|
||||
@ -120,6 +121,8 @@ ${dockerignoreHelp}
|
||||
options.source = params.source;
|
||||
delete params.source;
|
||||
|
||||
await this.resolveArchFromDeviceType(sdk, options);
|
||||
|
||||
await this.validateOptions(options, sdk);
|
||||
|
||||
// Build args are under consideration for removal - warn user
|
||||
@ -133,7 +136,7 @@ ${dockerignoreHelp}
|
||||
|
||||
try {
|
||||
await this.buildProject(docker, logger, composeOpts, {
|
||||
app,
|
||||
appType: app?.application_type?.[0],
|
||||
arch: options.arch!,
|
||||
deviceType: options.deviceType!,
|
||||
buildEmulated: options.emulated,
|
||||
@ -154,14 +157,14 @@ ${dockerignoreHelp}
|
||||
(opts.fleet == null && (opts.arch == null || opts.deviceType == null)) ||
|
||||
(opts.fleet != null && (opts.arch != null || opts.deviceType != null))
|
||||
) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
throw new ExpectedError(
|
||||
'You must specify either a fleet (-f), or the device type (-d) and architecture (-A)',
|
||||
'You must specify either a fleet (-f), or the device type (-d) and optionally the architecture (-A)',
|
||||
);
|
||||
}
|
||||
|
||||
// Validate project directory
|
||||
const { validateProjectDirectory } = await import('../utils/compose_ts');
|
||||
const { validateProjectDirectory } = await import('../../utils/compose_ts');
|
||||
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
|
||||
sdk,
|
||||
{
|
||||
@ -176,9 +179,42 @@ ${dockerignoreHelp}
|
||||
opts['registry-secrets'] = registrySecrets;
|
||||
}
|
||||
|
||||
protected async resolveArchFromDeviceType(sdk: BalenaSDK, opts: FlagsDef) {
|
||||
if (opts.deviceType != null && opts.arch == null) {
|
||||
try {
|
||||
const deviceTypeOpts = {
|
||||
$select: 'is_of__cpu_architecture',
|
||||
$expand: {
|
||||
is_of__cpu_architecture: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
} satisfies PineOptions<DeviceType>;
|
||||
opts.arch = (
|
||||
(await sdk.models.deviceType.get(
|
||||
opts.deviceType,
|
||||
deviceTypeOpts,
|
||||
)) as PineTypedResult<DeviceType, typeof deviceTypeOpts>
|
||||
).is_of__cpu_architecture[0].slug;
|
||||
} catch (err) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
if (err instanceof sdk.errors.BalenaInvalidDeviceType) {
|
||||
let message = err.message;
|
||||
if (!(await sdk.auth.isLoggedIn())) {
|
||||
message = `${message}. In case you are trying to use a private device type, please try to log in first.`;
|
||||
}
|
||||
throw new ExpectedError(message);
|
||||
}
|
||||
throw new ExpectedError(
|
||||
'Failed to resolve the architecture of the provided device type. If you are in an air-gapped environment please also define the architecture (-A) parameter.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async getAppAndResolveArch(opts: FlagsDef) {
|
||||
if (opts.fleet) {
|
||||
const { getAppWithArch } = await import('../utils/helpers');
|
||||
const { getAppWithArch } = await import('../../utils/helpers');
|
||||
const app = await getAppWithArch(opts.fleet);
|
||||
opts.arch = app.arch;
|
||||
opts.deviceType = app.is_for__device_type[0].slug;
|
||||
@ -187,7 +223,7 @@ ${dockerignoreHelp}
|
||||
}
|
||||
|
||||
protected async prepareBuild(options: FlagsDef) {
|
||||
const { getDocker, generateBuildOpts } = await import('../utils/docker');
|
||||
const { getDocker, generateBuildOpts } = await import('../../utils/docker');
|
||||
const [docker, buildOpts, composeOpts] = await Promise.all([
|
||||
getDocker(options),
|
||||
generateBuildOpts(options),
|
||||
@ -208,24 +244,24 @@ ${dockerignoreHelp}
|
||||
* buildEmulated
|
||||
* buildOpts: arguments to forward to docker build command
|
||||
*
|
||||
* @param {DockerToolbelt} docker
|
||||
* @param {Dockerode} docker
|
||||
* @param {Logger} logger
|
||||
* @param {ComposeOpts} composeOpts
|
||||
* @param opts
|
||||
*/
|
||||
protected async buildProject(
|
||||
docker: import('dockerode'),
|
||||
logger: import('../utils/logger'),
|
||||
logger: import('../../utils/logger'),
|
||||
composeOpts: ComposeOpts,
|
||||
opts: {
|
||||
app?: Application;
|
||||
appType?: Pick<ApplicationType, 'supports_multicontainer'>;
|
||||
arch: string;
|
||||
deviceType: string;
|
||||
buildEmulated: boolean;
|
||||
buildOpts: BuildOpts;
|
||||
},
|
||||
) {
|
||||
const { loadProject } = await import('../utils/compose_ts');
|
||||
const { loadProject } = await import('../../utils/compose_ts');
|
||||
|
||||
const project = await loadProject(
|
||||
logger,
|
||||
@ -234,11 +270,10 @@ ${dockerignoreHelp}
|
||||
opts.buildOpts.t,
|
||||
);
|
||||
|
||||
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
|
||||
if (
|
||||
appType != null &&
|
||||
opts.appType != null &&
|
||||
project.descriptors.length > 1 &&
|
||||
!appType.supports_multicontainer
|
||||
!opts.appType.supports_multicontainer
|
||||
) {
|
||||
logger.logWarn(
|
||||
'Target fleet does not support multiple containers.\n' +
|
@ -15,31 +15,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import type { Interfaces } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
|
||||
import type { PineDeferred } from 'balena-sdk';
|
||||
|
||||
interface FlagsDef {
|
||||
version: string; // OS version
|
||||
fleet?: string;
|
||||
dev?: boolean; // balenaOS development variant
|
||||
device?: string;
|
||||
deviceApiKey?: string;
|
||||
deviceType?: string;
|
||||
'generate-device-api-key': boolean;
|
||||
output?: string;
|
||||
// Options for non-interactive configuration
|
||||
network?: string;
|
||||
wifiSsid?: string;
|
||||
wifiKey?: string;
|
||||
appUpdatePollInterval?: string;
|
||||
'provisioning-key-name'?: string;
|
||||
'provisioning-key-expiry-date'?: string;
|
||||
help: void;
|
||||
}
|
||||
import {
|
||||
applicationIdInfo,
|
||||
devModeInfo,
|
||||
secureBootInfo,
|
||||
} from '../../utils/messages';
|
||||
import type { BalenaSDK, PineDeferred } from 'balena-sdk';
|
||||
|
||||
export default class ConfigGenerateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -51,6 +37,8 @@ export default class ConfigGenerateCmd extends Command {
|
||||
|
||||
${devModeInfo.split('\n').join('\n\t\t')}
|
||||
|
||||
${secureBootInfo.split('\n').join('\n\t\t')}
|
||||
|
||||
To configure an image for a fleet of mixed device types, use the --fleet option
|
||||
alongside the --deviceType option to specify the target device type.
|
||||
|
||||
@ -66,6 +54,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>',
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
|
||||
@ -73,13 +62,14 @@ export default class ConfigGenerateCmd extends Command {
|
||||
|
||||
public static usage = 'config generate';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
version: flags.string({
|
||||
public static flags = {
|
||||
version: Flags.string({
|
||||
description: 'a balenaOS version',
|
||||
required: true,
|
||||
}),
|
||||
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||
dev: cf.dev,
|
||||
secureBoot: cf.secureBoot,
|
||||
device: {
|
||||
...cf.device,
|
||||
exclusive: [
|
||||
@ -88,44 +78,44 @@ export default class ConfigGenerateCmd extends Command {
|
||||
'provisioning-key-expiry-date',
|
||||
],
|
||||
},
|
||||
deviceApiKey: flags.string({
|
||||
deviceApiKey: Flags.string({
|
||||
description:
|
||||
'custom device key - note that this is only supported on balenaOS 2.0.3+',
|
||||
char: 'k',
|
||||
}),
|
||||
deviceType: flags.string({
|
||||
deviceType: Flags.string({
|
||||
description:
|
||||
"device type slug (run 'balena devices supported' for possible values)",
|
||||
}),
|
||||
'generate-device-api-key': flags.boolean({
|
||||
'generate-device-api-key': Flags.boolean({
|
||||
description: 'generate a fresh device key for the device',
|
||||
}),
|
||||
output: flags.string({
|
||||
output: Flags.string({
|
||||
description: 'path of output file',
|
||||
char: 'o',
|
||||
}),
|
||||
// Options for non-interactive configuration
|
||||
network: flags.string({
|
||||
network: Flags.string({
|
||||
description: 'the network type to use: ethernet or wifi',
|
||||
options: ['ethernet', 'wifi'],
|
||||
}),
|
||||
wifiSsid: flags.string({
|
||||
wifiSsid: Flags.string({
|
||||
description:
|
||||
'the wifi ssid to use (used only if --network is set to wifi)',
|
||||
}),
|
||||
wifiKey: flags.string({
|
||||
wifiKey: Flags.string({
|
||||
description:
|
||||
'the wifi key to use (used only if --network is set to wifi)',
|
||||
}),
|
||||
appUpdatePollInterval: flags.string({
|
||||
appUpdatePollInterval: Flags.string({
|
||||
description:
|
||||
'supervisor cloud polling interval in minutes (e.g. for device variables)',
|
||||
}),
|
||||
'provisioning-key-name': flags.string({
|
||||
'provisioning-key-name': Flags.string({
|
||||
description: 'custom key name assigned to generated provisioning api key',
|
||||
exclusive: ['device'],
|
||||
}),
|
||||
'provisioning-key-expiry-date': flags.string({
|
||||
'provisioning-key-expiry-date': Flags.string({
|
||||
description:
|
||||
'expiry date assigned to generated provisioning api key (format: YYYY-MM-DD)',
|
||||
exclusive: ['device'],
|
||||
@ -135,17 +125,25 @@ export default class ConfigGenerateCmd extends Command {
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
|
||||
|
||||
public async getApplication(balena: BalenaSDK, fleet: string) {
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
return await getApplication(balena, fleet, {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = await this.parse(ConfigGenerateCmd);
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
await this.validateOptions(options);
|
||||
|
||||
let resourceDeviceType: string;
|
||||
let application: ApplicationWithDeviceType | null = null;
|
||||
let application: Awaited<ReturnType<typeof this.getApplication>> | null =
|
||||
null;
|
||||
let device:
|
||||
| (DeviceWithDeviceType & { belongs_to__application: PineDeferred })
|
||||
| null = null;
|
||||
@ -165,11 +163,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
resourceDeviceType = device.is_of__device_type[0].slug;
|
||||
} else {
|
||||
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
||||
application = (await getApplication(balena, options.fleet!, {
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
})) as ApplicationWithDeviceType;
|
||||
application = await this.getApplication(balena, options.fleet!);
|
||||
resourceDeviceType = application.is_for__device_type[0].slug;
|
||||
}
|
||||
|
||||
@ -191,8 +185,16 @@ export default class ConfigGenerateCmd extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
const deviceManifest = await balena.models.device.getManifestBySlug(
|
||||
const deviceManifest =
|
||||
await balena.models.config.getDeviceTypeManifestBySlug(deviceType);
|
||||
|
||||
const { validateSecureBootOptionAndWarn } = await import(
|
||||
'../../utils/config'
|
||||
);
|
||||
await validateSecureBootOptionAndWarn(
|
||||
options.secureBoot,
|
||||
deviceType,
|
||||
options.version,
|
||||
);
|
||||
|
||||
// Prompt for values
|
||||
@ -203,6 +205,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
});
|
||||
answers.version = options.version;
|
||||
answers.developmentMode = options.dev;
|
||||
answers.secureBoot = options.secureBoot;
|
||||
answers.provisioningKeyName = options['provisioning-key-name'];
|
||||
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];
|
||||
|
||||
@ -244,7 +247,9 @@ export default class ConfigGenerateCmd extends Command {
|
||||
protected readonly deviceTypeNotAllowedMessage =
|
||||
'The --deviceType option can only be used alongside the --fleet option';
|
||||
|
||||
protected async validateOptions(options: FlagsDef) {
|
||||
protected async validateOptions(
|
||||
options: Interfaces.InferredFlags<typeof ConfigGenerateCmd.flags>,
|
||||
) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
|
||||
if (options.device == null && options.fleet == null) {
|
||||
@ -254,6 +259,8 @@ export default class ConfigGenerateCmd extends Command {
|
||||
if (!options.fleet && options.deviceType) {
|
||||
throw new ExpectedError(this.deviceTypeNotAllowedMessage);
|
||||
}
|
||||
const { normalizeOsVersion } = await import('../../utils/normalization');
|
||||
options.version = normalizeOsVersion(options.version);
|
||||
const { validateDevOptionAndWarn } = await import('../../utils/config');
|
||||
await validateDevOptionAndWarn(options.dev, options.version);
|
||||
}
|
@ -15,21 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type?: string;
|
||||
drive?: string;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
file: string;
|
||||
}
|
||||
|
||||
export default class ConfigInjectCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Inject a config.json file to a balenaOS image or attached media.
|
||||
@ -46,17 +36,16 @@ export default class ConfigInjectCmd extends Command {
|
||||
'$ balena config inject my/config.json --drive /dev/disk2',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'file',
|
||||
public static args = {
|
||||
file: Args.string({
|
||||
description: 'the path to the config.json file to inject',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'config inject <file>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
@ -65,9 +54,7 @@ export default class ConfigInjectCmd extends Command {
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
ConfigInjectCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(ConfigInjectCmd);
|
||||
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
|
@ -15,18 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type?: string;
|
||||
drive?: string;
|
||||
help: void;
|
||||
json: boolean;
|
||||
}
|
||||
|
||||
export default class ConfigReadCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Read the config.json file of a balenaOS image or attached media.
|
||||
@ -46,7 +38,7 @@ export default class ConfigReadCmd extends Command {
|
||||
|
||||
public static usage = 'config read';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
json: cf.json,
|
||||
@ -56,7 +48,7 @@ export default class ConfigReadCmd extends Command {
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReadCmd);
|
||||
const { flags: options } = await this.parse(ConfigReadCmd);
|
||||
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
|
@ -15,19 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type?: string;
|
||||
drive?: string;
|
||||
advanced: boolean;
|
||||
help: void;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export default class ConfigReconfigureCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Interactively reconfigure a balenaOS image file or attached media.
|
||||
@ -49,14 +41,14 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
|
||||
public static usage = 'config reconfigure';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
drive: cf.driveOrImg,
|
||||
advanced: flags.boolean({
|
||||
advanced: Flags.boolean({
|
||||
description: 'show advanced commands',
|
||||
char: 'v',
|
||||
}),
|
||||
help: cf.help,
|
||||
version: flags.string({
|
||||
version: Flags.string({
|
||||
description: 'balenaOS version, for example "2.32.0" or "2.44.0+rev1"',
|
||||
}),
|
||||
};
|
||||
@ -65,7 +57,7 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
public static root = true;
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReconfigureCmd);
|
||||
const { flags: options } = await this.parse(ConfigReconfigureCmd);
|
||||
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
|
@ -15,22 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type?: string;
|
||||
drive?: string;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export default class ConfigWriteCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Write a key-value pair to the config.json file of an OS image or attached media.
|
||||
@ -48,22 +37,20 @@ export default class ConfigWriteCmd extends Command {
|
||||
'$ balena config write --drive balena.img os.network.connectivity.interval 300',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'key',
|
||||
public static args = {
|
||||
key: Args.string({
|
||||
description: 'the key of the config parameter to write',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
}),
|
||||
value: Args.string({
|
||||
description: 'the value of the config parameter to write',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'config write <key> <value>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
@ -72,9 +59,7 @@ export default class ConfigWriteCmd extends Command {
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
ConfigWriteCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(ConfigWriteCmd);
|
||||
|
||||
const { denyMount, safeUmount } = await import('../../utils/umount');
|
||||
|
@ -15,45 +15,46 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Args, Flags } from '@oclif/core';
|
||||
import type { ImageDescriptor } from '@balena/compose/dist/parse';
|
||||
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import { getBalenaSdk, getChalk, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import { getBalenaSdk, getChalk, stripIndent } from '../../utils/lazy';
|
||||
import {
|
||||
dockerignoreHelp,
|
||||
registrySecretsHelp,
|
||||
buildArgDeprecation,
|
||||
} from '../utils/messages';
|
||||
import * as ca from '../utils/common-args';
|
||||
import * as compose from '../utils/compose';
|
||||
} from '../../utils/messages';
|
||||
import * as ca from '../../utils/common-args';
|
||||
import * as compose from '../../utils/compose';
|
||||
import type {
|
||||
BuiltImage,
|
||||
ComposeCliFlags,
|
||||
ComposeOpts,
|
||||
Release as ComposeReleaseInfo,
|
||||
} from '../utils/compose-types';
|
||||
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
|
||||
} from '../../utils/compose-types';
|
||||
import type { BuildOpts, DockerCliFlags } from '../../utils/docker';
|
||||
import {
|
||||
applyReleaseTagKeysAndValues,
|
||||
buildProject,
|
||||
composeCliFlags,
|
||||
isBuildConfig,
|
||||
parseReleaseTagKeysAndValues,
|
||||
} from '../utils/compose_ts';
|
||||
import { dockerCliFlags } from '../utils/docker';
|
||||
import type {
|
||||
Application,
|
||||
ApplicationType,
|
||||
DeviceType,
|
||||
Release,
|
||||
} from 'balena-sdk';
|
||||
} from '../../utils/compose_ts';
|
||||
import { dockerCliFlags } from '../../utils/docker';
|
||||
import type { ApplicationType, DeviceType, Release } from 'balena-sdk';
|
||||
|
||||
interface ApplicationWithArch extends Application {
|
||||
interface ApplicationWithArch {
|
||||
id: number;
|
||||
arch: string;
|
||||
is_for__device_type: [Pick<DeviceType, 'slug'>];
|
||||
application_type: [Pick<ApplicationType, 'slug' | 'supports_multicontainer'>];
|
||||
}
|
||||
|
||||
// TODO: For this special one we can't use Interfaces.InferredFlags/InferredArgs
|
||||
// because of the 'registry-secrets' type which is defined in the actual code
|
||||
// as a path (string | undefined) but then the cli turns it into an object
|
||||
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
|
||||
source?: string;
|
||||
build: boolean;
|
||||
@ -64,11 +65,6 @@ interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
fleet: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export default class DeployCmd extends Command {
|
||||
public static description = `\
|
||||
Deploy a single image or a multicontainer project to a balena fleet.
|
||||
@ -107,31 +103,28 @@ ${dockerignoreHelp}
|
||||
'$ balena deploy myFleet myRepo/myImage --release-tag key1 "" key2 "value2 with spaces"',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
ca.fleetRequired,
|
||||
{
|
||||
name: 'image',
|
||||
description: 'the image to deploy',
|
||||
},
|
||||
];
|
||||
public static args = {
|
||||
fleet: ca.fleetRequired,
|
||||
image: Args.string({ description: 'the image to deploy' }),
|
||||
};
|
||||
|
||||
public static usage = 'deploy <fleet> [image]';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
source: flags.string({
|
||||
public static flags = {
|
||||
source: Flags.string({
|
||||
description:
|
||||
'specify an alternate source directory; default is the working directory',
|
||||
char: 's',
|
||||
}),
|
||||
build: flags.boolean({
|
||||
build: Flags.boolean({
|
||||
description: 'force a rebuild before deploy',
|
||||
char: 'b',
|
||||
}),
|
||||
nologupload: flags.boolean({
|
||||
nologupload: Flags.boolean({
|
||||
description:
|
||||
"don't upload build logs to the dashboard with image (if building)",
|
||||
}),
|
||||
'release-tag': flags.string({
|
||||
'release-tag': Flags.string({
|
||||
description: stripIndent`
|
||||
Set release tags if the image deployment is successful. Multiple
|
||||
arguments may be provided, alternating tag keys and values (see examples).
|
||||
@ -139,7 +132,7 @@ ${dockerignoreHelp}
|
||||
`,
|
||||
multiple: true,
|
||||
}),
|
||||
draft: flags.boolean({
|
||||
draft: Flags.boolean({
|
||||
description: stripIndent`
|
||||
Deploy the release as a draft. Draft releases are ignored
|
||||
by the 'track latest' release policy but can be used through release pinning.
|
||||
@ -147,12 +140,12 @@ ${dockerignoreHelp}
|
||||
as final by default unless this option is given.`,
|
||||
default: false,
|
||||
}),
|
||||
note: flags.string({ description: 'The notes for this release' }),
|
||||
note: Flags.string({ description: 'The notes for this release' }),
|
||||
...composeCliFlags,
|
||||
...dockerCliFlags,
|
||||
// NOTE: Not supporting -h for help, because of clash with -h in DockerCliFlags
|
||||
// Revisit this in future release.
|
||||
help: flags.help({}),
|
||||
help: Flags.help({}),
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
@ -160,9 +153,7 @@ ${dockerignoreHelp}
|
||||
public static primary = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeployCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(DeployCmd);
|
||||
|
||||
(await import('events')).defaultMaxListeners = 1000;
|
||||
|
||||
@ -184,7 +175,7 @@ ${dockerignoreHelp}
|
||||
|
||||
const sdk = getBalenaSdk();
|
||||
const { getRegistrySecrets, validateProjectDirectory } = await import(
|
||||
'../utils/compose_ts'
|
||||
'../../utils/compose_ts'
|
||||
);
|
||||
|
||||
const { releaseTagKeys, releaseTagValues } = parseReleaseTagKeysAndValues(
|
||||
@ -192,7 +183,7 @@ ${dockerignoreHelp}
|
||||
);
|
||||
|
||||
if (image) {
|
||||
options['registry-secrets'] = await getRegistrySecrets(
|
||||
(options as FlagsDef)['registry-secrets'] = await getRegistrySecrets(
|
||||
sdk,
|
||||
options['registry-secrets'],
|
||||
);
|
||||
@ -205,16 +196,16 @@ ${dockerignoreHelp}
|
||||
registrySecretsPath: options['registry-secrets'],
|
||||
});
|
||||
options.dockerfile = dockerfilePath;
|
||||
options['registry-secrets'] = registrySecrets;
|
||||
(options as FlagsDef)['registry-secrets'] = registrySecrets;
|
||||
}
|
||||
|
||||
const helpers = await import('../utils/helpers');
|
||||
const helpers = await import('../../utils/helpers');
|
||||
const app = await helpers.getAppWithArch(fleet);
|
||||
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
const dockerUtils = await import('../../utils/docker');
|
||||
const [docker, buildOpts, composeOpts] = await Promise.all([
|
||||
dockerUtils.getDocker(options),
|
||||
dockerUtils.generateBuildOpts(options),
|
||||
dockerUtils.generateBuildOpts(options as FlagsDef),
|
||||
compose.generateOpts(options),
|
||||
]);
|
||||
|
||||
@ -235,13 +226,13 @@ ${dockerignoreHelp}
|
||||
releaseTagValues,
|
||||
);
|
||||
if (options.note) {
|
||||
await sdk.models.release.note(release.id, options.note);
|
||||
await sdk.models.release.setNote(release.id, options.note);
|
||||
}
|
||||
}
|
||||
|
||||
async deployProject(
|
||||
docker: import('dockerode'),
|
||||
logger: import('../utils/logger'),
|
||||
logger: import('../../utils/logger'),
|
||||
composeOpts: ComposeOpts,
|
||||
opts: {
|
||||
app: ApplicationWithArch; // the application instance to deploy to
|
||||
@ -259,10 +250,10 @@ ${dockerignoreHelp}
|
||||
const doodles = await import('resin-doodles');
|
||||
const sdk = getBalenaSdk();
|
||||
const { deployProject: $deployProject, loadProject } = await import(
|
||||
'../utils/compose_ts'
|
||||
'../../utils/compose_ts'
|
||||
);
|
||||
|
||||
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
|
||||
const appType = opts.app.application_type[0];
|
||||
|
||||
try {
|
||||
const project = await loadProject(
|
||||
@ -319,7 +310,7 @@ ${dockerignoreHelp}
|
||||
projectName: project.name,
|
||||
composition: compositionToBuild,
|
||||
arch: opts.app.arch,
|
||||
deviceType: (opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
|
||||
deviceType: opts.app.is_for__device_type[0].slug,
|
||||
emulated: opts.buildEmulated,
|
||||
buildOpts: opts.buildOpts,
|
||||
inlineLogs: composeOpts.inlineLogs,
|
||||
@ -340,17 +331,17 @@ ${dockerignoreHelp}
|
||||
);
|
||||
|
||||
let release: Release | ComposeReleaseInfo['release'];
|
||||
if (appType?.is_legacy) {
|
||||
const { deployLegacy } = require('../utils/deploy-legacy');
|
||||
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
|
||||
const { deployLegacy } = require('../../utils/deploy-legacy');
|
||||
|
||||
const msg = getChalk().yellow(
|
||||
'Target fleet requires legacy deploy method.',
|
||||
);
|
||||
logger.logWarn(msg);
|
||||
|
||||
const [token, username, url, options] = await Promise.all([
|
||||
const [token, { username }, url, options] = await Promise.all([
|
||||
sdk.auth.getToken(),
|
||||
sdk.auth.whoami(),
|
||||
sdk.auth.getUserInfo(),
|
||||
sdk.settings.get('balenaUrl'),
|
||||
{
|
||||
// opts.appName may be prefixed by 'owner/', unlike opts.app.app_name
|
||||
@ -373,20 +364,13 @@ ${dockerignoreHelp}
|
||||
$select: ['commit'],
|
||||
});
|
||||
} else {
|
||||
const [userId, auth, apiEndpoint] = await Promise.all([
|
||||
sdk.auth.getUserId(),
|
||||
sdk.auth.getToken(),
|
||||
sdk.settings.get('apiUrl'),
|
||||
]);
|
||||
release = await $deployProject(
|
||||
docker,
|
||||
sdk,
|
||||
logger,
|
||||
project.composition,
|
||||
images,
|
||||
opts.app.id,
|
||||
userId,
|
||||
`Bearer ${auth}`,
|
||||
apiEndpoint,
|
||||
!opts.shouldUploadLogs,
|
||||
composeOpts.projectPath,
|
||||
opts.createAsDraft,
|
@ -15,21 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
yes: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceDeactivateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Deactivate a device.
|
||||
@ -44,17 +34,16 @@ export default class DeviceDeactivateCmd extends Command {
|
||||
'$ balena device deactivate 7cf02a6 --yes',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the UUID of the device to be deactivated',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device deactivate <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
yes: cf.yes,
|
||||
help: cf.help,
|
||||
};
|
||||
@ -62,9 +51,8 @@ export default class DeviceDeactivateCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceDeactivateCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceDeactivateCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const patterns = await import('../../utils/patterns');
|
@ -15,21 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { ExpectedError } from '../../errors';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceIdentifyCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Identify a device.
|
||||
@ -38,24 +29,23 @@ export default class DeviceIdentifyCmd extends Command {
|
||||
`;
|
||||
public static examples = ['$ balena device identify 23c73a1'];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to identify',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device identify <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceIdentifyCmd);
|
||||
const { args: params } = await this.parse(DeviceIdentifyCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -15,12 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { IArg } from '@oclif/parser/lib/args';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { jsonInfo } from '../../utils/messages';
|
||||
|
||||
import type { Application, Release } from 'balena-sdk';
|
||||
|
||||
@ -41,39 +41,33 @@ interface ExtendedDevice extends DeviceWithDeviceType {
|
||||
undervoltage_detected?: boolean;
|
||||
}
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
view: boolean;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Show info about a single device.
|
||||
|
||||
Show information about a single device.
|
||||
|
||||
${jsonInfo.split('\n').join('\n\t\t')}
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena device 7cf02a6',
|
||||
'$ balena device 7cf02a6 --view',
|
||||
'$ balena device 7cf02a6 --json',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the device uuid',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
json: cf.json,
|
||||
help: cf.help,
|
||||
view: flags.boolean({
|
||||
view: Flags.boolean({
|
||||
default: false,
|
||||
description: 'open device dashboard page',
|
||||
}),
|
||||
@ -83,39 +77,49 @@ export default class DeviceCmd extends Command {
|
||||
public static primary = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(DeviceCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const device = (await balena.models.device.get(params.uuid, {
|
||||
$select: [
|
||||
'device_name',
|
||||
'id',
|
||||
'overall_status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'mac_address',
|
||||
'last_connectivity_event',
|
||||
'uuid',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'memory_usage',
|
||||
'memory_total',
|
||||
'public_address',
|
||||
'storage_block_device',
|
||||
'storage_usage',
|
||||
'storage_total',
|
||||
'cpu_usage',
|
||||
'cpu_temp',
|
||||
'cpu_id',
|
||||
'is_undervolted',
|
||||
],
|
||||
...expandForAppName,
|
||||
})) as ExtendedDevice;
|
||||
const device = (await balena.models.device.get(
|
||||
params.uuid,
|
||||
options.json
|
||||
? {
|
||||
$expand: {
|
||||
device_tag: {
|
||||
$select: ['tag_key', 'value'],
|
||||
},
|
||||
...expandForAppName.$expand,
|
||||
},
|
||||
}
|
||||
: {
|
||||
$select: [
|
||||
'device_name',
|
||||
'id',
|
||||
'overall_status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'mac_address',
|
||||
'last_connectivity_event',
|
||||
'uuid',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'memory_usage',
|
||||
'memory_total',
|
||||
'public_address',
|
||||
'storage_block_device',
|
||||
'storage_usage',
|
||||
'storage_total',
|
||||
'cpu_usage',
|
||||
'cpu_temp',
|
||||
'cpu_id',
|
||||
'is_undervolted',
|
||||
],
|
||||
...expandForAppName,
|
||||
},
|
||||
)) as ExtendedDevice;
|
||||
|
||||
if (options.view) {
|
||||
const open = await import('open');
|
||||
@ -179,6 +183,11 @@ export default class DeviceCmd extends Command {
|
||||
);
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(device, null, 4));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
getVisuals().table.vertical(device, [
|
||||
`$${device.device_name}$`,
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
@ -76,14 +76,14 @@ export default class DeviceInitCmd extends Command {
|
||||
|
||||
public static usage = 'device init';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
fleet: cf.fleet,
|
||||
yes: cf.yes,
|
||||
advanced: flags.boolean({
|
||||
advanced: Flags.boolean({
|
||||
char: 'v',
|
||||
description: 'show advanced configuration options',
|
||||
}),
|
||||
'os-version': flags.string({
|
||||
'os-version': Flags.string({
|
||||
description: stripIndent`
|
||||
exact version number, or a valid semver range,
|
||||
or 'latest' (includes pre-releases),
|
||||
@ -93,13 +93,13 @@ export default class DeviceInitCmd extends Command {
|
||||
`,
|
||||
}),
|
||||
drive: cf.drive,
|
||||
config: flags.string({
|
||||
config: Flags.string({
|
||||
description: 'path to the config JSON file, see `balena os build-config`',
|
||||
}),
|
||||
'provisioning-key-name': flags.string({
|
||||
'provisioning-key-name': Flags.string({
|
||||
description: 'custom key name assigned to generated provisioning api key',
|
||||
}),
|
||||
'provisioning-key-expiry-date': flags.string({
|
||||
'provisioning-key-expiry-date': Flags.string({
|
||||
description:
|
||||
'expiry date assigned to generated provisioning api key (format: YYYY-MM-DD)',
|
||||
}),
|
||||
@ -109,7 +109,7 @@ export default class DeviceInitCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(DeviceInitCmd);
|
||||
const { flags: options } = await this.parse(DeviceInitCmd);
|
||||
|
||||
// Imports
|
||||
const { promisify } = await import('util');
|
||||
@ -124,20 +124,16 @@ export default class DeviceInitCmd extends Command {
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
// Get application and
|
||||
const application = (await getApplication(
|
||||
balena,
|
||||
options.fleet ||
|
||||
(
|
||||
await (await import('../../utils/patterns')).selectApplication()
|
||||
).slug,
|
||||
{
|
||||
$expand: {
|
||||
is_for__device_type: {
|
||||
$select: 'slug',
|
||||
const application = options.fleet
|
||||
? await getApplication(balena, options.fleet, {
|
||||
$select: ['id', 'slug'],
|
||||
$expand: {
|
||||
is_for__device_type: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)) as ApplicationWithDeviceType;
|
||||
})
|
||||
: await (await import('../../utils/patterns')).selectApplication();
|
||||
|
||||
// Register new device
|
||||
const deviceUuid = balena.models.device.generateUniqueKey();
|
@ -15,23 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
enable: boolean;
|
||||
disable: boolean;
|
||||
status: boolean;
|
||||
help?: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string | number;
|
||||
}
|
||||
|
||||
export default class DeviceLocalModeCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Get or manage the local mode status for a device.
|
||||
@ -47,26 +35,25 @@ export default class DeviceLocalModeCmd extends Command {
|
||||
'$ balena device local-mode 23c73a1 --status',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to manage',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device local-mode <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
enable: flags.boolean({
|
||||
public static flags = {
|
||||
enable: Flags.boolean({
|
||||
description: 'enable local mode',
|
||||
exclusive: ['disable', 'status'],
|
||||
}),
|
||||
disable: flags.boolean({
|
||||
disable: Flags.boolean({
|
||||
description: 'disable local mode',
|
||||
exclusive: ['enable', 'status'],
|
||||
}),
|
||||
status: flags.boolean({
|
||||
status: Flags.boolean({
|
||||
description: 'output boolean indicating local mode status',
|
||||
exclusive: ['enable', 'disable'],
|
||||
}),
|
||||
@ -76,9 +63,8 @@ export default class DeviceLocalModeCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceLocalModeCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceLocalModeCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -15,12 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import type {
|
||||
BalenaSDK,
|
||||
Device,
|
||||
DeviceType,
|
||||
PineOptions,
|
||||
PineTypedResult,
|
||||
} from 'balena-sdk';
|
||||
import Command from '../../command';
|
||||
@ -29,22 +28,6 @@ import { ExpectedError } from '../../errors';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
|
||||
type ExtendedDevice = PineTypedResult<
|
||||
Device,
|
||||
typeof import('../../utils/helpers').expandForAppNameAndCpuArch
|
||||
> & {
|
||||
application_name?: string;
|
||||
};
|
||||
|
||||
interface FlagsDef {
|
||||
fleet?: string;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceMoveCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Move one or more devices to another fleet.
|
||||
@ -63,61 +46,66 @@ export default class DeviceMoveCmd extends Command {
|
||||
'$ balena device move 7cf02a6 -f myorg/mynewfleet',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description:
|
||||
'comma-separated list (no blank spaces) of device UUIDs to be moved',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device move <uuid(s)>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
fleet: cf.fleet,
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceMoveCmd,
|
||||
private async getDevices(balena: BalenaSDK, deviceUuids: string[]) {
|
||||
const deviceOptions = {
|
||||
$select: 'belongs_to__application',
|
||||
$expand: {
|
||||
is_of__device_type: {
|
||||
$select: 'is_of__cpu_architecture',
|
||||
$expand: {
|
||||
is_of__cpu_architecture: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies PineOptions<Device>;
|
||||
|
||||
// TODO: Refacor once `device.get()` accepts an array of uuids`
|
||||
const devices = await Promise.all(
|
||||
deviceUuids.map(
|
||||
(uuid) =>
|
||||
balena.models.device.get(uuid, deviceOptions) as Promise<
|
||||
PineTypedResult<Device, typeof deviceOptions>
|
||||
>,
|
||||
),
|
||||
);
|
||||
return devices;
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(DeviceMoveCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const { expandForAppNameAndCpuArch } = await import('../../utils/helpers');
|
||||
|
||||
// Split uuids string into array of uuids
|
||||
const deviceUuids = params.uuid.split(',');
|
||||
|
||||
// Get devices
|
||||
const devices = await Promise.all(
|
||||
deviceUuids.map(
|
||||
(uuid) =>
|
||||
balena.models.device.get(
|
||||
uuid,
|
||||
expandForAppNameAndCpuArch,
|
||||
) as Promise<ExtendedDevice>,
|
||||
),
|
||||
);
|
||||
|
||||
// Map application name for each device
|
||||
for (const device of devices) {
|
||||
const belongsToApplication = device.belongs_to__application;
|
||||
device.application_name = belongsToApplication?.[0]
|
||||
? belongsToApplication[0].app_name
|
||||
: 'N/a';
|
||||
}
|
||||
const devices = await this.getDevices(balena, deviceUuids);
|
||||
|
||||
// Disambiguate application
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
// Get destination application
|
||||
const application = options.fleet
|
||||
? await getApplication(balena, options.fleet)
|
||||
? await getApplication(balena, options.fleet, { $select: ['id', 'slug'] })
|
||||
: await this.interactivelySelectApplication(balena, devices);
|
||||
|
||||
// Move each device
|
||||
@ -134,9 +122,8 @@ export default class DeviceMoveCmd extends Command {
|
||||
|
||||
async interactivelySelectApplication(
|
||||
balena: BalenaSDK,
|
||||
devices: ExtendedDevice[],
|
||||
devices: Awaited<ReturnType<typeof this.getDevices>>,
|
||||
) {
|
||||
const { getExpandedProp } = await import('../../utils/pine');
|
||||
// deduplicate the slugs
|
||||
const deviceCpuArchs = Array.from(
|
||||
new Set(
|
||||
@ -146,46 +133,44 @@ export default class DeviceMoveCmd extends Command {
|
||||
),
|
||||
);
|
||||
|
||||
const deviceTypeOptions = {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
is_of__cpu_architecture: {
|
||||
$select: 'slug',
|
||||
},
|
||||
const allCpuArches = await balena.pine.get({
|
||||
resource: 'cpu_architecture',
|
||||
options: {
|
||||
$select: ['id', 'slug'],
|
||||
},
|
||||
} as const;
|
||||
const deviceTypes = (await balena.models.deviceType.getAllSupported(
|
||||
deviceTypeOptions,
|
||||
)) as Array<PineTypedResult<DeviceType, typeof deviceTypeOptions>>;
|
||||
});
|
||||
|
||||
const compatibleDeviceTypeSlugs = new Set(
|
||||
deviceTypes
|
||||
.filter((deviceType) => {
|
||||
const deviceTypeArch = getExpandedProp(
|
||||
deviceType.is_of__cpu_architecture,
|
||||
'slug',
|
||||
)!;
|
||||
return deviceCpuArchs.every((deviceCpuArch) =>
|
||||
balena.models.os.isArchitectureCompatibleWith(
|
||||
deviceCpuArch,
|
||||
deviceTypeArch,
|
||||
),
|
||||
);
|
||||
})
|
||||
.map((deviceType) => deviceType.slug),
|
||||
);
|
||||
const compatibleCpuArchIds = allCpuArches
|
||||
.filter((cpuArch) => {
|
||||
return deviceCpuArchs.every((deviceCpuArch) =>
|
||||
balena.models.os.isArchitectureCompatibleWith(
|
||||
deviceCpuArch,
|
||||
cpuArch.slug,
|
||||
),
|
||||
);
|
||||
})
|
||||
.map((deviceType) => deviceType.id);
|
||||
|
||||
const patterns = await import('../../utils/patterns');
|
||||
try {
|
||||
const application = await patterns.selectApplication(
|
||||
(app) =>
|
||||
compatibleDeviceTypeSlugs.has(app.is_for__device_type[0].slug) &&
|
||||
devices.some((device) => device.application_name !== app.app_name),
|
||||
{
|
||||
is_for__device_type: {
|
||||
$any: {
|
||||
$alias: 'dt',
|
||||
$expr: {
|
||||
dt: {
|
||||
is_of__cpu_architecture: { $in: compatibleCpuArchIds },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
return application;
|
||||
} catch (err) {
|
||||
if (!compatibleDeviceTypeSlugs.size) {
|
||||
if (!compatibleCpuArchIds.length) {
|
||||
throw new ExpectedError(
|
||||
`${err.message}\nDo all devices have a compatible architecture?`,
|
||||
);
|
@ -15,24 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||
import type { Device } from 'balena-sdk';
|
||||
import { ExpectedError } from '../../errors';
|
||||
|
||||
interface FlagsDef {
|
||||
version?: string;
|
||||
yes: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceOsUpdateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Start a Host OS update for a device.
|
||||
@ -48,21 +37,27 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
'$ balena device os-update 23c73a1',
|
||||
'$ balena device os-update 23c73a1 --version 2.101.7',
|
||||
'$ balena device os-update 23c73a1 --version 2.31.0+rev1.prod',
|
||||
'$ balena device os-update 23c73a1 --include-draft',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to update',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device os-update <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
version: flags.string({
|
||||
public static flags = {
|
||||
version: Flags.string({
|
||||
description: 'a balenaOS version',
|
||||
exclusive: ['include-draft'],
|
||||
}),
|
||||
'include-draft': Flags.boolean({
|
||||
description: 'include pre-release balenaOS versions',
|
||||
default: false,
|
||||
exclusive: ['version'],
|
||||
}),
|
||||
yes: cf.yes,
|
||||
help: cf.help,
|
||||
@ -71,9 +66,8 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceOsUpdateCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceOsUpdateCmd);
|
||||
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
@ -99,10 +93,25 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
);
|
||||
}
|
||||
|
||||
let includeDraft = options['include-draft'];
|
||||
if (!includeDraft && options.version != null) {
|
||||
const bSemver = await import('balena-semver');
|
||||
const parsedVersion = bSemver.parse(options.version);
|
||||
// When the user provides a draft version, we need to pass `includeDraft`
|
||||
// to the os.getSupportedOsUpdateVersions() since w/o it the results
|
||||
// will for sure not include the user provided version and the command
|
||||
// would return a "not in the Host OS update targets" error.
|
||||
includeDraft =
|
||||
parsedVersion != null && parsedVersion.prerelease.length > 0;
|
||||
}
|
||||
|
||||
// Get supported OS update versions
|
||||
const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions(
|
||||
is_of__device_type[0].slug,
|
||||
currentOsVersion,
|
||||
{
|
||||
includeDraft,
|
||||
},
|
||||
);
|
||||
if (hupVersionInfo.versions.length === 0) {
|
||||
throw new ExpectedError(
|
||||
@ -113,6 +122,8 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
// Get target OS version
|
||||
let targetOsVersion = options.version;
|
||||
if (targetOsVersion != null) {
|
||||
const { normalizeOsVersion } = await import('../../utils/normalization');
|
||||
targetOsVersion = normalizeOsVersion(targetOsVersion);
|
||||
if (!hupVersionInfo.versions.includes(targetOsVersion)) {
|
||||
throw new ExpectedError(
|
||||
`The provided version ${targetOsVersion} is not in the Host OS update targets for this device`,
|
@ -15,22 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { getExpandedProp } from '../../utils/pine';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
releaseToPinTo?: string;
|
||||
}
|
||||
|
||||
export default class DevicePinCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Pin a device to a release.
|
||||
@ -44,28 +34,26 @@ export default class DevicePinCmd extends Command {
|
||||
'$ balena device pin 7cf02a6 91165e5',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to pin to a release',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'releaseToPinTo',
|
||||
}),
|
||||
releaseToPinTo: Args.string({
|
||||
description: 'the commit of the release for the device to get pinned to',
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device pin <uuid> [releaseToPinTo]';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(DevicePinCmd);
|
||||
const { args: params } = await this.parse(DevicePinCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -15,24 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
enable: boolean;
|
||||
disable: boolean;
|
||||
status: boolean;
|
||||
help?: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DevicePublicUrlCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Get or manage the public URL for a device.
|
||||
@ -49,26 +37,25 @@ export default class DevicePublicUrlCmd extends Command {
|
||||
'$ balena device public-url 23c73a1 --status',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to manage',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device public-url <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
enable: flags.boolean({
|
||||
public static flags = {
|
||||
enable: Flags.boolean({
|
||||
description: 'enable the public URL',
|
||||
exclusive: ['disable', 'status'],
|
||||
}),
|
||||
disable: flags.boolean({
|
||||
disable: Flags.boolean({
|
||||
description: 'disable the public URL',
|
||||
exclusive: ['enable', 'status'],
|
||||
}),
|
||||
status: flags.boolean({
|
||||
status: Flags.boolean({
|
||||
description: 'determine if public URL is enabled',
|
||||
exclusive: ['enable', 'disable'],
|
||||
}),
|
||||
@ -78,9 +65,8 @@ export default class DevicePublicUrlCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DevicePublicUrlCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DevicePublicUrlCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -15,20 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DevicePurgeCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Purge data from a device.
|
||||
@ -46,22 +37,21 @@ export default class DevicePurgeCmd extends Command {
|
||||
|
||||
public static usage = 'device purge <uuid>';
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of device UUIDs',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(DevicePurgeCmd);
|
||||
const { args: params } = await this.parse(DevicePurgeCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const ux = getCliUx();
|
@ -15,21 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
force: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceRebootCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Restart a device.
|
||||
@ -38,17 +28,16 @@ export default class DeviceRebootCmd extends Command {
|
||||
`;
|
||||
public static examples = ['$ balena device reboot 23c73a1'];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to reboot',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device reboot <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
force: cf.force,
|
||||
help: cf.help,
|
||||
};
|
||||
@ -56,9 +45,7 @@ export default class DeviceRebootCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceRebootCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(DeviceRebootCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -15,24 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import * as ca from '../../utils/common-args';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
|
||||
interface FlagsDef {
|
||||
uuid?: string;
|
||||
deviceType?: string;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
fleet: string;
|
||||
}
|
||||
|
||||
export default class DeviceRegisterCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Register a new device.
|
||||
@ -51,16 +40,18 @@ export default class DeviceRegisterCmd extends Command {
|
||||
'$ balena device register myorg/myfleet --uuid <uuid> --deviceType <deviceTypeSlug>',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [ca.fleetRequired];
|
||||
public static args = {
|
||||
fleet: ca.fleetRequired,
|
||||
};
|
||||
|
||||
public static usage = 'device register <fleet>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
uuid: flags.string({
|
||||
public static flags = {
|
||||
uuid: Flags.string({
|
||||
description: 'custom uuid',
|
||||
char: 'u',
|
||||
}),
|
||||
deviceType: flags.string({
|
||||
deviceType: Flags.string({
|
||||
description:
|
||||
"device type slug (run 'balena devices supported' for possible values)",
|
||||
}),
|
||||
@ -70,15 +61,16 @@ export default class DeviceRegisterCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceRegisterCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceRegisterCmd);
|
||||
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const application = await getApplication(balena, params.fleet);
|
||||
const application = await getApplication(balena, params.fleet, {
|
||||
$select: ['id', 'slug'],
|
||||
});
|
||||
const uuid = options.uuid ?? balena.models.device.generateUniqueKey();
|
||||
|
||||
console.info(`Registering to ${application.slug}: ${uuid}`);
|
@ -15,21 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
newName?: string;
|
||||
}
|
||||
|
||||
export default class DeviceRenameCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Rename a device.
|
||||
@ -43,28 +33,26 @@ export default class DeviceRenameCmd extends Command {
|
||||
'$ balena device rename 7cf02a6 MyPi',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to rename',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'newName',
|
||||
}),
|
||||
newName: Args.string({
|
||||
description: 'the new name for the device',
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device rename <uuid> [newName]';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceRenameCmd);
|
||||
const { args: params } = await this.parse(DeviceRenameCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -15,8 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
@ -26,15 +25,6 @@ import type {
|
||||
CurrentServiceWithCommit,
|
||||
} from 'balena-sdk';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
service?: string;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceRestartCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Restart containers on a device.
|
||||
@ -55,19 +45,18 @@ export default class DeviceRestartCmd extends Command {
|
||||
'$ balena device restart 23c73a1 -s myService1,myService2',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description:
|
||||
'comma-separated list (no blank spaces) of device UUIDs to restart',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device restart <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
service: flags.string({
|
||||
public static flags = {
|
||||
service: Flags.string({
|
||||
description:
|
||||
'comma-separated list (no blank spaces) of service names to restart',
|
||||
char: 's',
|
||||
@ -78,9 +67,7 @@ export default class DeviceRestartCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceRestartCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(DeviceRestartCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const ux = getCliUx();
|
@ -15,21 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
yes: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceRmCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Remove one or more devices.
|
||||
@ -45,18 +35,17 @@ export default class DeviceRmCmd extends Command {
|
||||
'$ balena device rm 7cf02a6 --yes',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description:
|
||||
'comma-separated list (no blank spaces) of device UUIDs to be removed',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device rm <uuid(s)>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
yes: cf.yes,
|
||||
help: cf.help,
|
||||
};
|
||||
@ -64,9 +53,7 @@ export default class DeviceRmCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceRmCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(DeviceRmCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const patterns = await import('../../utils/patterns');
|
@ -15,22 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { ExpectedError } from '../../errors';
|
||||
|
||||
interface FlagsDef {
|
||||
force: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceShutdownCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Shutdown a device.
|
||||
@ -39,17 +29,16 @@ export default class DeviceShutdownCmd extends Command {
|
||||
`;
|
||||
public static examples = ['$ balena device shutdown 23c73a1'];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'the uuid of the device to shutdown',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device shutdown <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
force: cf.force,
|
||||
help: cf.help,
|
||||
};
|
||||
@ -57,9 +46,8 @@ export default class DeviceShutdownCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceShutdownCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceShutdownCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
139
src/commands/device/start-service.ts
Normal file
139
src/commands/device/start-service.ts
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
import type { BalenaSDK } from 'balena-sdk';
|
||||
|
||||
export default class DeviceStartServiceCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Start containers on a device.
|
||||
|
||||
Start containers on a device.
|
||||
|
||||
Multiple devices and services may be specified with a comma-separated list
|
||||
of values (no spaces).
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena device start-service 23c73a1 myService',
|
||||
'$ balena device start-service 23c73a1 myService1,myService2',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of device UUIDs',
|
||||
required: true,
|
||||
}),
|
||||
service: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of service names',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device start-service <uuid>';
|
||||
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(DeviceStartServiceCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const ux = getCliUx();
|
||||
|
||||
const deviceUuids = params.uuid.split(',');
|
||||
const serviceNames = params.service.split(',');
|
||||
|
||||
// Iterate sequentially through deviceUuids.
|
||||
// We may later want to add a batching feature,
|
||||
// so that n devices are processed in parallel
|
||||
for (const uuid of deviceUuids) {
|
||||
ux.action.start(`Starting services on device ${uuid}`);
|
||||
await this.startServices(balena, uuid, serviceNames);
|
||||
ux.action.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async startServices(
|
||||
balena: BalenaSDK,
|
||||
deviceUuid: string,
|
||||
serviceNames: string[],
|
||||
) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
const { getExpandedProp } = await import('../../utils/pine');
|
||||
|
||||
// Get device
|
||||
const device = await balena.models.device.getWithServiceDetails(
|
||||
deviceUuid,
|
||||
{
|
||||
$expand: {
|
||||
is_running__release: { $select: 'commit' },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const activeReleaseCommit = getExpandedProp(
|
||||
device.is_running__release,
|
||||
'commit',
|
||||
);
|
||||
|
||||
// Check specified services exist on this device before startinganything
|
||||
serviceNames.forEach((service) => {
|
||||
if (!device.current_services[service]) {
|
||||
throw new ExpectedError(
|
||||
`Service ${service} not found on device ${deviceUuid}.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Start services
|
||||
const startPromises: Array<Promise<void>> = [];
|
||||
for (const serviceName of serviceNames) {
|
||||
const service = device.current_services[serviceName];
|
||||
// Each service is an array of `CurrentServiceWithCommit`
|
||||
// because when service is updating, it will actually hold 2 services
|
||||
// Target commit matching `device.is_running__release`
|
||||
const serviceContainer = service.find((s) => {
|
||||
return s.commit === activeReleaseCommit;
|
||||
});
|
||||
|
||||
if (serviceContainer) {
|
||||
startPromises.push(
|
||||
balena.models.device.startService(
|
||||
deviceUuid,
|
||||
serviceContainer.image_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(startPromises);
|
||||
} catch (e) {
|
||||
if (e.message.toLowerCase().includes('no online device')) {
|
||||
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
139
src/commands/device/stop-service.ts
Normal file
139
src/commands/device/stop-service.ts
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
import type { BalenaSDK } from 'balena-sdk';
|
||||
|
||||
export default class DeviceStopServiceCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Stop containers on a device.
|
||||
|
||||
Stop containers on a device.
|
||||
|
||||
Multiple devices and services may be specified with a comma-separated list
|
||||
of values (no spaces).
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena device stop-service 23c73a1 myService',
|
||||
'$ balena device stop-service 23c73a1 myService1,myService2',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of device UUIDs',
|
||||
required: true,
|
||||
}),
|
||||
service: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of service names',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device stop-service <uuid>';
|
||||
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(DeviceStopServiceCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const ux = getCliUx();
|
||||
|
||||
const deviceUuids = params.uuid.split(',');
|
||||
const serviceNames = params.service.split(',');
|
||||
|
||||
// Iterate sequentially through deviceUuids.
|
||||
// We may later want to add a batching feature,
|
||||
// so that n devices are processed in parallel
|
||||
for (const uuid of deviceUuids) {
|
||||
ux.action.start(`Stopping services on device ${uuid}`);
|
||||
await this.stopServices(balena, uuid, serviceNames);
|
||||
ux.action.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async stopServices(
|
||||
balena: BalenaSDK,
|
||||
deviceUuid: string,
|
||||
serviceNames: string[],
|
||||
) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
const { getExpandedProp } = await import('../../utils/pine');
|
||||
|
||||
// Get device
|
||||
const device = await balena.models.device.getWithServiceDetails(
|
||||
deviceUuid,
|
||||
{
|
||||
$expand: {
|
||||
is_running__release: { $select: 'commit' },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const activeReleaseCommit = getExpandedProp(
|
||||
device.is_running__release,
|
||||
'commit',
|
||||
);
|
||||
|
||||
// Check specified services exist on this device before stoppinganything
|
||||
serviceNames.forEach((service) => {
|
||||
if (!device.current_services[service]) {
|
||||
throw new ExpectedError(
|
||||
`Service ${service} not found on device ${deviceUuid}.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Stop services
|
||||
const stopPromises: Array<Promise<void>> = [];
|
||||
for (const serviceName of serviceNames) {
|
||||
const service = device.current_services[serviceName];
|
||||
// Each service is an array of `CurrentServiceWithCommit`
|
||||
// because when service is updating, it will actually hold 2 services
|
||||
// Target commit matching `device.is_running__release`
|
||||
const serviceContainer = service.find((s) => {
|
||||
return s.commit === activeReleaseCommit;
|
||||
});
|
||||
|
||||
if (serviceContainer) {
|
||||
stopPromises.push(
|
||||
balena.models.device.stopService(
|
||||
deviceUuid,
|
||||
serviceContainer.image_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(stopPromises);
|
||||
} catch (e) {
|
||||
if (e.message.toLowerCase().includes('no online device')) {
|
||||
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,20 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export default class DeviceTrackFleetCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Make a device track the fleet's pinned release.
|
||||
@ -37,24 +28,23 @@ export default class DeviceTrackFleetCmd extends Command {
|
||||
`;
|
||||
public static examples = ['$ balena device track-fleet 7cf02a6'];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: "the uuid of the device to make track the fleet's release",
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device track-fleet <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceTrackFleetCmd);
|
||||
const { args: params } = await this.parse(DeviceTrackFleetCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -15,26 +15,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo, jsonInfo } from '../../utils/messages';
|
||||
|
||||
import type { Application } from 'balena-sdk';
|
||||
import type { Device, PineOptions } from 'balena-sdk';
|
||||
|
||||
interface ExtendedDevice extends DeviceWithDeviceType {
|
||||
dashboard_url?: string;
|
||||
fleet?: string | null; // 'org/name' slug
|
||||
device_type?: string | null;
|
||||
}
|
||||
|
||||
interface FlagsDef {
|
||||
fleet?: string;
|
||||
help: void;
|
||||
json: boolean;
|
||||
}
|
||||
const devicesSelectFields = {
|
||||
$select: [
|
||||
'id',
|
||||
'uuid',
|
||||
'device_name',
|
||||
'status',
|
||||
'is_online',
|
||||
'supervisor_version',
|
||||
'os_version',
|
||||
],
|
||||
} satisfies PineOptions<Device>;
|
||||
|
||||
export default class DevicesCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -56,7 +55,7 @@ export default class DevicesCmd extends Command {
|
||||
|
||||
public static usage = 'devices';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
fleet: cf.fleet,
|
||||
json: cf.json,
|
||||
help: cf.help,
|
||||
@ -67,39 +66,42 @@ export default class DevicesCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(DevicesCmd);
|
||||
const { flags: options } = await this.parse(DevicesCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const devicesOptions = {
|
||||
...devicesSelectFields,
|
||||
...expandForAppName,
|
||||
$orderby: { device_name: 'asc' },
|
||||
} satisfies PineOptions<Device>;
|
||||
|
||||
let devices;
|
||||
const devices = (
|
||||
await (async () => {
|
||||
if (options.fleet != null) {
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const application = await getApplication(balena, options.fleet, {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
owns__device: devicesOptions,
|
||||
},
|
||||
});
|
||||
return application.owns__device;
|
||||
}
|
||||
|
||||
if (options.fleet != null) {
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const application = await getApplication(balena, options.fleet);
|
||||
devices = (await balena.models.device.getAllByApplication(
|
||||
application.id,
|
||||
expandForAppName,
|
||||
)) as ExtendedDevice[];
|
||||
} else {
|
||||
devices = (await balena.models.device.getAll(
|
||||
expandForAppName,
|
||||
)) as ExtendedDevice[];
|
||||
}
|
||||
return await balena.pine.get({
|
||||
resource: 'device',
|
||||
options: devicesOptions,
|
||||
});
|
||||
})()
|
||||
).map((device) => ({
|
||||
...device,
|
||||
dashboard_url: balena.models.device.getDashboardUrl(device.uuid),
|
||||
fleet: device.belongs_to__application?.[0]?.slug || null,
|
||||
uuid: options.json ? device.uuid : device.uuid.slice(0, 7),
|
||||
device_type: device.is_of__device_type?.[0]?.slug || null,
|
||||
}));
|
||||
|
||||
devices = devices.map(function (device) {
|
||||
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
|
||||
|
||||
const belongsToApplication =
|
||||
device.belongs_to__application as Application[];
|
||||
device.fleet = belongsToApplication?.[0]?.slug || null;
|
||||
|
||||
device.uuid = options.json ? device.uuid : device.uuid.slice(0, 7);
|
||||
|
||||
device.device_type = device.is_of__device_type?.[0]?.slug || null;
|
||||
return device;
|
||||
});
|
||||
|
||||
const fields = [
|
||||
const fields: Array<keyof (typeof devices)[number]> = [
|
||||
'id',
|
||||
'uuid',
|
||||
'device_name',
|
@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import * as _ from 'lodash';
|
||||
import Command from '../../command';
|
||||
@ -23,11 +23,6 @@ import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export default class DevicesSupportedCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
List the supported device types (like 'raspberrypi3' or 'intel-nuc').
|
||||
@ -50,26 +45,26 @@ export default class DevicesSupportedCmd extends Command {
|
||||
new CommandHelp({ args: DevicesSupportedCmd.args }).defaultUsage()
|
||||
).trim();
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
json: flags.boolean({
|
||||
json: Flags.boolean({
|
||||
char: 'j',
|
||||
description: 'produce JSON output instead of tabular output',
|
||||
}),
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
|
||||
const { flags: options } = await this.parse(DevicesSupportedCmd);
|
||||
const pineOptions = {
|
||||
$select: (['slug', 'name'] as const).slice(),
|
||||
$select: ['slug', 'name'],
|
||||
$expand: {
|
||||
is_of__cpu_architecture: { $select: 'slug' },
|
||||
device_type_alias: {
|
||||
$select: 'is_referenced_by__alias',
|
||||
$orderby: 'is_referenced_by__alias asc',
|
||||
$orderby: { is_referenced_by__alias: 'asc' },
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
|
||||
const dts = (await getBalenaSdk().models.deviceType.getAllSupported(
|
||||
pineOptions,
|
||||
)) as Array<
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Args } from '@oclif/core';
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
@ -78,23 +78,23 @@ export default class EnvAddCmd extends Command {
|
||||
'$ balena env add EDITOR vim --device 7cf02a6,d6f1433 --service MyService,MyService2',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'name',
|
||||
public static args = {
|
||||
name: Args.string({
|
||||
required: true,
|
||||
description: 'environment or config variable name',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
}),
|
||||
value: Args.string({
|
||||
required: false,
|
||||
description:
|
||||
"variable value; if omitted, use value from this process' environment",
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
// Required for supporting empty string ('') `value` args.
|
||||
public static strict = false;
|
||||
public static usage = 'env add <name> [value]';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||
device: { ...cf.device, exclusive: ['fleet'] },
|
||||
help: cf.help,
|
||||
@ -103,9 +103,7 @@ export default class EnvAddCmd extends Command {
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
EnvAddCmd,
|
||||
);
|
||||
const { args: params, flags: options } = await this.parse(EnvAddCmd);
|
||||
const cmd = this;
|
||||
|
||||
if (!options.fleet && !options.device) {
|
||||
@ -151,16 +149,15 @@ export default class EnvAddCmd extends Command {
|
||||
|
||||
const varType = isConfigVar ? 'configVar' : 'envVar';
|
||||
if (options.fleet) {
|
||||
const { getFleetSlug } = await import('../../utils/sdk');
|
||||
for (const app of options.fleet.split(',')) {
|
||||
for (const appSlug of await resolveFleetSlugs(balena, options.fleet)) {
|
||||
try {
|
||||
await balena.models.application[varType].set(
|
||||
await getFleetSlug(balena, app),
|
||||
appSlug,
|
||||
params.name,
|
||||
params.value,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(`${err.message}, fleet: ${app}`);
|
||||
console.error(`${err.message}, fleet: ${appSlug}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@ -181,6 +178,25 @@ export default class EnvAddCmd extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Stop accepting application names in the next major
|
||||
// and just drop this in favor of doing the .split(',') directly.
|
||||
async function resolveFleetSlugs(
|
||||
balena: BalenaSdk.BalenaSDK,
|
||||
fleetOption: string,
|
||||
) {
|
||||
const fleetSlugs: string[] = [];
|
||||
const { getFleetSlug } = await import('../../utils/sdk');
|
||||
for (const appNameOrSlug of fleetOption.split(',')) {
|
||||
try {
|
||||
fleetSlugs.push(await getFleetSlug(balena, appNameOrSlug));
|
||||
} catch (err) {
|
||||
console.error(`${err.message}, fleet: ${appNameOrSlug}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
return fleetSlugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add service variables for a device or fleet.
|
||||
*/
|
||||
@ -190,17 +206,17 @@ async function setServiceVars(
|
||||
options: FlagsDef,
|
||||
) {
|
||||
if (options.fleet) {
|
||||
for (const app of options.fleet.split(',')) {
|
||||
for (const appSlug of await resolveFleetSlugs(sdk, options.fleet)) {
|
||||
for (const service of options.service!.split(',')) {
|
||||
try {
|
||||
const serviceId = await getServiceIdForApp(sdk, app, service);
|
||||
const serviceId = await getServiceIdForApp(sdk, appSlug, service);
|
||||
await sdk.models.service.var.set(
|
||||
serviceId,
|
||||
params.name,
|
||||
params.value!,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(`${err.message}, fleet: ${app}`);
|
||||
console.error(`${err.message}, fleet: ${appSlug}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@ -245,11 +261,12 @@ async function setServiceVars(
|
||||
*/
|
||||
async function getServiceIdForApp(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
appName: string,
|
||||
appSlug: string,
|
||||
serviceName: string,
|
||||
): Promise<number> {
|
||||
let serviceId: number | undefined;
|
||||
const services = await sdk.models.service.getAllByApplication(appName, {
|
||||
const services = await sdk.models.service.getAllByApplication(appSlug, {
|
||||
$select: 'id',
|
||||
$filter: { service_name: serviceName },
|
||||
});
|
||||
if (services.length > 0) {
|
||||
@ -257,7 +274,7 @@ async function getServiceIdForApp(
|
||||
}
|
||||
if (serviceId === undefined) {
|
||||
throw new ExpectedError(
|
||||
`Cannot find service ${serviceName} for fleet ${appName}`,
|
||||
`Cannot find service ${serviceName} for fleet ${appSlug}`,
|
||||
);
|
||||
}
|
||||
return serviceId;
|
@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { flags } from '@oclif/command';
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
|
||||
import * as cf from '../../utils/common-flags';
|
||||
@ -22,20 +22,6 @@ import * as ec from '../../utils/env-common';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { parseAsInteger } from '../../utils/validation';
|
||||
|
||||
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
||||
|
||||
interface FlagsDef {
|
||||
config: boolean;
|
||||
device: boolean;
|
||||
service: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
id: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export default class EnvRenameCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Change the value of a config or env var for a fleet, device or service.
|
||||
@ -54,24 +40,22 @@ export default class EnvRenameCmd extends Command {
|
||||
'$ balena env rename 678678 1 --device --config',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'id',
|
||||
public static args = {
|
||||
id: Args.integer({
|
||||
required: true,
|
||||
description: "variable's numeric database ID",
|
||||
parse: (input) => parseAsInteger(input, 'id'),
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
parse: async (input) => parseAsInteger(input, 'id'),
|
||||
}),
|
||||
value: Args.string({
|
||||
required: true,
|
||||
description:
|
||||
"variable value; if omitted, use value from this process' environment",
|
||||
},
|
||||
];
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'env rename <id> <value>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
config: ec.booleanConfig,
|
||||
device: ec.booleanDevice,
|
||||
service: ec.booleanService,
|
||||
@ -79,9 +63,7 @@ export default class EnvRenameCmd extends Command {
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: opt } = this.parse<FlagsDef, ArgsDef>(
|
||||
EnvRenameCmd,
|
||||
);
|
||||
const { args: params, flags: opt } = await this.parse(EnvRenameCmd);
|
||||
|
||||
await Command.checkLoggedIn();
|
||||
|
34
lib/commands/env/rm.ts → src/commands/env/rm.ts
vendored
34
lib/commands/env/rm.ts → src/commands/env/rm.ts
vendored
@ -15,26 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
|
||||
import * as ec from '../../utils/env-common';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { parseAsInteger } from '../../utils/validation';
|
||||
|
||||
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
||||
|
||||
interface FlagsDef {
|
||||
config: boolean;
|
||||
device: boolean;
|
||||
service: boolean;
|
||||
yes: boolean;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export default class EnvRmCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Remove a config or env var from a fleet, device or service.
|
||||
@ -57,22 +44,21 @@ export default class EnvRmCmd extends Command {
|
||||
'$ balena env rm 789789 --device --service --yes',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'id',
|
||||
public static args = {
|
||||
id: Args.integer({
|
||||
required: true,
|
||||
description: "variable's numeric database ID",
|
||||
parse: (input) => parseAsInteger(input, 'id'),
|
||||
},
|
||||
];
|
||||
parse: async (input) => parseAsInteger(input, 'id'),
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'env rm <id>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
config: ec.booleanConfig,
|
||||
device: ec.booleanDevice,
|
||||
service: ec.booleanService,
|
||||
yes: flags.boolean({
|
||||
yes: Flags.boolean({
|
||||
char: 'y',
|
||||
description:
|
||||
'do not prompt for confirmation before deleting the variable',
|
||||
@ -81,9 +67,7 @@ export default class EnvRmCmd extends Command {
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: opt } = this.parse<FlagsDef, ArgsDef>(
|
||||
EnvRmCmd,
|
||||
);
|
||||
const { args: params, flags: opt } = await this.parse(EnvRmCmd);
|
||||
|
||||
await Command.checkLoggedIn();
|
||||
|
@ -14,23 +14,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { flags } from '@oclif/command';
|
||||
import { Flags } from '@oclif/core';
|
||||
import type { Interfaces } from '@oclif/core';
|
||||
import type * as SDK from 'balena-sdk';
|
||||
import * as _ from 'lodash';
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
|
||||
interface FlagsDef {
|
||||
fleet?: string;
|
||||
config: boolean;
|
||||
device?: string; // device UUID
|
||||
json: boolean;
|
||||
help: void;
|
||||
service?: string; // service name
|
||||
}
|
||||
type FlagsDef = Interfaces.InferredFlags<typeof EnvsCmd.flags>;
|
||||
|
||||
interface EnvironmentVariableInfo extends SDK.EnvironmentVariableBase {
|
||||
fleet?: string | null; // fleet slug
|
||||
@ -93,7 +87,6 @@ export default class EnvsCmd extends Command {
|
||||
'$ balena envs --fleet myorg/myfleet',
|
||||
'$ balena envs --fleet MyFleet --json',
|
||||
'$ balena envs --fleet MyFleet --service MyService',
|
||||
'$ balena envs --fleet MyFleet --service MyService',
|
||||
'$ balena envs --fleet MyFleet --config',
|
||||
'$ balena envs --device 7cf02a6',
|
||||
'$ balena envs --device 7cf02a6 --json',
|
||||
@ -103,9 +96,9 @@ export default class EnvsCmd extends Command {
|
||||
|
||||
public static usage = 'envs';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
public static flags = {
|
||||
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||
config: flags.boolean({
|
||||
config: Flags.boolean({
|
||||
default: false,
|
||||
char: 'c',
|
||||
description: 'show configuration variables only',
|
||||
@ -118,7 +111,7 @@ export default class EnvsCmd extends Command {
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
|
||||
const { flags: options } = await this.parse(EnvsCmd);
|
||||
|
||||
const variables: EnvironmentVariableInfo[] = [];
|
||||
|
||||
@ -131,12 +124,16 @@ export default class EnvsCmd extends Command {
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
let fleetSlug: string | undefined = options.fleet
|
||||
? await (await import('../utils/sdk')).getFleetSlug(balena, options.fleet)
|
||||
? await (
|
||||
await import('../../utils/sdk')
|
||||
).getFleetSlug(balena, options.fleet)
|
||||
: undefined;
|
||||
let fullUUID: string | undefined; // as oppposed to the short, 7-char UUID
|
||||
|
||||
if (options.device) {
|
||||
const { getDeviceAndMaybeAppFromUUID } = await import('../utils/cloud');
|
||||
const { getDeviceAndMaybeAppFromUUID } = await import(
|
||||
'../../utils/cloud'
|
||||
);
|
||||
const [device, app] = await getDeviceAndMaybeAppFromUUID(
|
||||
balena,
|
||||
options.device,
|
||||
@ -189,7 +186,7 @@ export default class EnvsCmd extends Command {
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
const { pickAndRename } = await import('../utils/helpers');
|
||||
const { pickAndRename } = await import('../../utils/helpers');
|
||||
const mapped = varArray.map((o) => pickAndRename(o, fields));
|
||||
this.log(JSON.stringify(mapped, null, 4));
|
||||
} else {
|
||||
@ -209,6 +206,7 @@ async function validateServiceName(
|
||||
fleetSlug: string,
|
||||
) {
|
||||
const services = await sdk.models.service.getAllByApplication(fleetSlug, {
|
||||
$select: 'id',
|
||||
$filter: { service_name: serviceName },
|
||||
});
|
||||
if (services.length === 0) {
|
||||
@ -234,9 +232,10 @@ async function getAppVars(
|
||||
if (!fleetSlug) {
|
||||
return appVars;
|
||||
}
|
||||
const vars = await sdk.models.application[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByApplication(fleetSlug);
|
||||
const vars =
|
||||
await sdk.models.application[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByApplication(fleetSlug);
|
||||
fillInInfoFields(vars, fleetSlug);
|
||||
appVars.push(...vars);
|
||||
if (!options.config) {
|
||||
@ -275,9 +274,8 @@ async function getDeviceVars(
|
||||
const printedUUID = options.json ? fullUUID : options.device!;
|
||||
const deviceVars: EnvironmentVariableInfo[] = [];
|
||||
if (options.config) {
|
||||
const deviceConfigVars = await sdk.models.device.configVar.getAllByDevice(
|
||||
fullUUID,
|
||||
);
|
||||
const deviceConfigVars =
|
||||
await sdk.models.device.configVar.getAllByDevice(fullUUID);
|
||||
fillInInfoFields(deviceConfigVars, fleetSlug, printedUUID);
|
||||
deviceVars.push(...deviceConfigVars);
|
||||
} else {
|
||||
@ -302,9 +300,8 @@ async function getDeviceVars(
|
||||
fillInInfoFields(deviceServiceVars, fleetSlug, printedUUID);
|
||||
deviceVars.push(...deviceServiceVars);
|
||||
|
||||
const deviceEnvVars = await sdk.models.device.envVar.getAllByDevice(
|
||||
fullUUID,
|
||||
);
|
||||
const deviceEnvVars =
|
||||
await sdk.models.device.envVar.getAllByDevice(fullUUID);
|
||||
fillInInfoFields(deviceEnvVars, fleetSlug, printedUUID);
|
||||
deviceVars.push(...deviceEnvVars);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user