canvaslms-cli 1.3.3 → 1.4.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/commands/announcements.js +102 -80
- package/commands/assignments.js +100 -130
- package/commands/config.js +239 -242
- package/commands/grades.js +201 -205
- package/commands/list.js +68 -70
- package/commands/profile.js +35 -34
- package/commands/submit.js +98 -151
- package/lib/api-client.js +4 -8
- package/lib/config-validator.js +60 -63
- package/lib/config.js +103 -135
- package/lib/file-upload.js +73 -78
- package/lib/interactive.js +180 -132
- package/package.json +4 -2
- package/src/index.js +25 -47
package/lib/config.js
CHANGED
|
@@ -1,135 +1,103 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration management for Canvas CLI
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Configuration file path in user's home directory
|
|
10
|
-
const CONFIG_FILE = path.join(os.homedir(), '.canvaslms-cli-config.json');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
106
|
-
fs.unlinkSync(CONFIG_FILE);
|
|
107
|
-
console.log('✅ Configuration file deleted successfully.');
|
|
108
|
-
return true;
|
|
109
|
-
} else {
|
|
110
|
-
console.log('ℹ️ No configuration file found.');
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.error(`❌ Error deleting configuration: ${error.message}`);
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get the Canvas instance configuration
|
|
121
|
-
*/
|
|
122
|
-
function getInstanceConfig() {
|
|
123
|
-
return loadConfig();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
module.exports = {
|
|
127
|
-
loadConfig,
|
|
128
|
-
getInstanceConfig,
|
|
129
|
-
saveConfig,
|
|
130
|
-
getConfigPath,
|
|
131
|
-
configExists,
|
|
132
|
-
readConfig,
|
|
133
|
-
deleteConfig,
|
|
134
|
-
getDefaultConfig
|
|
135
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Canvas CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
|
|
9
|
+
// Configuration file path in user's home directory
|
|
10
|
+
const CONFIG_FILE = path.join(os.homedir(), '.canvaslms-cli-config.json');
|
|
11
|
+
|
|
12
|
+
// Get default configuration structure
|
|
13
|
+
export function getDefaultConfig() {
|
|
14
|
+
return {
|
|
15
|
+
domain: '',
|
|
16
|
+
token: '',
|
|
17
|
+
createdAt: new Date().toISOString(),
|
|
18
|
+
lastUpdated: new Date().toISOString()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Load configuration from file
|
|
23
|
+
export function loadConfig() {
|
|
24
|
+
try {
|
|
25
|
+
// Load from config file
|
|
26
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
27
|
+
const configData = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
28
|
+
if (configData.domain && configData.token) {
|
|
29
|
+
// Clean up domain - remove https:// and trailing slashes
|
|
30
|
+
const domain = configData.domain.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
31
|
+
return { domain, token: configData.token };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// No configuration found
|
|
35
|
+
console.error('No Canvas configuration found!');
|
|
36
|
+
console.error('\nPlease run "canvas config setup" to configure your Canvas credentials.');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(`Error loading configuration: ${error.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Save configuration to file
|
|
45
|
+
export function saveConfig(domain, token) {
|
|
46
|
+
try {
|
|
47
|
+
const config = {
|
|
48
|
+
domain: domain.replace(/^https?:\/\//, '').replace(/\/$/, ''),
|
|
49
|
+
token,
|
|
50
|
+
createdAt: new Date().toISOString(),
|
|
51
|
+
lastUpdated: new Date().toISOString()
|
|
52
|
+
};
|
|
53
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`Error saving configuration: ${error.message}`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if config file exists
|
|
62
|
+
export function configExists() {
|
|
63
|
+
return fs.existsSync(CONFIG_FILE);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get config file path
|
|
67
|
+
export function getConfigPath() {
|
|
68
|
+
return CONFIG_FILE;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Delete config file
|
|
72
|
+
export function deleteConfig() {
|
|
73
|
+
try {
|
|
74
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
75
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`Error deleting configuration: ${error.message}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Read current configuration (if exists)
|
|
86
|
+
export function readConfig() {
|
|
87
|
+
try {
|
|
88
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
89
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`Error reading configuration: ${error.message}`);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the Canvas instance configuration
|
|
100
|
+
*/
|
|
101
|
+
export function getInstanceConfig() {
|
|
102
|
+
return loadConfig();
|
|
103
|
+
}
|
package/lib/file-upload.js
CHANGED
|
@@ -1,78 +1,73 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File upload utilities for Canvas
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Upload single file to Canvas and return the file ID
|
|
12
|
-
*/
|
|
13
|
-
async function uploadSingleFileToCanvas(courseId, assignmentId, filePath) {
|
|
14
|
-
try {
|
|
15
|
-
// Check if file exists
|
|
16
|
-
if (!fs.existsSync(filePath)) {
|
|
17
|
-
throw new Error(`File not found: ${filePath}`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const fileName = require('path').basename(filePath);
|
|
21
|
-
const fileContent = fs.readFileSync(filePath);
|
|
22
|
-
|
|
23
|
-
// Step 1: Get upload URL from Canvas
|
|
24
|
-
const uploadParams = [
|
|
25
|
-
`name=${encodeURIComponent(fileName)}`,
|
|
26
|
-
`size=${fileContent.length}`,
|
|
27
|
-
'parent_folder_path=/assignments'
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const uploadData = await makeCanvasRequest('post', `courses/${courseId}/assignments/${assignmentId}/submissions/self/files`, uploadParams);
|
|
31
|
-
|
|
32
|
-
if (!uploadData.upload_url) {
|
|
33
|
-
throw new Error('Failed to get upload URL from Canvas');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Step 2: Upload file to the provided URL
|
|
37
|
-
const form = new FormData();
|
|
38
|
-
|
|
39
|
-
// Add all the required fields from Canvas response
|
|
40
|
-
Object.keys(uploadData.upload_params).forEach(key => {
|
|
41
|
-
form.append(key, uploadData.upload_params[key]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Add the file
|
|
45
|
-
form.append('file', fileContent, fileName);
|
|
46
|
-
|
|
47
|
-
const uploadResponse = await axios.post(uploadData.upload_url, form, {
|
|
48
|
-
headers: form.getHeaders(),
|
|
49
|
-
maxRedirects: 0,
|
|
50
|
-
validateStatus: (status) => status < 400
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Return the file ID for later submission
|
|
54
|
-
return uploadData.id || uploadResponse.data.id;
|
|
55
|
-
|
|
56
|
-
} catch (error) {
|
|
57
|
-
throw new Error(`Failed to upload file ${filePath}: ${error.message}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Submit assignment with uploaded files
|
|
63
|
-
*/
|
|
64
|
-
async function submitAssignmentWithFiles(courseId, assignmentId, fileIds) {
|
|
65
|
-
const submissionData = {
|
|
66
|
-
submission: {
|
|
67
|
-
submission_type: 'online_upload',
|
|
68
|
-
file_ids: fileIds
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
return await makeCanvasRequest('post', `courses/${courseId}/assignments/${assignmentId}/submissions`, [], JSON.stringify(submissionData));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
module.exports = {
|
|
76
|
-
uploadSingleFileToCanvas,
|
|
77
|
-
submitAssignmentWithFiles
|
|
78
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* File upload utilities for Canvas
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import FormData from 'form-data';
|
|
8
|
+
import { makeCanvasRequest } from './api-client.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Upload single file to Canvas and return the file ID
|
|
12
|
+
*/
|
|
13
|
+
export async function uploadSingleFileToCanvas(courseId, assignmentId, filePath) {
|
|
14
|
+
try {
|
|
15
|
+
// Check if file exists
|
|
16
|
+
if (!fs.existsSync(filePath)) {
|
|
17
|
+
throw new Error(`File not found: ${filePath}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const fileName = require('path').basename(filePath);
|
|
21
|
+
const fileContent = fs.readFileSync(filePath);
|
|
22
|
+
|
|
23
|
+
// Step 1: Get upload URL from Canvas
|
|
24
|
+
const uploadParams = [
|
|
25
|
+
`name=${encodeURIComponent(fileName)}`,
|
|
26
|
+
`size=${fileContent.length}`,
|
|
27
|
+
'parent_folder_path=/assignments'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const uploadData = await makeCanvasRequest('post', `courses/${courseId}/assignments/${assignmentId}/submissions/self/files`, uploadParams);
|
|
31
|
+
|
|
32
|
+
if (!uploadData.upload_url) {
|
|
33
|
+
throw new Error('Failed to get upload URL from Canvas');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Step 2: Upload file to the provided URL
|
|
37
|
+
const form = new FormData();
|
|
38
|
+
|
|
39
|
+
// Add all the required fields from Canvas response
|
|
40
|
+
Object.keys(uploadData.upload_params).forEach(key => {
|
|
41
|
+
form.append(key, uploadData.upload_params[key]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Add the file
|
|
45
|
+
form.append('file', fileContent, fileName);
|
|
46
|
+
|
|
47
|
+
const uploadResponse = await axios.post(uploadData.upload_url, form, {
|
|
48
|
+
headers: form.getHeaders(),
|
|
49
|
+
maxRedirects: 0,
|
|
50
|
+
validateStatus: (status) => status < 400
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Return the file ID for later submission
|
|
54
|
+
return uploadData.id || uploadResponse.data.id;
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(`Failed to upload file ${filePath}: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Submit assignment with uploaded files
|
|
63
|
+
*/
|
|
64
|
+
export async function submitAssignmentWithFiles(courseId, assignmentId, fileIds) {
|
|
65
|
+
const submissionData = {
|
|
66
|
+
submission: {
|
|
67
|
+
submission_type: 'online_upload',
|
|
68
|
+
file_ids: fileIds
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return await makeCanvasRequest('post', `courses/${courseId}/assignments/${assignmentId}/submissions`, [], JSON.stringify(submissionData));
|
|
73
|
+
}
|