@jscad/cli 3.0.1-alpha.0 → 3.0.3-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +17 -15
- package/cli.conversions.test.js +71 -5
- package/cli.js +36 -40
- package/cli.parameters.test.js +5 -5
- package/example/main.js +23 -0
- package/example/part.js +7 -0
- package/package.json +7 -6
- package/src/env.js +4 -1
- package/src/parseArgs.js +94 -100
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [3.0.3-alpha.0](https://github.com/jscad/OpenJSCAD/compare/@jscad/cli@3.0.2-alpha.0...@jscad/cli@3.0.3-alpha.0) (2026-01-18)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **cli:** reworked command line parameter parsing to use commander ([225b541](https://github.com/jscad/OpenJSCAD/commit/225b5412cb92e0faa50c3c67a50b6dc5c24f424e))
|
|
11
|
+
|
|
12
|
+
## [3.0.2-alpha.0](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/cli@3.0.1-alpha.0...@jscad/cli@3.0.2-alpha.0) (2025-09-06)
|
|
13
|
+
|
|
14
|
+
**Note:** Version bump only for package @jscad/cli
|
|
15
|
+
|
|
6
16
|
## [3.0.1-alpha.0](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/cli@2.2.24...@jscad/cli@3.0.1-alpha.0) (2025-01-03)
|
|
7
17
|
|
|
8
18
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -40,8 +40,7 @@ npm install -g @jscad/cli
|
|
|
40
40
|
```
|
|
41
41
|
Once installed, the CLI can be invoked using
|
|
42
42
|
```
|
|
43
|
-
jscad
|
|
44
|
-
jscad
|
|
43
|
+
jscad --help
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
### Install the CLI as Part of a Project
|
|
@@ -54,45 +53,48 @@ npm install -D @jscad/cli
|
|
|
54
53
|
|
|
55
54
|
This will add the CLI package as a development dependency. The CLI can be invoked using
|
|
56
55
|
```
|
|
57
|
-
npx jscad
|
|
58
|
-
npx jscad
|
|
56
|
+
npx jscad --help
|
|
59
57
|
```
|
|
60
|
-
These two commands will show the
|
|
58
|
+
These two commands will show the various options of the CLI, and a general usage example.
|
|
61
59
|
|
|
62
60
|
### Using the CLI
|
|
63
61
|
|
|
64
62
|
Simply invoke 'jscad' using various options. Here are some examples.
|
|
65
63
|
|
|
64
|
+
There is also an exmaple JSCAD design in the 'example' directory.
|
|
65
|
+
|
|
66
66
|
Examples:
|
|
67
67
|
|
|
68
68
|
```jscad mydesign.js # -- convert mydesign.js to mydesign.stl as default```
|
|
69
69
|
|
|
70
|
-
```jscad
|
|
70
|
+
```jscad -o test.stl mydesign.js # -- convert mydesign.js to test.stl```
|
|
71
71
|
|
|
72
72
|
```jscad frog.stl -o test.js # -- convert frog.stl to test.js```
|
|
73
73
|
|
|
74
|
-
```jscad mydesign.js
|
|
74
|
+
```jscad -f 3mf mydesign.js # -- convert mydesign.js into 3MF, i.e. mydesign.3mf```
|
|
75
75
|
|
|
76
76
|
For multi-part models, you can pass the `generateParts` flag `-gp` to output each part as a separate, numbered file:
|
|
77
77
|
|
|
78
|
-
```jscad mydesign.js
|
|
78
|
+
```jscad -p mydesign.js # -- convert mydesign.js into mydesign-part-1-of-2.stl and mydesign-part-2-of-2.stl```
|
|
79
79
|
|
|
80
80
|
You may also pass the `zip` flag `-z` to zip generated files into one .zip file:
|
|
81
81
|
|
|
82
|
-
```jscad mydesign.js
|
|
82
|
+
```jscad -z mydesign.js # -- convert mydesign.js into mydesign.zip which contains: mydesign.stl```
|
|
83
83
|
|
|
84
|
-
```jscad
|
|
84
|
+
```jscad -p -z mydesign.js # -- convert mydesign.js into mydesign.zip which contains: mydesign-part-1-of-2.stl and mydesign-part-2-of-2.stl```
|
|
85
85
|
|
|
86
86
|
The '-o' option can be used to control where the output will be placed.
|
|
87
|
-
While, the '-
|
|
87
|
+
While, the '-f' option can be used to control the format of the output.
|
|
88
|
+
|
|
89
|
+
You can also provide the parameters to a JSCAD design by passing --<paramName> <value> to the CLI.
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
```jscad -o ouptput.stl mydesign.js -- --name "Just Me" --title "Geek"```
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
NOTE: Be sure to seperate the JSCAD design parameters with '--'
|
|
92
94
|
|
|
93
95
|
Also, design projects (directories) can be used as the input to the CLI.
|
|
94
96
|
|
|
95
|
-
```jscad
|
|
97
|
+
```jscad -o test.stl example/ # -- convert the project 'example' to test.stl```
|
|
96
98
|
|
|
97
99
|
> Note: The CLI will search for the design entry point just like NPM.
|
|
98
100
|
> - if there is a package.json file in the project, then try to load the 'main' property
|
|
@@ -118,7 +120,7 @@ ln -s node_modules/@jscad/examples ./examples
|
|
|
118
120
|
The examples are just single file designs, or multiple file projects.
|
|
119
121
|
```
|
|
120
122
|
npx jscad examples/core/booleans/basicBooleans.js -o ./test.stl
|
|
121
|
-
npx jscad examples/module-design/ -
|
|
123
|
+
npx jscad examples/module-design/ -f dxf
|
|
122
124
|
```
|
|
123
125
|
|
|
124
126
|
## Documentation
|
package/cli.conversions.test.js
CHANGED
|
@@ -96,7 +96,7 @@ test('cli (conversions STL)', (t) => {
|
|
|
96
96
|
|
|
97
97
|
t.context.file3Path = file3Path
|
|
98
98
|
|
|
99
|
-
cmd = `node ${cliPath} ${file2Path} -o ${file3Path} -v
|
|
99
|
+
cmd = `node ${cliPath} ${file2Path} -o ${file3Path} -v --add-metadata`
|
|
100
100
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
101
101
|
t.true(fs.existsSync(file3Path))
|
|
102
102
|
})
|
|
@@ -129,7 +129,7 @@ test('cli (conversions DXF)', (t) => {
|
|
|
129
129
|
|
|
130
130
|
t.context.file3Path = file3Path
|
|
131
131
|
|
|
132
|
-
cmd = `node ${cliPath} ${file2Path} -
|
|
132
|
+
cmd = `node ${cliPath} ${file2Path} -f js`
|
|
133
133
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
134
134
|
t.true(fs.existsSync(file3Path))
|
|
135
135
|
})
|
|
@@ -162,7 +162,40 @@ test('cli (conversions JSON)', (t) => {
|
|
|
162
162
|
|
|
163
163
|
t.context.file3Path = file3Path
|
|
164
164
|
|
|
165
|
-
cmd = `node ${cliPath} ${file2Path} -
|
|
165
|
+
cmd = `node ${cliPath} ${file2Path} -f js`
|
|
166
|
+
execSync(cmd, { stdio: [0, 1, 2] })
|
|
167
|
+
t.true(fs.existsSync(file3Path))
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('cli (conversions OBJ)', (t) => {
|
|
171
|
+
const testID = 17
|
|
172
|
+
|
|
173
|
+
// convert from JSCAD to OBJ
|
|
174
|
+
const file1Path = createJscad(testID)
|
|
175
|
+
t.true(fs.existsSync(file1Path))
|
|
176
|
+
|
|
177
|
+
t.context.file1Path = file1Path
|
|
178
|
+
|
|
179
|
+
const file2Name = `./test${testID}.obj`
|
|
180
|
+
const file2Path = path.resolve(cwd(), file2Name)
|
|
181
|
+
t.false(fs.existsSync(file2Path))
|
|
182
|
+
|
|
183
|
+
t.context.file2Path = file2Path
|
|
184
|
+
|
|
185
|
+
const cliPath = t.context.cliPath
|
|
186
|
+
|
|
187
|
+
let cmd = `node ${cliPath} ${file1Path} -o ${file2Path}`
|
|
188
|
+
execSync(cmd, { stdio: [0, 1, 2] })
|
|
189
|
+
t.true(fs.existsSync(file2Path))
|
|
190
|
+
|
|
191
|
+
// convert from OBJ to JS
|
|
192
|
+
const file3Name = `./test${testID}.js`
|
|
193
|
+
const file3Path = path.resolve(cwd(), file3Name)
|
|
194
|
+
t.false(fs.existsSync(file3Path))
|
|
195
|
+
|
|
196
|
+
t.context.file3Path = file3Path
|
|
197
|
+
|
|
198
|
+
cmd = `node ${cliPath} ${file2Path} -f js`
|
|
166
199
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
167
200
|
t.true(fs.existsSync(file3Path))
|
|
168
201
|
})
|
|
@@ -195,7 +228,7 @@ test('cli (conversions SVG)', (t) => {
|
|
|
195
228
|
|
|
196
229
|
t.context.file3Path = file3Path
|
|
197
230
|
|
|
198
|
-
cmd = `node ${cliPath} ${file2Path} -
|
|
231
|
+
cmd = `node ${cliPath} ${file2Path} -f js`
|
|
199
232
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
200
233
|
t.true(fs.existsSync(file3Path))
|
|
201
234
|
})
|
|
@@ -228,7 +261,40 @@ test('cli (conversions X3D)', (t) => {
|
|
|
228
261
|
|
|
229
262
|
t.context.file3Path = file3Path
|
|
230
263
|
|
|
231
|
-
cmd = `node ${cliPath} ${file2Path} -
|
|
264
|
+
cmd = `node ${cliPath} ${file2Path} -f js`
|
|
265
|
+
execSync(cmd, { stdio: [0, 1, 2] })
|
|
266
|
+
t.true(fs.existsSync(file3Path))
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('cli (conversions 3MF)', (t) => {
|
|
270
|
+
const testID = 16
|
|
271
|
+
|
|
272
|
+
// convert from JSCAD to 3MF
|
|
273
|
+
const file1Path = createJscad(testID)
|
|
274
|
+
t.true(fs.existsSync(file1Path))
|
|
275
|
+
|
|
276
|
+
t.context.file1Path = file1Path
|
|
277
|
+
|
|
278
|
+
const file2Name = `./test${testID}.3mf`
|
|
279
|
+
const file2Path = path.resolve(cwd(), file2Name)
|
|
280
|
+
t.false(fs.existsSync(file2Path))
|
|
281
|
+
|
|
282
|
+
t.context.file2Path = file2Path
|
|
283
|
+
|
|
284
|
+
const cliPath = t.context.cliPath
|
|
285
|
+
|
|
286
|
+
let cmd = `node ${cliPath} ${file1Path} -o ${file2Path}`
|
|
287
|
+
execSync(cmd, { stdio: [0, 1, 2] })
|
|
288
|
+
t.true(fs.existsSync(file2Path))
|
|
289
|
+
|
|
290
|
+
// convert from 3MF to JS
|
|
291
|
+
const file3Name = `./test${testID}.js`
|
|
292
|
+
const file3Path = path.resolve(cwd(), file3Name)
|
|
293
|
+
t.false(fs.existsSync(file3Path))
|
|
294
|
+
|
|
295
|
+
t.context.file3Path = file3Path
|
|
296
|
+
|
|
297
|
+
cmd = `node ${cliPath} ${file2Path} -f js`
|
|
232
298
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
233
299
|
t.true(fs.existsSync(file3Path))
|
|
234
300
|
})
|
package/cli.js
CHANGED
|
@@ -1,46 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// --log_all
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// e.g.
|
|
11
|
-
// jscad test.jscad
|
|
12
|
-
// jscad test.jscad -o test.stl
|
|
13
|
-
// jscad test.jscad -o test.amf
|
|
14
|
-
// jscad test.jscad -o test.dxf
|
|
15
|
-
// jscad test.scad -o testFromSCAD.jscad
|
|
16
|
-
// jscad test.scad -o test.stl
|
|
17
|
-
// jscad test.stl -o test2.stl # reprocessed: stl -> jscad -> stl
|
|
18
|
-
// jscad test.amf -o test2.jscad
|
|
19
|
-
// jscad test.jscad -of amf
|
|
20
|
-
// jscad test.jscad -of dxf
|
|
21
|
-
// jscad test.jscad -of stl
|
|
22
|
-
// jscad name_plate.jscad --name "Just Me" --title "CEO" -o amf test.amf
|
|
23
|
-
//
|
|
4
|
+
/*
|
|
5
|
+
* Command Line Interface (CLI) for converting JSCAD designs to differnt external formats.
|
|
6
|
+
*
|
|
7
|
+
* Example:
|
|
8
|
+
* node ./cli.js --help
|
|
9
|
+
*/
|
|
24
10
|
import fs from 'fs'
|
|
11
|
+
import path from 'path'
|
|
12
|
+
|
|
25
13
|
import JSZip from 'jszip'
|
|
26
14
|
|
|
27
15
|
import { supportedFormats } from '@jscad/io'
|
|
28
16
|
|
|
17
|
+
import { parseArgs } from './src/parseArgs.js'
|
|
29
18
|
import { generateOutputData } from './src/generateOutputData.js'
|
|
30
|
-
import { determineOutputNameAndFormat } from './src/determineOutputNameAndFormat.js'
|
|
31
19
|
import { writeOutput } from './src/writeOutput.js'
|
|
32
|
-
import { parseArgs } from './src/parseArgs.js'
|
|
33
|
-
|
|
34
|
-
const version = '[VI]{version}[/VI]' // version is injected by rollup
|
|
35
|
-
|
|
36
|
-
// handle arguments (inputs, outputs, etc)
|
|
37
|
-
const args = process.argv.splice(2)
|
|
38
|
-
let { inputFile, inputFormat, outputFile, outputFormat, generateParts, zip, params, addMetaData, inputIsDirectory } = parseArgs(args)
|
|
39
|
-
|
|
40
|
-
// outputs
|
|
41
|
-
const output = determineOutputNameAndFormat(outputFormat, outputFile, inputFile)
|
|
42
|
-
outputFormat = output.outputFormat
|
|
43
|
-
outputFile = output.outputFile
|
|
44
20
|
|
|
45
21
|
const clicolors = {
|
|
46
22
|
red: '\u{1b}[31m',
|
|
@@ -50,10 +26,30 @@ const clicolors = {
|
|
|
50
26
|
black: '\u{1b}[0m'
|
|
51
27
|
}
|
|
52
28
|
|
|
53
|
-
const
|
|
29
|
+
const version = '[VI]{version}[/VI]' // version is injected by rollup
|
|
30
|
+
|
|
31
|
+
// handle arguments (inputs, outputs, etc)
|
|
32
|
+
let { filepaths, outputFile, outputFormat, generateParts, zip, params, addMetaData, inputIsDirectory } = parseArgs()
|
|
33
|
+
|
|
34
|
+
// FIXME handle N input files
|
|
35
|
+
const inputFile = filepaths[0]
|
|
36
|
+
const inputFormat = path.extname(inputFile).substring(1)
|
|
37
|
+
|
|
38
|
+
// outputs
|
|
39
|
+
if (!outputFile) {
|
|
40
|
+
// create a base name from the input file
|
|
41
|
+
const fileElements = path.parse(inputFile)
|
|
42
|
+
fileElements.ext = '.' + outputFormat
|
|
43
|
+
if (fileElements.ext === '.stla') fileElements.ext = '.stl'
|
|
44
|
+
if (fileElements.ext === '.stlb') fileElements.ext = '.stl'
|
|
45
|
+
fileElements.base = undefined
|
|
46
|
+
outputFile = path.format(fileElements)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const logFileOutput = (inputPath, outputPath) => {
|
|
54
50
|
console.log(`${clicolors.blue}JSCAD: generating output ${clicolors.red}
|
|
55
|
-
from: ${clicolors.green} ${
|
|
56
|
-
to: ${clicolors.green} ${
|
|
51
|
+
from: ${clicolors.green} ${inputPath} ${clicolors.red}
|
|
52
|
+
to: ${clicolors.green} ${outputPath} ${clicolors.yellow}(${supportedFormats[outputFormat].description}) ${clicolors.black}
|
|
57
53
|
`)
|
|
58
54
|
}
|
|
59
55
|
|
|
@@ -81,14 +77,14 @@ generateOutputData(src, params, { outputFile, outputFormat, inputFile, inputForm
|
|
|
81
77
|
if (err) {
|
|
82
78
|
console.error(err)
|
|
83
79
|
} else {
|
|
84
|
-
logFileOutput(zipFilename)
|
|
80
|
+
logFileOutput(inputFile, zipFilename)
|
|
85
81
|
}
|
|
86
82
|
})
|
|
87
83
|
})
|
|
88
84
|
} else {
|
|
89
85
|
for (let i = 0; i < outputData.length; i++) {
|
|
90
86
|
const filename = outputFile.replace(/\.(\w+)$/, `-part-${i + 1}-of-${outputData.length}.$1`)
|
|
91
|
-
logFileOutput(filename)
|
|
87
|
+
logFileOutput(inputFile, filename)
|
|
92
88
|
writeOutput(filename, outputData[i])
|
|
93
89
|
}
|
|
94
90
|
}
|
|
@@ -102,12 +98,12 @@ generateOutputData(src, params, { outputFile, outputFormat, inputFile, inputForm
|
|
|
102
98
|
if (err) {
|
|
103
99
|
console.error(err)
|
|
104
100
|
} else {
|
|
105
|
-
logFileOutput(zipFilename)
|
|
101
|
+
logFileOutput(inputFile, zipFilename)
|
|
106
102
|
}
|
|
107
103
|
})
|
|
108
104
|
})
|
|
109
105
|
} else {
|
|
110
|
-
logFileOutput(outputFile)
|
|
106
|
+
logFileOutput(inputFile, outputFile)
|
|
111
107
|
writeOutput(outputFile, outputData)
|
|
112
108
|
}
|
|
113
109
|
}
|
package/cli.parameters.test.js
CHANGED
|
@@ -111,7 +111,7 @@ test('cli (single input file, output format)', (t) => {
|
|
|
111
111
|
|
|
112
112
|
const cliPath = t.context.cliPath
|
|
113
113
|
|
|
114
|
-
const cmd = `node ${cliPath} ${inputPath} -
|
|
114
|
+
const cmd = `node ${cliPath} ${inputPath} -f dxf`
|
|
115
115
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
116
116
|
t.true(fs.existsSync(outputPath))
|
|
117
117
|
})
|
|
@@ -167,7 +167,7 @@ test('cli (folder, output format)', (t) => {
|
|
|
167
167
|
|
|
168
168
|
const cliPath = t.context.cliPath
|
|
169
169
|
|
|
170
|
-
const cmd = `node ${cliPath} ${folderPath} -
|
|
170
|
+
const cmd = `node ${cliPath} ${folderPath} -f dxf`
|
|
171
171
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
172
172
|
t.true(fs.existsSync(outputPath))
|
|
173
173
|
})
|
|
@@ -188,7 +188,7 @@ test('cli (single input file, parameters)', (t) => {
|
|
|
188
188
|
|
|
189
189
|
const cliPath = t.context.cliPath
|
|
190
190
|
|
|
191
|
-
const cmd = `node ${cliPath} ${inputPath} --segments 32 --nothing "Yes"`
|
|
191
|
+
const cmd = `node ${cliPath} ${inputPath} -- --segments 32 --nothing "Yes"`
|
|
192
192
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
193
193
|
t.true(fs.existsSync(outputPath))
|
|
194
194
|
})
|
|
@@ -241,7 +241,7 @@ test('cli (single input file, multiple output files)', (t) => {
|
|
|
241
241
|
|
|
242
242
|
const cliPath = t.context.cliPath
|
|
243
243
|
|
|
244
|
-
const cmd = `node ${cliPath} ${inputPath}
|
|
244
|
+
const cmd = `node ${cliPath} -p ${inputPath}`
|
|
245
245
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
246
246
|
t.true(fs.existsSync(outputPath1))
|
|
247
247
|
t.true(fs.existsSync(outputPath2))
|
|
@@ -265,7 +265,7 @@ test('cli (single multipart input file, zipped output file)', async (t) => {
|
|
|
265
265
|
|
|
266
266
|
const cliPath = t.context.cliPath
|
|
267
267
|
|
|
268
|
-
const cmd = `node ${cliPath} ${inputPath} -
|
|
268
|
+
const cmd = `node ${cliPath} ${inputPath} -p -z`
|
|
269
269
|
execSync(cmd, { stdio: [0, 1, 2] })
|
|
270
270
|
t.true(fs.existsSync(outputPath))
|
|
271
271
|
|
package/example/main.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// example JSCAD script
|
|
2
|
+
import { flatten } from '@jscad/array-utils'
|
|
3
|
+
import { arc, ellipse } from '@jscad/modeling'
|
|
4
|
+
|
|
5
|
+
import { createPart } from './part.js'
|
|
6
|
+
|
|
7
|
+
export const getParameterDefinitions = () => {
|
|
8
|
+
return flatten([
|
|
9
|
+
{ name: 'segments', caption: 'Segments:', type: 'int', initial: 10, min: 5, max: 20, step: 1 }
|
|
10
|
+
])
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const main = (params) => {
|
|
14
|
+
// parameters
|
|
15
|
+
const segments = params.segments || 16
|
|
16
|
+
|
|
17
|
+
// shapes
|
|
18
|
+
const apath2 = arc({ segments })
|
|
19
|
+
const ageom2 = ellipse({ segments, center: [5, 5] })
|
|
20
|
+
const ageom3 = createPart(segments)
|
|
21
|
+
|
|
22
|
+
return [apath2, ageom2, ageom3]
|
|
23
|
+
}
|
package/example/part.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3-alpha.0",
|
|
4
4
|
"description": "Command Line Interface (CLI) for JSCAD",
|
|
5
5
|
"homepage": "https://openjscad.xyz/",
|
|
6
6
|
"repository": "https://github.com/jscad/OpenJSCAD.org",
|
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@jscad/array-utils": "3.0.1-alpha.0",
|
|
40
|
-
"@jscad/core": "3.0.
|
|
41
|
-
"@jscad/io": "3.0.
|
|
42
|
-
"@jscad/io-utils": "3.0.
|
|
43
|
-
"@jscad/modeling": "3.0.
|
|
40
|
+
"@jscad/core": "3.0.3-alpha.0",
|
|
41
|
+
"@jscad/io": "3.0.3-alpha.0",
|
|
42
|
+
"@jscad/io-utils": "3.0.3-alpha.0",
|
|
43
|
+
"@jscad/modeling": "3.0.3-alpha.0",
|
|
44
|
+
"commander": "^14.0.0",
|
|
44
45
|
"jszip": "^3.10.1"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"url": "https://opencollective.com/openjscad",
|
|
53
54
|
"logo": "https://opencollective.com/openjscad/logo.txt"
|
|
54
55
|
},
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "b0a83db054ec02ccddd9e19e51f86bf30f72a69e"
|
|
56
57
|
}
|
package/src/env.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import os from 'os'
|
|
2
|
+
import process from 'process'
|
|
2
3
|
|
|
3
4
|
const version = '[VI]{version}[/VI]' // version is injected by rollup
|
|
4
5
|
|
|
5
6
|
export const env = () => {
|
|
6
|
-
|
|
7
|
+
const nodeVersion = process.version
|
|
8
|
+
|
|
9
|
+
let env = 'JSCAD CLI ' + version + ', Node.js ' + nodeVersion
|
|
7
10
|
if (typeof document !== 'undefined') {
|
|
8
11
|
const w = document.defaultView
|
|
9
12
|
env = env + ' [' + w.navigator.userAgent + ']'
|
package/src/parseArgs.js
CHANGED
|
@@ -1,121 +1,115 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
|
|
4
|
+
import { Command, Option } from 'commander'
|
|
5
|
+
|
|
4
6
|
import { loading } from '@jscad/core'
|
|
5
7
|
|
|
6
|
-
import { supportedInputExtensions,
|
|
8
|
+
import { supportedInputExtensions, supportedOutputFormats } from '@jscad/io'
|
|
7
9
|
|
|
8
10
|
import { env } from './env.js'
|
|
9
11
|
|
|
10
12
|
const { getDesignEntryPoint } = loading
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const outputFormats = supportedOutputFormats()
|
|
15
|
+
const inputExtensions = supportedInputExtensions()
|
|
16
|
+
|
|
17
|
+
const isValidInputFileFormat = (input) => {
|
|
18
|
+
if (input === undefined || input === null || !(typeof input === 'string')) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
return inputExtensions.reduce((acc, format) => input.toLowerCase().endsWith('.' + format) || acc, false)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const parseArgs = () => {
|
|
25
|
+
const filepaths = [] // list of input file paths
|
|
26
|
+
const parameters = [] // parameter values for main(parameters) and serialize(parameters)
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Setup command line arguments
|
|
30
|
+
*/
|
|
31
|
+
const program = new Command()
|
|
32
|
+
program.name('cli.js')
|
|
33
|
+
program.usage('[options] <files...> -- parameter values')
|
|
34
|
+
program.argument('[files...]')
|
|
35
|
+
program.addOption(new Option('-f, --output-format <format>', 'output format').choices(outputFormats).default('stla'))
|
|
36
|
+
program.option('-p, --generate-parts', 'generate unique parts from the files', false)
|
|
37
|
+
program.option('-z, --zip', 'zip the output file contents', false)
|
|
38
|
+
program.option('-o, --output-file <filepath>', 'output file name (optional)')
|
|
39
|
+
program.option('-m, --add-metadata', 'add metadata to output format', false)
|
|
40
|
+
program.option('-v, --version', 'show version and environment information', false)
|
|
41
|
+
program.action((args) => {
|
|
42
|
+
// handle the provided arguments
|
|
43
|
+
args.forEach((arg) => {
|
|
44
|
+
try {
|
|
45
|
+
fs.statSync(arg)
|
|
46
|
+
filepaths.push(arg)
|
|
47
|
+
} catch (e) {
|
|
48
|
+
parameters.push(arg)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
program.parse()
|
|
53
|
+
|
|
54
|
+
const options = program.opts()
|
|
55
|
+
|
|
56
|
+
// show the runtime environment if requested
|
|
57
|
+
if (options.version) {
|
|
58
|
+
env()
|
|
26
59
|
}
|
|
27
60
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
// console.log(options)
|
|
62
|
+
// console.log(filepaths)
|
|
63
|
+
// console.log(parameters)
|
|
64
|
+
|
|
65
|
+
if (filepaths.length === 0) process.exit(1)
|
|
66
|
+
|
|
67
|
+
if (options.outputFile) {
|
|
68
|
+
// check that the output file name implies a valid output format
|
|
69
|
+
let outputFormat = path.extname(options.outputFile).substring(1)
|
|
70
|
+
if (outputFormat === 'stl') outputFormat = 'stla'
|
|
71
|
+
if (!outputFormats.includes(outputFormat)) {
|
|
72
|
+
console.log('ERROR: invalid output file format <' + outputFormat + '>')
|
|
73
|
+
process.exit(1)
|
|
41
74
|
}
|
|
42
|
-
|
|
75
|
+
// check for toxic combinations
|
|
76
|
+
if (filepaths.length !== 1) {
|
|
77
|
+
console.log('ERROR: multiple inputs cannot be converted to a single output file.')
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
options.outputFormat = outputFormat
|
|
81
|
+
} else {
|
|
82
|
+
options.outputFile = undefined
|
|
43
83
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else if (args[i].match(/^-o(\S.+)/)) { // -o<output>
|
|
56
|
-
outputFile = args[i]
|
|
57
|
-
outputFile = outputFile.replace(/^-o(\S+)$/, '$1')
|
|
58
|
-
} else if (args[i] === '-o') { // -o <output>
|
|
59
|
-
outputFile = args[++i]
|
|
60
|
-
} else if (args[i] === '-add-metadata') { // -metadata true/false
|
|
61
|
-
addMetaData = parseBool(args[++i])
|
|
62
|
-
} else if (args[i].match(/^--(\w+)=(.*)/)) { // params for main()
|
|
63
|
-
params[RegExp.$1] = RegExp.$2
|
|
64
|
-
} else if (args[i].match(/^--(\w+)$/)) { // params for main()
|
|
65
|
-
params[RegExp.$1] = args[++i]
|
|
66
|
-
} else if (isValidInputFileFormat(args[i])) {
|
|
67
|
-
inputFile = args[i]
|
|
68
|
-
inputFormat = getFileExtensionFromString(args[i])
|
|
69
|
-
if (!fs.statSync(inputFile).isFile()) {
|
|
70
|
-
console.log('ERROR: cannot open input file/directory <' + inputFile + '>')
|
|
71
|
-
process.exit(1)
|
|
72
|
-
}
|
|
73
|
-
} else if (args[i].match(/^-v$/)) { // show the version and the environment information
|
|
74
|
-
env()
|
|
75
|
-
} else {
|
|
76
|
-
inputFile = args[i]
|
|
77
|
-
if (fs.statSync(inputFile).isDirectory()) {
|
|
78
|
-
inputIsDirectory = true
|
|
79
|
-
// get actual design entry point if applicable (if passed a folder as input etc)
|
|
80
|
-
inputFile = getDesignEntryPoint(fs, inputFile)
|
|
81
|
-
if (!inputFile) {
|
|
82
|
-
console.log('ERROR: could not determine entry point of project.')
|
|
83
|
-
console.log('Verify main or index exists')
|
|
84
|
-
process.exit(1)
|
|
85
|
-
}
|
|
86
|
-
inputFormat = path.extname(inputFile).substring(1)
|
|
87
|
-
} else {
|
|
88
|
-
console.log('ERROR: invalid file name or argument <' + args[i] + '>')
|
|
89
|
-
console.log("Type 'jscad' for a list of supported types")
|
|
84
|
+
|
|
85
|
+
// check for use of a directory, and determine the design entry point
|
|
86
|
+
options.inputIsDirectory = false
|
|
87
|
+
if (filepaths.length === 1) {
|
|
88
|
+
if (fs.statSync(filepaths[0]).isDirectory()) {
|
|
89
|
+
options.inputIsDirectory = true
|
|
90
|
+
// get actual design entry point
|
|
91
|
+
const filepath = getDesignEntryPoint(fs, filepaths[0])
|
|
92
|
+
if (!filepath) {
|
|
93
|
+
console.log('ERROR: could not determine entry point of project <' + filepaths[0] + '>')
|
|
94
|
+
console.log('Verify project main or index exists')
|
|
90
95
|
process.exit(1)
|
|
91
96
|
}
|
|
97
|
+
filepaths[0] = filepath // use the entry point for conversion
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
|
-
// exit if a input file was not provided
|
|
95
|
-
if (!inputFile) process.exit(1)
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
if (!outputFormats.includes(outputFormat)) {
|
|
105
|
-
console.log('ERROR: invalid output format <' + outputFormat + '>')
|
|
106
|
-
console.log("Type 'jscad' for a list of supported types")
|
|
107
|
-
process.exit(1)
|
|
108
|
-
}
|
|
101
|
+
// check that all input files are valid formats for conversion
|
|
102
|
+
filepaths.forEach((filepath) => {
|
|
103
|
+
if (!isValidInputFileFormat(filepath)) {
|
|
104
|
+
console.log('ERROR: invalid input file format <' + filepath + '>')
|
|
105
|
+
process.exit(1)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
zip,
|
|
117
|
-
params,
|
|
118
|
-
addMetaData,
|
|
119
|
-
inputIsDirectory
|
|
120
|
-
}
|
|
109
|
+
options.filepaths = filepaths
|
|
110
|
+
options.params = parameters
|
|
111
|
+
|
|
112
|
+
// console.log('RETURN',options)
|
|
113
|
+
|
|
114
|
+
return options
|
|
121
115
|
}
|