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 CHANGED
@@ -1,5 +1,71 @@
1
1
  # Change Log
2
2
 
3
+ ## [10.4.2] - 2022-04-03
4
+
5
+ ### Fixed
6
+
7
+ - Make sure actions depending on an `init` fail in a non-project directory to
8
+ keep the project state consistent. This fixes #51.
9
+ - `update` action now tries to update the toolchain image as well. Previously
10
+ this was not the case contrary to what someone would expect. Considering it won't
11
+ change the behaviour for non-latest images and the toolchain did not have any
12
+ breaking changes for a long time, this is not considered a breaking change either.
13
+ - `start` action was printing stuff other than the container id. It doesn't
14
+ anymore.
15
+ - Stop unnecessarily printing container id and a few messages related to updates.
16
+ - Fix a potential race condition that might cause unexpected failures.
17
+ - Correct some errors' exit codes.
18
+ ### Added
19
+
20
+ - A new exit code (`4`) to represent unexpected conditions.
21
+
22
+ ### Changed
23
+
24
+ - Deprecated providing the image flag for `install` action by displaying a
25
+ warning and removing it from documentation, without changing behaviour even
26
+ though it is higly unlikely this feature was ever used. It mainly exists for
27
+ historical reasons and it wil be removed in next major release.
28
+ - Update documentation to warn against changing strategy is a one way operation.
29
+ - Update documentation to reflect `update` action changes.
30
+ - Minor refactors.
31
+ - Update submodule for local environment.
32
+
33
+ ## [10.4.1] - 2022-03-23
34
+
35
+ ### Fixed
36
+
37
+ - Update the root makefile to utilize `SOURCE_DIR` for example builds. Then we are
38
+ able to map container files to local files properly with a generic regex in the
39
+ problem matcher. This fixes #13 and does not change any behaviour.
40
+ - Add missing examples to the vscode run configurations.
41
+ - Install and build libdragon related things in the container when `exec` and
42
+ `make` causes a new container run. This was previously prevented on `v10.3.1`
43
+ because it was unnecessarily delaying all exec operations when the container
44
+ is started. Refactoring things allowed me to realize this can be improved
45
+ instead of forcing the user to do a manual `install`.
46
+ - Fix a potential issue that may cause the git commands to run in current folder
47
+ instead of the project root.
48
+ - Attach the error handler once for spawnProcess.
49
+ - Update vulnerable dependencies.
50
+
51
+ ### Added
52
+
53
+ - `--directory` option to customize vendoring location.
54
+ - `--strategy` option to select a vendoring strategy. Currently supported options
55
+ are `submodule`, `subtree` and `manual`. The default is `submodule` and `manual`
56
+ can be used to opt-out of auto vendoring. Useful if the user wants to utilize
57
+ a different vendoring strategy and opt-out of the auto-managed git flows.
58
+
59
+ ### Changed
60
+
61
+ - Migrate to a json file for persistent project information.
62
+ - Only save the configuration file on successful exit except for the initial
63
+ migration.
64
+ - Do not prevent init if there is a file named libdragon in the target folder.
65
+ This used to cause problems on windows but I cannot reproduce it anymore
66
+ with `2.33.1.windows.1`. It may be something caused by my old configuration.
67
+ - Minor performance improvements.
68
+
3
69
  ## [10.3.1] - 2022-01-25
4
70
 
5
71
  ### Fixed
@@ -19,7 +85,7 @@
19
85
 
20
86
  ### Changed
21
87
 
22
- - Only accept he image flag for init, install, and update actions as documented.
88
+ - Only accept the image flag for init, install, and update actions as documented.
23
89
  - Improve documentation for the `init` and `install` actions.
24
90
  - Do not attempt an `install` when running `exec`, just start the container. If
25
91
  there is a half-baked container, a manual `install` will potentially restore it.
package/README.md CHANGED
@@ -22,7 +22,7 @@ To update the library and rebuild/install all the artifacts;
22
22
 
23
23
  libdragon update
24
24
 
