mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
514 Commits
add-device
...
esm2
Author | SHA1 | Date | |
---|---|---|---|
b86e26f9aa | |||
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 |
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
/completion/*
|
||||
/bin/*
|
21
.eslintrc.cjs
Normal file
21
.eslintrc.cjs
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
|
||||
|
72
.github/actions/publish/action.yml
vendored
72
.github/actions/publish/action.yml
vendored
@ -3,32 +3,34 @@ 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"
|
||||
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"
|
||||
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"
|
||||
default: 'accounts+apple@balena.io'
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
# FIXME: (please) https://github.com/balena-io/balena-cli/issues/2165
|
||||
default: "12.x"
|
||||
default: '20.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: "true"
|
||||
default: 'true'
|
||||
|
||||
runs:
|
||||
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||
using: "composite"
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Download custom source artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
|
||||
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
|
||||
@ -37,11 +39,17 @@ runs:
|
||||
run: tar -xf ${{ runner.temp }}/custom.tgz
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
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
|
||||
@ -58,7 +66,7 @@ runs:
|
||||
# https://github.com/Apple-Actions/import-codesign-certs
|
||||
- name: Import Apple code signing certificate
|
||||
if: runner.os == 'macOS'
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
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 }}
|
||||
@ -67,18 +75,11 @@ runs:
|
||||
if: runner.os == 'Windows'
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE
|
||||
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/certificate.pfx
|
||||
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
|
||||
|
||||
Import-PfxCertificate `
|
||||
-FilePath ${{ runner.temp }}/certificate.pfx `
|
||||
-CertStoreLocation Cert:\CurrentUser\My `
|
||||
-Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
|
||||
|
||||
env:
|
||||
WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
|
||||
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
|
||||
@ -98,12 +99,21 @@ runs:
|
||||
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||
|
||||
elif [[ $runner_os =~ windows|win ]]; then
|
||||
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
|
||||
CSC_LINK='${{ runner.temp }}\certificate.pfx'
|
||||
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 }}
|
||||
|
||||
# patches/all/oclif.patch
|
||||
MSYSSHELLPATH="$(which bash)"
|
||||
MSYSTEM=MSYS
|
||||
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}"
|
||||
@ -117,15 +127,17 @@ runs:
|
||||
# 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://sectigo.com/resource-library/time-stamping-server
|
||||
TIMESTAMP_SERVER: http://timestamp.sectigo.com
|
||||
# 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@v3
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
|
||||
with:
|
||||
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}
|
||||
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
|
||||
|
24
.github/actions/test/action.yml
vendored
24
.github/actions/test/action.yml
vendored
@ -8,12 +8,14 @@ inputs:
|
||||
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
|
||||
# FIXME: (please) https://github.com/balena-io/balena-cli/issues/2165
|
||||
default: "12.x"
|
||||
default: '20.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: "true"
|
||||
@ -24,11 +26,17 @@ runs:
|
||||
steps:
|
||||
# https://github.com/actions/setup-node#caching-global-packages-data
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
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: |
|
||||
@ -36,22 +44,22 @@ runs:
|
||||
|
||||
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||
|
||||
if [[ -e package-lock.json ]]; then
|
||||
if [[ -e package-lock.json ]] || [[ -e npm-shrinkwrap.json ]]; then
|
||||
npm ci
|
||||
else
|
||||
npm i
|
||||
fi
|
||||
|
||||
npm run build
|
||||
npm run test
|
||||
npm run test:core
|
||||
|
||||
- name: Compress custom source
|
||||
shell: pwsh
|
||||
run: tar -acf ${{ runner.temp }}/custom.tgz .
|
||||
run: tar --exclude-vcs -acf ${{ runner.temp }}/custom.tgz .
|
||||
|
||||
- name: Upload custom artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
|
||||
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"]
|
||||
}
|
41
.github/workflows/flowzone.yml
vendored
41
.github/workflows/flowzone.yml
vendored
@ -1,16 +1,45 @@
|
||||
name: Flowzone
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, closed]
|
||||
branches:
|
||||
- "main"
|
||||
- "master"
|
||||
|
||||
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:
|
||||
tests_run_on: '["ubuntu-20.04","macos-11","windows-2019"]'
|
||||
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
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@
|
||||
*.seed
|
||||
/.idea/
|
||||
/.lock-wscript
|
||||
/.nvmrc
|
||||
/.nyc_output/
|
||||
/.vscode/
|
||||
/coverage/
|
||||
|
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@ -0,0 +1 @@
|
||||
node automation/check-npm-version.cjs && npx tsc automation/check-doc.ts
|
File diff suppressed because it is too large
Load Diff
2816
CHANGELOG.md
2816
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,32 +15,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { JsonVersions } from '../lib/commands/version';
|
||||
import type { JsonVersions } from '../src/commands/version/index.js';
|
||||
|
||||
import { run as oclifRun } from 'oclif';
|
||||
import * as archiver from 'archiver';
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { execFile } from 'child_process';
|
||||
import * as filehound from 'filehound';
|
||||
import { 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 { run as oclifRun } from '@oclif/core';
|
||||
import archiver from 'archiver';
|
||||
import { exec, execFile } from 'child_process';
|
||||
import filehound from 'filehound';
|
||||
import type { Stats } from 'fs';
|
||||
import fs from 'fs-extra';
|
||||
import klaw from 'klaw';
|
||||
import path from 'path';
|
||||
import { rimraf } from 'rimraf';
|
||||
import semver from 'semver';
|
||||
import { promisify } from 'util';
|
||||
import { notarize } from '@electron/notarize';
|
||||
|
||||
import { stripIndent } from '../build/utils/lazy';
|
||||
import { stripIndent } from '../build/utils/lazy.js';
|
||||
import {
|
||||
diffLines,
|
||||
loadPackageJson,
|
||||
ROOT,
|
||||
StdOutTap,
|
||||
whichSpawn,
|
||||
} from './utils';
|
||||
} from './utils.js';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export const packageJSON = loadPackageJson();
|
||||
export const version = 'v' + packageJSON.version;
|
||||
@ -60,9 +60,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 = {
|
||||
@ -82,12 +86,12 @@ export const finalReleaseAssets: { [platform: string]: string[] } = {
|
||||
* Throw an error if the diff is not empty.
|
||||
*/
|
||||
async function diffPkgOutput(pkgOut: string) {
|
||||
const { monochrome } = await import('../tests/helpers');
|
||||
const { monochrome } = await import('../tests/helpers.js');
|
||||
const relSavedPath = path.join(
|
||||
'tests',
|
||||
'test-data',
|
||||
'pkg',
|
||||
`expected-warnings-${process.platform}.txt`,
|
||||
`expected-warnings-${process.platform}-${arch}.txt`,
|
||||
);
|
||||
const absSavedPath = path.join(ROOT, relSavedPath);
|
||||
const ignoreStartsWith = [
|
||||
@ -146,7 +150,8 @@ sections in the CLI's 'package.json' file, or a matter of updating the
|
||||
patching dependencies: See for example 'patches/all/open+7.0.2.patch'.
|
||||
${sep}
|
||||
`;
|
||||
throw new Error(msg);
|
||||
// throw new Error(msg);
|
||||
console.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,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();
|
||||
@ -180,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',
|
||||
@ -197,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(
|
||||
@ -250,7 +263,7 @@ async function testPkg() {
|
||||
'version',
|
||||
'-j',
|
||||
]);
|
||||
const { filterCliOutputForTests } = await import('../tests/helpers');
|
||||
const { filterCliOutputForTests } = await import('../tests/helpers.js');
|
||||
const filtered = filterCliOutputForTests({
|
||||
err: stderr.split(/\r?\n/),
|
||||
out: stdout.split(/\r?\n/),
|
||||
@ -313,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/')
|
||||
@ -413,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],
|
||||
@ -427,18 +445,20 @@ async function renameInstallerFiles() {
|
||||
* 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}"`);
|
||||
// trust ...
|
||||
await execFileAsync('signtool.exe', [
|
||||
'sign',
|
||||
'-t',
|
||||
'-sha1',
|
||||
process.env.SM_CODE_SIGNING_CERT_SHA1_HASH,
|
||||
'-tr',
|
||||
process.env.TIMESTAMP_SERVER || 'http://timestamp.comodoca.com',
|
||||
'-f',
|
||||
process.env.CSC_LINK,
|
||||
'-p',
|
||||
process.env.CSC_KEY_PASSWORD,
|
||||
'-td',
|
||||
'SHA256',
|
||||
'-fd',
|
||||
'SHA256',
|
||||
'-d',
|
||||
`balena-cli ${version}`,
|
||||
exeName,
|
||||
@ -456,16 +476,15 @@ async function signWindowsInstaller() {
|
||||
* Wait for Apple Installer Notarization to continue
|
||||
*/
|
||||
async function notarizeMacInstaller(): Promise<void> {
|
||||
const teamId = process.env.XCODE_APP_LOADER_TEAM_ID || '66H43P8FRG';
|
||||
const appleId =
|
||||
process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io';
|
||||
const appBundleId = packageJSON.oclif.macos.identifier || 'io.balena.cli';
|
||||
const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD;
|
||||
|
||||
if (appleIdPassword) {
|
||||
const { notarize } = await import('electron-notarize');
|
||||
// https://github.com/electron/notarize/blob/main/README.md
|
||||
if (appleIdPassword && teamId) {
|
||||
await notarize({
|
||||
appBundleId,
|
||||
tool: 'notarytool',
|
||||
teamId,
|
||||
appPath: renamedOclifInstallers.darwin,
|
||||
appleId,
|
||||
appleIdPassword,
|
||||
@ -484,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}`);
|
||||
@ -497,17 +517,14 @@ export async function buildOclifInstaller() {
|
||||
}
|
||||
for (const dir of dirs) {
|
||||
console.log(`rimraf(${dir})`);
|
||||
await Bluebird.fromCallback((cb) => rimraf(dir, cb));
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
console.log('Signing files for notarization...');
|
||||
await signFilesForNotarization();
|
||||
await rimraf(dir);
|
||||
}
|
||||
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
|
||||
@ -553,6 +570,8 @@ export async function testShrinkwrap(): Promise<void> {
|
||||
console.error(`[debug] platform=${process.platform}`);
|
||||
}
|
||||
if (process.platform !== 'win32') {
|
||||
await whichSpawn(path.resolve(__dirname, 'test-lock-deduplicated.sh'));
|
||||
await whichSpawn(
|
||||
path.resolve(import.meta.dirname, 'test-lock-deduplicated.sh'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { MarkdownFileParser } from './utils';
|
||||
import { MarkdownFileParser } from './utils.js';
|
||||
import { globSync } from 'glob';
|
||||
|
||||
/**
|
||||
* This is the skeleton of CLI documentation/reference web page at:
|
||||
@ -24,179 +25,126 @@ 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 = globSync('build/commands/**/*.js', {
|
||||
ignore: 'build/commands/internal/**',
|
||||
});
|
||||
|
||||
// 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.
|
||||
*/
|
||||
export async function getCapitanoDoc(): Promise<typeof capitanoDoc> {
|
||||
const readmePath = path.join(__dirname, '..', '..', 'README.md');
|
||||
const readmePath = path.join(import.meta.dirname, '..', '..', 'README.md');
|
||||
const mdParser = new MarkdownFileParser(readmePath);
|
||||
const sections: string[] = await Promise.all([
|
||||
mdParser.getSectionOfTitle('About').then((sectionLines: string) => {
|
||||
|
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;
|
||||
|
||||
|
@ -15,11 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import * as path from 'path';
|
||||
import { getCapitanoDoc } from './capitanodoc';
|
||||
import { Category, Document, OclifCommand } from './doc-types';
|
||||
import * as markdown from './markdown';
|
||||
import { stripIndent } from '../../lib/utils/lazy';
|
||||
import { getCapitanoDoc } from './capitanodoc.js';
|
||||
import type { Category, Document, OclifCommand } from './doc-types.js';
|
||||
import * as markdown from './markdown.js';
|
||||
import { stripIndent } from '../../src/utils/lazy.js';
|
||||
import { Module } from 'node:module';
|
||||
|
||||
const require = Module.createRequire(import.meta.url);
|
||||
/**
|
||||
* Generates the markdown document (as a string) for the CLI documentation
|
||||
* page on the web: https://www.balena.io/docs/reference/cli/
|
||||
@ -62,12 +64,11 @@ class FakeHelpCommand {
|
||||
'$ balena help os download',
|
||||
];
|
||||
|
||||
args = [
|
||||
{
|
||||
name: 'command',
|
||||
args = {
|
||||
command: {
|
||||
description: 'command to show help for',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
usage = 'help [command]';
|
||||
|
||||
@ -105,5 +106,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 _ 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.js';
|
||||
import { capitanoizeOclifUsage } from '../../src/utils/oclif-utils.js';
|
||||
import type { Category, Document, OclifCommand } from './doc-types.js';
|
||||
|
||||
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,14 +15,14 @@
|
||||
* 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 _ from 'lodash';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
import { simpleGit } from 'simple-git';
|
||||
|
||||
const ROOT = path.normalize(path.join(__dirname, '..'));
|
||||
const ROOT = path.normalize(path.join(import.meta.dirname, '..'));
|
||||
|
||||
/**
|
||||
* Compare the timestamp of balena-cli.md with the timestamp of staged files,
|
||||
@ -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,49 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import _ from 'lodash';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { finalReleaseAssets, version } from './build-bin';
|
||||
|
||||
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 = (
|
||||
@ -122,13 +84,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 +116,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,
|
||||
|
@ -15,18 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {
|
||||
buildOclifInstaller,
|
||||
buildStandaloneZip,
|
||||
catchUncommitted,
|
||||
signFilesForNotarization,
|
||||
testShrinkwrap,
|
||||
} from './build-bin';
|
||||
import {
|
||||
release,
|
||||
updateDescriptionOfReleasesAffectedByIssue1359,
|
||||
} from './deploy-bin';
|
||||
} from './build-bin.js';
|
||||
import { updateDescriptionOfReleasesAffectedByIssue1359 } from './deploy-bin.js';
|
||||
|
||||
// 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();
|
||||
|
@ -54,7 +54,10 @@ interface Upstream {
|
||||
|
||||
const getUpstreams = async () => {
|
||||
const fs = await import('fs');
|
||||
const repoYaml = fs.readFileSync(__dirname + '/../repo.yml', 'utf8');
|
||||
const repoYaml = fs.readFileSync(
|
||||
import.meta.dirname + '/../repo.yml',
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const yaml = await import('js-yaml');
|
||||
const { upstream } = yaml.load(repoYaml) as {
|
||||
@ -107,11 +110,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 +139,5 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
main();
|
||||
|
@ -16,10 +16,12 @@
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import { diffTrimmedLines } from 'diff';
|
||||
import { Module } from 'node:module';
|
||||
|
||||
export const ROOT = path.join(__dirname, '..');
|
||||
const require = Module.createRequire(import.meta.url);
|
||||
export const ROOT = path.join(import.meta.dirname, '..');
|
||||
|
||||
/** Tap and buffer this process' stdout and stderr */
|
||||
export class StdOutTap {
|
||||
@ -65,7 +67,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,8 +78,8 @@ 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;
|
||||
@ -98,7 +99,7 @@ export function loadPackageJson() {
|
||||
* @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE'
|
||||
*/
|
||||
export async function which(program: string): Promise<string> {
|
||||
const whichMod = await import('which');
|
||||
const { default: whichMod } = await import('which');
|
||||
let programPath: string;
|
||||
try {
|
||||
programPath = await whichMod(program);
|
||||
|
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" %*
|
90
bin/dev.js
Executable file
90
bin/dev.js
Executable file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning
|
||||
|
||||
// ****************************************************************************
|
||||
// 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`.
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
const rootDir = path.join(import.meta.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
|
||||
(await import('@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'.
|
||||
// (await import('ts-node')).register({
|
||||
// project: path.join(rootDir, 'tsconfig.json'),
|
||||
// transpileOnly: true,
|
||||
// });
|
||||
(await import('../src/app.js')).run(undefined, {
|
||||
dir: import.meta.url,
|
||||
development: true,
|
||||
});
|
||||
|
||||
// Modify package.json oclif paths from build/ -> src/, or vice versa
|
||||
function modifyOclifPaths(revert) {
|
||||
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" %*
|
20
bin/run.js
Executable file
20
bin/run.js
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
|
||||
async function run() {
|
||||
// Use fast-boot to cache require lookups, speeding up startup
|
||||
await (await import('../build/fast-boot.js')).start();
|
||||
|
||||
// Set the desired es version for downstream modules that support it
|
||||
(await import('@balena/es-version')).set('es2018');
|
||||
|
||||
// Run the CLI
|
||||
await (
|
||||
await import('../build/app.js')
|
||||
).run(undefined, { dir: import.meta.url });
|
||||
}
|
||||
|
||||
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 services 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 )
|
||||
@ -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 services 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"
|
||||
@ -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]);
|
4917
docs/balena-cli.md
4917
docs/balena-cli.md
File diff suppressed because it is too large
Load Diff
@ -1,93 +0,0 @@
|
||||
/**
|
||||
* @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/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import { ImageInstall } from 'balena-sdk';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { getExpandedProp } from '../../utils/pine';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
interface AugmentedImageInstall extends ImageInstall {
|
||||
name?: string;
|
||||
release?: string;
|
||||
}
|
||||
|
||||
export default class DeviceServicesCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Show info about a device's services.
|
||||
|
||||
Show info about a device's services.
|
||||
`;
|
||||
public static examples = ['$ balena device services 23c73a1'];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
description: 'the uuid of the device whose services to show info about',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
public static usage = 'device services <uuid>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceServicesCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
try {
|
||||
const device = await balena.models.device.getWithServiceDetails(
|
||||
params.uuid,
|
||||
);
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
device.image_install?.map((imageInstall) => {
|
||||
const newImageInstall: AugmentedImageInstall = { ...imageInstall };
|
||||
newImageInstall.name = getExpandedProp(
|
||||
getExpandedProp(imageInstall.image, 'is_a_build_of__service')!,
|
||||
'service_name',
|
||||
);
|
||||
newImageInstall.release = getExpandedProp(
|
||||
imageInstall.is_provided_by__release,
|
||||
'commit',
|
||||
);
|
||||
return newImageInstall;
|
||||
}),
|
||||
['name', 'status', 'release', 'id'],
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019-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 { stripIndent } from './lazy';
|
||||
import { lowercaseIfSlug } from './normalization';
|
||||
|
||||
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
|
||||
import type { DataOutputOptions, DataSetOutputOptions } from '../framework';
|
||||
|
||||
export const fleet = flags.string({
|
||||
char: 'f',
|
||||
description: 'fleet name or slug (preferred)',
|
||||
parse: lowercaseIfSlug,
|
||||
});
|
||||
|
||||
export const device = flags.string({
|
||||
char: 'd',
|
||||
description: 'device UUID',
|
||||
});
|
||||
|
||||
export const help: IBooleanFlag<void> = flags.help({ char: 'h' });
|
||||
|
||||
export const quiet: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'q',
|
||||
description: 'suppress warning messages',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const release = flags.string({
|
||||
char: 'r',
|
||||
description: 'release id',
|
||||
});
|
||||
|
||||
export const service = flags.string({
|
||||
char: 's',
|
||||
description: 'service name',
|
||||
});
|
||||
|
||||
export const verbose: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'v',
|
||||
description: 'produce verbose output',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const yes: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'y',
|
||||
description: 'answer "yes" to all questions (non interactive use)',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const force: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'f',
|
||||
description: 'force action if the update lock is set',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const dev: IBooleanFlag<boolean> = flags.boolean({
|
||||
description: 'Configure balenaOS to operate in development mode',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const drive = flags.string({
|
||||
char: 'd',
|
||||
description: stripIndent`
|
||||
the drive to write the image to, eg. \`/dev/sdb\` or \`/dev/mmcblk0\`.
|
||||
Careful with this as you can erase your hard drive.
|
||||
Check \`balena util available-drives\` for available options.
|
||||
`,
|
||||
});
|
||||
|
||||
export const driveOrImg = flags.string({
|
||||
char: 'd',
|
||||
description:
|
||||
'path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)',
|
||||
});
|
||||
|
||||
export const deviceType = flags.string({
|
||||
description:
|
||||
'device type (Check available types with `balena devices supported`)',
|
||||
char: 't',
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const json: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'j',
|
||||
description: 'produce JSON output instead of tabular output',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const dataOutputFlags: flags.Input<DataOutputOptions> = {
|
||||
fields: flags.string({
|
||||
description: 'only show provided fields (comma-separated)',
|
||||
}),
|
||||
json: flags.boolean({
|
||||
char: 'j',
|
||||
exclusive: ['no-truncate'],
|
||||
description: 'output in json format',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
export const dataSetOutputFlags: flags.Input<DataOutputOptions> &
|
||||
flags.Input<DataSetOutputOptions> = {
|
||||
...dataOutputFlags,
|
||||
filter: flags.string({
|
||||
description:
|
||||
'filter results by substring matching of a given field, eg: --filter field=foo',
|
||||
}),
|
||||
'no-header': flags.boolean({
|
||||
exclusive: ['json'],
|
||||
description: 'hide table header from output',
|
||||
default: false,
|
||||
}),
|
||||
'no-truncate': flags.boolean({
|
||||
exclusive: ['json'],
|
||||
description: 'do not truncate output to fit screen',
|
||||
default: false,
|
||||
}),
|
||||
sort: flags.string({
|
||||
description: `field to sort by (prepend '-' for descending order)`,
|
||||
}),
|
||||
};
|
28737
npm-shrinkwrap.json
generated
28737
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
192
package.json
192
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "14.5.15",
|
||||
"version": "19.0.2",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -9,38 +9,38 @@
|
||||
"url": "git@github.com:balena-io/balena-cli.git"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"type": "module",
|
||||
"files": [
|
||||
"bin/.fast-boot.json",
|
||||
"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,34 +48,36 @@
|
||||
"scripts": {
|
||||
"postinstall": "node patches/apply-patches.js",
|
||||
"prebuild": "rimraf build/ build-bin/",
|
||||
"pretarball": "npx tsx ../../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",
|
||||
"build:completion": "node completion/generate-completion.js",
|
||||
"build:standalone": "ts-node --transpile-only automation/run.ts build:standalone",
|
||||
"build:installer": "ts-node --transpile-only automation/run.ts build:installer",
|
||||
"build:doc": "npx tsx automation/capitanodoc/index.ts > docs/balena-cli.md",
|
||||
"build:completion": "node completion/generate-completion.cjs",
|
||||
"build:standalone": "npx tsx automation/run.ts build:standalone",
|
||||
"build:installer": "npx tsx 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:shrinkwrap": "ts-node --transpile-only automation/run.ts test-shrinkwrap",
|
||||
"test": "npm run test:shrinkwrap && npm run test:core",
|
||||
"test:core": "npm run test:source && npm run test:standalone",
|
||||
"test:shrinkwrap": "npx tsx 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",
|
||||
"catch-uncommitted": "npx tsx 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",
|
||||
"update": "ts-node --transpile-only ./automation/update-module.ts",
|
||||
"prepare": "echo {} > bin/.fast-boot.json",
|
||||
"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": "npx tsx ./automation/update-module.ts",
|
||||
"prepare": "echo {} > bin/.fast-boot.json && husky",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
@ -90,181 +92,170 @@
|
||||
"author": "Balena Inc. (https://balena.io/)",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12 <16"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "node automation/check-npm-version.js && ts-node automation/check-doc.ts"
|
||||
}
|
||||
"node": "^20.11.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/chai-as-promised": "^8.0.0",
|
||||
"@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/rimraf": "^3.0.2",
|
||||
"@types/rewire": "^2.5.30",
|
||||
"@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/which": "^2.0.1",
|
||||
"archiver": "^5.3.0",
|
||||
"@types/update-notifier": "^6.0.8",
|
||||
"@types/which": "^3.0.4",
|
||||
"@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",
|
||||
"chai": "^5.1.1",
|
||||
"chai-as-promised": "^8.0.0",
|
||||
"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",
|
||||
"oclif": "^4.14.0",
|
||||
"parse-link-header": "^2.0.0",
|
||||
"pkg": "^5.5.1",
|
||||
"publish-release": "^1.6.1",
|
||||
"rewire": "^5.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",
|
||||
"@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.2.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",
|
||||
"filenamify": "^4.3.0",
|
||||
"get-stdin": "^8.0.0",
|
||||
"glob": "^7.2.0",
|
||||
"get-stdin": "^9.0.0",
|
||||
"glob": "^11.0.0",
|
||||
"global-agent": "^2.2.0",
|
||||
"global-tunnel-ng": "^2.1.1",
|
||||
"got": "^11.8.3",
|
||||
"humanize": "0.0.9",
|
||||
"inquirer": "^7.3.3",
|
||||
"is-elevated": "^3.0.0",
|
||||
"is-root": "^2.1.0",
|
||||
"is-elevated": "^4.0.0",
|
||||
"is-root": "^3.0.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",
|
||||
"open": "^10.1.0",
|
||||
"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",
|
||||
"rimraf": "^6.0.1",
|
||||
"semver": "^7.3.5",
|
||||
"shell-escape": "^0.2.0",
|
||||
"split": "^1.0.1",
|
||||
@ -276,14 +267,19 @@
|
||||
"through2": "^2.0.3",
|
||||
"tmp": "^0.2.1",
|
||||
"typed-error": "^3.2.1",
|
||||
"update-notifier": "^5.1.0",
|
||||
"which": "^2.0.2",
|
||||
"update-notifier": "^7.3.0",
|
||||
"which": "^4.0.0",
|
||||
"window-size": "^1.1.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"windosu": "^0.3.0"
|
||||
},
|
||||
"overrides": {
|
||||
"inline-source-cli": {
|
||||
"inline-source": "^8.0.3"
|
||||
}
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2022-12-12T13:41:12.779Z"
|
||||
"publishedAt": "2024-09-03T14:56:14.237Z"
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -15,22 +15,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as packageJSON from '../package.json';
|
||||
import type { AppOptions } from './preparser.js';
|
||||
import {
|
||||
AppOptions,
|
||||
checkDeletedCommand,
|
||||
preparseArgs,
|
||||
unsupportedFlag,
|
||||
} from './preparser';
|
||||
import { CliSettings } from './utils/bootstrap';
|
||||
import { onceAsync } from './utils/lazy';
|
||||
} from './preparser.js';
|
||||
import { CliSettings } from './utils/bootstrap.js';
|
||||
import { onceAsync, getPackageJson } from './utils/lazy.js';
|
||||
import { run as mainRun, settings } from '@oclif/core';
|
||||
import { Module } from 'node:module';
|
||||
|
||||
const require = Module.createRequire(import.meta.url);
|
||||
const packageJSON = getPackageJson();
|
||||
|
||||
/**
|
||||
* Sentry.io setup
|
||||
* @see https://docs.sentry.io/error-reporting/quickstart/?platform=node
|
||||
*/
|
||||
export const setupSentry = onceAsync(async () => {
|
||||
const config = await import('./config');
|
||||
const config = await import('./config.js');
|
||||
const Sentry = await import('@sentry/node');
|
||||
Sentry.init({
|
||||
autoSessionTracking: false,
|
||||
@ -50,7 +54,7 @@ export const setupSentry = onceAsync(async () => {
|
||||
async function checkNodeVersion() {
|
||||
const validNodeVersions = packageJSON.engines.node;
|
||||
if (!(await import('semver')).satisfies(process.version, validNodeVersions)) {
|
||||
const { getNodeEngineVersionWarn } = await import('./utils/messages');
|
||||
const { getNodeEngineVersionWarn } = await import('./utils/messages.js');
|
||||
console.warn(getNodeEngineVersionWarn(process.version, validNodeVersions));
|
||||
}
|
||||
}
|
||||
@ -88,13 +92,13 @@ async function init() {
|
||||
const settings = new CliSettings();
|
||||
|
||||
// Proxy setup should be done early on, before loading balena-sdk
|
||||
await (await import('./utils/proxy')).setupGlobalHttpProxy(settings);
|
||||
await (await import('./utils/proxy.js')).setupGlobalHttpProxy(settings);
|
||||
|
||||
setupBalenaSdkSharedOptions(settings);
|
||||
|
||||
// check for CLI updates once a day
|
||||
if (!process.env.BALENARC_OFFLINE_MODE) {
|
||||
(await import('./utils/update')).notify();
|
||||
(await import('./utils/update.js')).notify();
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +109,7 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
if (unsupportedFlag || process.env.BALENARC_UNSUPPORTED) {
|
||||
deprecationPromise = Promise.resolve();
|
||||
} else {
|
||||
const { DeprecationChecker } = await import('./deprecation');
|
||||
const { DeprecationChecker } = await import('./deprecation.js');
|
||||
const deprecationChecker = new DeprecationChecker(packageJSON.version);
|
||||
// warnAndAbortIfDeprecated uses previously cached data only
|
||||
await deprecationChecker.warnAndAbortIfDeprecated();
|
||||
@ -114,10 +118,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 +140,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.
|
||||
@ -141,20 +152,20 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
// the try/catch block above, execution does not get past the
|
||||
// Promise.all() call below, but I don't understand why.
|
||||
if (isEEXIT) {
|
||||
(await import('./fast-boot')).stop();
|
||||
(await import('./fast-boot.js')).stop();
|
||||
}
|
||||
})(!options.noFlush);
|
||||
|
||||
const { trackPromise } = await import('./hooks/prerun/track');
|
||||
const { trackPromise } = await import('./hooks/prerun/track.js');
|
||||
|
||||
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'
|
||||
'./utils/bootstrap.js'
|
||||
);
|
||||
setOfflineModeEnvVars();
|
||||
normalizeEnvVars();
|
||||
@ -168,15 +179,15 @@ export async function run(cliArgs = process.argv, options: AppOptions = {}) {
|
||||
await init();
|
||||
|
||||
// Look for commands that have been removed and if so, exit with a notice
|
||||
checkDeletedCommand(cliArgs.slice(2));
|
||||
await checkDeletedCommand(cliArgs.slice(2));
|
||||
|
||||
const args = await preparseArgs(cliArgs);
|
||||
await oclifRun(args, options);
|
||||
} catch (err) {
|
||||
await (await import('./errors')).handleError(err);
|
||||
await (await import('./errors.js')).handleError(err);
|
||||
} finally {
|
||||
try {
|
||||
(await import('./fast-boot')).stop();
|
||||
(await import('./fast-boot.js')).stop();
|
||||
} catch (e) {
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`[debug] Stopping fast-boot: ${e}`);
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getBalenaSdk } from '../utils/lazy';
|
||||
import { LoginServer } from './server';
|
||||
import { getBalenaSdk } from '../utils/lazy.js';
|
||||
import { LoginServer } from './server.js';
|
||||
|
||||
/**
|
||||
* @module auth
|
||||
@ -42,7 +42,7 @@ import { LoginServer } from './server';
|
||||
* console.log("My session token is: #{sessionToken}")
|
||||
*/
|
||||
export async function login({ host = '127.0.0.1', port = 0 }) {
|
||||
const utils = await import('./utils');
|
||||
const utils = await import('./utils.js');
|
||||
|
||||
const loginServer = new LoginServer();
|
||||
const {
|
||||
@ -55,7 +55,7 @@ export async function login({ host = '127.0.0.1', port = 0 }) {
|
||||
const loginUrl = await utils.getDashboardLoginURL(callbackUrl);
|
||||
|
||||
console.info(`Opening web browser for URL:\n${loginUrl}`);
|
||||
const open = await import('open');
|
||||
const { default: open } = await import('open');
|
||||
await open(loginUrl, { wait: false });
|
||||
|
||||
const balena = getBalenaSdk();
|
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 |
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as bodyParser from 'body-parser';
|
||||
import bodyParser from 'body-parser';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as express from 'express';
|
||||
import express from 'express';
|
||||
import type { Socket } from 'net';
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
import * as utils from './utils';
|
||||
import { ExpectedError } from '../errors';
|
||||
import * as utils from './utils.js';
|
||||
import { ExpectedError } from '../errors.js';
|
||||
|
||||
export class LoginServer extends EventEmitter {
|
||||
protected expressApp: express.Express;
|
||||
@ -56,7 +56,7 @@ export class LoginServer extends EventEmitter {
|
||||
);
|
||||
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, 'pages'));
|
||||
app.set('views', path.join(import.meta.dirname, 'pages'));
|
||||
|
||||
this.server = await new Promise<import('net').Server>((resolve, reject) => {
|
||||
const callback = (err: Error) => {
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getBalenaSdk } from '../utils/lazy';
|
||||
import { getBalenaSdk } from '../utils/lazy.js';
|
||||
|
||||
/**
|
||||
* Get dashboard CLI login URL
|
@ -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';
|
||||
} from './errors.js';
|
||||
import { stripIndent } from './utils/lazy.js';
|
||||
|
||||
export default abstract class BalenaCommand extends Command {
|
||||
/**
|
||||
@ -71,7 +70,7 @@ export default abstract class BalenaCommand extends Command {
|
||||
* - other code needs to execute before check
|
||||
*/
|
||||
protected static async checkElevatedPrivileges() {
|
||||
const isElevated = await (await import('is-elevated'))();
|
||||
const isElevated = await (await import('is-elevated')).default();
|
||||
if (!isElevated) {
|
||||
throw new InsufficientPrivilegesError(
|
||||
'You need root/admin privileges to run this command',
|
||||
@ -94,7 +93,7 @@ export default abstract class BalenaCommand extends Command {
|
||||
* @throws {NotLoggedInError}
|
||||
*/
|
||||
public static async checkLoggedIn() {
|
||||
await (await import('./utils/patterns')).checkLoggedIn();
|
||||
await (await import('./utils/patterns.js')).checkLoggedIn();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,14 +138,14 @@ export default abstract class BalenaCommand extends Command {
|
||||
* values from stdin based in configuration, minimising command implementation.
|
||||
*/
|
||||
protected async getStdin() {
|
||||
this.stdin = await (await import('get-stdin'))();
|
||||
this.stdin = await (await import('get-stdin')).default();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logger instance.
|
||||
*/
|
||||
protected static async getLogger() {
|
||||
return (await import('./utils/logger')).getLogger();
|
||||
return (await import('./utils/logger.js')).default.getLogger();
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
@ -168,7 +167,4 @@ export default abstract class BalenaCommand extends Command {
|
||||
await this.getStdin();
|
||||
}
|
||||
}
|
||||
|
||||
protected outputMessage = output.outputMessage;
|
||||
protected outputData = output.outputData;
|
||||
}
|
@ -15,19 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class GenerateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
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.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
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.js');
|
||||
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.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
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.js')
|
||||
).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.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
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.js')
|
||||
).applicationCreateBase('block', options, params);
|
||||
}
|
||||
}
|
@ -15,22 +15,35 @@
|
||||
* 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.js';
|
||||
import { getBalenaSdk } from '../../utils/lazy.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import * as compose from '../../utils/compose.js';
|
||||
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.js';
|
||||
import type {
|
||||
ComposeCliFlags,
|
||||
ComposeOpts,
|
||||
} from '../../utils/compose-types.js';
|
||||
import { buildProject, composeCliFlags } from '../../utils/compose_ts.js';
|
||||
import type { BuildOpts, DockerCliFlags } from '../../utils/docker.js';
|
||||
import { dockerCliFlags } from '../../utils/docker.js';
|
||||
import type Dockerode from 'dockerode';
|
||||
|
||||
// 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 +52,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 +77,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 +104,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 +125,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 +140,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 +161,16 @@ ${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.js');
|
||||
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.js'
|
||||
);
|
||||
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
|
||||
sdk,
|
||||
{
|
||||
@ -176,9 +185,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.js');
|
||||
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.js');
|
||||
const app = await getAppWithArch(opts.fleet);
|
||||
opts.arch = app.arch;
|
||||
opts.deviceType = app.is_for__device_type[0].slug;
|
||||
@ -186,8 +228,14 @@ ${dockerignoreHelp}
|
||||
}
|
||||
}
|
||||
|
||||
protected async prepareBuild(options: FlagsDef) {
|
||||
const { getDocker, generateBuildOpts } = await import('../utils/docker');
|
||||
protected async prepareBuild(options: FlagsDef): Promise<{
|
||||
docker: Dockerode;
|
||||
buildOpts: BuildOpts;
|
||||
composeOpts: ComposeOpts;
|
||||
}> {
|
||||
const { getDocker, generateBuildOpts } = await import(
|
||||
'../../utils/docker.js'
|
||||
);
|
||||
const [docker, buildOpts, composeOpts] = await Promise.all([
|
||||
getDocker(options),
|
||||
generateBuildOpts(options),
|
||||
@ -208,24 +256,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.js').default,
|
||||
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.js');
|
||||
|
||||
const project = await loadProject(
|
||||
logger,
|
||||
@ -234,11 +282,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 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 { Flags } from '@oclif/core';
|
||||
import type { Interfaces } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy.js';
|
||||
import {
|
||||
applicationIdInfo,
|
||||
devModeInfo,
|
||||
secureBootInfo,
|
||||
} from '../../utils/messages.js';
|
||||
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,59 +62,57 @@ 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'] },
|
||||
fleet: cf.fleetExclusive(['device']),
|
||||
dev: cf.dev,
|
||||
device: {
|
||||
...cf.device,
|
||||
exclusive: [
|
||||
'fleet',
|
||||
'provisioning-key-name',
|
||||
'provisioning-key-expiry-date',
|
||||
],
|
||||
},
|
||||
deviceApiKey: flags.string({
|
||||
secureBoot: cf.secureBoot,
|
||||
device: cf.deviceExclusive([
|
||||
'fleet',
|
||||
'provisioning-key-name',
|
||||
'provisioning-key-expiry-date',
|
||||
]),
|
||||
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 +122,25 @@ export default class ConfigGenerateCmd extends Command {
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async getApplication(balena: BalenaSDK, fleet: string) {
|
||||
const { getApplication } = await import('../../utils/sdk.js');
|
||||
return await getApplication(balena, fleet, {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
|
||||
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
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;
|
||||
@ -154,7 +149,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
$expand: { is_of__device_type: { $select: 'slug' } },
|
||||
});
|
||||
if (!rawDevice.belongs_to__application) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
const { ExpectedError } = await import('../../errors.js');
|
||||
throw new ExpectedError(stripIndent`
|
||||
Device ${options.device} does not appear to belong to an accessible fleet.
|
||||
Try with a different device, or use '--fleet' instead of '--device'.`);
|
||||
@ -165,11 +160,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;
|
||||
}
|
||||
|
||||
@ -177,22 +168,30 @@ export default class ConfigGenerateCmd extends Command {
|
||||
|
||||
// Check compatibility if application and deviceType provided
|
||||
if (options.fleet && options.deviceType) {
|
||||
const helpers = await import('../../utils/helpers');
|
||||
const helpers = await import('../../utils/helpers.js');
|
||||
if (
|
||||
!(await helpers.areDeviceTypesCompatible(
|
||||
resourceDeviceType,
|
||||
deviceType,
|
||||
))
|
||||
) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
const { ExpectedError } = await import('../../errors.js');
|
||||
throw new ExpectedError(
|
||||
`Device type ${options.deviceType} is incompatible with fleet ${options.fleet}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const deviceManifest = await balena.models.device.getManifestBySlug(
|
||||
const deviceManifest =
|
||||
await balena.models.config.getDeviceTypeManifestBySlug(deviceType);
|
||||
|
||||
const { validateSecureBootOptionAndWarn } = await import(
|
||||
'../../utils/config.js'
|
||||
);
|
||||
await validateSecureBootOptionAndWarn(
|
||||
options.secureBoot,
|
||||
deviceType,
|
||||
options.version,
|
||||
);
|
||||
|
||||
// Prompt for values
|
||||
@ -203,12 +202,13 @@ 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'];
|
||||
|
||||
// Generate config
|
||||
const { generateDeviceConfig, generateApplicationConfig } = await import(
|
||||
'../../utils/config'
|
||||
'../../utils/config.js'
|
||||
);
|
||||
|
||||
let config;
|
||||
@ -244,8 +244,10 @@ 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) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
protected async validateOptions(
|
||||
options: Interfaces.InferredFlags<typeof ConfigGenerateCmd.flags>,
|
||||
) {
|
||||
const { ExpectedError } = await import('../../errors.js');
|
||||
|
||||
if (options.device == null && options.fleet == null) {
|
||||
throw new ExpectedError(this.missingDeviceOrAppMessage);
|
||||
@ -254,7 +256,9 @@ export default class ConfigGenerateCmd extends Command {
|
||||
if (!options.fleet && options.deviceType) {
|
||||
throw new ExpectedError(this.deviceTypeNotAllowedMessage);
|
||||
}
|
||||
const { validateDevOptionAndWarn } = await import('../../utils/config');
|
||||
const { normalizeOsVersion } = await import('../../utils/normalization.js');
|
||||
options.version = normalizeOsVersion(options.version);
|
||||
const { validateDevOptionAndWarn } = await import('../../utils/config.js');
|
||||
await validateDevOptionAndWarn(options.dev, options.version);
|
||||
}
|
||||
}
|
@ -15,20 +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;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
file: string;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class ConfigInjectCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,11 +54,9 @@ 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');
|
||||
const { safeUmount } = await import('../../utils/umount.js');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device/OS drive'));
|
@ -15,17 +15,9 @@
|
||||
* 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;
|
||||
}
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class ConfigReadCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,9 +48,9 @@ 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');
|
||||
const { safeUmount } = await import('../../utils/umount.js');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
@ -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;
|
||||
advanced: boolean;
|
||||
help: void;
|
||||
version?: string;
|
||||
}
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class ConfigReconfigureCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,9 +57,9 @@ 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');
|
||||
const { safeUmount } = await import('../../utils/umount.js');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
||||
@ -78,7 +70,7 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
await safeUmount(drive);
|
||||
|
||||
if (!uuid) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
const { ExpectedError } = await import('../../errors.js');
|
||||
throw new ExpectedError(
|
||||
`Error: UUID not found in 'config.json' file for '${drive}'`,
|
||||
);
|
||||
@ -92,7 +84,7 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
configureCommand.push('--advanced');
|
||||
}
|
||||
|
||||
const { runCommand } = await import('../../utils/helpers');
|
||||
const { runCommand } = await import('../../utils/helpers.js');
|
||||
await runCommand(configureCommand);
|
||||
|
||||
console.info('Done');
|
@ -15,21 +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;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class ConfigWriteCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,11 +59,9 @@ 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');
|
||||
const { denyMount, safeUmount } = await import('../../utils/umount.js');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
@ -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.js';
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
import { getBalenaSdk, getChalk, stripIndent } from '../../utils/lazy.js';
|
||||
import {
|
||||
dockerignoreHelp,
|
||||
registrySecretsHelp,
|
||||
buildArgDeprecation,
|
||||
} from '../utils/messages';
|
||||
import * as ca from '../utils/common-args';
|
||||
import * as compose from '../utils/compose';
|
||||
} from '../../utils/messages.js';
|
||||
import * as ca from '../../utils/common-args.js';
|
||||
import * as compose from '../../utils/compose.js';
|
||||
import type {
|
||||
BuiltImage,
|
||||
ComposeCliFlags,
|
||||
ComposeOpts,
|
||||
Release as ComposeReleaseInfo,
|
||||
} from '../utils/compose-types';
|
||||
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
|
||||
} from '../../utils/compose-types.js';
|
||||
import type { BuildOpts, DockerCliFlags } from '../../utils/docker.js';
|
||||
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.js';
|
||||
import { dockerCliFlags } from '../../utils/docker.js';
|
||||
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.js'
|
||||
);
|
||||
|
||||
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.js');
|
||||
const app = await helpers.getAppWithArch(fleet);
|
||||
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
const dockerUtils = await import('../../utils/docker.js');
|
||||
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.js').default,
|
||||
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.js'
|
||||
);
|
||||
|
||||
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,
|
||||
@ -330,7 +321,7 @@ ${dockerignoreHelp}
|
||||
builtImagesByService = _.keyBy(builtImages, 'serviceName');
|
||||
}
|
||||
const images: BuiltImage[] = project.descriptors.map(
|
||||
(d) =>
|
||||
(d: ImageDescriptor) =>
|
||||
builtImagesByService[d.serviceName] ?? {
|
||||
serviceName: d.serviceName,
|
||||
name: (isBuildConfig(d.image) ? d.image.tag : d.image) || '',
|
||||
@ -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,20 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class DeviceDeactivateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,12 +51,11 @@ 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');
|
||||
const patterns = await import('../../utils/patterns.js');
|
||||
|
||||
const uuid = params.uuid;
|
||||
const deactivationWarning = `
|
@ -15,20 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
|
||||
export default class DeviceIdentifyCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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 Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { expandForAppName } from '../../utils/helpers.js';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
import { jsonInfo } from '../../utils/messages.js';
|
||||
|
||||
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,42 +77,52 @@ 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');
|
||||
const { default: open } = await import('open');
|
||||
const dashboardUrl = balena.models.device.getDashboardUrl(device.uuid);
|
||||
await open(dashboardUrl, { wait: false });
|
||||
return;
|
||||
@ -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,12 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
import { runCommand } from '../../utils/helpers';
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
import { applicationIdInfo } from '../../utils/messages.js';
|
||||
import { runCommand } from '../../utils/helpers.js';
|
||||
|
||||
interface FlagsDef {
|
||||
fleet?: string;
|
||||
@ -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,35 +109,31 @@ 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');
|
||||
const rimraf = promisify(await import('rimraf'));
|
||||
const { rimraf } = await import('rimraf');
|
||||
const tmp = await import('tmp');
|
||||
const tmpNameAsync = promisify(tmp.tmpName);
|
||||
tmp.setGracefulCleanup();
|
||||
const { downloadOSImage } = await import('../../utils/cloud');
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const { downloadOSImage } = await import('../../utils/cloud.js');
|
||||
const { getApplication } = await import('../../utils/sdk.js');
|
||||
|
||||
const logger = await Command.getLogger();
|
||||
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.js')).selectApplication();
|
||||
|
||||
// Register new device
|
||||
const deviceUuid = balena.models.device.generateUniqueKey();
|
@ -15,22 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class DeviceLocalModeCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,35 +15,18 @@
|
||||
* 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';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
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;
|
||||
}
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
import { applicationIdInfo } from '../../utils/messages.js';
|
||||
|
||||
export default class DeviceMoveCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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');
|
||||
const { getApplication } = await import('../../utils/sdk.js');
|
||||
|
||||
// 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');
|
||||
const patterns = await import('../../utils/patterns.js');
|
||||
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,23 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy.js';
|
||||
import type { Device } from 'balena-sdk';
|
||||
import { ExpectedError } from '../../errors';
|
||||
|
||||
interface FlagsDef {
|
||||
version?: string;
|
||||
yes: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
uuid: string;
|
||||
}
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
|
||||
export default class DeviceOsUpdateCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,10 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
// Get target OS version
|
||||
let targetOsVersion = options.version;
|
||||
if (targetOsVersion != null) {
|
||||
const { normalizeOsVersion } = await import(
|
||||
'../../utils/normalization.js'
|
||||
);
|
||||
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`,
|
||||
@ -132,7 +145,7 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
});
|
||||
}
|
||||
|
||||
const patterns = await import('../../utils/patterns');
|
||||
const patterns = await import('../../utils/patterns.js');
|
||||
// Confirm and start update
|
||||
await patterns.confirm(
|
||||
options.yes || false,
|
@ -15,21 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
import { getExpandedProp } from '../../utils/pine.js';
|
||||
|
||||
export default class DevicePinCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,23 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class DevicePublicUrlCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,19 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class DevicePurgeCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,20 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class DeviceRebootCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,23 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import * as ca from '../../utils/common-args.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
import { applicationIdInfo } from '../../utils/messages.js';
|
||||
|
||||
export default class DeviceRegisterCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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 { getApplication } = await import('../../utils/sdk.js');
|
||||
|
||||
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,20 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy.js';
|
||||
|
||||
export default class DeviceRenameCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,26 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy.js';
|
||||
import type {
|
||||
BalenaSDK,
|
||||
DeviceWithServiceDetails,
|
||||
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();
|
||||
@ -107,8 +94,8 @@ export default class DeviceRestartCmd extends Command {
|
||||
deviceUuid: string,
|
||||
serviceNames: string[],
|
||||
) {
|
||||
const { ExpectedError, instanceOf } = await import('../../errors');
|
||||
const { getExpandedProp } = await import('../../utils/pine');
|
||||
const { ExpectedError, instanceOf } = await import('../../errors.js');
|
||||
const { getExpandedProp } = await import('../../utils/pine.js');
|
||||
|
||||
// Get device
|
||||
let device: DeviceWithServiceDetails<CurrentServiceWithCommit>;
|
||||
@ -174,7 +161,7 @@ export default class DeviceRestartCmd extends Command {
|
||||
// Note: device.restartApplication throws `BalenaDeviceNotFound: Device not found` if device not online.
|
||||
// Need to use device.get first to distinguish between non-existant and offline devices.
|
||||
// Remove this workaround when SDK issue resolved: https://github.com/balena-io/balena-sdk/issues/649
|
||||
const { instanceOf, ExpectedError } = await import('../../errors');
|
||||
const { instanceOf, ExpectedError } = await import('../../errors.js');
|
||||
try {
|
||||
const device = await balena.models.device.get(deviceUuid);
|
||||
if (!device.is_online) {
|
@ -15,20 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class DeviceRmCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,12 +53,10 @@ 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');
|
||||
const patterns = await import('../../utils/patterns.js');
|
||||
|
||||
// Confirm
|
||||
const uuids = params.uuid.split(',');
|
@ -15,21 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
|
||||
export default class DeviceShutdownCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy.js';
|
||||
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.js');
|
||||
const { getExpandedProp } = await import('../../utils/pine.js');
|
||||
|
||||
// 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.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy.js';
|
||||
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.js');
|
||||
const { getExpandedProp } = await import('../../utils/pine.js');
|
||||
|
||||
// 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,19 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IArg } from '@oclif/parser/lib/args';
|
||||
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;
|
||||
}
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
|
||||
export default class DeviceTrackFleetCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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 Command from '../../command.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { expandForAppName } from '../../utils/helpers.js';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
import { applicationIdInfo, jsonInfo } from '../../utils/messages.js';
|
||||
|
||||
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.js');
|
||||
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',
|
||||
@ -113,7 +115,7 @@ export default class DevicesCmd extends Command {
|
||||
];
|
||||
|
||||
if (options.json) {
|
||||
const { pickAndRename } = await import('../../utils/helpers');
|
||||
const { pickAndRename } = await import('../../utils/helpers.js');
|
||||
const mapped = devices.map((device) => pickAndRename(device, fields));
|
||||
console.log(JSON.stringify(mapped, null, 4));
|
||||
} else {
|
@ -14,19 +14,14 @@
|
||||
* 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';
|
||||
import _ from 'lodash';
|
||||
import Command from '../../command.js';
|
||||
|
||||
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;
|
||||
}
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy.js';
|
||||
import { CommandHelp } from '../../utils/oclif-utils.js';
|
||||
|
||||
export default class DevicesSupportedCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -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,13 +15,13 @@
|
||||
* 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';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
import Command from '../../command.js';
|
||||
import { ExpectedError } from '../../errors.js';
|
||||
import * as cf from '../../utils/common-flags.js';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy.js';
|
||||
import { applicationIdInfo } from '../../utils/messages.js';
|
||||
|
||||
interface FlagsDef {
|
||||
fleet?: string;
|
||||
@ -78,34 +78,32 @@ 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> = {
|
||||
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||
device: { ...cf.device, exclusive: ['fleet'] },
|
||||
public static flags = {
|
||||
fleet: cf.fleetExclusive(['device']),
|
||||
device: cf.deviceExclusive(['fleet']),
|
||||
help: cf.help,
|
||||
quiet: cf.quiet,
|
||||
service: cf.service,
|
||||
};
|
||||
|
||||
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.js');
|
||||
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,23 +206,23 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (options.device) {
|
||||
const { getDeviceAndAppFromUUID } = await import('../../utils/cloud');
|
||||
const { getDeviceAndAppFromUUID } = await import('../../utils/cloud.js');
|
||||
for (const uuid of options.device.split(',')) {
|
||||
let device;
|
||||
let app;
|
||||
@ -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;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user