libdragon 10.8.1 → 10.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -8
- package/index.js +22 -1
- package/modules/actions/destroy.js +7 -1
- package/modules/actions/disasm.js +1 -1
- package/modules/actions/docker-utils.js +0 -1
- package/modules/actions/exec.js +38 -19
- package/modules/actions/init.js +30 -22
- package/modules/actions/install.js +18 -15
- package/modules/actions/npm-utils.js +50 -48
- package/modules/actions/start.js +22 -1
- package/modules/actions/stop.js +8 -1
- package/modules/actions/update-and-start.js +8 -1
- package/modules/actions/utils.js +37 -4
- package/modules/constants.js +5 -0
- package/modules/helpers.js +96 -52
- package/modules/parameters.js +2 -2
- package/modules/project-info.js +42 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Docker Libdragon
|
|
2
2
|
|
|
3
|
-
[](https://github.com/anacierdem/libdragon-docker/actions/workflows/release.yml)
|
|
4
4
|
|
|
5
5
|
This is a wrapper for a docker container to make managing the libdragon toolchain easier. It has the additional advantage that libdragon toolchain and library can be installed on a per-project basis instead of managing system-wide installations.
|
|
6
6
|
|
|
@@ -184,14 +184,14 @@ For a quick development loop it really helps linking the code in this repository
|
|
|
184
184
|
|
|
185
185
|
npm link
|
|
186
186
|
|
|
187
|
-
in the root of the repository. Once you do this, running `libdragon` will use the code here rather than the actual npm installation. Then you can test your changes in the libdragon project here or elsewhere on your computer.
|
|
187
|
+
in the root of the repository. Once you do this, running `libdragon` will use the code here rather than the actual npm installation. Then you can test your changes in the libdragon project here or elsewhere on your computer. This setup is automatically done if you use the [devcontainer](#experimental-devcontainer-support).
|
|
188
188
|
|
|
189
189
|
When you are happy with your changes, you can verify you conform to the coding standards via:
|
|
190
190
|
|
|
191
191
|
npm run format-check
|
|
192
192
|
npm run lint-check
|
|
193
193
|
|
|
194
|
-
You can auto-fix applicable errors by running `format` and `lint` scripts instead. Additionally, typescript is used as the type system. To be able to get away with transpiling the code during development, jsDoc flavor of types are used instead of inline ones. To check
|
|
194
|
+
You can auto-fix applicable errors by running `format` and `lint` scripts instead. Additionally, typescript is used as the type system. To be able to get away with transpiling the code during development, jsDoc flavor of types are used instead of inline ones. To check your types, run:
|
|
195
195
|
|
|
196
196
|
npm run tsc
|
|
197
197
|
|
|
@@ -201,6 +201,29 @@ This repository uses [`semantic-release`](https://github.com/semantic-release/se
|
|
|
201
201
|
|
|
202
202
|
It will create a `semantic-release` compatible commit from your current staged changes.
|
|
203
203
|
|
|
204
|
+
### Experimental devcontainer support
|
|
205
|
+
|
|
206
|
+
The repository provides a configuration (in `.devcontainer`) so that IDEs that support it can create and run the Docker container for you. Then, you can start working on it as if you are working on a machine with libdragon installed.
|
|
207
|
+
|
|
208
|
+
With the provided setup, you can continue using the cli in the container and it will work for non-container specific actions like `install`, `disasm` etc. You don't have to use the cli in the container, but you can. In general it will be easier and faster to just run `make` in the container but this setup is included to ease developing the cli as well.
|
|
209
|
+
|
|
210
|
+
To create your own dev container backed project, you can use the contents of the `.devcontainer` folder as reference. You don't need to include nodejs or the cli and you can just run `build.sh` as `postCreateCommand`. See the `devcontainer.json` for more details. As long as your container have the `DOCKER_CONTAINER` environment variable, the tool can work inside a container.
|
|
211
|
+
|
|
212
|
+
#### Caveats
|
|
213
|
+
|
|
214
|
+
- In the devcontainer, uploading via USB will not work.
|
|
215
|
+
- Error matching is not yet tested.
|
|
216
|
+
- Ideally the necessary extensions should be automatically installed. This is not configured yet.
|
|
217
|
+
|
|
218
|
+
<details>
|
|
219
|
+
<summary>vscode instructions</summary>
|
|
220
|
+
|
|
221
|
+
- Make sure you have the [Dev container extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed and you fulfill its [requirements](https://code.visualstudio.com/docs/devcontainers/containers).
|
|
222
|
+
- Clone this repository with `--recurse-submodules` or run `git submodule update --init`.
|
|
223
|
+
- Open command palette and run `Dev Containers: Reopen in container`.
|
|
224
|
+
- It will prepare the container and open it in the editor.
|
|
225
|
+
</details>
|
|
226
|
+
|
|
204
227
|
## As an NPM dependency
|
|
205
228
|
|
|
206
229
|
You can install libdragon as an NPM dependency by `npm install libdragon --save` in order to use docker in your N64 projects. A `libdragon` command similar to global intallation is provided that can be used in your NPM scripts as follows;
|
|
@@ -242,11 +265,6 @@ will init the container for this project and run `make && make install` for `ed6
|
|
|
242
265
|
|
|
243
266
|
This is an experimental dependency management.
|
|
244
267
|
|
|
245
|
-
## TODOS
|
|
246
|
-
|
|
247
|
-
- [ ] Skip CI checks for irrelevant changes.
|
|
248
|
-
- [ ] Verify the NPM dependency mechanism is still working and add a test.
|
|
249
|
-
|
|
250
268
|
## Funding
|
|
251
269
|
|
|
252
270
|
If this tool helped you, consider supporting its development by sponsoring it!
|
package/index.js
CHANGED
|
@@ -18,9 +18,30 @@ const {
|
|
|
18
18
|
const { parseParameters } = require('./modules/parameters');
|
|
19
19
|
const { readProjectInfo, writeProjectInfo } = require('./modules/project-info');
|
|
20
20
|
|
|
21
|
+
// Note: it is not possible to merge these type definitions in a single comment
|
|
22
|
+
/**
|
|
23
|
+
* @template {any} [U=any]
|
|
24
|
+
* @typedef {(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never} UnionToIntersection
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* @template {any} [K=any]
|
|
28
|
+
* never does not break the call `info.options.CURRENT_ACTION.fn`
|
|
29
|
+
* @typedef {[K] extends [UnionToIntersection<K>] ? any : unknown} NoUnion
|
|
30
|
+
* @typedef {NoUnion<Exclude<Parameters<import('./modules/parameters').Actions[import('./modules/project-info').ActionsNoProject]['fn']>[0], undefined>>} EitherCLIOrLibdragonInfo
|
|
31
|
+
*/
|
|
32
|
+
|
|
21
33
|
parseParameters(process.argv)
|
|
22
34
|
.then(readProjectInfo)
|
|
23
|
-
.then((info) =>
|
|
35
|
+
.then((info) => {
|
|
36
|
+
return info.options.CURRENT_ACTION.fn(
|
|
37
|
+
/** @type {EitherCLIOrLibdragonInfo} */ (info)
|
|
38
|
+
);
|
|
39
|
+
// This type make sure a similar restriction to this code block is enforced
|
|
40
|
+
// without adding unnecessary javascript.
|
|
41
|
+
// return isProjectAction(info)
|
|
42
|
+
// ? info.options.CURRENT_ACTION.fn(info)
|
|
43
|
+
// : info.options.CURRENT_ACTION.fn(info);
|
|
44
|
+
})
|
|
24
45
|
.catch((e) => {
|
|
25
46
|
if (e instanceof ParameterError) {
|
|
26
47
|
log(chalk.red(e.message));
|
|
@@ -3,13 +3,19 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
const { destroyContainer } = require('./utils');
|
|
5
5
|
const { CONFIG_FILE, LIBDRAGON_PROJECT_MANIFEST } = require('../constants');
|
|
6
|
-
const { fileExists, dirExists, log } = require('../helpers');
|
|
6
|
+
const { fileExists, dirExists, log, ValidationError } = require('../helpers');
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
11
11
|
*/
|
|
12
12
|
const destroy = async (libdragonInfo) => {
|
|
13
|
+
if (process.env.DOCKER_CONTAINER) {
|
|
14
|
+
throw new ValidationError(
|
|
15
|
+
`Not possible to destroy the container from inside.`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
await destroyContainer(libdragonInfo);
|
|
14
20
|
|
|
15
21
|
const projectPath = path.join(libdragonInfo.root, LIBDRAGON_PROJECT_MANIFEST);
|
package/modules/actions/exec.js
CHANGED
|
@@ -8,6 +8,8 @@ const {
|
|
|
8
8
|
toPosixPath,
|
|
9
9
|
fileExists,
|
|
10
10
|
dirExists,
|
|
11
|
+
CommandError,
|
|
12
|
+
spawnProcess,
|
|
11
13
|
} = require('../helpers');
|
|
12
14
|
|
|
13
15
|
const { start } = require('./start');
|
|
@@ -16,7 +18,6 @@ const { installDependencies } = require('./utils');
|
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
19
|
-
* @returns
|
|
20
21
|
*/
|
|
21
22
|
function dockerRelativeWorkdir(libdragonInfo) {
|
|
22
23
|
return (
|
|
@@ -27,18 +28,14 @@ function dockerRelativeWorkdir(libdragonInfo) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
31
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
32
|
-
* @returns
|
|
33
32
|
*/
|
|
34
33
|
function dockerRelativeWorkdirParams(libdragonInfo) {
|
|
35
34
|
return ['--workdir', dockerRelativeWorkdir(libdragonInfo)];
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
|
-
*
|
|
40
38
|
* @param {import('../project-info').LibdragonInfo} info
|
|
41
|
-
* @returns
|
|
42
39
|
*/
|
|
43
40
|
const exec = async (info) => {
|
|
44
41
|
const parameters = info.options.EXTRA_PARAMS.slice(1);
|
|
@@ -49,20 +46,42 @@ const exec = async (info) => {
|
|
|
49
46
|
true
|
|
50
47
|
);
|
|
51
48
|
|
|
49
|
+
// Don't even bother here, we are already in a container.
|
|
50
|
+
if (process.env.DOCKER_CONTAINER) {
|
|
51
|
+
const enableTTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
52
|
+
await spawnProcess(info.options.EXTRA_PARAMS[0], parameters, {
|
|
53
|
+
userCommand: true,
|
|
54
|
+
// Inherit stdin/out in tandem if we are going to disable TTY o/w the input
|
|
55
|
+
// stream remains inherited by the node process while the output pipe is
|
|
56
|
+
// waiting data from stdout and it behaves like we are still controlling
|
|
57
|
+
// the spawned process while the terminal is actually displaying say for
|
|
58
|
+
// example `less`.
|
|
59
|
+
inheritStdout: enableTTY,
|
|
60
|
+
inheritStdin: enableTTY,
|
|
61
|
+
inheritStderr: true,
|
|
62
|
+
});
|
|
63
|
+
return info;
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
const stdin = new PassThrough();
|
|
53
67
|
|
|
54
68
|
/** @type {string[]} */
|
|
55
|
-
const paramsWithConvertedPaths =
|
|
56
|
-
|
|
69
|
+
const paramsWithConvertedPaths = await Promise.all(
|
|
70
|
+
parameters.map(async (item) => {
|
|
71
|
+
if (item.startsWith('-')) {
|
|
72
|
+
return item;
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
item.includes(path.sep) &&
|
|
76
|
+
((await fileExists(item)) || (await dirExists(item)))
|
|
77
|
+
) {
|
|
78
|
+
return toPosixPath(
|
|
79
|
+
path.isAbsolute(item) ? path.relative(process.cwd(), item) : item
|
|
80
|
+
);
|
|
81
|
+
}
|
|
57
82
|
return item;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return toPosixPath(
|
|
61
|
-
path.isAbsolute(item) ? path.relative(process.cwd(), item) : item
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
return item;
|
|
65
|
-
});
|
|
83
|
+
})
|
|
84
|
+
);
|
|
66
85
|
|
|
67
86
|
/**
|
|
68
87
|
*
|
|
@@ -101,9 +120,7 @@ const exec = async (info) => {
|
|
|
101
120
|
|
|
102
121
|
let started = false;
|
|
103
122
|
/**
|
|
104
|
-
*
|
|
105
123
|
* @param {import('fs').ReadStream=} stdin
|
|
106
|
-
* @returns
|
|
107
124
|
*/
|
|
108
125
|
const startOnceAndCmd = async (stdin) => {
|
|
109
126
|
if (!started) {
|
|
@@ -147,14 +164,16 @@ const exec = async (info) => {
|
|
|
147
164
|
// In the first run, pass the stdin to the process if it is not a TTY
|
|
148
165
|
// o/w we loose a user input unnecesarily somehow.
|
|
149
166
|
stdin:
|
|
150
|
-
!process.stdin.isTTY &&
|
|
167
|
+
(!process.stdin.isTTY || undefined) &&
|
|
151
168
|
/** @type {import('fs').ReadStream} */ (
|
|
152
169
|
/** @type {unknown} */ (process.stdin)
|
|
153
170
|
),
|
|
154
171
|
});
|
|
155
172
|
} catch (e) {
|
|
173
|
+
if (!(e instanceof CommandError)) {
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
156
176
|
if (
|
|
157
|
-
!e.out ||
|
|
158
177
|
// TODO: is there a better way?
|
|
159
178
|
!e.out.toString().includes(info.containerId)
|
|
160
179
|
) {
|
package/modules/actions/init.js
CHANGED
|
@@ -30,7 +30,7 @@ const { syncImageAndStart } = require('./update-and-start');
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* @param {import('../project-info').LibdragonInfo} info
|
|
33
|
-
* @
|
|
33
|
+
* @returns {Promise<"submodule" | "subtree" | undefined>}
|
|
34
34
|
*/
|
|
35
35
|
const autoDetect = async (info) => {
|
|
36
36
|
const vendorTarget = path.relative(
|
|
@@ -108,7 +108,11 @@ const autoVendor = async (info) => {
|
|
|
108
108
|
return info;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
// Container re-init breaks file modes assume there is git for this case.
|
|
112
|
+
// TODO: we should remove the unnecessary inits in the future.
|
|
113
|
+
if (!process.env.DOCKER_CONTAINER) {
|
|
114
|
+
await runGitMaybeHost(info, ['init']);
|
|
115
|
+
}
|
|
112
116
|
|
|
113
117
|
// TODO: TS thinks this is already defined
|
|
114
118
|
const detectedStrategy = await autoDetect(info);
|
|
@@ -188,6 +192,8 @@ const autoVendor = async (info) => {
|
|
|
188
192
|
]);
|
|
189
193
|
return info;
|
|
190
194
|
}
|
|
195
|
+
|
|
196
|
+
return info;
|
|
191
197
|
};
|
|
192
198
|
|
|
193
199
|
/**
|
|
@@ -218,31 +224,33 @@ async function init(info) {
|
|
|
218
224
|
LIBDRAGON_PROJECT_MANIFEST
|
|
219
225
|
)} exists. This is already a libdragon project, starting it...`
|
|
220
226
|
);
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
...info,
|
|
231
|
-
containerId: await start(info),
|
|
232
|
-
};
|
|
227
|
+
if (!process.env.DOCKER_CONTAINER) {
|
|
228
|
+
if (info.options.DOCKER_IMAGE) {
|
|
229
|
+
info = await syncImageAndStart(info);
|
|
230
|
+
} else {
|
|
231
|
+
info = {
|
|
232
|
+
...info,
|
|
233
|
+
containerId: await start(info),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
233
236
|
}
|
|
237
|
+
|
|
234
238
|
info = await autoVendor(info);
|
|
235
239
|
await installDependencies(info);
|
|
236
240
|
return info;
|
|
237
241
|
}
|
|
238
242
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
if (!process.env.DOCKER_CONTAINER) {
|
|
244
|
+
// Download image and start it
|
|
245
|
+
await updateImage(info, info.imageName);
|
|
246
|
+
info.containerId = await start(info);
|
|
247
|
+
// We have created a new container, save the new info ASAP
|
|
248
|
+
// When in a container, we should already have git
|
|
249
|
+
// Re-initing breaks file modes anyways
|
|
250
|
+
await initGitAndCacheContainerId(
|
|
251
|
+
/** @type Parameters<initGitAndCacheContainerId>[0] */ (info)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
246
254
|
|
|
247
255
|
info = await autoVendor(info);
|
|
248
256
|
|
|
@@ -270,7 +278,7 @@ module.exports = /** @type {const} */ ({
|
|
|
270
278
|
|
|
271
279
|
By default, a git repository and a submodule at \`./libdragon\` will be created to automatically update the vendored libdragon files on subsequent \`update\`s. If you intend to opt-out from this feature, see the \`--strategy manual\` flag to provide your self-managed libdragon copy. The default behaviour is intended for users who primarily want to consume libdragon as is.
|
|
272
280
|
|
|
273
|
-
If this is the first time you are creating a libdragon project at that location, this action will also create skeleton project files to kickstart things with the given image, if provided. For subsequent runs, it will act like \`start\` thus can be used to revive an existing project without modifying it.
|
|
281
|
+
If this is the first time you are creating a libdragon project at that location, this action will also create skeleton project files to kickstart things with the given image, if provided. For subsequent runs without any parameter, it will act like \`start\` thus can be used to revive an existing project without modifying it.
|
|
274
282
|
|
|
275
283
|
If you have an existing project with an already vendored submodule or subtree libdragon copy, \`init\` will automatically detect it at the provided \`--directory\`.`,
|
|
276
284
|
group: ['docker', 'vendoring'],
|
|
@@ -13,21 +13,24 @@ const { log } = require('../helpers');
|
|
|
13
13
|
*/
|
|
14
14
|
const install = async (libdragonInfo) => {
|
|
15
15
|
let updatedInfo = libdragonInfo;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
|
|
17
|
+
if (!process.env.DOCKER_CONTAINER) {
|
|
18
|
+
const imageName = libdragonInfo.options.DOCKER_IMAGE;
|
|
19
|
+
// If an image is provided, attempt to install
|
|
20
|
+
if (imageName) {
|
|
21
|
+
log(
|
|
22
|
+
chalk.yellow(
|
|
23
|
+
'Using `install` action to update the docker image is deprecated. Use the `update` action instead.'
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
updatedInfo = await syncImageAndStart(libdragonInfo);
|
|
27
|
+
} else {
|
|
28
|
+
// Make sure existing one is running
|
|
29
|
+
updatedInfo = {
|
|
30
|
+
...updatedInfo,
|
|
31
|
+
containerId: await start(libdragonInfo),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
// Re-install vendors on new image
|
|
@@ -55,59 +55,61 @@ const installNPMDependencies = async (libdragonInfo) => {
|
|
|
55
55
|
|
|
56
56
|
await Promise.all(
|
|
57
57
|
deps.map(({ name, paths }) => {
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
return /** @type Promise<void> */ (
|
|
59
|
+
new Promise((resolve, reject) => {
|
|
60
|
+
fsClassic.access(
|
|
61
|
+
path.join(paths[0], 'Makefile'),
|
|
62
|
+
fsClassic.constants.F_OK,
|
|
63
|
+
async (e) => {
|
|
64
|
+
if (e) {
|
|
65
|
+
// File does not exist - skip
|
|
66
|
+
resolve();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
if (paths.length > 1) {
|
|
71
|
+
reject(
|
|
72
|
+
new ValidationError(
|
|
73
|
+
`Using same dependency with different versions is not supported! ${name}`
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
try {
|
|
80
|
+
const relativePath = toPosixPath(
|
|
81
|
+
path.relative(libdragonInfo.root, paths[0])
|
|
82
|
+
);
|
|
83
|
+
const containerPath = path.posix.join(
|
|
84
|
+
CONTAINER_TARGET_PATH,
|
|
85
|
+
relativePath
|
|
86
|
+
);
|
|
87
|
+
const makePath = path.posix.join(containerPath, 'Makefile');
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
89
|
+
await dockerExec(
|
|
90
|
+
libdragonInfo,
|
|
91
|
+
[...dockerHostUserParams(libdragonInfo)],
|
|
92
|
+
[
|
|
93
|
+
'/bin/bash',
|
|
94
|
+
'-c',
|
|
95
|
+
'[ -f ' +
|
|
96
|
+
makePath +
|
|
97
|
+
' ] && make -C ' +
|
|
98
|
+
containerPath +
|
|
99
|
+
' && make -C ' +
|
|
100
|
+
containerPath +
|
|
101
|
+
' install',
|
|
102
|
+
]
|
|
103
|
+
);
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
resolve();
|
|
106
|
+
} catch (e) {
|
|
107
|
+
reject(e);
|
|
108
|
+
}
|
|
107
109
|
}
|
|
108
|
-
|
|
109
|
-
)
|
|
110
|
-
|
|
110
|
+
);
|
|
111
|
+
})
|
|
112
|
+
);
|
|
111
113
|
})
|
|
112
114
|
);
|
|
113
115
|
}
|
package/modules/actions/start.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
const chalk = require('chalk').stderr;
|
|
2
2
|
|
|
3
3
|
const { CONTAINER_TARGET_PATH } = require('../constants');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
spawnProcess,
|
|
6
|
+
log,
|
|
7
|
+
print,
|
|
8
|
+
dockerExec,
|
|
9
|
+
assert,
|
|
10
|
+
ValidationError,
|
|
11
|
+
} = require('../helpers');
|
|
5
12
|
|
|
6
13
|
const {
|
|
7
14
|
checkContainerAndClean,
|
|
@@ -14,6 +21,11 @@ const {
|
|
|
14
21
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
15
22
|
*/
|
|
16
23
|
const initContainer = async (libdragonInfo) => {
|
|
24
|
+
assert(
|
|
25
|
+
!process.env.DOCKER_CONTAINER,
|
|
26
|
+
new Error('initContainer does not make sense in a container')
|
|
27
|
+
);
|
|
28
|
+
|
|
17
29
|
let newId;
|
|
18
30
|
try {
|
|
19
31
|
log('Creating new container...');
|
|
@@ -79,6 +91,11 @@ const initContainer = async (libdragonInfo) => {
|
|
|
79
91
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
80
92
|
*/
|
|
81
93
|
const start = async (libdragonInfo) => {
|
|
94
|
+
assert(
|
|
95
|
+
!process.env.DOCKER_CONTAINER,
|
|
96
|
+
new Error('Cannot start a container when we are already in a container.')
|
|
97
|
+
);
|
|
98
|
+
|
|
82
99
|
const running =
|
|
83
100
|
libdragonInfo.containerId &&
|
|
84
101
|
(await checkContainerRunning(libdragonInfo.containerId));
|
|
@@ -108,6 +125,10 @@ module.exports = /** @type {const} */ ({
|
|
|
108
125
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
109
126
|
*/
|
|
110
127
|
fn: async (libdragonInfo) => {
|
|
128
|
+
if (process.env.DOCKER_CONTAINER) {
|
|
129
|
+
throw new ValidationError(`We are already in a container.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
111
132
|
const containerId = await start(libdragonInfo);
|
|
112
133
|
print(containerId);
|
|
113
134
|
return { ...libdragonInfo, containerId };
|
package/modules/actions/stop.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
const { spawnProcess } = require('../helpers');
|
|
1
|
+
const { spawnProcess, ValidationError } = require('../helpers');
|
|
2
2
|
|
|
3
3
|
const { checkContainerRunning } = require('./utils');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
7
|
+
* @returns {Promise<import('../project-info').LibdragonInfo | void>}
|
|
7
8
|
*/
|
|
8
9
|
const stop = async (libdragonInfo) => {
|
|
10
|
+
if (process.env.DOCKER_CONTAINER) {
|
|
11
|
+
throw new ValidationError(
|
|
12
|
+
`Not possible to stop the container from inside.`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
const running =
|
|
10
17
|
libdragonInfo.containerId &&
|
|
11
18
|
(await checkContainerRunning(libdragonInfo.containerId));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { log } = require('../helpers');
|
|
1
|
+
const { log, assert } = require('../helpers');
|
|
2
2
|
const { updateImage, destroyContainer } = require('./utils');
|
|
3
3
|
const { start } = require('./start');
|
|
4
4
|
|
|
@@ -6,6 +6,13 @@ const { start } = require('./start');
|
|
|
6
6
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
7
7
|
*/
|
|
8
8
|
async function syncImageAndStart(libdragonInfo) {
|
|
9
|
+
assert(
|
|
10
|
+
!process.env.DOCKER_CONTAINER,
|
|
11
|
+
new Error(
|
|
12
|
+
'[syncImageAndStart] We should already know we are in a container.'
|
|
13
|
+
)
|
|
14
|
+
);
|
|
15
|
+
|
|
9
16
|
const oldImageName = libdragonInfo.imageName;
|
|
10
17
|
const imageName = libdragonInfo.options.DOCKER_IMAGE ?? oldImageName;
|
|
11
18
|
// If an image is provided, always attempt to install it
|
package/modules/actions/utils.js
CHANGED
|
@@ -53,11 +53,16 @@ const installDependencies = async (libdragonInfo) => {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Downloads the given docker image. Returns false if the local image is the
|
|
56
|
-
* same,
|
|
56
|
+
* same, true otherwise.
|
|
57
57
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
58
58
|
* @param {string} newImageName
|
|
59
59
|
*/
|
|
60
60
|
const updateImage = async (libdragonInfo, newImageName) => {
|
|
61
|
+
assert(
|
|
62
|
+
!process.env.DOCKER_CONTAINER,
|
|
63
|
+
new Error('[updateImage] should not be called in a container')
|
|
64
|
+
);
|
|
65
|
+
|
|
61
66
|
// Will not take too much time if already have the same
|
|
62
67
|
const download = async () => {
|
|
63
68
|
log(`Downloading docker image: ${newImageName}`);
|
|
@@ -89,13 +94,18 @@ const updateImage = async (libdragonInfo, newImageName) => {
|
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
log(`Image is different: ${newImageName}`, true);
|
|
92
|
-
return
|
|
97
|
+
return true;
|
|
93
98
|
};
|
|
94
99
|
|
|
95
100
|
/**
|
|
96
101
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
97
102
|
*/
|
|
98
103
|
const destroyContainer = async (libdragonInfo) => {
|
|
104
|
+
assert(
|
|
105
|
+
!process.env.DOCKER_CONTAINER,
|
|
106
|
+
new Error('[destroyContainer] should not be called in a container')
|
|
107
|
+
);
|
|
108
|
+
|
|
99
109
|
if (libdragonInfo.containerId) {
|
|
100
110
|
await spawnProcess('docker', [
|
|
101
111
|
'container',
|
|
@@ -121,7 +131,6 @@ const destroyContainer = async (libdragonInfo) => {
|
|
|
121
131
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
122
132
|
* @param {string[]} params
|
|
123
133
|
* @param {import('../helpers').SpawnOptions} options
|
|
124
|
-
* @returns
|
|
125
134
|
*/
|
|
126
135
|
async function runGitMaybeHost(libdragonInfo, params, options = {}) {
|
|
127
136
|
assert(
|
|
@@ -145,6 +154,11 @@ async function runGitMaybeHost(libdragonInfo, params, options = {}) {
|
|
|
145
154
|
throw e;
|
|
146
155
|
}
|
|
147
156
|
|
|
157
|
+
assert(
|
|
158
|
+
!process.env.DOCKER_CONTAINER,
|
|
159
|
+
new Error('[runGitMaybeHost] Native git should exist in a container.')
|
|
160
|
+
);
|
|
161
|
+
|
|
148
162
|
return await dockerExec(
|
|
149
163
|
libdragonInfo,
|
|
150
164
|
// Use the host user when initializing git as we will need access
|
|
@@ -160,6 +174,13 @@ async function runGitMaybeHost(libdragonInfo, params, options = {}) {
|
|
|
160
174
|
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
161
175
|
*/
|
|
162
176
|
async function checkContainerAndClean(libdragonInfo) {
|
|
177
|
+
assert(
|
|
178
|
+
!process.env.DOCKER_CONTAINER,
|
|
179
|
+
new Error(
|
|
180
|
+
'[checkContainerAndClean] We should already know we are in a container.'
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
|
|
163
184
|
const id =
|
|
164
185
|
libdragonInfo.containerId &&
|
|
165
186
|
(
|
|
@@ -189,6 +210,13 @@ async function checkContainerAndClean(libdragonInfo) {
|
|
|
189
210
|
* @param {string} containerId
|
|
190
211
|
*/
|
|
191
212
|
async function checkContainerRunning(containerId) {
|
|
213
|
+
assert(
|
|
214
|
+
!process.env.DOCKER_CONTAINER,
|
|
215
|
+
new Error(
|
|
216
|
+
'[checkContainerRunning] We should already know we are in a container.'
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
|
|
192
220
|
const running = (
|
|
193
221
|
await spawnProcess('docker', [
|
|
194
222
|
'container',
|
|
@@ -201,14 +229,19 @@ async function checkContainerRunning(containerId) {
|
|
|
201
229
|
}
|
|
202
230
|
|
|
203
231
|
/**
|
|
204
|
-
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
232
|
+
* @param {import('../project-info').LibdragonInfo & {containerId: string}} libdragonInfo
|
|
205
233
|
*/
|
|
206
234
|
async function initGitAndCacheContainerId(libdragonInfo) {
|
|
235
|
+
if (!libdragonInfo.containerId) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
207
239
|
// If there is managed vendoring, make sure we have a git repo. `git init` is
|
|
208
240
|
// safe anyways...
|
|
209
241
|
if (libdragonInfo.vendorStrategy !== 'manual') {
|
|
210
242
|
await runGitMaybeHost(libdragonInfo, ['init']);
|
|
211
243
|
}
|
|
244
|
+
|
|
212
245
|
const gitFolder = path.join(libdragonInfo.root, '.git');
|
|
213
246
|
if (await dirExists(gitFolder)) {
|
|
214
247
|
await fs.writeFile(
|
package/modules/constants.js
CHANGED
|
@@ -11,6 +11,11 @@ module.exports = /** @type {const} */ ({
|
|
|
11
11
|
|
|
12
12
|
ACCEPTED_STRATEGIES: ['submodule', 'subtree', 'manual'],
|
|
13
13
|
|
|
14
|
+
// These do not need a project to exist and their actions do not need the whole
|
|
15
|
+
// structure. Actions that need the full project information should not be
|
|
16
|
+
// listed here.
|
|
17
|
+
NO_PROJECT_ACTIONS: ['help', 'version'],
|
|
18
|
+
|
|
14
19
|
// cli exit codes
|
|
15
20
|
STATUS_OK: 0,
|
|
16
21
|
STATUS_ERROR: 1,
|
package/modules/helpers.js
CHANGED
|
@@ -5,6 +5,7 @@ const chalk = require('chalk').stderr;
|
|
|
5
5
|
const { spawn } = require('child_process');
|
|
6
6
|
|
|
7
7
|
const { globals } = require('./globals');
|
|
8
|
+
const { NO_PROJECT_ACTIONS } = require('./constants');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* A structure to keep additional error information
|
|
@@ -156,6 +157,10 @@ function spawnProcess(
|
|
|
156
157
|
spawnOptions: {},
|
|
157
158
|
}
|
|
158
159
|
) {
|
|
160
|
+
assert(
|
|
161
|
+
cmd !== 'docker' || !process.env.DOCKER_CONTAINER,
|
|
162
|
+
new Error('Trying to invoke docker inside a container.')
|
|
163
|
+
);
|
|
159
164
|
return new Promise((resolve, reject) => {
|
|
160
165
|
/** @type {Buffer[]} */
|
|
161
166
|
const stdout = [];
|
|
@@ -197,26 +202,26 @@ function spawnProcess(
|
|
|
197
202
|
resolve('');
|
|
198
203
|
};
|
|
199
204
|
|
|
200
|
-
if (!enableInTTY && stdin) {
|
|
205
|
+
if (!enableInTTY && stdin && command.stdin) {
|
|
201
206
|
stdin.pipe(command.stdin);
|
|
202
207
|
}
|
|
203
208
|
|
|
204
|
-
if (!enableOutTTY && (globals.verbose || userCommand)) {
|
|
209
|
+
if (!enableOutTTY && (globals.verbose || userCommand) && command.stdout) {
|
|
205
210
|
command.stdout.pipe(process.stdout);
|
|
206
211
|
process.stdout.once('error', eatEpipe);
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
if (!inheritStdout) {
|
|
214
|
+
if (!inheritStdout && command.stdout) {
|
|
210
215
|
command.stdout.on('data', function (data) {
|
|
211
216
|
stdout.push(Buffer.from(data));
|
|
212
217
|
});
|
|
213
218
|
}
|
|
214
219
|
|
|
215
|
-
if (!enableErrorTTY && (globals.verbose || userCommand)) {
|
|
220
|
+
if (!enableErrorTTY && (globals.verbose || userCommand) && command.stderr) {
|
|
216
221
|
command.stderr.pipe(process.stderr);
|
|
217
222
|
}
|
|
218
223
|
|
|
219
|
-
if (!inheritStderr) {
|
|
224
|
+
if (!inheritStderr && command.stderr) {
|
|
220
225
|
command.stderr.on('data', function (data) {
|
|
221
226
|
stderr.push(Buffer.from(data));
|
|
222
227
|
});
|
|
@@ -264,58 +269,83 @@ function spawnProcess(
|
|
|
264
269
|
* (libdragonInfo: import('./project-info').LibdragonInfo, cmdWithParams: string[], options?: SpawnOptions, unused?: unknown): Promise<string>;
|
|
265
270
|
* }} DockerExec
|
|
266
271
|
*/
|
|
267
|
-
const dockerExec = /** @type {DockerExec} */
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
const dockerExec = /** @type {DockerExec} */ (
|
|
273
|
+
function (
|
|
274
|
+
libdragonInfo,
|
|
275
|
+
dockerParams,
|
|
276
|
+
cmdWithParams,
|
|
277
|
+
/** @type {SpawnOptions | undefined} */
|
|
278
|
+
options
|
|
279
|
+
) {
|
|
280
|
+
// TODO: assert for invalid args
|
|
281
|
+
const haveDockerParams =
|
|
282
|
+
Array.isArray(dockerParams) && Array.isArray(cmdWithParams);
|
|
278
283
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
284
|
+
if (!haveDockerParams) {
|
|
285
|
+
options = /** @type {SpawnOptions} */ (cmdWithParams);
|
|
286
|
+
}
|
|
282
287
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
288
|
+
const finalCmdWithParams = haveDockerParams ? cmdWithParams : dockerParams;
|
|
289
|
+
const finalDockerParams = haveDockerParams ? dockerParams : [];
|
|
286
290
|
|
|
287
|
-
|
|
291
|
+
assert(
|
|
292
|
+
finalDockerParams.findIndex(
|
|
293
|
+
(val) => val === '--workdir=' || val === '-w='
|
|
294
|
+
) === -1,
|
|
295
|
+
new Error('Do not use `=` syntax when setting working dir')
|
|
296
|
+
);
|
|
288
297
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
298
|
+
// Convert docker execs into regular commands in the correct cwd
|
|
299
|
+
if (process.env.DOCKER_CONTAINER) {
|
|
300
|
+
const workDirIndex = finalDockerParams.findIndex(
|
|
301
|
+
(val) => val === '--workdir' || val === '-w'
|
|
302
|
+
);
|
|
303
|
+
const workDir =
|
|
304
|
+
workDirIndex >= 0 ? finalDockerParams[workDirIndex + 1] : undefined;
|
|
305
|
+
return spawnProcess(finalCmdWithParams[0], finalCmdWithParams.slice(1), {
|
|
306
|
+
...options,
|
|
307
|
+
spawnOptions: { cwd: workDir, ...options?.spawnOptions },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
296
310
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
311
|
+
assert(
|
|
312
|
+
!!libdragonInfo.containerId,
|
|
313
|
+
new Error('Trying to invoke dockerExec without a containerId.')
|
|
314
|
+
);
|
|
300
315
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
/** @type string[] */
|
|
317
|
+
const additionalParams = [];
|
|
318
|
+
|
|
319
|
+
// Docker TTY wants in & out streams both to be a TTY
|
|
320
|
+
// If no options are provided, disable TTY as spawnProcess defaults to no
|
|
321
|
+
// inherit as well.
|
|
322
|
+
const enableTTY = options
|
|
323
|
+
? options.inheritStdout && options.inheritStdin
|
|
324
|
+
: false;
|
|
325
|
+
const ttyEnabled = enableTTY && process.stdout.isTTY && process.stdin.isTTY;
|
|
326
|
+
|
|
327
|
+
if (ttyEnabled) {
|
|
328
|
+
additionalParams.push('-t');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Always enable stdin, also see; https://github.com/anacierdem/libdragon-docker/issues/45
|
|
332
|
+
// Currently we run all exec commands in stdin mode even if the actual process
|
|
333
|
+
// does not need any input. This will eat any user input by default.
|
|
334
|
+
additionalParams.push('-i');
|
|
335
|
+
|
|
336
|
+
return spawnProcess(
|
|
337
|
+
'docker',
|
|
338
|
+
[
|
|
339
|
+
'exec',
|
|
340
|
+
...finalDockerParams,
|
|
341
|
+
...additionalParams,
|
|
342
|
+
libdragonInfo.containerId,
|
|
343
|
+
...finalCmdWithParams,
|
|
344
|
+
],
|
|
345
|
+
options
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
);
|
|
319
349
|
|
|
320
350
|
/**
|
|
321
351
|
* Recursively copies directories and files
|
|
@@ -386,8 +416,9 @@ function toNativePath(p) {
|
|
|
386
416
|
}
|
|
387
417
|
|
|
388
418
|
/**
|
|
389
|
-
* @param {
|
|
419
|
+
* @param {any} condition
|
|
390
420
|
* @param {Error} error
|
|
421
|
+
* @returns {asserts condition}
|
|
391
422
|
*/
|
|
392
423
|
function assert(condition, error) {
|
|
393
424
|
if (!condition) {
|
|
@@ -422,6 +453,18 @@ function log(text, verboseOnly = false) {
|
|
|
422
453
|
}
|
|
423
454
|
}
|
|
424
455
|
|
|
456
|
+
/**
|
|
457
|
+
* @param {import('./project-info').CLIInfo | import('./project-info').LibdragonInfo} info
|
|
458
|
+
* @returns {info is import('./project-info').LibdragonInfo}
|
|
459
|
+
*/
|
|
460
|
+
function isProjectAction(info) {
|
|
461
|
+
return !NO_PROJECT_ACTIONS.includes(
|
|
462
|
+
/** @type {import('./project-info').ActionsNoProject} */ (
|
|
463
|
+
info.options.CURRENT_ACTION.name
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
425
468
|
module.exports = {
|
|
426
469
|
spawnProcess,
|
|
427
470
|
toPosixPath,
|
|
@@ -436,4 +479,5 @@ module.exports = {
|
|
|
436
479
|
CommandError,
|
|
437
480
|
ParameterError,
|
|
438
481
|
ValidationError,
|
|
482
|
+
isProjectAction,
|
|
439
483
|
};
|
package/modules/parameters.js
CHANGED
|
@@ -26,7 +26,7 @@ const { globals } = require('./globals');
|
|
|
26
26
|
* @param {string[]} argv
|
|
27
27
|
*/
|
|
28
28
|
const parseParameters = async (argv) => {
|
|
29
|
-
/** @type CommandlineOptions */
|
|
29
|
+
/** @type Partial<CommandlineOptions> & {EXTRA_PARAMS: string[] } */
|
|
30
30
|
const options = {
|
|
31
31
|
EXTRA_PARAMS: [],
|
|
32
32
|
CURRENT_ACTION: undefined,
|
|
@@ -143,7 +143,7 @@ const parseParameters = async (argv) => {
|
|
|
143
143
|
process.exit(STATUS_BAD_PARAM);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
return { options };
|
|
146
|
+
return { options: /** @type CommandlineOptions */ (options) };
|
|
147
147
|
};
|
|
148
148
|
|
|
149
149
|
module.exports = {
|
package/modules/project-info.js
CHANGED
|
@@ -27,39 +27,48 @@ const {
|
|
|
27
27
|
toPosixPath,
|
|
28
28
|
assert,
|
|
29
29
|
ParameterError,
|
|
30
|
+
isProjectAction,
|
|
30
31
|
} = require('./helpers');
|
|
31
32
|
|
|
32
|
-
// These do not need a project to exist.
|
|
33
|
-
const NO_PROJECT_ACTIONS = /** @type {const} */ (['help', 'version']);
|
|
34
|
-
|
|
35
33
|
/**
|
|
36
|
-
* @typedef { typeof NO_PROJECT_ACTIONS[number] } ActionsNoProject
|
|
34
|
+
* @typedef { typeof import('./constants').NO_PROJECT_ACTIONS[number] } ActionsNoProject
|
|
37
35
|
* @typedef { Exclude<keyof import('./parameters').Actions, ActionsNoProject> } ActionsWithProject
|
|
38
36
|
* @typedef { import('./parameters').CommandlineOptions<ActionsNoProject> } ActionsNoProjectOptions
|
|
39
37
|
* @typedef { import('./parameters').CommandlineOptions<ActionsWithProject> } ActionsWithProjectOptions
|
|
40
38
|
*
|
|
39
|
+
* This is all the potential CLI combinations
|
|
41
40
|
* @typedef { {
|
|
42
|
-
* options:
|
|
41
|
+
* options: import('./parameters').CommandlineOptions
|
|
43
42
|
* } } CLIInfo
|
|
44
43
|
*
|
|
44
|
+
* Then readProjectInfo creates two possible set of outputs. One is for actions
|
|
45
|
+
* that don't need a project and one with project. This setup forces the actions
|
|
46
|
+
* to not use detailed information if they are listed in NO_PROJECT_ACTIONS
|
|
47
|
+
* @typedef { {
|
|
48
|
+
* options: ActionsNoProjectOptions
|
|
49
|
+
* } } NoProjectInfo
|
|
50
|
+
*
|
|
45
51
|
* @typedef { {
|
|
52
|
+
* options: ActionsWithProjectOptions
|
|
46
53
|
* root: string;
|
|
47
54
|
* userInfo: os.UserInfo<string>;
|
|
48
55
|
* haveProjectConfig: boolean;
|
|
49
56
|
* imageName: string;
|
|
50
57
|
* vendorDirectory: string;
|
|
51
58
|
* vendorStrategy: import('./parameters').VendorStrategy;
|
|
52
|
-
* containerId
|
|
53
|
-
* } }
|
|
54
|
-
*
|
|
55
|
-
* @typedef { CLIInfo & Partial<ExtendedInfo> } LibdragonInfo
|
|
59
|
+
* containerId?: string
|
|
60
|
+
* } } LibdragonInfo
|
|
56
61
|
*/
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
64
|
* @param {LibdragonInfo} libdragonInfo
|
|
60
|
-
* @returns string
|
|
61
65
|
*/
|
|
62
66
|
async function findContainerId(libdragonInfo) {
|
|
67
|
+
assert(
|
|
68
|
+
!process.env.DOCKER_CONTAINER,
|
|
69
|
+
new Error('[findContainerId] We should already know we are in a container.')
|
|
70
|
+
);
|
|
71
|
+
|
|
63
72
|
const idFile = path.join(libdragonInfo.root, '.git', CACHED_CONTAINER_FILE);
|
|
64
73
|
if (await fileExists(idFile)) {
|
|
65
74
|
const id = (await fs.readFile(idFile, { encoding: 'utf8' })).trim();
|
|
@@ -109,7 +118,7 @@ async function findContainerId(libdragonInfo) {
|
|
|
109
118
|
/**
|
|
110
119
|
* @param {string} start
|
|
111
120
|
* @param {string} relativeFile
|
|
112
|
-
* @
|
|
121
|
+
* @returns {Promise<string | undefined>}
|
|
113
122
|
*/
|
|
114
123
|
async function findLibdragonRoot(
|
|
115
124
|
start = '.',
|
|
@@ -139,18 +148,15 @@ async function findGitRoot() {
|
|
|
139
148
|
}
|
|
140
149
|
|
|
141
150
|
/**
|
|
142
|
-
* @param {
|
|
151
|
+
* @param {CLIInfo} optionInfo
|
|
152
|
+
* @returns {Promise<LibdragonInfo | NoProjectInfo>}
|
|
143
153
|
*/
|
|
144
154
|
const readProjectInfo = async function (optionInfo) {
|
|
145
155
|
// No need to do anything here if the action does not depend on the project
|
|
146
156
|
// The only exception is the init and destroy actions, which do not need an
|
|
147
157
|
// existing project but readProjectInfo must always run to analyze the situation
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
/** @type {ActionsNoProject} */ (optionInfo.options.CURRENT_ACTION.name)
|
|
151
|
-
)
|
|
152
|
-
) {
|
|
153
|
-
return /** @type {CLIInfo} */ (optionInfo);
|
|
158
|
+
if (!isProjectAction(optionInfo)) {
|
|
159
|
+
return /** @type {NoProjectInfo} */ (optionInfo);
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
const migratedRoot = await findLibdragonRoot();
|
|
@@ -173,10 +179,16 @@ const readProjectInfo = async function (optionInfo) {
|
|
|
173
179
|
);
|
|
174
180
|
}
|
|
175
181
|
|
|
182
|
+
const foundRoot =
|
|
183
|
+
projectRoot ?? (await findNPMRoot()) ?? (await findGitRoot());
|
|
184
|
+
if (!foundRoot) {
|
|
185
|
+
log('Could not find project root, set as cwd.', true);
|
|
186
|
+
}
|
|
187
|
+
|
|
176
188
|
/** @type {LibdragonInfo} */
|
|
177
189
|
let info = {
|
|
178
190
|
...optionInfo,
|
|
179
|
-
root:
|
|
191
|
+
root: foundRoot ?? process.cwd(),
|
|
180
192
|
userInfo: os.userInfo(),
|
|
181
193
|
|
|
182
194
|
// Use this to discriminate if there is a project when the command is run
|
|
@@ -190,11 +202,6 @@ const readProjectInfo = async function (optionInfo) {
|
|
|
190
202
|
vendorStrategy: DEFAULT_STRATEGY,
|
|
191
203
|
};
|
|
192
204
|
|
|
193
|
-
if (!info.root) {
|
|
194
|
-
log('Could not find project root, set as cwd.', true);
|
|
195
|
-
info.root = process.cwd();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
205
|
log(`Project root: ${info.root}`, true);
|
|
199
206
|
|
|
200
207
|
if (migratedRoot) {
|
|
@@ -223,15 +230,23 @@ const readProjectInfo = async function (optionInfo) {
|
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
|
|
226
|
-
|
|
227
|
-
|
|
233
|
+
if (!process.env.DOCKER_CONTAINER) {
|
|
234
|
+
info.containerId = await findContainerId(info);
|
|
235
|
+
log(`Active container id: ${info.containerId}`, true);
|
|
236
|
+
}
|
|
228
237
|
|
|
229
238
|
// For imageName, flag has the highest priority followed by the one read from
|
|
230
239
|
// the file and then if there is any matching container, name is read from it.
|
|
231
240
|
// As last option fallback to default value.
|
|
232
241
|
|
|
233
242
|
// If still have the container, read the image name from it
|
|
234
|
-
if
|
|
243
|
+
// No need to do anything if we are in a container
|
|
244
|
+
if (
|
|
245
|
+
!process.env.DOCKER_CONTAINER &&
|
|
246
|
+
!info.imageName &&
|
|
247
|
+
info.containerId &&
|
|
248
|
+
(await checkContainerAndClean(info))
|
|
249
|
+
) {
|
|
235
250
|
info.imageName = (
|
|
236
251
|
await spawnProcess('docker', [
|
|
237
252
|
'container',
|