25
- See [next section](#overall-usage) for more details.
25
+ `git` is not strictly required to use this tool as docker's git will be used instead. Still, it is highly recommended to have git on your host machine.
26
26
 
27
27
  ## Overall usage
28
28
 
@@ -38,16 +38,6 @@ You can invoke libdragon as follows;
38
38
 
39
39
  Run `libdragon help [action]` for more details on individual actions.
40
40
 
41
- ### Available flags
42
-
43
- **`--image <docker-image>`**
44
-
45
- Use this flag to provide a custom image to use instead of the default. It should include the toolchain at `/n64_toolchain`. It will be effective for `init`, `install` and `update` actions and will cause a re-initialization of the container if an image different from what was written to project configuration is provided.
46
-
47
- **`--verbose`**
48
-
49
- Be verbose. This will print all commands dispatched and their outputs as well.
50
-
51
41
  ## Working on this repository
52
42
 
53
43
  After cloning this repository on a system with node.js (`>= 14`) & docker (`>= 18`), in this repository's root do;
@@ -92,7 +82,7 @@ To update the submodule and re-build everything;
92
82
 
93
83
  ### Local test bench
94
84
 
95
- This repository also uses [ed64](https://github.com/anacierdem/ed64), so you can just hit F5 on vscode (The `Run Test Bench` launch configuration) to run the test code in `src` folder to develop libdragon itself quicker if you have an everdrive. There is a caveat though: If you want the problem matcher to work properly, you should name this repository folder `libdragon` exactly.
85
+ This repository also uses [ed64](https://github.com/anacierdem/ed64), so you can just hit F5 on vscode (The `Run Test Bench` launch configuration) to run the test code in `src` folder to develop libdragon itself quicker if you have an everdrive.
96
86
 
97
87
  There are also additional vscode launch configurations to build libdragon examples and tests based on the currently built and installed libdragon in the docker container. Most of these will always rebuild so that they will use the latest if you made and installed an alternative libdragon. The test bench itself and a few examples (that use the new build system) will already rebuild and reinstall libdragon automatically. These will always produce a rom image using the latest libdragon code in the active repository via its make dependencies. You can clean everything with the `clean` task (open the command palette and choose `Run Task -> clean`).
98
88
 
package/index.js CHANGED
@@ -1,13 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const chalk = require('chalk');
4
- const { readProjectInfo, CommandError, globals } = require('./modules/helpers');
5
- const actions = require('./modules/actions');
6
- const { printUsage } = require('./modules/usage');
7
4
 
8
- const STATUS_OK = 0;
9
- const STATUS_ERROR = 1;
10
- const STATUS_BAD_PARAM = 2;
5
+ const actions = require('./modules/actions');
6
+ const { fn: printUsage } = require('./modules/actions/help');
7
+ const {
8
+ STATUS_OK,
9
+ STATUS_BAD_PARAM,
10
+ STATUS_ERROR,
11
+ STATUS_VALIDATION_ERROR,
12
+ } = require('./modules/constants');
13
+ const { globals } = require('./modules/globals');
14
+ const {
15
+ CommandError,
16
+ ParameterError,
17
+ ValidationError,
18
+ } = require('./modules/helpers');
19
+ const { readProjectInfo, writeProjectInfo } = require('./modules/project-info');
11
20
 
12
21
  let options = {},
13
22
  currentAction;
@@ -32,6 +41,22 @@ for (let i = 2; i < process.argv.length; i++) {
32
41
  continue;
33
42
  }
34
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
+
35
60
  if (val.indexOf('--') >= 0) {
36
61
  console.error(chalk.red(`Invalid flag \`${val}\``));
37
62
  printUsage();
@@ -81,6 +106,15 @@ if (
81
106
  process.exit(STATUS_BAD_PARAM);
82
107
  }
83
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
+
84
118
  readProjectInfo()
85
119
  .then((info) =>
86
120
  currentAction.fn(
@@ -93,6 +127,17 @@ readProjectInfo()
93
127
  )
94
128
  )
95
129
  .catch((e) => {
130
+ if (e instanceof ParameterError) {
131
+ console.error(chalk.red(e.message));
132
+ printUsage(undefined, [currentAction.name]);
133
+ process.exit(STATUS_BAD_PARAM);
134
+ }
135
+
136
+ if (e instanceof ValidationError) {
137
+ console.error(chalk.red(e.message));
138
+ process.exit(STATUS_VALIDATION_ERROR);
139
+ }
140
+
96
141
  const userTargetedError = e instanceof CommandError && e.userCommand;
97
142
 
98
143
  // Show additional information to user if verbose or we did a mistake
@@ -115,6 +160,10 @@ readProjectInfo()
115
160
  // We don't have a user targeted error anymore, we did a mistake for sure
116
161
  process.exit(STATUS_ERROR);
117
162
  })
163
+ .then(() => {
164
+ // Everything was done, update the configuration file if not exiting early
165
+ return writeProjectInfo();
166
+ })
118
167
  .finally(() => {
119
168
  process.exit(STATUS_OK);
120
169
  });
@@ -0,0 +1,97 @@
1
+ const path = require('path');
2
+
3
+ const { CONTAINER_TARGET_PATH } = require('../constants');
4
+ const { log, dockerExec, toPosixPath } = require('../helpers');
5
+
6
+ const { start } = require('./start');
7
+ const {
8
+ dockerHostUserParams,
9
+ installDependencies,
10
+ mustHaveProject,
11
+ } = require('./utils');
12
+
13
+ function dockerRelativeWorkdir(libdragonInfo) {
14
+ return (
15
+ CONTAINER_TARGET_PATH +
16
+ '/' +
17
+ toPosixPath(path.relative(libdragonInfo.root, process.cwd()))
18
+ );
19
+ }
20
+
21
+ function dockerRelativeWorkdirParams(libdragonInfo) {
22
+ return ['--workdir', dockerRelativeWorkdir(libdragonInfo)];
23
+ }
24
+
25
+ const exec = async (libdragonInfo, commandAndParams) => {
26
+ await mustHaveProject(libdragonInfo);
27
+
28
+ log(
29
+ `Running ${commandAndParams[0]} at ${dockerRelativeWorkdir(
30
+ libdragonInfo
31
+ )} with [${commandAndParams.slice(1)}]`,
32
+ true
33
+ );
34
+
35
+ const tryCmd = (libdragonInfo) =>
36
+ libdragonInfo.containerId &&
37
+ dockerExec(
38
+ libdragonInfo,
39
+ [
40
+ ...dockerRelativeWorkdirParams(libdragonInfo),
41
+ ...dockerHostUserParams(libdragonInfo),
42
+ ],
43
+ commandAndParams,
44
+ true,
45
+ true // Cannot use "full" here, we need to know if the container is alive
46
+ );
47
+
48
+ let started = false;
49
+ const startOnceAndCmd = async () => {
50
+ if (!started) {
51
+ const newId = await start(libdragonInfo);
52
+ started = true;
53
+
54
+ // Re-install vendors on new container if one was created upon start
55
+ // Ideally we would want the consumer to handle dependencies and rebuild
56
+ // libdragon if necessary. Currently this saves the day with a little bit
57
+ // extra waiting when the container is deleted.
58
+ if (libdragonInfo.containerId !== newId) {
59
+ await installDependencies({
60
+ ...libdragonInfo,
61
+ containerId: newId,
62
+ });
63
+ }
64
+ await tryCmd({
65
+ ...libdragonInfo,
66
+ containerId: newId,
67
+ });
68
+ return newId;
69
+ }
70
+ };
71
+
72
+ if (!libdragonInfo.containerId) {
73
+ log(`Container does not exist for sure, restart`, true);
74
+ await startOnceAndCmd();
75
+ return;
76
+ }
77
+
78
+ try {
79
+ await tryCmd(libdragonInfo);
80
+ } catch (e) {
81
+ if (
82
+ !e.out ||
83
+ // TODO: is there a better way?
84
+ !e.out.toString().includes(libdragonInfo.containerId)
85
+ ) {
86
+ throw e;
87
+ }
88
+ await startOnceAndCmd();
89
+ }
90
+ };
91
+
92
+ module.exports = {
93
+ name: 'exec',
94
+ fn: exec,
95
+ forwardsRestParams: true,
96
+ showStatus: true,
97
+ };
@@ -0,0 +1,163 @@
1
+ const chalk = require('chalk');
2
+ const commandLineUsage = require('command-line-usage');
3
+
4
+ const { log } = require('../helpers');
5
+
6
+ const printUsage = (_, actionArr) => {
7
+ const globalOptionDefinitions = [
8
+ {
9
+ name: 'verbose',
10
+ description:
11
+ 'Be verbose. This will print all commands dispatched and their outputs as well. Will also start printing error stack traces.',
12
+ alias: 'v',
13
+ typeLabel: ' ',
14
+ group: 'global',
15
+ },
16
+ ];
17
+
18
+ const optionDefinitions = [
19
+ {
20
+ name: 'image',
21
+ description:
22
+ 'Use this flag to provide a custom image to use instead of the default. It should include the toolchain at `/n64_toolchain`. It will cause a re-initialization of the container if a different image is provided.\n',
23
+ alias: 'i',
24
+ typeLabel: '<docker-image>',
25
+ group: 'docker',
26
+ },
27
+ {
28
+ name: 'directory',
29
+ description: `Directory where libdragon files are expected. It must be a project relative path as it will be mounted on the docker container. The cli will create and manage it when using a non-manual strategy. Defaults to \`./libdragon\` if not provided.\n`,
30
+ alias: 'd',
31
+ typeLabel: '<path>',
32
+ group: 'vendoring',
33
+ },
34
+ {
35
+ name: 'strategy',
36
+ description: `libdragon Vendoring strategy. Defaults to \`submodule\`, which safely creates a git repository at project root and a submodule at \`--directory\` to automatically update the vendored libdragon files.
37
+
38
+ With \`subtree\`, the cli will create a subtree at \`--directory\` instead. Keep in mind that git user name and email must be set up for this to work. Do not use if you are not using git yourself.
39
+
40
+ To disable auto-vendoring, init with \`manual\`. With \`manual\`, libdragon files are expected at the location provided by \`--directory\` flag and the user is responsible for vendoring and updating them. This will allow using any other manual vendoring method.
41
+
42
+ You can always switch to manual by re-running \`init\` with \`--strategy manual\`, though you will be responsible for managing the existing submodule/subtree. Also it is not possible to automatically switch back.
43
+
44
+ With the \`manual\` strategy, it is still recommended to have a git repository at project root such that container actions can execute faster by caching the container id inside the \`.git\` folder.\n`,
45
+ alias: 'v',
46
+ typeLabel: '<strategy>',
47
+ group: 'vendoring',
48
+ },
49
+ ];
50
+
51
+ // TODO: move these to respective actions
52
+
53
+ const actions = {
54
+ help: {
55
+ name: 'help [action]',
56
+ summary: 'Display this help information or details for the given action.',
57
+ },
58
+ init: {
59
+ name: 'init',
60
+ summary: 'Create a libdragon project in the current directory.',
61
+ description: `Creates a libdragon project in the current directory. Every libdragon project will have its own docker container instance. If you are in a git repository or an NPM project, libdragon will be initialized at their root also marking there with a \`.libdragon\` folder. Do not remove the \`.libdragon\` folder and commit its contents if you are using source control, as it keeps persistent libdragon project information.
62
+
63
+ 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.
64
+
65
+ 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.`,
66
+ group: ['docker', 'vendoring'],
67
+ },
68
+ make: {
69
+ name: 'make [params]',
70
+ summary: 'Run the libdragon build system in the current directory.',
71
+ description: `Runs the libdragon build system in the current directory. It will mirror your current working directory to the container.
72
+
73
+ Must be run in an initialized libdragon project. This action is a shortcut to the \`exec\` action under the hood.`,
74
+ },
75
+ exec: {
76
+ name: 'exec <command>',
77
+ summary: 'Execute given command in the current directory.',
78
+ 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.
79
+
80
+ 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.
81
+
82
+ 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.
83
+
84
+ Must be run in an initialized libdragon project.`,
85
+ },
86
+ start: {
87
+ name: 'start',
88
+ summary: 'Start the container for current project.',
89
+ description: `Start the container assigned to the current libdragon project. Will first attempt to start an existing container if found, followed by a new container run and installation similar to the \`install\` action. Will always print out the container id on success.
90
+
91
+ Must be run in an initialized libdragon project.`,
92
+ },
93
+ name: {
94
+ name: 'stop',
95
+ summary: 'Stop the container for current project.',
96
+ description: `Stop the container assigned to the current libdragon project.
97
+
98
+ Must be run in an initialized libdragon project.`,
99
+ },
100
+ install: {
101
+ name: 'install',
102
+ summary: 'Vendor libdragon as is.',
103
+ description: `Attempts to build and install everything libdragon related into the container. This includes all the tools and third parties used by libdragon except for the toolchain. If you have made changes to libdragon, you can execute this action to build everything based on your changes. Requires you to have an intact vendoring target (also see the \`--directory\` flag). If you are not working on libdragon itself, you can just use the \`update\` action instead.
104
+
105
+ Must be run in an initialized libdragon project. This can be useful to recover from a half-baked container.`,
106
+ },
107
+ update: {
108
+ name: 'update',
109
+ summary: 'Update libdragon and do an install.',
110
+ description: `Will update the docker image and if you are using auto-vendoring (see \`--strategy\`), will also update the submodule/subtree from the remote branch (\`trunk\`) with a merge/squash strategy and then perform a \`libdragon install\`. You can use the \`install\` action to only update all libdragon related artifacts in the container.
111
+
112
+ Must be run in an initialized libdragon project.`,
113
+ group: ['docker'],
114
+ },
115
+ };
116
+
117
+ const actionsToShow = actionArr
118
+ ?.filter((action) => Object.keys(actions).includes(action))
119
+ .filter((action) => !['help'].includes(action));
120
+
121
+ const sections = [
122
+ {
123
+ header: chalk.green('Usage:'),
124
+ content: 'libdragon [flags] <action>',
125
+ },
126
+ ...(actionsToShow?.length
127
+ ? actionsToShow.flatMap((action) => [
128
+ {
129
+ header: chalk.green(`${action} action:`),
130
+ content: actions[action].description,
131
+ },
132
+ actions[action].group
133
+ ? {
134
+ header: `accepted flags:`,
135
+ optionList: optionDefinitions,
136
+ group: actions[action].group,
137
+ }
138
+ : {},
139
+ ])
140
+ : [
141
+ {
142
+ header: chalk.green('Available Commands:'),
143
+ content: Object.values(actions).map((action) => ({
144
+ name: action.name,
145
+ summary: action.summary,
146
+ })),
147
+ },
148
+ ]),
149
+ {
150
+ header: chalk.green('Global flags:'),
151
+ optionList: globalOptionDefinitions,
152
+ },
153
+ ];
154
+ const usage = commandLineUsage(sections);
155
+ log(usage);
156
+ };
157
+
158
+ module.exports = {
159
+ name: 'help',
160
+ fn: printUsage,
161
+ showStatus: true,
162
+ forwardsRestParams: true,
163
+ };
@@ -0,0 +1,25 @@
1
+ const { fn: exec } = require('./exec');
2
+
3
+ const make = async (libdragonInfo, params) => {
4
+ await exec(libdragonInfo, ['make', ...params]);
5
+ };
6
+
7
+ // TODO: separate into files
8
+ module.exports = {
9
+ start: require('./start'),
10
+ stop: require('./stop'),
11
+ init: require('./init'),
12
+
13
+ exec: require('./exec'),
14
+ make: {
15
+ name: 'make',
16
+ fn: make,
17
+ forwardsRestParams: true,
18
+ showStatus: true,
19
+ },
20
+
21
+ install: require('./install'),
22
+ update: require('./update'),
23
+
24
+ help: require('./help'),
25
+ };