track-cli 4.0.3 → 4.1.0-rc2

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.
Files changed (93) hide show
  1. package/esm/_dnt.shims.d.ts +1 -1
  2. package/esm/_dnt.test_shims.d.ts +20 -0
  3. package/esm/_dnt.test_shims.js +77 -0
  4. package/esm/deps/deno.land/std@0.195.0/_util/diff.d.ts +26 -0
  5. package/esm/deps/deno.land/std@0.195.0/_util/diff.js +311 -0
  6. package/esm/deps/deno.land/std@0.195.0/assert/_constants.d.ts +1 -0
  7. package/esm/deps/deno.land/std@0.195.0/assert/_constants.js +2 -0
  8. package/esm/deps/deno.land/std@0.195.0/assert/_format.d.ts +1 -0
  9. package/esm/deps/deno.land/std@0.195.0/assert/_format.js +23 -0
  10. package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.d.ts +18 -0
  11. package/esm/deps/deno.land/std@0.195.0/assert/assert_almost_equals.js +32 -0
  12. package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.d.ts +14 -0
  13. package/esm/deps/deno.land/std@0.195.0/assert/assert_array_includes.js +38 -0
  14. package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.d.ts +17 -0
  15. package/esm/deps/deno.land/std@0.195.0/assert/assert_equals.js +45 -0
  16. package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.d.ts +5 -0
  17. package/esm/deps/deno.land/std@0.195.0/assert/assert_exists.js +14 -0
  18. package/esm/deps/deno.land/std@0.195.0/assert/assert_false.d.ts +4 -0
  19. package/esm/deps/deno.land/std@0.195.0/assert/assert_false.js +7 -0
  20. package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.d.ts +8 -0
  21. package/esm/deps/deno.land/std@0.195.0/assert/assert_instance_of.js +38 -0
  22. package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.d.ts +7 -0
  23. package/esm/deps/deno.land/std@0.195.0/assert/assert_is_error.js +26 -0
  24. package/esm/deps/deno.land/std@0.195.0/assert/assert_match.d.ts +5 -0
  25. package/esm/deps/deno.land/std@0.195.0/assert/assert_match.js +13 -0
  26. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.d.ts +14 -0
  27. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_equals.js +37 -0
  28. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.d.ts +5 -0
  29. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_instance_of.js +14 -0
  30. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.d.ts +5 -0
  31. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_match.js +14 -0
  32. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.d.ts +11 -0
  33. package/esm/deps/deno.land/std@0.195.0/assert/assert_not_strict_equals.js +20 -0
  34. package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.d.ts +5 -0
  35. package/esm/deps/deno.land/std@0.195.0/assert/assert_object_match.js +78 -0
  36. package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.d.ts +64 -0
  37. package/esm/deps/deno.land/std@0.195.0/assert/assert_rejects.js +50 -0
  38. package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.d.ts +23 -0
  39. package/esm/deps/deno.land/std@0.195.0/assert/assert_strict_equals.js +60 -0
  40. package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.d.ts +5 -0
  41. package/esm/deps/deno.land/std@0.195.0/assert/assert_string_includes.js +13 -0
  42. package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.d.ts +54 -0
  43. package/esm/deps/deno.land/std@0.195.0/assert/assert_throws.js +44 -0
  44. package/esm/deps/deno.land/std@0.195.0/assert/equal.d.ts +6 -0
  45. package/esm/deps/deno.land/std@0.195.0/assert/equal.js +102 -0
  46. package/esm/deps/deno.land/std@0.195.0/assert/fail.d.ts +4 -0
  47. package/esm/deps/deno.land/std@0.195.0/assert/fail.js +9 -0
  48. package/esm/deps/deno.land/std@0.195.0/assert/mod.d.ts +32 -0
  49. package/esm/deps/deno.land/std@0.195.0/assert/mod.js +33 -0
  50. package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.d.ts +2 -0
  51. package/esm/deps/deno.land/std@0.195.0/assert/unimplemented.js +7 -0
  52. package/esm/deps/deno.land/std@0.195.0/assert/unreachable.d.ts +2 -0
  53. package/esm/deps/deno.land/std@0.195.0/assert/unreachable.js +6 -0
  54. package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.d.ts +70 -0
  55. package/esm/deps/deno.land/std@0.195.0/testing/_test_suite.js +321 -0
  56. package/esm/deps/deno.land/std@0.195.0/testing/asserts.d.ts +329 -0
  57. package/esm/deps/deno.land/std@0.195.0/testing/asserts.js +330 -0
  58. package/esm/deps/deno.land/std@0.195.0/testing/bdd.d.ts +440 -0
  59. package/esm/deps/deno.land/std@0.195.0/testing/bdd.js +215 -0
  60. package/esm/deps/deno.land/std@0.195.0/testing/mock.d.ts +110 -0
  61. package/esm/deps/deno.land/std@0.195.0/testing/mock.js +746 -0
  62. package/esm/src/action/clone.js +4 -3
  63. package/esm/src/action/frontend-template-switch.d.ts +1 -0
  64. package/esm/src/action/frontend-template-switch.js +53 -0
  65. package/esm/src/action/frontend-template.d.ts +2 -0
  66. package/esm/src/action/frontend-template.js +12 -0
  67. package/esm/src/action/lang.js +11 -2
  68. package/esm/src/action/run.js +6 -7
  69. package/esm/src/main.js +9 -2
  70. package/esm/src/meta.d.ts +1 -1
  71. package/esm/src/meta.js +108 -25
  72. package/esm/src/orca/client.js +1 -1
  73. package/esm/src/shared/config.d.ts +1 -0
  74. package/esm/src/shared/errors.d.ts +7 -1
  75. package/esm/src/shared/errors.js +16 -3
  76. package/esm/src/shared/file.js +1 -1
  77. package/esm/src/shared/mod.d.ts +1 -1
  78. package/esm/src/shared/mod.js +10 -2
  79. package/esm/src/shared/types.d.ts +16 -0
  80. package/esm/src/shared/types.js +2 -0
  81. package/esm/src/track/client.d.ts +4 -1
  82. package/esm/src/track/test.d.ts +6 -2
  83. package/esm/src/track/test.js +28 -5
  84. package/esm/src/track/training.d.ts +4 -1
  85. package/esm/src/track/training.js +9 -0
  86. package/esm/test/shared/config_test.d.ts +1 -0
  87. package/esm/test/shared/config_test.js +57 -0
  88. package/esm/test/shared/file_test.d.ts +1 -0
  89. package/esm/test/shared/file_test.js +265 -0
  90. package/esm/test/shared/mod_test.d.ts +1 -0
  91. package/esm/test/shared/mod_test.js +353 -0
  92. package/package.json +1 -1
  93. package/test_runner.js +186 -0
