libdragon 10.3.1 → 10.4.2
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 +67 -1
- package/README.md +2 -12
- package/index.js +55 -6
- package/modules/actions/exec.js +97 -0
- package/modules/actions/help.js +163 -0
- package/modules/actions/index.js +25 -0
- package/modules/actions/init.js +201 -0
- package/modules/actions/install.js +48 -0
- package/modules/actions/start.js +114 -0
- package/modules/actions/stop.js +21 -0
- package/modules/actions/update-and-start.js +33 -0
- package/modules/actions/update.js +46 -0
- package/modules/actions/utils.js +327 -0
- package/modules/constants.js +10 -4
- package/modules/globals.js +5 -0
- package/modules/helpers.js +93 -276
- package/modules/project-info.js +214 -0
- package/package.json +4 -4
- package/modules/actions.js +0 -611
- package/modules/usage.js +0 -129
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
const fs = require('fs/promises');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
const { fn: install } = require('./install');
|
|
7
|
+
const { start } = require('./start');
|
|
8
|
+
const {
|
|
9
|
+
installDependencies,
|
|
10
|
+
tryCacheContainerId,
|
|
11
|
+
updateImage,
|
|
12
|
+
runGitMaybeHost,
|
|
13
|
+
} = require('./utils');
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
LIBDRAGON_PROJECT_MANIFEST,
|
|
17
|
+
LIBDRAGON_SUBMODULE,
|
|
18
|
+
LIBDRAGON_BRANCH,
|
|
19
|
+
LIBDRAGON_GIT,
|
|
20
|
+
} = require('../constants');
|
|
21
|
+
const {
|
|
22
|
+
log,
|
|
23
|
+
copyDirContents,
|
|
24
|
+
CommandError,
|
|
25
|
+
ParameterError,
|
|
26
|
+
ValidationError,
|
|
27
|
+
} = require('../helpers');
|
|
28
|
+
const { setProjectInfoToSave } = require('../project-info');
|
|
29
|
+
|
|
30
|
+
const autoVendor = async (libdragonInfo) => {
|
|
31
|
+
await runGitMaybeHost(libdragonInfo, ['init']);
|
|
32
|
+
|
|
33
|
+
if (libdragonInfo.vendorStrategy === 'submodule') {
|
|
34
|
+
await runGitMaybeHost(libdragonInfo, [
|
|
35
|
+
'submodule',
|
|
36
|
+
'add',
|
|
37
|
+
'--force',
|
|
38
|
+
'--name',
|
|
39
|
+
LIBDRAGON_SUBMODULE,
|
|
40
|
+
'--branch',
|
|
41
|
+
LIBDRAGON_BRANCH,
|
|
42
|
+
LIBDRAGON_GIT,
|
|
43
|
+
libdragonInfo.vendorDirectory,
|
|
44
|
+
]);
|
|
45
|
+
} else if (libdragonInfo.vendorStrategy === 'subtree') {
|
|
46
|
+
// Create a commit if it does not exist. This is required for subtree.
|
|
47
|
+
try {
|
|
48
|
+
await runGitMaybeHost(libdragonInfo, ['rev-parse', 'HEAD']);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (!(e instanceof CommandError)) throw e;
|
|
51
|
+
|
|
52
|
+
// This will throw if git user name/email is not set up. Let's not assume
|
|
53
|
+
// anything for now. This means subtree is not supported for someone without
|
|
54
|
+
// git on the host machine.
|
|
55
|
+
await runGitMaybeHost(libdragonInfo, [
|
|
56
|
+
'commit',
|
|
57
|
+
'--allow-empty',
|
|
58
|
+
'-n',
|
|
59
|
+
'-m',
|
|
60
|
+
'Initial commit.',
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await runGitMaybeHost(libdragonInfo, [
|
|
65
|
+
'subtree',
|
|
66
|
+
'add',
|
|
67
|
+
'--prefix',
|
|
68
|
+
path.relative(libdragonInfo.root, libdragonInfo.vendorDirectory),
|
|
69
|
+
LIBDRAGON_GIT,
|
|
70
|
+
LIBDRAGON_BRANCH,
|
|
71
|
+
'--squash',
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return libdragonInfo;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Initialize a new libdragon project in current working directory
|
|
80
|
+
* Also downloads the image
|
|
81
|
+
*/
|
|
82
|
+
async function init(libdragonInfo) {
|
|
83
|
+
log(`Initializing a libdragon project at ${libdragonInfo.root}`);
|
|
84
|
+
|
|
85
|
+
let newInfo = libdragonInfo;
|
|
86
|
+
|
|
87
|
+
// Validate manifest
|
|
88
|
+
const manifestPath = path.join(newInfo.root, LIBDRAGON_PROJECT_MANIFEST);
|
|
89
|
+
const manifestStats = await fs.stat(manifestPath).catch((e) => {
|
|
90
|
+
if (e.code !== 'ENOENT') throw e;
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (manifestStats && !manifestStats.isDirectory()) {
|
|
95
|
+
throw new ValidationError(
|
|
96
|
+
'There is already a `.libdragon` file and it is not a directory.'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Validate vendoring strategy. Do not allow a switch after successful initialization
|
|
101
|
+
if (
|
|
102
|
+
newInfo.haveProjectConfig &&
|
|
103
|
+
newInfo.options.VENDOR_STRAT &&
|
|
104
|
+
newInfo.options.VENDOR_STRAT !== 'manual' &&
|
|
105
|
+
newInfo.vendorStrategy !== newInfo.options.VENDOR_STRAT
|
|
106
|
+
) {
|
|
107
|
+
throw new ParameterError(
|
|
108
|
+
`Requested strategy switch: ${newInfo.vendorStrategy} -> ${newInfo.options.VENDOR_STRAT} It is not possible to switch vendoring strategy after initializing a project. You can always switch to manual and handle libdragon yourself.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Update the strategy information for the project if the flag is provided
|
|
113
|
+
if (newInfo.options.VENDOR_STRAT) {
|
|
114
|
+
newInfo = setProjectInfoToSave({
|
|
115
|
+
...newInfo,
|
|
116
|
+
vendorStrategy: newInfo.options.VENDOR_STRAT,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Update the directory information for the project if the flag is provided
|
|
121
|
+
if (newInfo.options.VENDOR_DIR) {
|
|
122
|
+
// Validate vendoring path
|
|
123
|
+
if (path.isAbsolute(newInfo.options.VENDOR_DIR)) {
|
|
124
|
+
throw new ParameterError(
|
|
125
|
+
'`--directory` must be project-relative as it will be mounted to the docker container.'
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
newInfo = setProjectInfoToSave({
|
|
130
|
+
...newInfo,
|
|
131
|
+
vendorDirectory: newInfo.options.VENDOR_DIR,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (newInfo.haveProjectConfig) {
|
|
136
|
+
log(
|
|
137
|
+
`${path.join(
|
|
138
|
+
newInfo.root,
|
|
139
|
+
LIBDRAGON_PROJECT_MANIFEST
|
|
140
|
+
)} exists. This is already a libdragon project, starting it...`
|
|
141
|
+
);
|
|
142
|
+
if (newInfo.options.DOCKER_IMAGE) {
|
|
143
|
+
log(
|
|
144
|
+
`Not changing docker image. Use the install action if you want to override the image.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
// TODO: we may make sure git and submodule is initialized here
|
|
148
|
+
await install(newInfo);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
newInfo.imageName =
|
|
153
|
+
(await updateImage(newInfo, newInfo.imageName)) || newInfo.imageName;
|
|
154
|
+
// Download image and start it
|
|
155
|
+
const containerReadyPromise = start(newInfo, true).then((newId) => ({
|
|
156
|
+
...newInfo,
|
|
157
|
+
containerId: newId,
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
let vendorAndGitReadyPromise = containerReadyPromise;
|
|
161
|
+
if (newInfo.vendorStrategy !== 'manual') {
|
|
162
|
+
const vendorTarget = path.relative(newInfo.root, newInfo.vendorDirectory);
|
|
163
|
+
const [vendorTargetExists] = await Promise.all([
|
|
164
|
+
fs.stat(vendorTarget).catch((e) => {
|
|
165
|
+
if (e.code !== 'ENOENT') throw e;
|
|
166
|
+
return false;
|
|
167
|
+
}),
|
|
168
|
+
containerReadyPromise,
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
if (vendorTargetExists) {
|
|
172
|
+
throw new ValidationError(
|
|
173
|
+
`${path.resolve(
|
|
174
|
+
newInfo.root,
|
|
175
|
+
newInfo.vendorDirectory
|
|
176
|
+
)} already exists. That is the libdragon vendoring target, please remove and retry.`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
vendorAndGitReadyPromise = containerReadyPromise.then(autoVendor);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
log(`Preparing project files...`);
|
|
184
|
+
const skeletonFolder = path.join(__dirname, '../../skeleton');
|
|
185
|
+
|
|
186
|
+
await Promise.all([
|
|
187
|
+
// We have created a new container, save the new info
|
|
188
|
+
vendorAndGitReadyPromise.then(tryCacheContainerId),
|
|
189
|
+
vendorAndGitReadyPromise.then(installDependencies),
|
|
190
|
+
// node copy functions does not work with pkg
|
|
191
|
+
copyDirContents(skeletonFolder, newInfo.root),
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
log(chalk.green(`libdragon ready at \`${newInfo.root}\`.`));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
name: 'init',
|
|
199
|
+
fn: init,
|
|
200
|
+
showStatus: true,
|
|
201
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const { installDependencies, mustHaveProject } = require('./utils');
|
|
4
|
+
const { start } = require('./start');
|
|
5
|
+
const { updateAndStart } = require('./update-and-start');
|
|
6
|
+
const { log } = require('../helpers');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Updates the image if flag is provided and install vendors onto the container.
|
|
10
|
+
* We should probably remove the image installation responsibility from this
|
|
11
|
+
* action but it might be a breaking change.
|
|
12
|
+
* @param libdragonInfo
|
|
13
|
+
* @param skipUpdate This is added to skip the update when calling this from
|
|
14
|
+
* the update action as it already does an update itself. install doing an image
|
|
15
|
+
* update is pretty much a useless operation, but let's keep it in case someone
|
|
16
|
+
* depends on it. It used to only update the image if the flag is provided and
|
|
17
|
+
* we still keep that logic but with a deprecation warning.
|
|
18
|
+
*/
|
|
19
|
+
const install = async (libdragonInfo, skipUpdate) => {
|
|
20
|
+
await mustHaveProject(libdragonInfo);
|
|
21
|
+
let updatedInfo = libdragonInfo;
|
|
22
|
+
const imageName = libdragonInfo.options.DOCKER_IMAGE;
|
|
23
|
+
// If an image is provided, attempt to install
|
|
24
|
+
if (imageName && skipUpdate !== true) {
|
|
25
|
+
log(
|
|
26
|
+
chalk.yellow(
|
|
27
|
+
'Using `install` action to update the docker image is deprecated. Use the `update` action instead.'
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
updatedInfo = await updateAndStart(libdragonInfo);
|
|
31
|
+
} else {
|
|
32
|
+
// Make sure existing one is running
|
|
33
|
+
updatedInfo = {
|
|
34
|
+
...updatedInfo,
|
|
35
|
+
containerId: await start(libdragonInfo),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Re-install vendors on new image
|
|
40
|
+
// TODO: skip this if unnecessary
|
|
41
|
+
await installDependencies(updatedInfo);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
name: 'install',
|
|
46
|
+
fn: install,
|
|
47
|
+
showStatus: true,
|
|
48
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const { CONTAINER_TARGET_PATH } = require('../constants');
|
|
4
|
+
const { spawnProcess, log, dockerExec } = require('../helpers');
|
|
5
|
+
const { setProjectInfoToSave } = require('../project-info');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
checkContainerAndClean,
|
|
9
|
+
checkContainerRunning,
|
|
10
|
+
destroyContainer,
|
|
11
|
+
mustHaveProject,
|
|
12
|
+
} = require('./utils');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a new container
|
|
16
|
+
*/
|
|
17
|
+
const initContainer = async (libdragonInfo) => {
|
|
18
|
+
let newId;
|
|
19
|
+
try {
|
|
20
|
+
// Create a new container
|
|
21
|
+
libdragonInfo.showStatus && log('Creating new container...');
|
|
22
|
+
newId = (
|
|
23
|
+
await spawnProcess('docker', [
|
|
24
|
+
'run',
|
|
25
|
+
'-d', // Detached
|
|
26
|
+
'--mount',
|
|
27
|
+
'type=bind,source=' +
|
|
28
|
+
libdragonInfo.root +
|
|
29
|
+
',target=' +
|
|
30
|
+
CONTAINER_TARGET_PATH, // Mount files
|
|
31
|
+
'-w=' + CONTAINER_TARGET_PATH, // Set working directory
|
|
32
|
+
libdragonInfo.imageName,
|
|
33
|
+
'tail',
|
|
34
|
+
'-f',
|
|
35
|
+
'/dev/null',
|
|
36
|
+
])
|
|
37
|
+
).trim();
|
|
38
|
+
|
|
39
|
+
const newInfo = {
|
|
40
|
+
...libdragonInfo,
|
|
41
|
+
containerId: newId,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// chown the installation folder once on init
|
|
45
|
+
const { uid, gid } = libdragonInfo.userInfo;
|
|
46
|
+
await dockerExec(newInfo, [
|
|
47
|
+
'chown',
|
|
48
|
+
'-R',
|
|
49
|
+
`${uid >= 0 ? uid : ''}:${gid >= 0 ? gid : ''}`,
|
|
50
|
+
'/n64_toolchain',
|
|
51
|
+
]);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// Dispose the invalid container, clean and exit
|
|
54
|
+
await destroyContainer({
|
|
55
|
+
...libdragonInfo,
|
|
56
|
+
containerId: newId,
|
|
57
|
+
});
|
|
58
|
+
log(
|
|
59
|
+
chalk.red(
|
|
60
|
+
'We were unable to initialize libdragon. Done cleanup. Check following logs for the actual error.'
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const name = await spawnProcess('docker', [
|
|
67
|
+
'container',
|
|
68
|
+
'inspect',
|
|
69
|
+
newId,
|
|
70
|
+
'--format',
|
|
71
|
+
'{{.Name}}',
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
libdragonInfo.showStatus &&
|
|
75
|
+
log(
|
|
76
|
+
chalk.green(`Successfully initialized docker container: ${name.trim()}`)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Schedule an update to write image name
|
|
80
|
+
setProjectInfoToSave(libdragonInfo);
|
|
81
|
+
return newId;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const start = async (libdragonInfo, skipProjectCheck) => {
|
|
85
|
+
!skipProjectCheck && (await mustHaveProject(libdragonInfo));
|
|
86
|
+
const running =
|
|
87
|
+
libdragonInfo.containerId &&
|
|
88
|
+
(await checkContainerRunning(libdragonInfo.containerId));
|
|
89
|
+
|
|
90
|
+
if (running) {
|
|
91
|
+
log(`Container ${running} already running.`, true);
|
|
92
|
+
return running;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let id = await checkContainerAndClean(libdragonInfo);
|
|
96
|
+
|
|
97
|
+
if (!id) {
|
|
98
|
+
log(`Container does not exist, re-initializing...`, true);
|
|
99
|
+
id = await initContainer(libdragonInfo);
|
|
100
|
+
return id;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log(`Starting container: ${id}`, true);
|
|
104
|
+
await spawnProcess('docker', ['container', 'start', id]);
|
|
105
|
+
|
|
106
|
+
return id;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
name: 'start',
|
|
111
|
+
fn: async (libdragonInfo) => log(await start(libdragonInfo)),
|
|
112
|
+
start,
|
|
113
|
+
showStatus: false, // This will only print out the id
|
|
114
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { spawnProcess } = require('../helpers');
|
|
2
|
+
|
|
3
|
+
const { checkContainerRunning, mustHaveProject } = require('./utils');
|
|
4
|
+
|
|
5
|
+
const stop = async (libdragonInfo) => {
|
|
6
|
+
await mustHaveProject(libdragonInfo);
|
|
7
|
+
const running =
|
|
8
|
+
libdragonInfo.containerId &&
|
|
9
|
+
(await checkContainerRunning(libdragonInfo.containerId));
|
|
10
|
+
if (!running) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
await spawnProcess('docker', ['container', 'stop', running]);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
name: 'stop',
|
|
19
|
+
fn: stop,
|
|
20
|
+
showStatus: false, // This will only print out the id
|
|
21
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { log } = require('../helpers');
|
|
2
|
+
const { updateImage, destroyContainer } = require('./utils');
|
|
3
|
+
const { start } = require('./start');
|
|
4
|
+
|
|
5
|
+
async function updateAndStart(libdragonInfo) {
|
|
6
|
+
const oldImageName = libdragonInfo.imageName;
|
|
7
|
+
const imageName = libdragonInfo.options.DOCKER_IMAGE ?? oldImageName;
|
|
8
|
+
// If an image is provided, always attempt to install it
|
|
9
|
+
// See https://github.com/anacierdem/libdragon-docker/issues/47
|
|
10
|
+
if (oldImageName !== imageName) {
|
|
11
|
+
log(`Updating image from \`${oldImageName}\` to \`${imageName}\``);
|
|
12
|
+
} else {
|
|
13
|
+
log(`Updating image \`${oldImageName}\``);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Download the new image and if it is different, re-create the container
|
|
17
|
+
if (await updateImage(libdragonInfo, imageName)) {
|
|
18
|
+
await destroyContainer(libdragonInfo);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
...libdragonInfo,
|
|
23
|
+
imageName,
|
|
24
|
+
containerId: await start({
|
|
25
|
+
...libdragonInfo,
|
|
26
|
+
imageName,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
updateAndStart,
|
|
33
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const { log } = require('../helpers');
|
|
4
|
+
const { LIBDRAGON_GIT, LIBDRAGON_BRANCH } = require('../constants');
|
|
5
|
+
const { runGitMaybeHost, mustHaveProject } = require('./utils');
|
|
6
|
+
const { fn: install } = require('./install');
|
|
7
|
+
const { updateAndStart } = require('./update-and-start');
|
|
8
|
+
|
|
9
|
+
const update = async (libdragonInfo) => {
|
|
10
|
+
await mustHaveProject(libdragonInfo);
|
|
11
|
+
const newInfo = await updateAndStart(libdragonInfo);
|
|
12
|
+
|
|
13
|
+
if (newInfo.vendorStrategy !== 'manual') {
|
|
14
|
+
log(`Updating ${newInfo.vendorStrategy}...`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (newInfo.vendorStrategy === 'submodule') {
|
|
18
|
+
await runGitMaybeHost(newInfo, [
|
|
19
|
+
'submodule',
|
|
20
|
+
'update',
|
|
21
|
+
'--remote',
|
|
22
|
+
'--merge',
|
|
23
|
+
newInfo.vendorDirectory,
|
|
24
|
+
]);
|
|
25
|
+
} else if (newInfo.vendorStrategy === 'subtree') {
|
|
26
|
+
await runGitMaybeHost(newInfo, [
|
|
27
|
+
'subtree',
|
|
28
|
+
'pull',
|
|
29
|
+
'--prefix',
|
|
30
|
+
path.relative(libdragonInfo.root, libdragonInfo.vendorDirectory),
|
|
31
|
+
LIBDRAGON_GIT,
|
|
32
|
+
LIBDRAGON_BRANCH,
|
|
33
|
+
'--squash',
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The second parameter forces it to skip the image update step as we already
|
|
38
|
+
// do that above.
|
|
39
|
+
await install(newInfo, true);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
name: 'update',
|
|
44
|
+
fn: update,
|
|
45
|
+
showStatus: true,
|
|
46
|
+
};
|