track-cli 4.1.0-rc1 → 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.
@@ -1,15 +1,15 @@
1
1
  import * as dntShim from "../../_dnt.shims.js";
2
2
  import { load as loadConfig, save as saveConfig, } from "../shared/config.js";
3
3
  import { OtherError } from "../shared/errors.js";
4
+ import { archiveExistingChallengeFiles, chooseFrontendTemplate, downloadChallengeFilesTo, getCommonChallengeContext, trackClientFromConfig, } from "../shared/mod.js";
4
5
  import { ChallengeStyle } from "../shared/types.js";
5
- import { archiveExistingChallengeFiles, chooseFrontendTemplate, getCommonChallengeContext, trackClientFromConfig, } from "../shared/mod.js";
6
- import { unzip } from "../shared/file.js";
7
6
  export async function switchFrontendTemplate() {
8
7
  const config = await loadConfig();
9
8
  const api = trackClientFromConfig(config);
10
9
  const [codingContext, challenge] = await getCommonChallengeContext(config, api);
11
10
  if (challenge.style !== ChallengeStyle.Frontend) {
12
- throw new OtherError("This challenge is not a frontend challenge");
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
13
  }
14
14
  // Get available frontend templates from Track
15
15
  const allFrontendTemplates = await api.frontendTemplates();
@@ -29,24 +29,21 @@ export async function switchFrontendTemplate() {
29
29
  const desiredTemplate = chooseFrontendTemplate(availableTemplates);
30
30
  if (desiredTemplate.seriesCode ===
31
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");
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
+ }
39
37
  }
40
- const currentDir = dntShim.Deno.cwd();
41
38
  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);
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);
50
47
  const updatedConfig = {
51
48
  ...config,
52
49
  frontendTemplate: desiredTemplate.seriesCode,
@@ -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/meta.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export declare const VERSION = "4.1.0-rc1";
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.1.0-rc1";
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) {
@@ -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
  }
@@ -24,7 +24,6 @@ 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>;
28
27
  export type GetUniqueDirNameOptions = {
29
28
  root?: string;
30
29
  };
@@ -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
  };
@@ -97,38 +97,6 @@ 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
- }
132
100
  export async function getUniqueDirName(base, options) {
133
101
  let counter = 1;
134
102
  const root = options?.root ?? dntShim.Deno.cwd();
@@ -124,6 +124,7 @@ export declare const ChallengeStyle: {
124
124
  readonly Quiz: 1;
125
125
  readonly Development: 2;
126
126
  readonly Algorithm: 3;
127
+ readonly Function: 7;
127
128
  readonly Ai: 4;
128
129
  readonly Frontend: 8;
129
130
  };
@@ -83,6 +83,7 @@ export const ChallengeStyle = {
83
83
  Quiz: 1,
84
84
  Development: 2,
85
85
  Algorithm: 3,
86
+ Function: 7,
86
87
  Ai: 4,
87
88
  Frontend: 8,
88
89
  };
@@ -15,13 +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[]>;
19
20
  frontendTemplates(): Promise<FrontendTemplateInfo[]>;
20
21
  frontendTemplate(code: string): Promise<FrontendTemplateInfo>;
21
22
  start(): Promise<ChallengeResult>;
22
23
  prepare(): Promise<ChallengeResult>;
23
24
  updateLanguage(language: ProgrammingLanguage): Promise<void>;
24
- updateFrontendTemplate(templateCode: string): Promise<void>;
25
+ updateFrontendTemplate(seriesCode: string): Promise<void>;
25
26
  timeLeft(): Promise<number>;
26
27
  context(): Promise<CodingContext>;
27
28
  presigned(files: string[]): Promise<{
@@ -1,4 +1,4 @@
1
- import { ExamCanceled, ExamExpired, ExamSubmitted, ExamUnread, InvalidChallengeId, OtherError, } from "../shared/errors.js";
1
+ import { ExamCanceled, ExamExpired, ExamSubmitted, ExamUnread, InvalidChallengeId, OtherError, UnsupportedChallenge, } from "../shared/errors.js";
2
2
  import { HttpStatus } from "../shared/mod.js";
3
3
  import { ChallengeStyle, } from "../shared/types.js";
4
4
  import { FormType, TrackClientBase, } from "./client.js";
@@ -71,6 +71,9 @@ export class TestClient extends TrackClientBase {
71
71
  if (!challenge) {
72
72
  throw new InvalidChallengeId(this.challengeId);
73
73
  }
74
+ else if (!this.isChallengeSupported(challenge)) {
75
+ throw new UnsupportedChallenge(this.challengeId);
76
+ }
74
77
  this.applicantExamId = applicantExam.id;
75
78
  this.resultId = result?.id || 0;
76
79
  return challenge;
@@ -100,10 +103,13 @@ export class TestClient extends TrackClientBase {
100
103
  return examSession.challengesSets
101
104
  .flatMap((s) => s.challenges)
102
105
  .map((esc) => toChallengeSession(esc, result))
103
- .find((c) => c.challengeId === this.challengeId &&
104
- (c.style === ChallengeStyle.Development ||
105
- c.style === ChallengeStyle.Algorithm ||
106
- c.style === ChallengeStyle.Frontend));
106
+ .find((c) => c.challengeId === this.challengeId);
107
+ }
108
+ isChallengeSupported(c) {
109
+ return (c.style === ChallengeStyle.Development ||
110
+ c.style === ChallengeStyle.Algorithm ||
111
+ c.style === ChallengeStyle.Function ||
112
+ c.style === ChallengeStyle.Frontend);
107
113
  }
108
114
  async languages() {
109
115
  return (await this._get(`/api/enum/challenges/programminglanguages`))
@@ -130,9 +136,10 @@ export class TestClient extends TrackClientBase {
130
136
  programmingLanguage: language,
131
137
  }); // ChallengeResult
132
138
  }
133
- async updateFrontendTemplate(templateCode) {
134
- return await this._put(`/api/applicants/exams/${this.applicantExamId}/results/${this.resultId}/frontend-template`, FormType.URLEncoded, {
135
- frontendTemplate: templateCode,
139
+ async updateFrontendTemplate(seriesCode) {
140
+ return await this._post(`/api/applicants/exams/${this.applicantExamId}/results/${this.resultId}/frontend/switch-frontend-template`, FormType.JSON, {
141
+ seriesCode,
142
+ forceReset: true,
136
143
  });
137
144
  }
138
145
  async timeLeft() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "track-cli",
3
- "version": "4.1.0-rc1",
3
+ "version": "4.1.0-rc2",
4
4
  "description": "A CLI for interacting with tracks.run and running code tests on track's servers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,6 @@
19
19
  "dependencies": {
20
20
  "@hazae41/foras": "2.0.8",
21
21
  "eventemitter3": "5.0.1",
22
- "jszip": "3.10.1",
23
22
  "node-fetch-cookies": "2.0.4",
24
23
  "proxy-agent": "6.3.0",
25
24
  "tar": "6.1.13",