@@ -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,53 @@
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 { archiveExistingChallengeFiles, chooseFrontendTemplate, downloadChallengeFilesTo, getCommonChallengeContext, trackClientFromConfig, } from "../shared/mod.js";
5
+ import { ChallengeStyle } from "../shared/types.js";
6
+ export async function switchFrontendTemplate() {
7
+ const config = await loadConfig();
8
+ const api = trackClientFromConfig(config);
9
+ const [codingContext, challenge] = await getCommonChallengeContext(config, api);
10
+ if (challenge.style !== ChallengeStyle.Frontend) {
11
+ throw new OtherError("This challenge is not a Frontend Framework (VSCode) challenge." +
12
+ "\n\thint: Use 'track lang' to change programming language instead.");
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
+ const shouldForceReset = dntShim.confirm(`You are already using the template '${desiredTemplate.seriesCode}'. Do you want to start-over from the template code anyway?`);
33
+ if (!shouldForceReset) {
34
+ // already using this template
35
+ return;
36
+ }
37
+ }
38
+ await archiveExistingChallengeFiles(config.orgName);
39
+ // Ask the backend to reset the user's solution to the starter code for this template.
40
+ await api.updateFrontendTemplate(desiredTemplate.seriesCode);
41
+ // Re-download the challenge files to reflect the reset solution in the backend.
42
+ const currentDir = dntShim.Deno.cwd();
43
+ const newCodingContext = await api.context();
44
+ const showFileDiff = false;
45
+ const includeTarball = false;
46
+ await downloadChallengeFilesTo(newCodingContext, currentDir, api, showFileDiff, includeTarball);
47
+ const updatedConfig = {
48
+ ...config,
49
+ frontendTemplate: desiredTemplate.seriesCode,
50
+ };
51
+ await saveConfig(updatedConfig);
52
+ console.debug("Frontend template switch completed.");
53
+ }
@@ -0,0 +1,2 @@
1
+ import { FrontendTemplateInfo } from "../shared/types.js";
2
+ export declare function chooseFrontendTemplate(availableTemplates: FrontendTemplateInfo[]): FrontendTemplateInfo;
@@ -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
+ }
@@ -2,15 +2,24 @@ import * as dntShim from "../../_dnt.shims.js";
2
2
  import { load as loadConfig, save as saveConfig, } from "../shared/config.js";
3
3
  import { CantChooseLanguage } from "../shared/errors.js";
4
4
  import { archiveExistingChallengeFiles, chooseProgrammingLanguage, downloadChallengeFilesTo, getCommonChallengeContext, trackClientFromConfig, } from "../shared/mod.js";
