libdragon 10.4.0 → 10.5.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.
@@ -1,9 +1,6 @@
1
1
  const path = require('path');
2
- const fsClassic = require('fs');
3
2
  const fs = require('fs/promises');
4
3
 
5
- const _ = require('lodash');
6
-
7
4
  const {
8
5
  CONTAINER_TARGET_PATH,
9
6
  CACHED_CONTAINER_FILE,
@@ -12,40 +9,27 @@ const {
12
9
  const {
13
10
  fileExists,
14
11
  log,
15
- toPosixPath,
16
12
  spawnProcess,
17
13
  dockerExec,
18
14
  dirExists,
19
15
  assert,
20
16
  CommandError,
17
+ ValidationError,
18
+ ParameterError,
19
+ toNativePath,
21
20
  } = require('../helpers');
22
21
 
23
- function dockerHostUserParams(libdragonInfo) {
24
- const { uid, gid } = libdragonInfo.userInfo;
25
- return ['-u', `${uid >= 0 ? uid : ''}:${gid >= 0 ? gid : ''}`];
26
- }
27
-
28
- async function findNPMRoot() {
29
- try {
30
- const root = path.resolve((await runNPM(['root'])).trim(), '..');
31
- // Only report if package.json really exists. npm fallbacks to cwd
32
- if (await fileExists(path.join(root, 'package.json'))) {
33
- return root;
34
- }
35
- } catch {
36
- // User does not have and does not care about NPM if it didn't work
37
- return undefined;
38
- }
39
- }
22
+ const { dockerHostUserParams } = require('./docker-utils');
23
+ const { installNPMDependencies } = require('./npm-utils');
40
24
 
41
25
  const installDependencies = async (libdragonInfo) => {
42
26
  const buildScriptPath = path.join(
43
27
  libdragonInfo.root,
44
- libdragonInfo.vendorDirectory,
28
+ toNativePath(libdragonInfo.vendorDirectory),
45
29
  'build.sh'
46
30
  );
47
31
  if (!(await fileExists(buildScriptPath))) {
48
- throw new Error(
32
+ throw new ValidationError(
49
33
  `build.sh not found. Make sure you have a vendored libdragon copy at ${libdragonInfo.vendorDirectory}`
50
34
  );
51
35
  }
@@ -56,96 +40,13 @@ const installDependencies = async (libdragonInfo) => {
56
40
  libdragonInfo,
57
41
  [
58
42
  '--workdir',
59
- CONTAINER_TARGET_PATH +
60
- '/' +
61
- toPosixPath(
62
- path.relative(libdragonInfo.root, libdragonInfo.vendorDirectory)
63
- ),
43
+ CONTAINER_TARGET_PATH + '/' + libdragonInfo.vendorDirectory,
64
44
  ...dockerHostUserParams(libdragonInfo),
65
45
  ],
66
46
  ['/bin/bash', './build.sh']
67
47
  );
68
48
 
69
- // Install other NPM dependencies if this is an NPM project
70
- const npmRoot = await findNPMRoot();
71
- if (npmRoot) {
72
- const packageJsonPath = path.join(npmRoot, 'package.json');
73
-
74
- const { dependencies, devDependencies } = require(packageJsonPath);
75
-
76
- const deps = await Promise.all(
77
- Object.keys({
78
- ...dependencies,
79
- ...devDependencies,
80
- })
81
- .filter((dep) => dep !== 'libdragon')
82
- .map(async (dep) => {
83
- const npmPath = await runNPM(['ls', dep, '--parseable=true']);
84
- return {
85
- name: dep,
86
- paths: _.uniq(npmPath.split('\n').filter((f) => f)),
87
- };
88
- })
89
- );
90
-
91
- await Promise.all(
92
- deps.map(({ name, paths }) => {
93
- return new Promise((resolve, reject) => {
94
- fsClassic.access(
95
- path.join(paths[0], 'Makefile'),
96
- fsClassic.F_OK,
97
- async (e) => {
98
- if (e) {
99
- // File does not exist - skip
100
- resolve();
101
- return;
102
- }
103
-
104
- if (paths.length > 1) {
105
- reject(
106
- new Error(
107
- `Using same dependency with different versions is not supported! ${name}`
108
- )
109
- );
110
- return;
111
- }
112
-
113
- try {
114
- const relativePath = toPosixPath(
115
- path.relative(libdragonInfo.root, paths[0])
116
- );
117
- const containerPath = path.posix.join(
118
- CONTAINER_TARGET_PATH,
119
- relativePath
120
- );
121
- const makePath = path.posix.join(containerPath, 'Makefile');
122
-
123
- await dockerExec(
124
- libdragonInfo,
125
- [...dockerHostUserParams(libdragonInfo)],
126
- [
127
- '/bin/bash',
128
- '-c',
129
- '[ -f ' +
130
- makePath +
131
- ' ] && make -C ' +
132
- containerPath +
133
- ' && make -C ' +
134
- containerPath +
135
- ' install',
136
- ]
137
- );
138
-
139
- resolve();
140
- } catch (e) {
141
- reject(e);
142
- }
143
- }
144
- );
145
- });
146
- })
147
- );
148
- }
49
+ await installNPMDependencies(libdragonInfo);
149
50
  };
150
51
 
151
52
  /**
@@ -173,7 +74,7 @@ const updateImage = async (libdragonInfo, newImageName) => {
173
74
  'docker',
174
75
  ['images', '-q', '--no-trunc', newImageName],
175
76
  false,
176
- libdragonInfo.showStatus
77
+ false
177
78
  );
178
79
 
179
80
  // Attempt to compare digests if the new image name is the same
@@ -186,14 +87,15 @@ const updateImage = async (libdragonInfo, newImageName) => {
186
87
  const newDigest = await getDigest();
187
88
 
188
89
  if (existingDigest === newDigest) {
189
- libdragonInfo.showStatus && log(`Image is the same: ${newImageName}`);
90
+ libdragonInfo.showStatus &&
91
+ log(`Image is the same: ${newImageName}`, true);
190
92
  return false;
191
93
  }
192
94
  } else {
193
95
  await download();
194
96
  }
195
97
 
196
- libdragonInfo.showStatus && log(`Image is different: ${newImageName}`);
98
+ libdragonInfo.showStatus && log(`Image is different: ${newImageName}`, true);
197
99
  return newImageName;
198
100
  };
199
101
 
@@ -246,13 +148,6 @@ async function runGitMaybeHost(libdragonInfo, params, interactive = 'full') {
246
148
  }
247
149
  }
248
150
 
249
- function runNPM(params) {
250
- return spawnProcess(
251
- /^win/.test(process.platform) ? 'npm.cmd' : 'npm',
252
- params
253
- );
254
- }
255
-
256
151
  async function checkContainerAndClean(libdragonInfo) {
257
152
  const id =
258
153
  libdragonInfo.containerId &&
@@ -301,14 +196,22 @@ async function tryCacheContainerId(libdragonInfo) {
301
196
  }
302
197
  }
303
198
 
199
+ // Throws if the project was not initialized for the current libdragonInfo
200
+ async function mustHaveProject(libdragonInfo) {
201
+ if (!libdragonInfo.haveProjectConfig) {
202
+ throw new ParameterError(
203
+ 'This is not a libdragon project. Initialize with `libdragon init` first.'
204
+ );
205
+ }
206
+ }
207
+
304
208
  module.exports = {
305
209
  installDependencies,
306
210
  updateImage,
307
211
  destroyContainer,
308
212
  checkContainerRunning,
309
213
  checkContainerAndClean,
310
- dockerHostUserParams,
311
214
  tryCacheContainerId,
312
215
  runGitMaybeHost,
313
- findNPMRoot,
216
+ mustHaveProject,
314
217
  };
@@ -0,0 +1,17 @@
1
+ const { version } = require('../../package.json');
2
+ const { log } = require('../helpers');
3
+
4
+ const printVersion = async () => {
5
+ log(`libdragon-cli v${version}`);
6
+ };
7
+
8
+ module.exports = {
9
+ name: 'version',
10
+ fn: printVersion,
11
+ showStatus: false,
12
+ usage: {
13
+ name: 'version',
14
+ summary: 'Display cli version.',
15
+ description: `Displays currently running cli version.`,
16
+ },
17
+ };
@@ -13,6 +13,7 @@ module.exports = {
13
13
  STATUS_OK: 0,
14
14
  STATUS_ERROR: 1,
15
15
  STATUS_BAD_PARAM: 2,
16
+ STATUS_VALIDATION_ERROR: 4,
16
17
 
17
18
  IMAGE_FILE: 'docker-image', // deprecated
18
19
  };
@@ -6,6 +6,7 @@ const { spawn } = require('child_process');
6
6
 
7
7
  const { globals } = require('./globals');
8
8
 
9
+ // An error caused by a command explicitly run by the user
9
10
  class CommandError extends Error {
10
11
  constructor(message, { code, out, userCommand }) {
11
12
  super(message);
@@ -15,12 +16,20 @@ class CommandError extends Error {
15
16
  }
16
17
  }
17
18
 
19
+ // The user provided an unexpected input
18
20
  class ParameterError extends Error {
19
21
  constructor(message) {
20
22
  super(message);
21
23
  }
22
24
  }
23
25
 
26
+ // Something was not as expected to continue the operation
27
+ class ValidationError extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ }
31
+ }
32
+
24
33
  async function fileExists(path) {
25
34
  return fs
26
35
  .stat(path)
@@ -200,7 +209,11 @@ async function copyDirContents(src, dst) {
200
209
  }
201
210
 
202
211
  function toPosixPath(p) {
203
- return p.replace(new RegExp('\\' + path.sep), path.posix.sep);
212
+ return p.replace(new RegExp('\\' + path.sep, 'g'), path.posix.sep);
213
+ }
214
+
215
+ function toNativePath(p) {
216
+ return p.replace(new RegExp('\\' + path.posix.sep, 'g'), path.sep);
204
217
  }
205
218
 
206
219
  function assert(condition, error) {
@@ -210,6 +223,7 @@ function assert(condition, error) {
210
223
  }
211
224
  }
212
225
 
226
+ // TODO: we can handle showStatus here
213
227
  function log(text, verboseOnly = false) {
214
228
  if (!verboseOnly) {
215
229
  // eslint-disable-next-line no-console
@@ -226,6 +240,7 @@ function log(text, verboseOnly = false) {
226
240
  module.exports = {
227
241
  spawnProcess,
228
242
  toPosixPath,
243
+ toNativePath,
229
244
  log,
230
245
  dockerExec,
231
246
  assert,
@@ -234,4 +249,5 @@ module.exports = {
234
249
  copyDirContents,
235
250
  CommandError,
236
251
  ParameterError,
252
+ ValidationError,
237
253
  };
@@ -4,11 +4,12 @@ const fs = require('fs/promises');
4
4
 
5
5
  const {
6
6
  checkContainerAndClean,
7
- findNPMRoot,
8
7
  runGitMaybeHost,
9
8
  tryCacheContainerId,
10
9
  } = require('./actions/utils');
11
10
 
11
+ const { findNPMRoot } = require('./actions/npm-utils');
12
+
12
13
  const {
13
14
  LIBDRAGON_PROJECT_MANIFEST,
14
15
  CONFIG_FILE,
@@ -24,8 +25,8 @@ const {
24
25
  fileExists,
25
26
  log,
26
27
  spawnProcess,
27
- dirExists,
28
28
  toPosixPath,
29
+ assert,
29
30
  } = require('./helpers');
30
31
 
31
32
  async function findContainerId(libdragonInfo) {
@@ -67,8 +68,8 @@ async function findContainerId(libdragonInfo) {
67
68
  }
68
69
 
69
70
  async function findLibdragonRoot(start = '.') {
70
- const manifest = path.join(start, LIBDRAGON_PROJECT_MANIFEST);
71
- if (await dirExists(manifest)) {
71
+ const manifest = path.join(start, LIBDRAGON_PROJECT_MANIFEST, CONFIG_FILE);
72
+ if (await fileExists(manifest)) {
72
73
  return path.resolve(start);
73
74
  } else {
74
75
  const parent = path.resolve(start, '..');
@@ -89,41 +90,21 @@ async function findGitRoot() {
89
90
  }
90
91
  }
91
92
 
92
- /**
93
- * Creates the manifest folder if it does not exist. Will return true if
94
- * created, false otherwise.
95
- */
96
- async function createManifestIfNotExist(libdragonInfo) {
97
- const manifestPath = path.join(
98
- libdragonInfo.root,
99
- LIBDRAGON_PROJECT_MANIFEST
100
- );
101
- const manifestExists = await fs.stat(manifestPath).catch((e) => {
102
- if (e.code !== 'ENOENT') throw e;
103
- return false;
104
- });
105
-
106
- if (manifestExists && !manifestExists.isDirectory()) {
107
- throw new Error(
108
- 'There is already a `.libdragon` file and it is not a directory.'
109
- );
110
- }
111
-
112
- if (!manifestExists) {
113
- log(
114
- `Creating libdragon project configuration at \`${libdragonInfo.root}\`.`
115
- );
116
- await fs.mkdir(manifestPath);
117
- }
118
- }
119
-
120
93
  async function readProjectInfo() {
94
+ const projectRoot = await findLibdragonRoot();
95
+
121
96
  let info = {
122
- root:
123
- (await findLibdragonRoot()) ??
124
- (await findNPMRoot()) ??
125
- (await findGitRoot()),
97
+ root: projectRoot ?? (await findNPMRoot()) ?? (await findGitRoot()),
126
98
  userInfo: os.userInfo(),
99
+
100
+ // Use this to discriminate if there is a project when the command is run
101
+ haveProjectConfig: !!projectRoot,
102
+
103
+ // Set the defaults immediately, these should be present at all times even
104
+ // if we are migrating from the old config because they did not exist before
105
+ imageName: DOCKER_HUB_IMAGE,
106
+ vendorDirectory: toPosixPath(path.join('.', LIBDRAGON_SUBMODULE)),
107
+ vendorStrategy: DEFAULT_STRATEGY,
127
108
  };
128
109
 
129
110
  if (!info.root) {
@@ -180,47 +161,45 @@ async function readProjectInfo() {
180
161
  ).trim();
181
162
  }
182
163
 
183
- info.imageName = info.imageName ?? DOCKER_HUB_IMAGE;
184
164
  log(`Active image name: ${info.imageName}`, true);
185
-
186
- info.vendorDirectory =
187
- info.vendorDirectory ?? path.join('.', LIBDRAGON_SUBMODULE);
188
165
  log(`Active vendor directory: ${info.vendorDirectory}`, true);
189
-
190
- info.vendorStrategy = info.vendorStrategy ?? DEFAULT_STRATEGY;
191
166
  log(`Active vendor strategy: ${info.vendorStrategy}`, true);
192
167
 
193
- // Cache the latest image name
194
- setProjectInfoToSave(info);
195
168
  return info;
196
169
  }
197
170
 
198
- let projectInfoToWrite = {};
199
171
  /**
200
- * Updates project info to be written. The provided keys are overwritten without
201
- * changing the existing values. When the process exists successfully these will
202
- * get written to the configuration file. Echoes back the given info.
203
- * @param libdragonInfo
172
+ * @param info This is only the base info without action properties like showStatus
173
+ * fn and command line options
204
174
  */
205
- function setProjectInfoToSave(libdragonInfo) {
206
- projectInfoToWrite = { ...projectInfoToWrite, ...libdragonInfo };
207
- return libdragonInfo;
208
- }
175
+ async function writeProjectInfo(info) {
176
+ // Do not log anything here as it may litter the output being always run on exit
177
+ if (!info) return;
178
+
179
+ const projectPath = path.join(info.root, LIBDRAGON_PROJECT_MANIFEST);
209
180
 
210
- async function writeProjectInfo(libdragonInfo = projectInfoToWrite) {
211
- await createManifestIfNotExist(libdragonInfo);
212
- const manifestPath = path.join(
213
- libdragonInfo.root,
214
- LIBDRAGON_PROJECT_MANIFEST
181
+ const pathExists = await fs.stat(projectPath).catch((e) => {
182
+ if (e.code !== 'ENOENT') throw e;
183
+ return false;
184
+ });
185
+
186
+ if (!pathExists) {
187
+ log(`Creating libdragon project configuration at \`${info.root}\`.`, true);
188
+ await fs.mkdir(projectPath);
189
+ }
190
+
191
+ assert(
192
+ toPosixPath(info.vendorDirectory) === info.vendorDirectory,
193
+ new Error('vendorDirectory should always be in posix format')
215
194
  );
195
+
216
196
  await fs.writeFile(
217
- path.join(manifestPath, CONFIG_FILE),
197
+ path.join(projectPath, CONFIG_FILE),
218
198
  JSON.stringify(
219
199
  {
220
- imageName: libdragonInfo.imageName,
221
- // Always save this in posix format
222
- vendorDirectory: toPosixPath(libdragonInfo.vendorDirectory),
223
- vendorStrategy: libdragonInfo.vendorStrategy,
200
+ imageName: info.imageName,
201
+ vendorDirectory: info.vendorDirectory,
202
+ vendorStrategy: info.vendorStrategy,
224
203
  },
225
204
  null,
226
205
  ' '
@@ -229,4 +208,4 @@ async function writeProjectInfo(libdragonInfo = projectInfoToWrite) {
229
208
  log(`Configuration file updated`, true);
230
209
  }
231
210
 
232
- module.exports = { readProjectInfo, writeProjectInfo, setProjectInfoToSave };
211
+ module.exports = { readProjectInfo, writeProjectInfo };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libdragon",
3
- "version": "10.4.0",
3
+ "version": "10.5.0",
4
4
  "description": "This is a docker wrapper for libdragon",
5
5
  "main": "index.js",
6
6
  "engines": {
package/skeleton/Makefile CHANGED
@@ -3,16 +3,17 @@ SOURCE_DIR=src
3
3
  BUILD_DIR=build
4
4
  include $(N64_INST)/include/n64.mk
5
5
 
6
- src=main.c
7
-
8
6
  all: hello.z64
7
+ .PHONY: all
8
+
9
+ OBJS = $(BUILD_DIR)/main.o
9
10
 
10
11
  hello.z64: N64_ROM_TITLE="Hello World"
11
- $(BUILD_DIR)/hello.elf: $(src:%.c=$(BUILD_DIR)/%.o)
12
12
 
13
- clean:
14
- rm -f $(BUILD_DIR)/* hello.z64
13
+ $(BUILD_DIR)/hello.elf: $(OBJS)
15
14
 
16
- -include $(wildcard $(BUILD_DIR)/*.d)
15
+ clean:
16
+ rm -f $(BUILD_DIR) *.z64
17
+ .PHONY: clean
17
18
 
18
- .PHONY: all clean
19
+ -include $(wildcard $(BUILD_DIR)/*.d)