track-cli 4.0.3 → 4.1.0-rc1
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/esm/_dnt.shims.d.ts +1 -1
- package/esm/_dnt.test_shims.d.ts +20 -0
- package/esm/_dnt.test_shims.js +77 -0
- package/esm/deps/deno.land/std@0.195.0/_util/diff.d.ts +26 -0
- package/esm/deps/deno.land/std@0.195.0/_util/diff.js +311 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_constants.d.ts +1 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_constants.js +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_format.d.ts +1 -0
- package/esm/deps/deno.land/std@0.195.0/assert/_format.js +23 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.d.ts +18 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.js +32 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.d.ts +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.js +38 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.d.ts +17 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.js +45 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_false.d.ts +4 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_false.js +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.d.ts +8 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.js +38 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.d.ts +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.js +26 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_match.js +13 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.d.ts +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.js +37 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.js +14 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.d.ts +11 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.js +20 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.js +78 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.d.ts +64 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.js +50 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.d.ts +23 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.js +60 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.d.ts +5 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.js +13 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.d.ts +54 -0
- package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.js +44 -0
- package/esm/deps/deno.land/std@0.195.0/assert/equal.d.ts +6 -0
- package/esm/deps/deno.land/std@0.195.0/assert/equal.js +102 -0
- package/esm/deps/deno.land/std@0.195.0/assert/fail.d.ts +4 -0
- package/esm/deps/deno.land/std@0.195.0/assert/fail.js +9 -0
- package/esm/deps/deno.land/std@0.195.0/assert/mod.d.ts +32 -0
- package/esm/deps/deno.land/std@0.195.0/assert/mod.js +33 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.d.ts +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.js +7 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unreachable.d.ts +2 -0
- package/esm/deps/deno.land/std@0.195.0/assert/unreachable.js +6 -0
- package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.d.ts +70 -0
- package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.js +321 -0
- package/esm/deps/deno.land/std@0.195.0/testing/asserts.d.ts +329 -0
- package/esm/deps/deno.land/std@0.195.0/testing/asserts.js +330 -0
- package/esm/deps/deno.land/std@0.195.0/testing/bdd.d.ts +440 -0
- package/esm/deps/deno.land/std@0.195.0/testing/bdd.js +215 -0
- package/esm/deps/deno.land/std@0.195.0/testing/mock.d.ts +110 -0
- package/esm/deps/deno.land/std@0.195.0/testing/mock.js +746 -0
- package/esm/src/action/clone.js +4 -3
- package/esm/src/action/frontend-template-switch.d.ts +1 -0
- package/esm/src/action/frontend-template-switch.js +56 -0
- package/esm/src/action/frontend-template.d.ts +2 -0
- package/esm/src/action/frontend-template.js +12 -0
- package/esm/src/main.js +9 -2
- package/esm/src/meta.d.ts +1 -1
- package/esm/src/meta.js +108 -25
- package/esm/src/orca/client.js +1 -1
- package/esm/src/shared/config.d.ts +1 -0
- package/esm/src/shared/file.d.ts +1 -0
- package/esm/src/shared/file.js +32 -0
- package/esm/src/shared/mod.d.ts +1 -1
- package/esm/src/shared/mod.js +10 -2
- package/esm/src/shared/types.d.ts +15 -0
- package/esm/src/shared/types.js +1 -0
- package/esm/src/track/client.d.ts +4 -1
- package/esm/src/track/test.d.ts +5 -2
- package/esm/src/track/test.js +18 -2
- package/esm/src/track/training.d.ts +4 -1
- package/esm/src/track/training.js +9 -0
- package/esm/test/shared/config_test.d.ts +1 -0
- package/esm/test/shared/config_test.js +57 -0
- package/esm/test/shared/file_test.d.ts +1 -0
- package/esm/test/shared/file_test.js +265 -0
- package/esm/test/shared/mod_test.d.ts +1 -0
- package/esm/test/shared/mod_test.js +353 -0
- package/package.json +2 -1
- package/test_runner.js +186 -0
package/esm/src/action/clone.js
CHANGED
|
@@ -8,9 +8,9 @@ import { TrainingClient } from "../track/training.js";
|
|
|
8
8
|
import { exists } from "../../deps/deno.land/std@0.195.0/fs/mod.js";
|
|
9
9
|
export async function clone(url, options) {
|
|
10
10
|
// console.log(`url: ${url}`);
|
|
11
|
-
// console.log(`target_dir: ${targetDir}`);
|
|
12
|
-
// console.log(`basicAuthUser: ${basicAuthUser}`);
|
|
13
|
-
// console.log(`basicAuthPassword: ${basicAuthPassword}`);
|
|
11
|
+
// console.log(`target_dir: ${options.targetDir}`);
|
|
12
|
+
// console.log(`basicAuthUser: ${options.basicAuthUser}`);
|
|
13
|
+
// console.log(`basicAuthPassword: ${options.basicAuthPassword}`);
|
|
14
14
|
const cloneUrl = new CloneUrl(url);
|
|
15
15
|
const basicAuth = options?.basicAuthUser
|
|
16
16
|
? {
|
|
@@ -55,6 +55,7 @@ export async function clone(url, options) {
|
|
|
55
55
|
token: cloneUrl.token,
|
|
56
56
|
cookies: api.getCookies(),
|
|
57
57
|
programmingLanguage: result.programmingLanguage,
|
|
58
|
+
frontendTemplate: codingContext.selectedFrontendTemplate?.seriesCode,
|
|
58
59
|
});
|
|
59
60
|
await config.save(trackConfig, {
|
|
60
61
|
path: `${challengeDir}/.track/config.json`,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function switchFrontendTemplate(): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as dntShim from "../../_dnt.shims.js";
|
|
2
|
+
import { load as loadConfig, save as saveConfig, } from "../shared/config.js";
|
|
3
|
+
import { OtherError } from "../shared/errors.js";
|
|
4
|
+
import { ChallengeStyle } from "../shared/types.js";
|
|
5
|
+
import { archiveExistingChallengeFiles, chooseFrontendTemplate, getCommonChallengeContext, trackClientFromConfig, } from "../shared/mod.js";
|
|
6
|
+
import { unzip } from "../shared/file.js";
|
|
7
|
+
export async function switchFrontendTemplate() {
|
|
8
|
+
const config = await loadConfig();
|
|
9
|
+
const api = trackClientFromConfig(config);
|
|
10
|
+
const [codingContext, challenge] = await getCommonChallengeContext(config, api);
|
|
11
|
+
if (challenge.style !== ChallengeStyle.Frontend) {
|
|
12
|
+
throw new OtherError("This challenge is not a frontend challenge");
|
|
13
|
+
}
|
|
14
|
+
// Get available frontend templates from Track
|
|
15
|
+
const allFrontendTemplates = await api.frontendTemplates();
|
|
16
|
+
// Filter by allowed templates for this challenge
|
|
17
|
+
const allowedCodes = challenge.allowedTemplateSeriesCodes;
|
|
18
|
+
const availableTemplates = allowedCodes && allowedCodes.length > 0
|
|
19
|
+
? allFrontendTemplates.filter((t) => allowedCodes.includes(t.seriesCode))
|
|
20
|
+
: allFrontendTemplates;
|
|
21
|
+
if (availableTemplates.length === 0) {
|
|
22
|
+
throw new OtherError("This challenge does not support changing frontend templates");
|
|
23
|
+
}
|
|
24
|
+
console.log("Your existing code will be archived in this directory and the original challenge code for the new template will be downloaded");
|
|
25
|
+
const shouldContinue = dntShim.confirm("Are you sure you want to continue?");
|
|
26
|
+
if (!shouldContinue) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const desiredTemplate = chooseFrontendTemplate(availableTemplates);
|
|
30
|
+
if (desiredTemplate.seriesCode ===
|
|
31
|
+
codingContext.selectedFrontendTemplate?.seriesCode) {
|
|
32
|
+
// already using this template
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Get template details with presigned URLs
|
|
36
|
+
const templateDetails = await api.frontendTemplate(desiredTemplate.seriesCode);
|
|
37
|
+
if (!templateDetails.downloadUrl) {
|
|
38
|
+
throw new OtherError("Template does not have a download URL available");
|
|
39
|
+
}
|
|
40
|
+
const currentDir = dntShim.Deno.cwd();
|
|
41
|
+
await archiveExistingChallengeFiles(config.orgName);
|
|
42
|
+
// Download and extract the template ZIP file
|
|
43
|
+
const templateResp = await dntShim.fetch(templateDetails.downloadUrl);
|
|
44
|
+
if (!templateResp.ok) {
|
|
45
|
+
throw new OtherError(`Failed to download template: ${templateResp.status} ${templateResp.statusText}`);
|
|
46
|
+
}
|
|
47
|
+
// Extract template files
|
|
48
|
+
const templateZip = new Uint8Array(await templateResp.arrayBuffer());
|
|
49
|
+
await unzip(templateZip, currentDir);
|
|
50
|
+
const updatedConfig = {
|
|
51
|
+
...config,
|
|
52
|
+
frontendTemplate: desiredTemplate.seriesCode,
|
|
53
|
+
};
|
|
54
|
+
await saveConfig(updatedConfig);
|
|
55
|
+
console.debug("Frontend template switch completed.");
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Prompt } from "../shared/mod.js";
|
|
2
|
+
export function chooseFrontendTemplate(availableTemplates) {
|
|
3
|
+
while (true) {
|
|
4
|
+
const desiredTemplateIndex = Prompt.select("Please select a frontend template:", availableTemplates.map((t) => t.seriesName));
|
|
5
|
+
if (desiredTemplateIndex !== undefined) {
|
|
6
|
+
return availableTemplates[desiredTemplateIndex];
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
console.log("An invalid frontend template was specified");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
package/esm/src/main.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as dntShim from "../_dnt.shims.js";
|
|
|
4
4
|
import { clone as cloneAction } from "./action/clone.js";
|
|
5
5
|
import { run as runAction } from "./action/run.js";
|
|
6
6
|
import { lang as langAction } from "./action/lang.js";
|
|
7
|
+
import { switchFrontendTemplate as frontendTemplateAction } from "./action/frontend-template-switch.js";
|
|
7
8
|
import { reset as resetAction } from "./action/reset.js";
|
|
8
9
|
import { pull as pullAction } from "./action/pull.js";
|
|
9
10
|
import { add as addAction } from "./action/add.js";
|
|
@@ -37,6 +38,11 @@ import { Command } from "../deps/deno.land/x/cliffy@v0.25.7/command/mod.js";
|
|
|
37
38
|
.action(async () => {
|
|
38
39
|
await langAction();
|
|
39
40
|
});
|
|
41
|
+
const frontendTemplateCommand = new Command()
|
|
42
|
+
.description("Change the frontend template used for this challenge.")
|
|
43
|
+
.action(async () => {
|
|
44
|
+
await frontendTemplateAction();
|
|
45
|
+
});
|
|
40
46
|
const resetCommand = new Command()
|
|
41
47
|
.description("Reset the challenge files back to their starting point and archive your current challenge files.")
|
|
42
48
|
.action(async () => {
|
|
@@ -65,7 +71,7 @@ import { Command } from "../deps/deno.land/x/cliffy@v0.25.7/command/mod.js";
|
|
|
65
71
|
.action(async () => {
|
|
66
72
|
await statusAction();
|
|
67
73
|
});
|
|
68
|
-
if (!await checkUpdates()) {
|
|
74
|
+
if (!(await checkUpdates())) {
|
|
69
75
|
dntShim.Deno.exit(1);
|
|
70
76
|
}
|
|
71
77
|
await new Command()
|
|
@@ -78,6 +84,7 @@ import { Command } from "../deps/deno.land/x/cliffy@v0.25.7/command/mod.js";
|
|
|
78
84
|
.command("clone", cloneCommand)
|
|
79
85
|
.command("run", runCommand)
|
|
80
86
|
.command("lang", langCommand)
|
|
87
|
+
.command("frontend-template", frontendTemplateCommand)
|
|
81
88
|
.command("reset", resetCommand)
|
|
82
89
|
.command("pull", pullCommand)
|
|
83
90
|
.command("add", addCommand)
|
|
@@ -87,7 +94,7 @@ import { Command } from "../deps/deno.land/x/cliffy@v0.25.7/command/mod.js";
|
|
|
87
94
|
dntShim.Deno.exit(0);
|
|
88
95
|
})().catch((err) => {
|
|
89
96
|
if (err instanceof TrackError) {
|
|
90
|
-
console.error(`${red("[ERROR]")} ${err.message}`);
|
|
97
|
+
console.error(`${red("[ERROR]")} ${"message" in err ? String(err.message) : String(err)}`);
|
|
91
98
|
dntShim.Deno.exit(1);
|
|
92
99
|
}
|
|
93
100
|
else {
|
package/esm/src/meta.d.ts
CHANGED
package/esm/src/meta.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as colors from "../deps/deno.land/std@0.195.0/fmt/colors.js";
|
|
2
2
|
// @ts-ignore: has no exported member
|
|
3
3
|
import { CookieJar, fetch } from "node-fetch-cookies";
|
|
4
|
-
export const VERSION = "4.0
|
|
4
|
+
export const VERSION = "4.1.0-rc1";
|
|
5
5
|
export const DESCRIPTION = "A CLI for interacting with tracks.run and running code tests on track's servers";
|
|
6
6
|
const VERSION_RE = /^(\d+)\.(\d+)\.(\d+)(?:-?(.*))$/;
|
|
7
7
|
function parseSemver(s) {
|
|
@@ -9,43 +9,126 @@ function parseSemver(s) {
|
|
|
9
9
|
if (!match) {
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
|
-
return [
|
|
13
|
-
parseInt(match[1]),
|
|
14
|
-
parseInt(match[2]),
|
|
15
|
-
parseInt(match[3]),
|
|
16
|
-
match[4],
|
|
17
|
-
];
|
|
12
|
+
return [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), match[4]];
|
|
18
13
|
}
|
|
19
14
|
export async function checkUpdates() {
|
|
20
15
|
const res = await fetch(new CookieJar(), "https://track-cli.s3-ap-northeast-1.amazonaws.com/version.json");
|
|
21
16
|
const publishedVersion = await res.json();
|
|
22
|
-
const supportedFrom = parseSemver(publishedVersion.supported_from) ??
|
|
23
|
-
|
|
17
|
+
const supportedFrom = parseSemver(publishedVersion.supported_from) ?? [
|
|
18
|
+
1,
|
|
19
|
+
0,
|
|
20
|
+
0,
|
|
21
|
+
"",
|
|
22
|
+
];
|
|
24
23
|
const latest = parseSemver(publishedVersion.latest) ?? [1, 0, 0, ""];
|
|
25
24
|
const current = parseSemver(VERSION);
|
|
26
|
-
// deno-fmt-ignore
|
|
27
25
|
if (parseSemver(VERSION) < supportedFrom) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.log("| |");
|
|
35
|
-
console.log("╰─────────────────────────────────────────────────────────────────────────────────────────────╯");
|
|
26
|
+
new Box({ width: 95 }).printBox([
|
|
27
|
+
{ center: true },
|
|
28
|
+
[
|
|
29
|
+
applyFormatting("!!! OUTDATED !!!", (s) => colors.bold(colors.red(s))),
|
|
30
|
+
],
|
|
31
|
+
], [{ left: 22 }, ["This version of Track CLI is no longer supported."]], [{ left: 22 + 11 }, [`Installed version: ${VERSION}`]], [{ left: 22 + 11 }, [` Latest version: ${publishedVersion.latest}`]]);
|
|
36
32
|
return false;
|
|
37
33
|
}
|
|
38
34
|
else if (current < latest) {
|
|
39
|
-
|
|
40
|
-
console.log("| |");
|
|
41
|
-
console.log("| New version of Track CLI is available! |");
|
|
42
|
-
console.log(`| Current version: ${VERSION.padEnd(20)} |`);
|
|
43
|
-
console.log(`| Latest version: ${publishedVersion.latest.padEnd(20)} |`);
|
|
44
|
-
console.log("| |");
|
|
45
|
-
console.log("╰─────────────────────────────────────────────────────────────────────────────────────────────╯");
|
|
35
|
+
new Box({ width: 95 }).printBox([{ left: 22 }, ["A new version of Track CLI is available!"]], [{ left: 22 + 11 }, [`Installed version: ${VERSION}`]], [{ left: 22 + 11 }, [` Latest version: ${publishedVersion.latest}`]]);
|
|
46
36
|
return true;
|
|
47
37
|
}
|
|
48
38
|
else {
|
|
49
39
|
return true;
|
|
50
40
|
}
|
|
51
41
|
}
|
|
42
|
+
function applyFormatting(s, formatting) {
|
|
43
|
+
return { textWidth: s.length, formatted: formatting(s) };
|
|
44
|
+
}
|
|
45
|
+
class Box {
|
|
46
|
+
/**
|
|
47
|
+
* Inclusive of the pipes and the 1 space padding on each side:
|
|
48
|
+
* `"| content |"` has width 11.
|
|
49
|
+
*/
|
|
50
|
+
constructor(p) {
|
|
51
|
+
Object.defineProperty(this, "p", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
configurable: true,
|
|
54
|
+
writable: true,
|
|
55
|
+
value: p
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
print(message) {
|
|
59
|
+
console.log(message);
|
|
60
|
+
}
|
|
61
|
+
printBox(...lines) {
|
|
62
|
+
this.printTop();
|
|
63
|
+
this.printLine({ left: 0 }, [""]);
|
|
64
|
+
for (const [align, content] of lines) {
|
|
65
|
+
this.printLine(align, content);
|
|
66
|
+
}
|
|
67
|
+
this.printLine({ left: 0 }, [""]);
|
|
68
|
+
this.printBottom();
|
|
69
|
+
}
|
|
70
|
+
printTop() {
|
|
71
|
+
const message = Box.CORNERS[0][0] +
|
|
72
|
+
Box.HORIZONTAL.repeat(this.p.width - 2) +
|
|
73
|
+
Box.CORNERS[0][1];
|
|
74
|
+
this.print(message);
|
|
75
|
+
}
|
|
76
|
+
printBottom() {
|
|
77
|
+
const message = Box.CORNERS[1][0] +
|
|
78
|
+
Box.HORIZONTAL.repeat(this.p.width - 2) +
|
|
79
|
+
Box.CORNERS[1][1];
|
|
80
|
+
this.print(message);
|
|
81
|
+
}
|
|
82
|
+
printLine(align, content) {
|
|
83
|
+
let contentWidth = 0;
|
|
84
|
+
const parts = [];
|
|
85
|
+
for (const part of content) {
|
|
86
|
+
if (typeof part === "string") {
|
|
87
|
+
contentWidth += part.length;
|
|
88
|
+
parts.push(part);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
contentWidth += part.textWidth;
|
|
92
|
+
parts.push(part.formatted);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const innerWidth = this.p.width - 4;
|
|
96
|
+
let leftPadding = 0;
|
|
97
|
+
if (align.center) {
|
|
98
|
+
leftPadding = Math.max(0, Math.floor((innerWidth - contentWidth) / 2));
|
|
99
|
+
}
|
|
100
|
+
else if (align.left !== undefined) {
|
|
101
|
+
leftPadding = align.left;
|
|
102
|
+
}
|
|
103
|
+
const rightPadding = innerWidth - leftPadding - contentWidth;
|
|
104
|
+
const message = [
|
|
105
|
+
Box.PIPE,
|
|
106
|
+
" ".repeat(1 + leftPadding),
|
|
107
|
+
...parts,
|
|
108
|
+
rightPadding > 0 ? " ".repeat(rightPadding + 1) : "",
|
|
109
|
+
rightPadding >= 0 ? Box.PIPE : "",
|
|
110
|
+
].join("");
|
|
111
|
+
this.print(message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
Object.defineProperty(Box, "PIPE", {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
configurable: true,
|
|
117
|
+
writable: true,
|
|
118
|
+
value: "|"
|
|
119
|
+
});
|
|
120
|
+
Object.defineProperty(Box, "HORIZONTAL", {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
configurable: true,
|
|
123
|
+
writable: true,
|
|
124
|
+
value: "─"
|
|
125
|
+
});
|
|
126
|
+
Object.defineProperty(Box, "CORNERS", {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
configurable: true,
|
|
129
|
+
writable: true,
|
|
130
|
+
value: [
|
|
131
|
+
["╭", "╮"],
|
|
132
|
+
["╰", "╯"],
|
|
133
|
+
]
|
|
134
|
+
});
|
package/esm/src/orca/client.js
CHANGED
package/esm/src/shared/file.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare function getAllDirFiles(p: string): Promise<string[]>;
|
|
|
24
24
|
export declare function toNixStyle(p: string): string;
|
|
25
25
|
export declare function toNativeStyle(p: string): string;
|
|
26
26
|
export declare function untgz(compressed: Uint8Array, dest: string): Promise<void>;
|
|
27
|
+
export declare function unzip(zipData: Uint8Array, dest: string): Promise<void>;
|
|
27
28
|
export type GetUniqueDirNameOptions = {
|
|
28
29
|
root?: string;
|
|
29
30
|
};
|
package/esm/src/shared/file.js
CHANGED
|
@@ -97,6 +97,38 @@ export async function untgz(compressed, dest) {
|
|
|
97
97
|
cwd: dest,
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
+
export async function unzip(zipData, dest) {
|
|
101
|
+
const JSZip = (await import("jszip")).default;
|
|
102
|
+
try {
|
|
103
|
+
// destination directory exists
|
|
104
|
+
await fs.ensureDir(dest);
|
|
105
|
+
const zip = new JSZip();
|
|
106
|
+
await zip.loadAsync(zipData);
|
|
107
|
+
const promises = [];
|
|
108
|
+
zip.forEach((relativePath, file) => {
|
|
109
|
+
if (!file.dir) {
|
|
110
|
+
// file
|
|
111
|
+
promises.push((async () => {
|
|
112
|
+
const content = await file.async("uint8array");
|
|
113
|
+
const filePath = path.join(dest, relativePath);
|
|
114
|
+
await fs.ensureFile(filePath);
|
|
115
|
+
await dntShim.Deno.writeFile(filePath, content);
|
|
116
|
+
})());
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// directory
|
|
120
|
+
promises.push((async () => {
|
|
121
|
+
const dirPath = path.join(dest, relativePath);
|
|
122
|
+
await fs.ensureDir(dirPath);
|
|
123
|
+
})());
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
await Promise.all(promises);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw new Error(`Failed to extract ZIP file: ${error}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
100
132
|
export async function getUniqueDirName(base, options) {
|
|
101
133
|
let counter = 1;
|
|
102
134
|
const root = options?.root ?? dntShim.Deno.cwd();
|
package/esm/src/shared/mod.d.ts
CHANGED
|
@@ -39,5 +39,5 @@ export type ArchiveExistingChallengeFilesOptions = {
|
|
|
39
39
|
selectedLanguage?: ProgrammingLanguageInfo;
|
|
40
40
|
};
|
|
41
41
|
export declare function archiveExistingChallengeFiles(orgName: string): Promise<void>;
|
|
42
|
+
export { chooseFrontendTemplate } from "../action/frontend-template.js";
|
|
42
43
|
export declare function trackClientFromConfig(config: TrackConfig): TrackClient;
|
|
43
|
-
export {};
|
package/esm/src/shared/mod.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as dntShim from "../../_dnt.shims.js";
|
|
|
2
2
|
import { save as saveConfig, } from "./config.js";
|
|
3
3
|
import { BadTarballURL, ChallengeAlreadyFinished, OtherError, } from "./errors.js";
|
|
4
4
|
import { toNativeStyle, untgz } from "./file.js";
|
|
5
|
-
import { ChallengeResultStatus, FileListType, TrackApp, } from "./types.js";
|
|
5
|
+
import { ChallengeResultStatus, ChallengeStyle, FileListType, TrackApp, } from "./types.js";
|
|
6
6
|
import { TestClient } from "../track/test.js";
|
|
7
7
|
import { TrainingClient } from "../track/training.js";
|
|
8
8
|
import * as datetime from "../../deps/deno.land/std@0.195.0/datetime/mod.js";
|
|
@@ -100,6 +100,13 @@ export async function tryStartingChallenge(api, challenge) {
|
|
|
100
100
|
await api.updateLanguage(desiredLanguage.value);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
// Handle template selection for frontend challenges
|
|
104
|
+
if (challenge.style === ChallengeStyle.Frontend) {
|
|
105
|
+
const frontendTemplates = await api.frontendTemplates();
|
|
106
|
+
if (frontendTemplates.length > 0) {
|
|
107
|
+
console.log("Frontend challenge detected. Use 'track frontend-template' to switch templates after cloning.");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
103
110
|
result = await api.start();
|
|
104
111
|
break;
|
|
105
112
|
}
|
|
@@ -354,11 +361,12 @@ export /**
|
|
|
354
361
|
await fs.move(`${challengeDir}/${dirEntry.name}`, `${archiveDir}/${dirEntry.name}`);
|
|
355
362
|
}
|
|
356
363
|
}
|
|
364
|
+
export { chooseFrontendTemplate } from "../action/frontend-template.js";
|
|
357
365
|
export function trackClientFromConfig(config) {
|
|
358
366
|
let api;
|
|
359
367
|
switch (config.app) {
|
|
360
368
|
case TrackApp.Test:
|
|
361
|
-
api = new TestClient(config.baseUrl, config.orgName, config.token, config.challengeId, config.basicAuth, config.cookies);
|
|
369
|
+
api = new TestClient(config.baseUrl, config.orgName, config.token, config.challengeId, config.basicAuth, config.cookies, config.applicantExamId, config.challengeResultId);
|
|
362
370
|
break;
|
|
363
371
|
case TrackApp.Training:
|
|
364
372
|
api = new TrainingClient(config.baseUrl, config.orgName, config.token, config.courseMaterialId, config.klassId, config.klassResultId, config.basicAuth, config.cookies);
|
|
@@ -25,6 +25,7 @@ export interface ChallengeSession {
|
|
|
25
25
|
timeLimitMinutes?: number;
|
|
26
26
|
displayOrder: number;
|
|
27
27
|
programmingLanguages: ProgrammingLanguage[];
|
|
28
|
+
allowedTemplateSeriesCodes?: string[];
|
|
28
29
|
localCodingAllowed: boolean;
|
|
29
30
|
openTestcases?: number;
|
|
30
31
|
result?: ChallengeResult;
|
|
@@ -59,6 +60,8 @@ export interface CodingContext {
|
|
|
59
60
|
allowEdit?: boolean;
|
|
60
61
|
programmingLanguages: ProgrammingLanguage[];
|
|
61
62
|
selectedLanguage?: ProgrammingLanguage;
|
|
63
|
+
selectedFrontendTemplate?: FrontendTemplateInfo;
|
|
64
|
+
availableFrontendTemplates?: FrontendTemplateInfo[];
|
|
62
65
|
challengeLanguage: SpokenLanguage;
|
|
63
66
|
settings: ChallengeSettings;
|
|
64
67
|
answers: CodingAnswers;
|
|
@@ -122,6 +125,7 @@ export declare const ChallengeStyle: {
|
|
|
122
125
|
readonly Development: 2;
|
|
123
126
|
readonly Algorithm: 3;
|
|
124
127
|
readonly Ai: 4;
|
|
128
|
+
readonly Frontend: 8;
|
|
125
129
|
};
|
|
126
130
|
export type ChallengeStyle = (typeof ChallengeStyle)[keyof typeof ChallengeStyle];
|
|
127
131
|
export declare const ChallengeResultStatus: {
|
|
@@ -140,3 +144,14 @@ export declare const SpokenLanguage: {
|
|
|
140
144
|
};
|
|
141
145
|
export type SpokenLanguage = (typeof SpokenLanguage)[keyof typeof SpokenLanguage];
|
|
142
146
|
export type ProgrammingLanguage = number;
|
|
147
|
+
export interface FrontendTemplateInfo {
|
|
148
|
+
seriesCode: string;
|
|
149
|
+
seriesName: string;
|
|
150
|
+
versionId: number;
|
|
151
|
+
versionName: string;
|
|
152
|
+
downloadUrl?: string;
|
|
153
|
+
readmeUrl?: {
|
|
154
|
+
en?: string;
|
|
155
|
+
ja?: string;
|
|
156
|
+
};
|
|
157
|
+
}
|
package/esm/src/shared/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as dntShim from "../../_dnt.shims.js";
|
|
2
2
|
import { TrackTestConfigPart, TrackTrainingConfigPart } from "../shared/config.js";
|
|
3
|
-
import { ChallengeResult, ChallengeSession, CodingContext, ProgrammingLanguage, ProgrammingLanguageInfo, SaveFilesRequest } from "../shared/types.js";
|
|
3
|
+
import { ChallengeResult, ChallengeSession, CodingContext, FrontendTemplateInfo, ProgrammingLanguage, ProgrammingLanguageInfo, SaveFilesRequest } from "../shared/types.js";
|
|
4
4
|
import { CookieJar } from "node-fetch-cookies";
|
|
5
5
|
import { ProxyAgent } from "proxy-agent";
|
|
6
6
|
export interface BasicAuth {
|
|
@@ -41,9 +41,12 @@ export interface TrackClient {
|
|
|
41
41
|
};
|
|
42
42
|
startChallengeSession(): Promise<ChallengeSession>;
|
|
43
43
|
languages(): Promise<ProgrammingLanguageInfo[]>;
|
|
44
|
+
frontendTemplates(): Promise<FrontendTemplateInfo[]>;
|
|
45
|
+
frontendTemplate(code: string): Promise<FrontendTemplateInfo>;
|
|
44
46
|
start(): Promise<ChallengeResult>;
|
|
45
47
|
prepare(): Promise<ChallengeResult>;
|
|
46
48
|
updateLanguage(language: ProgrammingLanguage): Promise<void>;
|
|
49
|
+
updateFrontendTemplate(templateCode: string): Promise<void>;
|
|
47
50
|
timeLeft(): Promise<number>;
|
|
48
51
|
context(): Promise<CodingContext>;
|
|
49
52
|
presigned(files: string[]): Promise<{
|
package/esm/src/track/test.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TrackTestConfigPart } from "../shared/config.js";
|
|
2
|
-
import { ChallengeResult, ChallengeSession, CodingContext, ProgrammingLanguage, ProgrammingLanguageInfo, SaveFilesRequest } from "../shared/types.js";
|
|
2
|
+
import { ChallengeResult, ChallengeSession, CodingContext, FrontendTemplateInfo, ProgrammingLanguage, ProgrammingLanguageInfo, SaveFilesRequest } from "../shared/types.js";
|
|
3
3
|
import { BasicAuth, TrackClient, TrackClientBase } from "./client.js";
|
|
4
4
|
export declare class TestClient extends TrackClientBase implements TrackClient {
|
|
5
5
|
private orgName;
|
|
@@ -7,7 +7,7 @@ export declare class TestClient extends TrackClientBase implements TrackClient {
|
|
|
7
7
|
private challengeId;
|
|
8
8
|
private applicantExamId;
|
|
9
9
|
private resultId;
|
|
10
|
-
constructor(baseUrl: string, orgName: string, token: string, challengeId: number, basic?: BasicAuth, cookies?: Record<string, string
|
|
10
|
+
constructor(baseUrl: string, orgName: string, token: string, challengeId: number, basic?: BasicAuth, cookies?: Record<string, string>, applicantExamId?: number, challengeResultId?: number);
|
|
11
11
|
config(): TrackTestConfigPart;
|
|
12
12
|
authenticate(): Promise<void>;
|
|
13
13
|
startChallengeSession(): Promise<ChallengeSession>;
|
|
@@ -16,9 +16,12 @@ export declare class TestClient extends TrackClientBase implements TrackClient {
|
|
|
16
16
|
private getExamSession;
|
|
17
17
|
private getChallengeFromExamSession;
|
|
18
18
|
languages(): Promise<ProgrammingLanguageInfo[]>;
|
|
19
|
+
frontendTemplates(): Promise<FrontendTemplateInfo[]>;
|
|
20
|
+
frontendTemplate(code: string): Promise<FrontendTemplateInfo>;
|
|
19
21
|
start(): Promise<ChallengeResult>;
|
|
20
22
|
prepare(): Promise<ChallengeResult>;
|
|
21
23
|
updateLanguage(language: ProgrammingLanguage): Promise<void>;
|
|
24
|
+
updateFrontendTemplate(templateCode: string): Promise<void>;
|
|
22
25
|
timeLeft(): Promise<number>;
|
|
23
26
|
context(): Promise<CodingContext>;
|
|
24
27
|
presigned(files: string[]): Promise<{
|
package/esm/src/track/test.js
CHANGED
|
@@ -5,7 +5,7 @@ import { FormType, TrackClientBase, } from "./client.js";
|
|
|
5
5
|
// @ts-ignore: has no exported member
|
|
6
6
|
import { CookieJar, fetch } from "node-fetch-cookies";
|
|
7
7
|
export class TestClient extends TrackClientBase {
|
|
8
|
-
constructor(baseUrl, orgName, token, challengeId, basic, cookies) {
|
|
8
|
+
constructor(baseUrl, orgName, token, challengeId, basic, cookies, applicantExamId, challengeResultId) {
|
|
9
9
|
super(baseUrl, basic, cookies);
|
|
10
10
|
Object.defineProperty(this, "orgName", {
|
|
11
11
|
enumerable: true,
|
|
@@ -40,6 +40,8 @@ export class TestClient extends TrackClientBase {
|
|
|
40
40
|
this.orgName = orgName;
|
|
41
41
|
this.token = token;
|
|
42
42
|
this.challengeId = challengeId;
|
|
43
|
+
this.applicantExamId = applicantExamId ?? 0;
|
|
44
|
+
this.resultId = challengeResultId ?? 0;
|
|
43
45
|
}
|
|
44
46
|
config() {
|
|
45
47
|
return {
|
|
@@ -100,12 +102,19 @@ export class TestClient extends TrackClientBase {
|
|
|
100
102
|
.map((esc) => toChallengeSession(esc, result))
|
|
101
103
|
.find((c) => c.challengeId === this.challengeId &&
|
|
102
104
|
(c.style === ChallengeStyle.Development ||
|
|
103
|
-
c.style === ChallengeStyle.Algorithm
|
|
105
|
+
c.style === ChallengeStyle.Algorithm ||
|
|
106
|
+
c.style === ChallengeStyle.Frontend));
|
|
104
107
|
}
|
|
105
108
|
async languages() {
|
|
106
109
|
return (await this._get(`/api/enum/challenges/programminglanguages`))
|
|
107
110
|
.map(toProgrammingLanguageInfo);
|
|
108
111
|
}
|
|
112
|
+
async frontendTemplates() {
|
|
113
|
+
return await this._get(`/api/dock-public/v2/consumer/frontend-templates`);
|
|
114
|
+
}
|
|
115
|
+
async frontendTemplate(code) {
|
|
116
|
+
return await this._get(`/api/dock-public/v2/consumer/frontend-templates/${code}`);
|
|
117
|
+
}
|
|
109
118
|
async start() {
|
|
110
119
|
return await this._put(`/api/applicants/exams/${this.applicantExamId}/results/${this.resultId}/start`, FormType.URLEncoded, {
|
|
111
120
|
takenBy: 3, // 3 = LocalMachine
|
|
@@ -121,6 +130,11 @@ export class TestClient extends TrackClientBase {
|
|
|
121
130
|
programmingLanguage: language,
|
|
122
131
|
}); // ChallengeResult
|
|
123
132
|
}
|
|
133
|
+
async updateFrontendTemplate(templateCode) {
|
|
134
|
+
return await this._put(`/api/applicants/exams/${this.applicantExamId}/results/${this.resultId}/frontend-template`, FormType.URLEncoded, {
|
|
135
|
+
frontendTemplate: templateCode,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
124
138
|
async timeLeft() {
|
|
125
139
|
return await this._get(`/api/applicants/exams/${this.applicantExamId}/results/${this.resultId}/timeleft`);
|
|
126
140
|
}
|
|
@@ -171,6 +185,8 @@ function toChallengeSession(esc, result) {
|
|
|
171
185
|
timeLimitMinutes: esc.timeLimitMinutes,
|
|
172
186
|
displayOrder: esc.displayOrder,
|
|
173
187
|
programmingLanguages: esc.programmingLanguages,
|
|
188
|
+
allowedTemplateSeriesCodes: esc.frontendStyleConfig
|
|
189
|
+
?.allowedTemplateSeriesCodes,
|
|
174
190
|
localCodingAllowed: esc.localExamEnabled,
|
|
175
191
|
openTestcases: esc.openTestcases,
|
|
176
192
|
result,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TrackTrainingConfigPart } from "../shared/config.js";
|
|
2
|
-
import { ChallengeResult, ChallengeSession, CodingContext, ProgrammingLanguage, ProgrammingLanguageInfo, SaveFilesRequest } from "../shared/types.js";
|
|
2
|
+
import { ChallengeResult, ChallengeSession, CodingContext, FrontendTemplateInfo, ProgrammingLanguage, ProgrammingLanguageInfo, SaveFilesRequest } from "../shared/types.js";
|
|
3
3
|
import { BasicAuth, TrackClient, TrackClientBase } from "./client.js";
|
|
4
4
|
export declare class TrainingClient extends TrackClientBase implements TrackClient {
|
|
5
5
|
private orgName;
|
|
@@ -13,9 +13,12 @@ export declare class TrainingClient extends TrackClientBase implements TrackClie
|
|
|
13
13
|
startChallengeSession(): Promise<ChallengeSession>;
|
|
14
14
|
continueChallengeSession(): Promise<ChallengeSession>;
|
|
15
15
|
languages(): Promise<ProgrammingLanguageInfo[]>;
|
|
16
|
+
frontendTemplates(): Promise<FrontendTemplateInfo[]>;
|
|
17
|
+
frontendTemplate(_code: string): Promise<FrontendTemplateInfo>;
|
|
16
18
|
start(): Promise<ChallengeResult>;
|
|
17
19
|
prepare(): Promise<ChallengeResult>;
|
|
18
20
|
updateLanguage(language: ProgrammingLanguage): Promise<void>;
|
|
21
|
+
updateFrontendTemplate(_templateCode: string): Promise<void>;
|
|
19
22
|
timeLeft(): Promise<number>;
|
|
20
23
|
context(): Promise<CodingContext>;
|
|
21
24
|
presigned(files: string[]): Promise<{
|
|
@@ -74,6 +74,12 @@ export class TrainingClient extends TrackClientBase {
|
|
|
74
74
|
return (await this._get(`/cli/languages`))
|
|
75
75
|
.map(toProgrammingLanguageInfo);
|
|
76
76
|
}
|
|
77
|
+
frontendTemplates() {
|
|
78
|
+
throw new OtherError("Frontend templates are not supported in training mode");
|
|
79
|
+
}
|
|
80
|
+
frontendTemplate(_code) {
|
|
81
|
+
throw new OtherError("Frontend templates are not supported in training mode");
|
|
82
|
+
}
|
|
77
83
|
async start() {
|
|
78
84
|
return await this._put(`/api/train/${this.klassId}/challenges/results/${this.klassResultId}/start`, FormType.JSON, {
|
|
79
85
|
takenBy: 3, // 3 = LocalMachine
|
|
@@ -87,6 +93,9 @@ export class TrainingClient extends TrackClientBase {
|
|
|
87
93
|
lang: language,
|
|
88
94
|
});
|
|
89
95
|
}
|
|
96
|
+
updateFrontendTemplate(_templateCode) {
|
|
97
|
+
throw new OtherError("Frontend templates are not supported in training mode");
|
|
98
|
+
}
|
|
90
99
|
async timeLeft() {
|
|
91
100
|
return await this._get(`/api/train/${this.klassId}/challenges/results/${this.klassResultId}/timeleft`);
|
|
92
101
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|