5
+ import { ChallengeStyle, } from "../shared/types.js";
5
6
  export async function lang() {
6
7
  const config = await loadConfig();
7
8
  const api = trackClientFromConfig(config);
8
9
  const [codingContext, challenge] = await getCommonChallengeContext(config, api);
9
10
  if (challenge.programmingLanguages.length === 0) {
10
- throw new CantChooseLanguage();
11
+ throw new CantChooseLanguage({
12
+ hint: challenge.style === ChallengeStyle.Frontend
13
+ ? "Use 'track frontend-template' to change the frontend template for a Frontend Framework (VSCode) challenge."
14
+ : "",
15
+ });
11
16
  }
12
17
  const availableLanguages = (await api.languages())
13
- .filter((pl) => codingContext.programmingLanguages.find((n) => n === pl.value));
18
+ .filter((pl) => {
19
+ return codingContext.programmingLanguages.find((n) => {
20
+ return n === pl.value;
21
+ });
22
+ });
14
23
  console.log("Your existing code will be archived in this directory and the original challenge code for the new language will be downloaded");
15
24
  const shouldContinue = dntShim.confirm("Are you sure you want to continue?");
16
25
  if (!shouldContinue) {
@@ -59,17 +59,16 @@ export async function run(options) {
59
59
  const runId = dntShim.crypto.randomUUID();
60
60
  // Run the initialize/build commands if they exist
61
61
  let buildDoneData;
62
- if ((codingContext.answers.build?.length || 0) > 0 ||
63
- codingContext.settings.initialize.length > 0) {
64
- const shellCommand = []
65
- .concat(codingContext.settings.initialize)
66
- .concat(codingContext.answers.build || [])
67
- .filter((s) => s && s.length > 0);
62
+ const buildCommands = []
63
+ .concat(codingContext.settings.initialize || [])
64
+ .concat(codingContext.answers.build || [])
65
+ .filter((s) => s && s.length > 0);
66
+ if (buildCommands.length > 0) {
68
67
  const runCmd = {
69
68
  id: `${runId}-build`,
70
69
  imageName: envConfig.imageName,
71
70
  workingDir: envConfig.workingDir ?? "",
72
- shellCmd: shellCommand.join(" && "),
71
+ shellCmd: buildCommands.join(" && "),
73
72
  attachVolume: true,
74
73
  files,
75
74
  envVars: {},
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
@@ -1,3 +1,3 @@
1
- export declare const VERSION = "4.0.3";
1
+ export declare const VERSION = "4.1.0-rc2";
2
2
  export declare const DESCRIPTION = "A CLI for interacting with tracks.run and running code tests on track's servers";
3
3
  export declare function checkUpdates(): Promise<boolean>;
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.3";
4
+ export const VERSION = "4.1.0-rc2";
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
- [1, 0, 0, ""];
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
- console.log("╭─────────────────────────────────────────────────────────────────────────────────────────────╮");
29
- console.log("| |");
30
- console.log(`| ${colors.bold(colors.red("!!! OUTDATED !!!"))} |`);
31
- console.log("| This version of Track CLI is no more supported. |");
32
- console.log(`| Current version: ${VERSION.padEnd(20)} |`);
33
- console.log(`| Latest version: ${publishedVersion.latest.padEnd(20)} |`);
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
- console.log("╭─────────────────────────────────────────────────────────────────────────────────────────────╮");
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
+ });
@@ -128,7 +128,7 @@ export class OrcaClient {
128
128
  }));
129
129
  }
130
130
  catch (e) {
131
- rej(new OtherError(e.message));
131
+ rej(new OtherError(e && typeof e === "object" && "message" in e ? String(e.message) : String(e)));
132
132
  }
133
133
  });
134
134
  }
@@ -8,6 +8,7 @@ export interface TrackConfigCore {
8
8
  basicAuth?: BasicAuth;
9
9
  token: string;
10
10
  programmingLanguage?: ProgrammingLanguage;
11
+ frontendTemplate?: string;
11
12
  cookies: Record<string, string>;
12
13
  }
