libdragon 10.4.2 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/index.js +17 -119
- package/modules/actions/destroy.js +34 -0
- package/modules/actions/disasm.js +121 -0
- package/modules/actions/docker-utils.js +8 -0
- package/modules/actions/exec.js +97 -39
- package/modules/actions/help.js +29 -84
- package/modules/actions/index.js +8 -17
- package/modules/actions/init.js +46 -41
- package/modules/actions/install.js +10 -4
- package/modules/actions/make.js +24 -0
- package/modules/actions/npm-utils.js +122 -0
- package/modules/actions/start.js +17 -15
- package/modules/actions/stop.js +9 -3
- package/modules/actions/update.js +13 -9
- package/modules/actions/utils.js +42 -162
- package/modules/actions/version.js +17 -0
- package/modules/helpers.js +117 -45
- package/modules/parameters.js +129 -0
- package/modules/project-info.js +62 -33
- package/package.json +1 -1
- package/skeleton/Makefile +8 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## [10.7.0] - 2022-04-17
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Logs properly goes to stderr now. Previously they were written to stdout. This
|
|
8
|
+
means the id output of the `start` action is now written to stdout while we can
|
|
9
|
+
also display other information on the terminal. This allowed enabling the docker
|
|
10
|
+
logs for a more responsive experience. The output of `help` still goes to stdout.
|
|
11
|
+
- Skip reading the project information for `version` and `help` commands. This
|
|
12
|
+
was breaking things when these were run with a stopped docker service.
|
|
13
|
+
- Resolved a potential failure case where the host mount path is not reported
|
|
14
|
+
as a native path. That would prevent proper container discovery. This was not
|
|
15
|
+
being an issue previously but I realized it was not working for Windows now.
|
|
16
|
+
- Do not throw if the output stream is not finalized. This was previously causing
|
|
17
|
+
issues when the long output of a command is piped to another process like `less`.
|
|
18
|
+
Fixes #46
|
|
19
|
+
- Produce a correct error if an invalid flag with a singe `-` is provided.
|
|
20
|
+
- Sekeleton project's Makefile `clean` recipe now works properly. Fixes #56.
|
|
21
|
+
- A case where it is impossible to recover the container is fixed. This was
|
|
22
|
+
happening when the host does not have git and the cached container file is
|
|
23
|
+
missing. In that case the cli was attempting to run git without the containerId.
|
|
24
|
+
- `destroy` can now cleanup any remaining container even if the project init was
|
|
25
|
+
failed. This is also useful to clean up old folders that were once libdragon
|
|
26
|
+
projects.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Stdin consumption support. Now it is possible to pipe anything to `exec` and
|
|
31
|
+
it will pass it through to the target. In case of no running container, it will
|
|
32
|
+
keep a copy of the stdin stream until the docker process is ready. This enables
|
|
33
|
+
piping in data from the host if ever needed for some reason. This enables usages
|
|
34
|
+
like `cat file.txt | libdragon exec cat - | less`.
|
|
35
|
+
- Automatically convert host paths into posix format so that the user can use
|
|
36
|
+
the host's path autocompletion. It will also convert absolute host paths into
|
|
37
|
+
relative container paths automatically. Previously all paths were assumed to be
|
|
38
|
+
container paths relative to the location corresponding to the host cwd.
|
|
39
|
+
Closes #24.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Refactored process spawns.
|
|
44
|
+
- Refactored main flow and separated parsing logic.
|
|
45
|
+
- Reorder actions & correction on flag usage for help output.
|
|
46
|
+
- Setting `--verbose` for `start` does not guarantee the only-id output anymore.
|
|
47
|
+
- Refactored parameter parsing.
|
|
48
|
+
- Update submdule for local development.
|
|
49
|
+
|
|
50
|
+
## [10.6.0] - 2022-04-09
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Fix a path bug that would cause incorrect behaviour when the command is run
|
|
54
|
+
deeper than a single level in the project folder.
|
|
55
|
+
- Fix a potential issue where `build.sh`might be incorrectly found inexistant
|
|
56
|
+
if the OS is picky about the paths to have native separators.
|
|
57
|
+
- Only save project information when necessary. Previously actions like `help`
|
|
58
|
+
were saving project info mistakenly.
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- `disasm` action to simplify disassembling ELF files generated by the toolchain.
|
|
63
|
+
- `version` action to display current version.
|
|
64
|
+
- `destroy` action to remove the libdragon project.
|
|
65
|
+
- Additional documentation for flags.
|
|
66
|
+
- Print duration information when verbose.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- Refactored out NPM related functions.
|
|
71
|
+
- Moved usage parameters to respective actions files as a refactor.
|
|
72
|
+
- It is possible to provide an absolute path to init `--directory` as long as it
|
|
73
|
+
is inside the project directory. Previously it was possible to provide somewhere
|
|
74
|
+
outside the project, but it would fail with an unexpected error.
|
|
75
|
+
- Simplify saving mechanism. Each action now internally resolves into the data
|
|
76
|
+
to save if any.
|
|
77
|
+
|
|
3
78
|
## [10.4.2] - 2022-04-03
|
|
4
79
|
|
|
5
80
|
### Fixed
|
package/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const chalk = require('chalk');
|
|
3
|
+
const chalk = require('chalk').stderr;
|
|
4
4
|
|
|
5
|
-
const actions = require('./modules/actions');
|
|
6
5
|
const { fn: printUsage } = require('./modules/actions/help');
|
|
7
6
|
const {
|
|
8
7
|
STATUS_OK,
|
|
@@ -15,126 +14,26 @@ const {
|
|
|
15
14
|
CommandError,
|
|
16
15
|
ParameterError,
|
|
17
16
|
ValidationError,
|
|
17
|
+
log,
|
|
18
18
|
} = require('./modules/helpers');
|
|
19
|
+
const { parseParameters } = require('./modules/parameters');
|
|
19
20
|
const { readProjectInfo, writeProjectInfo } = require('./modules/project-info');
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
for (let i = 2; i < process.argv.length; i++) {
|
|
25
|
-
const val = process.argv[i];
|
|
26
|
-
|
|
27
|
-
// Allow console here
|
|
28
|
-
/* eslint-disable no-console */
|
|
29
|
-
|
|
30
|
-
if (['--verbose', '-v'].includes(val)) {
|
|
31
|
-
options.VERBOSE = true;
|
|
32
|
-
globals.verbose = true;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (['--image', '-i'].includes(val)) {
|
|
37
|
-
options.DOCKER_IMAGE = process.argv[++i];
|
|
38
|
-
continue;
|
|
39
|
-
} else if (val.indexOf('--image=') === 0) {
|
|
40
|
-
options.DOCKER_IMAGE = val.split('=')[1];
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (['--directory', '-d'].includes(val)) {
|
|
45
|
-
options.VENDOR_DIR = process.argv[++i];
|
|
46
|
-
continue;
|
|
47
|
-
} else if (val.indexOf('--directory=') === 0) {
|
|
48
|
-
options.VENDOR_DIR = val.split('=')[1];
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (['--strategy', '-s'].includes(val)) {
|
|
53
|
-
options.VENDOR_STRAT = process.argv[++i];
|
|
54
|
-
continue;
|
|
55
|
-
} else if (val.indexOf('--strategy=') === 0) {
|
|
56
|
-
options.VENDOR_STRAT = val.split('=')[1];
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (val.indexOf('--') >= 0) {
|
|
61
|
-
console.error(chalk.red(`Invalid flag \`${val}\``));
|
|
62
|
-
printUsage();
|
|
63
|
-
process.exit(STATUS_BAD_PARAM);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (currentAction) {
|
|
67
|
-
console.error(
|
|
68
|
-
chalk.red(`Expected only a single action, found: \`${val}\``)
|
|
69
|
-
);
|
|
70
|
-
printUsage();
|
|
71
|
-
process.exit(STATUS_BAD_PARAM);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
currentAction = actions[val];
|
|
75
|
-
|
|
76
|
-
if (!currentAction) {
|
|
77
|
-
console.error(chalk.red(`Invalid action \`${val}\``));
|
|
78
|
-
printUsage();
|
|
79
|
-
process.exit(STATUS_BAD_PARAM);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (currentAction.forwardsRestParams) {
|
|
83
|
-
options.PARAMS = process.argv.slice(i + 1);
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!currentAction) {
|
|
89
|
-
console.error(chalk.red('No action provided'));
|
|
90
|
-
printUsage();
|
|
91
|
-
process.exit(STATUS_BAD_PARAM);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (currentAction === actions.exec && options.PARAMS.length === 0) {
|
|
95
|
-
console.error(chalk.red('You should provide a command to exec'));
|
|
96
|
-
printUsage(undefined, [currentAction.name]);
|
|
97
|
-
process.exit(STATUS_BAD_PARAM);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
![actions.init, actions.install, actions.update].includes(currentAction) &&
|
|
102
|
-
options.DOCKER_IMAGE
|
|
103
|
-
) {
|
|
104
|
-
console.error(chalk.red('Invalid flag: image'));
|
|
105
|
-
printUsage(undefined, [currentAction.name]);
|
|
106
|
-
process.exit(STATUS_BAD_PARAM);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
options.VENDOR_STRAT &&
|
|
111
|
-
!['submodule', 'subtree', 'manual'].includes(options.VENDOR_STRAT)
|
|
112
|
-
) {
|
|
113
|
-
console.error(chalk.red(`Invalid strategy \`${options.VENDOR_STRAT}\``));
|
|
114
|
-
printUsage();
|
|
115
|
-
process.exit(STATUS_BAD_PARAM);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
readProjectInfo()
|
|
119
|
-
.then((info) =>
|
|
120
|
-
currentAction.fn(
|
|
121
|
-
{
|
|
122
|
-
...info,
|
|
123
|
-
options,
|
|
124
|
-
...currentAction,
|
|
125
|
-
},
|
|
126
|
-
options.PARAMS ?? []
|
|
127
|
-
)
|
|
128
|
-
)
|
|
22
|
+
parseParameters(process.argv)
|
|
23
|
+
.then(readProjectInfo)
|
|
24
|
+
.then((info) => info.options.CURRENT_ACTION.fn(info))
|
|
129
25
|
.catch((e) => {
|
|
130
26
|
if (e instanceof ParameterError) {
|
|
131
|
-
|
|
132
|
-
printUsage(
|
|
27
|
+
log(chalk.red(e.message));
|
|
28
|
+
printUsage({
|
|
29
|
+
options: {
|
|
30
|
+
EXTRA_PARAMS: [e.actionName],
|
|
31
|
+
},
|
|
32
|
+
});
|
|
133
33
|
process.exit(STATUS_BAD_PARAM);
|
|
134
34
|
}
|
|
135
|
-
|
|
136
35
|
if (e instanceof ValidationError) {
|
|
137
|
-
|
|
36
|
+
log(chalk.red(e.message));
|
|
138
37
|
process.exit(STATUS_VALIDATION_ERROR);
|
|
139
38
|
}
|
|
140
39
|
|
|
@@ -142,7 +41,7 @@ readProjectInfo()
|
|
|
142
41
|
|
|
143
42
|
// Show additional information to user if verbose or we did a mistake
|
|
144
43
|
if (globals.verbose || !userTargetedError) {
|
|
145
|
-
|
|
44
|
+
log(chalk.red(globals.verbose ? e.stack : e.message));
|
|
146
45
|
}
|
|
147
46
|
|
|
148
47
|
// Print the underlying error out only if not verbose and we did a mistake
|
|
@@ -160,10 +59,9 @@ readProjectInfo()
|
|
|
160
59
|
// We don't have a user targeted error anymore, we did a mistake for sure
|
|
161
60
|
process.exit(STATUS_ERROR);
|
|
162
61
|
})
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return writeProjectInfo();
|
|
166
|
-
})
|
|
62
|
+
// Everything was done, update the configuration file if not exiting early
|
|
63
|
+
.then(writeProjectInfo)
|
|
167
64
|
.finally(() => {
|
|
65
|
+
log(`Took: ${process.uptime()}s`, true);
|
|
168
66
|
process.exit(STATUS_OK);
|
|
169
67
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require('fs/promises');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const { destroyContainer } = require('./utils');
|
|
5
|
+
const { CONFIG_FILE, LIBDRAGON_PROJECT_MANIFEST } = require('../constants');
|
|
6
|
+
const { fileExists, dirExists, log } = require('../helpers');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
const destroy = async (libdragonInfo) => {
|
|
10
|
+
await destroyContainer(libdragonInfo);
|
|
11
|
+
|
|
12
|
+
const projectPath = path.join(libdragonInfo.root, LIBDRAGON_PROJECT_MANIFEST);
|
|
13
|
+
const configPath = path.join(projectPath, CONFIG_FILE);
|
|
14
|
+
|
|
15
|
+
if (await fileExists(configPath)) {
|
|
16
|
+
await fs.rm(configPath);
|
|
17
|
+
}
|
|
18
|
+
if (await dirExists(projectPath)) {
|
|
19
|
+
await fs.rmdir(projectPath);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
log(chalk.green('Done cleanup.'));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
name: 'destroy',
|
|
27
|
+
fn: destroy,
|
|
28
|
+
mustHaveProject: false,
|
|
29
|
+
usage: {
|
|
30
|
+
name: 'destroy',
|
|
31
|
+
summary: 'Do clean-up for current project.',
|
|
32
|
+
description: `Removes libdragon configuration from current project and removes any known containers but will not touch previously vendored files. \`libdragon\` will not work anymore for this project.`,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
|
|
4
|
+
const { fn: exec } = require('./exec');
|
|
5
|
+
const {
|
|
6
|
+
ValidationError,
|
|
7
|
+
toPosixPath,
|
|
8
|
+
dirExists,
|
|
9
|
+
fileExists,
|
|
10
|
+
ParameterError,
|
|
11
|
+
} = require('../helpers');
|
|
12
|
+
|
|
13
|
+
const findElf = async (stop, start = '.') => {
|
|
14
|
+
start = path.resolve(start);
|
|
15
|
+
|
|
16
|
+
const files = await fs.readdir(start);
|
|
17
|
+
|
|
18
|
+
const buildDir = path.join(start, 'build');
|
|
19
|
+
if (await dirExists(buildDir)) {
|
|
20
|
+
const elfFile = await findElf(buildDir, buildDir);
|
|
21
|
+
if (elfFile) return elfFile;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const elfFiles = files.filter((name) => name.endsWith('.elf'));
|
|
25
|
+
|
|
26
|
+
if (elfFiles.length > 1) {
|
|
27
|
+
throw new ValidationError(
|
|
28
|
+
`Multiple ELF files found in ${path.resolve(
|
|
29
|
+
start
|
|
30
|
+
)}. Use --file to specify.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (elfFiles.length === 1) {
|
|
35
|
+
return path.join(start, elfFiles[0]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const parent = path.join(start, '..');
|
|
39
|
+
if (start !== stop) {
|
|
40
|
+
return await findElf(stop, parent);
|
|
41
|
+
} else {
|
|
42
|
+
throw new ValidationError(`No ELF files found. Use --file to specify.`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const disasm = async (info) => {
|
|
47
|
+
let elfPath;
|
|
48
|
+
if (info.options.FILE) {
|
|
49
|
+
if (path.relative(info.root, info.options.FILE).startsWith('..')) {
|
|
50
|
+
throw new ParameterError(
|
|
51
|
+
`Provided file ${info.options.FILE} is outside the project directory.`,
|
|
52
|
+
info.options.CURRENT_ACTION.name
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (!(await fileExists(info.options.FILE)))
|
|
56
|
+
throw new ParameterError(
|
|
57
|
+
`Provided file ${info.options.FILE} does not exist`,
|
|
58
|
+
info.options.CURRENT_ACTION.name
|
|
59
|
+
);
|
|
60
|
+
elfPath = info.options.FILE;
|
|
61
|
+
}
|
|
62
|
+
elfPath = elfPath ?? (await findElf(info.root));
|
|
63
|
+
|
|
64
|
+
const haveSymbol =
|
|
65
|
+
info.options.EXTRA_PARAMS.length > 0 &&
|
|
66
|
+
!info.options.EXTRA_PARAMS[0].startsWith('-');
|
|
67
|
+
|
|
68
|
+
const finalArgs = haveSymbol
|
|
69
|
+
? [
|
|
70
|
+
`--disassemble=${info.options.EXTRA_PARAMS[0]}`,
|
|
71
|
+
...info.options.EXTRA_PARAMS.slice(1),
|
|
72
|
+
]
|
|
73
|
+
: info.options.EXTRA_PARAMS;
|
|
74
|
+
|
|
75
|
+
const intermixSourceParams =
|
|
76
|
+
info.options.EXTRA_PARAMS.length === 0 || haveSymbol ? ['-S'] : [];
|
|
77
|
+
|
|
78
|
+
return await exec({
|
|
79
|
+
...info,
|
|
80
|
+
options: {
|
|
81
|
+
...info.options,
|
|
82
|
+
EXTRA_PARAMS: [
|
|
83
|
+
'mips64-elf-objdump',
|
|
84
|
+
...finalArgs,
|
|
85
|
+
...intermixSourceParams,
|
|
86
|
+
toPosixPath(path.relative(process.cwd(), elfPath)),
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
name: 'disasm',
|
|
94
|
+
fn: disasm,
|
|
95
|
+
forwardsRestParams: true,
|
|
96
|
+
usage: {
|
|
97
|
+
name: 'disasm [symbol|flags]',
|
|
98
|
+
summary: 'Disassemble the nearest *.elf file.',
|
|
99
|
+
description: `Executes \`objdump\` for the nearest *.elf file starting from the working directory, going up. If there is a \`build\` directory in the searched paths, checks inside it as well. Any extra flags are passed down to \`objdump\` before the filename.
|
|
100
|
+
|
|
101
|
+
If a symbol name is provided after the action, it is converted to \`--disassembly=<symbol>\` and intermixed source* (\`-S\`) is automatically applied. This allows disassembling a single symbol by running;
|
|
102
|
+
|
|
103
|
+
\`libdragon disasm main\`
|
|
104
|
+
|
|
105
|
+
Again, any following flags are forwarded down. If run without extra symbol/flags, disassembles whole ELF with \`-S\` by default.
|
|
106
|
+
|
|
107
|
+
Must be run in an initialized libdragon project.
|
|
108
|
+
|
|
109
|
+
* Note that to be able to see the source, the code must be built with \`D=1\``,
|
|
110
|
+
|
|
111
|
+
optionList: [
|
|
112
|
+
{
|
|
113
|
+
name: 'file',
|
|
114
|
+
description:
|
|
115
|
+
'Provide a specific ELF file relative to current working directory and inside the libdragon project.',
|
|
116
|
+
alias: 'f',
|
|
117
|
+
typeLabel: ' ',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
};
|
package/modules/actions/exec.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
+
const { PassThrough } = require('stream');
|
|
2
3
|
|
|
3
4
|
const { CONTAINER_TARGET_PATH } = require('../constants');
|
|
4
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
log,
|
|
7
|
+
dockerExec,
|
|
8
|
+
toPosixPath,
|
|
9
|
+
fileExists,
|
|
10
|
+
dirExists,
|
|
11
|
+
} = require('../helpers');
|
|
5
12
|
|
|
6
13
|
const { start } = require('./start');
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
installDependencies,
|
|
10
|
-
mustHaveProject,
|
|
11
|
-
} = require('./utils');
|
|
14
|
+
const { dockerHostUserParams } = require('./docker-utils');
|
|
15
|
+
const { installDependencies } = require('./utils');
|
|
12
16
|
|
|
13
17
|
function dockerRelativeWorkdir(libdragonInfo) {
|
|
14
18
|
return (
|
|
@@ -22,71 +26,114 @@ function dockerRelativeWorkdirParams(libdragonInfo) {
|
|
|
22
26
|
return ['--workdir', dockerRelativeWorkdir(libdragonInfo)];
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
const exec = async (
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
const exec = async (info) => {
|
|
30
|
+
const parameters = info.options.EXTRA_PARAMS.slice(1);
|
|
28
31
|
log(
|
|
29
|
-
`Running ${
|
|
30
|
-
|
|
31
|
-
)} with [${
|
|
32
|
+
`Running ${info.options.EXTRA_PARAMS[0]} at ${dockerRelativeWorkdir(
|
|
33
|
+
info
|
|
34
|
+
)} with [${parameters}]`,
|
|
32
35
|
true
|
|
33
36
|
);
|
|
34
37
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
const stdin = new PassThrough();
|
|
39
|
+
|
|
40
|
+
const paramsWithConvertedPaths = parameters.map((item) => {
|
|
41
|
+
if (item.startsWith('-')) {
|
|
42
|
+
return item;
|
|
43
|
+
}
|
|
44
|
+
if (item.includes(path.sep) && (fileExists(item) || dirExists(item))) {
|
|
45
|
+
return toPosixPath(
|
|
46
|
+
path.isAbsolute(item) ? path.relative(process.cwd(), item) : item
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return item;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const tryCmd = (libdragonInfo, opts = {}) => {
|
|
53
|
+
const enableTTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
libdragonInfo.containerId &&
|
|
57
|
+
dockerExec(
|
|
58
|
+
libdragonInfo,
|
|
59
|
+
[
|
|
60
|
+
...dockerRelativeWorkdirParams(libdragonInfo),
|
|
61
|
+
...dockerHostUserParams(libdragonInfo),
|
|
62
|
+
],
|
|
63
|
+
[libdragonInfo.options.EXTRA_PARAMS[0], ...paramsWithConvertedPaths],
|
|
64
|
+
{
|
|
65
|
+
userCommand: true,
|
|
66
|
+
// Inherit stdin/out in tandem if we are going to disable TTY o/w the input
|
|
67
|
+
// stream remains inherited by the node process while the output pipe is
|
|
68
|
+
// waiting data from stdout and it behaves like we are still controlling
|
|
69
|
+
// the spawned process while the terminal is actually displaying say for
|
|
70
|
+
// example `less`.
|
|
71
|
+
inheritStdout: enableTTY,
|
|
72
|
+
inheritStdin: enableTTY,
|
|
73
|
+
// spawnProcess defaults does not apply to dockerExec so we need to
|
|
74
|
+
// provide these explicitly here.
|
|
75
|
+
inheritStderr: true,
|
|
76
|
+
...opts,
|
|
77
|
+
}
|
|
78
|
+
)
|
|
46
79
|
);
|
|
80
|
+
};
|
|
47
81
|
|
|
48
82
|
let started = false;
|
|
49
|
-
const startOnceAndCmd = async () => {
|
|
83
|
+
const startOnceAndCmd = async (stdin) => {
|
|
50
84
|
if (!started) {
|
|
51
|
-
const newId = await start(
|
|
85
|
+
const newId = await start(info);
|
|
86
|
+
const newInfo = {
|
|
87
|
+
...info,
|
|
88
|
+
containerId: newId,
|
|
89
|
+
};
|
|
52
90
|
started = true;
|
|
53
91
|
|
|
54
92
|
// Re-install vendors on new container if one was created upon start
|
|
55
93
|
// Ideally we would want the consumer to handle dependencies and rebuild
|
|
56
94
|
// libdragon if necessary. Currently this saves the day with a little bit
|
|
57
95
|
// extra waiting when the container is deleted.
|
|
58
|
-
if (
|
|
59
|
-
await installDependencies(
|
|
60
|
-
...libdragonInfo,
|
|
61
|
-
containerId: newId,
|
|
62
|
-
});
|
|
96
|
+
if (info.containerId !== newId) {
|
|
97
|
+
await installDependencies(newInfo);
|
|
63
98
|
}
|
|
64
|
-
await tryCmd({
|
|
65
|
-
...libdragonInfo,
|
|
66
|
-
containerId: newId,
|
|
67
|
-
});
|
|
99
|
+
await tryCmd(newInfo, { stdin });
|
|
68
100
|
return newId;
|
|
69
101
|
}
|
|
70
102
|
};
|
|
71
103
|
|
|
72
|
-
if (!
|
|
104
|
+
if (!info.containerId) {
|
|
73
105
|
log(`Container does not exist for sure, restart`, true);
|
|
74
106
|
await startOnceAndCmd();
|
|
75
|
-
return;
|
|
107
|
+
return info;
|
|
76
108
|
}
|
|
77
109
|
|
|
78
110
|
try {
|
|
79
|
-
|
|
111
|
+
// Start collecting stdin data on an auxiliary stream such that we can pipe
|
|
112
|
+
// it back to the container process if this fails the first time. Then the
|
|
113
|
+
// initial failed docker process would eat up the input stream. Here, we pass
|
|
114
|
+
// it to the target process eventually via startOnceAndCmd. If the input
|
|
115
|
+
// stream is from a TTY, spawnProcess will already inherit it. Listening
|
|
116
|
+
// to the stream here causes problems for unknown reasons.
|
|
117
|
+
!process.stdin.isTTY && process.stdin.pipe(stdin);
|
|
118
|
+
await tryCmd(info, {
|
|
119
|
+
// Disable the error tty to be able to read the error message in case
|
|
120
|
+
// the container is not running
|
|
121
|
+
inheritStderr: false,
|
|
122
|
+
// In the first run, pass the stdin to the process if it is not a TTY
|
|
123
|
+
// o/w we loose a user input unnecesarily somehow.
|
|
124
|
+
stdin: !process.stdin.isTTY && process.stdin,
|
|
125
|
+
});
|
|
80
126
|
} catch (e) {
|
|
81
127
|
if (
|
|
82
128
|
!e.out ||
|
|
83
129
|
// TODO: is there a better way?
|
|
84
|
-
!e.out.toString().includes(
|
|
130
|
+
!e.out.toString().includes(info.containerId)
|
|
85
131
|
) {
|
|
86
132
|
throw e;
|
|
87
133
|
}
|
|
88
|
-
await startOnceAndCmd();
|
|
134
|
+
await startOnceAndCmd(stdin);
|
|
89
135
|
}
|
|
136
|
+
return info;
|
|
90
137
|
};
|
|
91
138
|
|
|
92
139
|
module.exports = {
|
|
@@ -94,4 +141,15 @@ module.exports = {
|
|
|
94
141
|
fn: exec,
|
|
95
142
|
forwardsRestParams: true,
|
|
96
143
|
showStatus: true,
|
|
144
|
+
usage: {
|
|
145
|
+
name: 'exec <command>',
|
|
146
|
+
summary: 'Execute given command in the current directory.',
|
|
147
|
+
description: `Executes the given command in the container passing down any arguments provided. If you change your host working directory, the command will be executed in the corresponding folder in the container as well.
|
|
148
|
+
|
|
149
|
+
This action will first try to execute the command in the container and if the container is not accessible, it will attempt a complete \`start\` cycle.
|
|
150
|
+
|
|
151
|
+
This will properly passthrough your TTY if you have one. So by running \`libdragon exec bash\` you can start an interactive bash session with full TTY support.
|
|
152
|
+
|
|
153
|
+
Must be run in an initialized libdragon project.`,
|
|
154
|
+
},
|
|
97
155
|
};
|