13
14
  export interface TrackTestConfigPart {
@@ -47,8 +47,14 @@ export declare class InvalidChallengeId extends TrackError {
47
47
  challengeId: number;
48
48
  constructor(challengeId: number);
49
49
  }
50
+ export declare class UnsupportedChallenge extends TrackError {
51
+ challengeId: number;
52
+ constructor(challengeId: number);
53
+ }
50
54
  export declare class CantChooseLanguage extends TrackError {
51
- constructor();
55
+ constructor(p?: {
56
+ hint?: string;
57
+ });
52
58
  }
53
59
  export declare class NotASingleResponse extends TrackError {
54
60
  constructor();
@@ -119,7 +119,7 @@ export class ChallengeAlreadyFinished extends TrackError {
119
119
  }
120
120
  export class InvalidChallengeId extends TrackError {
121
121
  constructor(challengeId) {
122
- super(`The challenge id specified in your URL (${challengeId}), is invalid`);
122
+ super(`The challenge id specified in your URL (${challengeId}) is invalid`);
123
123
  Object.defineProperty(this, "challengeId", {
124
124
  enumerable: true,
125
125
  configurable: true,
@@ -129,9 +129,22 @@ export class InvalidChallengeId extends TrackError {
129
129
  this.name = "InvalidChallengeId";
130
130
  }
131
131
  }
132
+ export class UnsupportedChallenge extends TrackError {
133
+ constructor(challengeId) {
134
+ super(`The challenge id specified in your URL (${challengeId}) is not supported by the 'track' CLI`);
135
+ Object.defineProperty(this, "challengeId", {
136
+ enumerable: true,
137
+ configurable: true,
138
+ writable: true,
139
+ value: challengeId
140
+ });
141
+ this.name = "UnsupportedChallenge";
142
+ }
143
+ }
132
144
  export class CantChooseLanguage extends TrackError {
133
- constructor() {
134
- super("This challenge does not support changing programming languages");
145
+ constructor(p = {}) {
146
+ super("This challenge does not support changing programming languages." +
147
+ (p.hint ? `\n\thint: ${p.hint}` : ""));
135
148
  this.name = "CantChooseLanguage";
136
149
  }
137
150
  }
@@ -1,8 +1,8 @@
1
1
  import * as dntShim from "../../_dnt.shims.js";
2
- import * as path from "../../deps/deno.land/std@0.195.0/path/mod.js";
3
2
  import { Foras, gunzip } from "@hazae41/foras";
4
3
  import tar from "tar";
5
4
  import * as fs from "../../deps/deno.land/std@0.195.0/fs/mod.js";
5
+ import * as path from "../../deps/deno.land/std@0.195.0/path/mod.js";
6
6
  export const _internals = {
7
7
  SEP: path.SEP,
8
8
  };
@@ -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 {};
@@ -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;
@@ -121,7 +124,9 @@ export declare const ChallengeStyle: {
121
124
  readonly Quiz: 1;
122
125
  readonly Development: 2;
123
126
  readonly Algorithm: 3;
127
+ readonly Function: 7;
124
128
  readonly Ai: 4;
129
+ readonly Frontend: 8;
125
130
  };
126
131
  export type ChallengeStyle = (typeof ChallengeStyle)[keyof typeof ChallengeStyle];
127
132
  export declare const ChallengeResultStatus: {
@@ -140,3 +145,14 @@ export declare const SpokenLanguage: {
140
145
  };
141
146
  export type SpokenLanguage = (typeof SpokenLanguage)[keyof typeof SpokenLanguage];
142
147
  export type ProgrammingLanguage = number;
148
+ export interface FrontendTemplateInfo {
149
+ seriesCode: string;
150
+ seriesName: string;
151
+ versionId: number;
152
+ versionName: string;
153
+ downloadUrl?: string;
154
+ readmeUrl?: {
155
+ en?: string;
156
+ ja?: string;
157
+ };
158
+ }
@@ -83,7 +83,9 @@ export const ChallengeStyle = {
83
83
  Quiz: 1,
84
84
  Development: 2,
85
85
  Algorithm: 3,
86
+ Function: 7,
86
87
  Ai: 4,
88
+ Frontend: 8,
87
89
  };
88
90
  export const ChallengeResultStatus = {
89
91
  Prepared: 0,
@@ -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<{
@@ -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>;
@@ -15,10 +15,14 @@ export declare class TestClient extends TrackClientBase implements TrackClient {
15
15
  private ensureExamInProgress;
16
16
  private getExamSession;
17
17
  private getChallengeFromExamSession;
18
+ isChallengeSupported(c: ChallengeSession): boolean;
18
19
  languages(): Promise<ProgrammingLanguageInfo[]>;
20
+ frontendTemplates(): Promise<FrontendTemplateInfo[]>;
21
+ frontendTemplate(code: string): Promise<FrontendTemplateInfo>;
19
22
  start(): Promise<ChallengeResult>;
20
23
  prepare(): Promise<ChallengeResult>;
21
24
  updateLanguage(language: ProgrammingLanguage): Promise<void>;
25
+ updateFrontendTemplate(seriesCode: string): Promise<void>;
22
26
  timeLeft(): Promise<number>;
23
27
  context(): Promise<CodingContext>;
24
28
  presigned(files: string[]): Promise<{