@jrc03c/gt-cli 0.1.4 → 0.1.6

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 (3) hide show
  1. package/README.md +33 -10
  2. package/dist/index.js +67 -81
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -37,14 +37,14 @@ Set `GT_ENV` to target different GuidedTrack environments:
37
37
  ```bash
38
38
  gt init # Create gt.config.json by scanning for program files
39
39
  gt config # Print current project configuration
40
- gt push # Upload all local program files to the server
41
- gt push --only <name> # Push a single program
42
- gt push --build # Push and build
40
+ gt push # Upload local programs and build
41
+ gt push --only <key> # Push a single program
42
+ gt push --no-build # Push without building
43
43
  gt pull # Download all program sources from the server
44
- gt pull --only <name> # Pull a single program
45
- gt create [names...] # Create new programs on the server
44
+ gt pull --only <key> # Pull a single program
45
+ gt create [names...] # Create new programs (updates gt.config.json)
46
46
  gt build # Compile programs and report errors
47
- gt compare [args...] # Compare programs using gt-compare
47
+ gt build --only <key> # Build a single program
48
48
  ```
49
49
 
50
50
  ### Program management
@@ -89,16 +89,39 @@ gt request <path> -H "X-Custom:value" # Add custom headers
89
89
 
90
90
  ```json
91
91
  {
92
+ "email": "you@example.com",
93
+ "password": "your-password",
92
94
  "programs": {
93
- "my-program": {
94
- "id": 12345,
95
- "key": "abc1234"
95
+ "abc1234": {
96
+ "file": "my-program.gt",
97
+ "id": 12345
96
98
  }
97
99
  }
98
100
  }
99
101
  ```
100
102
 
101
- This file is gitignored. It maps local program file names to their server-side IDs and keys.
103
+ Programs are keyed by their 7-character program key, and each entry maps to a local `.gt` file and its server-side numeric ID.
104
+
105
+ The `email` and `password` fields are optional — they provide an alternative to environment variables for authentication (see [Authentication](#authentication) above).
106
+
107
+ > **NOTE:** 🚨 If storing `email` and `password` values in `gt.config.json`, then **DO NOT** commit that file to the repository history! Instead, add `gt.config.json` to your `.gitignore` file.
108
+
109
+ ### Separate push and pull files
110
+
111
+ If you use a build step to transform your `.gt` files before pushing, you can specify separate source and dist paths:
112
+
113
+ ```json
114
+ {
115
+ "programs": {
116
+ "abc1234": {
117
+ "file": { "src": "src/program.gt", "dist": "dist/program.gt" },
118
+ "id": 12345
119
+ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ With this configuration, `gt pull` writes to `src/program.gt` and `gt push` reads from `dist/program.gt`.
102
125
 
103
126
  ## Development
104
127
 
package/dist/index.js CHANGED
@@ -3,16 +3,18 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/commands/build.ts
7
- import { readFile as readFile2 } from "fs/promises";
8
- import { resolve as resolve2 } from "path";
9
-
10
6
  // src/types.ts
11
7
  var ENVIRONMENT_HOSTS = {
12
8
  development: "https://localhost:3000",
13
9
  stage: "https://guidedtrack-stage.herokuapp.com",
14
10
  production: "https://www.guidedtrack.com"
15
11
  };
12
+ function getPullFile(ref) {
13
+ return typeof ref.file === "string" ? ref.file : ref.file.src;
14
+ }
15
+ function getPushFile(ref) {
16
+ return typeof ref.file === "string" ? ref.file : ref.file.dist;
17
+ }
16
18
 
17
19
  // src/lib/api.ts
18
20
  function buildAuthHeader(credentials) {
@@ -136,10 +138,10 @@ async function saveConfig(config, dir) {
136
138
  // src/lib/auth.ts
137
139
  function prompt(question) {
138
140
  const rl2 = createInterface({ input: process.stdin, output: process.stdout });
139
- return new Promise((resolve7) => {
141
+ return new Promise((resolve5) => {
140
142
  rl2.question(question, (answer) => {
141
143
  rl2.close();
142
- resolve7(answer);
144
+ resolve5(answer);
143
145
  });
144
146
  });
145
147
  }
@@ -153,13 +155,13 @@ function promptSecret(question) {
153
155
  }
154
156
  return originalWrite(...args);
155
157
  });
156
- return new Promise((resolve7) => {
158
+ return new Promise((resolve5) => {
157
159
  rl2.question(question, (answer) => {
158
160
  muted = false;
159
161
  process.stdout.write = originalWrite;
160
162
  originalWrite("\n");
161
163
  rl2.close();
162
- resolve7(answer);
164
+ resolve5(answer);
163
165
  });
164
166
  muted = true;
165
167
  });
@@ -200,7 +202,7 @@ async function pollJob(jobId, credentials, options) {
200
202
  if (job.status !== "running") {
201
203
  return job;
202
204
  }
203
- await new Promise((resolve7) => setTimeout(resolve7, interval));
205
+ await new Promise((resolve5) => setTimeout(resolve5, interval));
204
206
  }
205
207
  }
206
208
 
@@ -282,68 +284,32 @@ async function buildProgram(name, key, credentials, environment) {
282
284
  }
283
285
 
284
286
  // src/commands/build.ts
285
- var PROJECTS_FILENAME = ".gt_projects";
286
287
  function registerBuild(program2) {
287
- program2.command("build").description("Compile programs and report errors").action(async () => {
288
+ program2.command("build").description("Compile programs and report errors").option("-o, --only <key>", "Build only the specified program (by key)").action(async (options) => {
288
289
  const credentials = await resolveCredentials();
289
290
  const environment = getEnvironment();
290
- const projects = await loadProjects();
291
- if (!projects) {
292
- console.log(`No ${PROJECTS_FILENAME} file, nothing to build`);
293
- return;
294
- }
295
- for (const project of projects) {
296
- const found = await findProgramByTitle(project, credentials, environment);
297
- if (!found) {
298
- console.error(`>> Program "${project}" not found, skipping...`);
299
- continue;
291
+ const config = await loadConfig();
292
+ const programs = config.programs ?? {};
293
+ let entries = Object.entries(programs);
294
+ if (options.only) {
295
+ const ref = programs[options.only];
296
+ if (!ref) {
297
+ console.error(
298
+ `Program with key "${options.only}" not found in config.`
299
+ );
300
+ process.exit(1);
300
301
  }
301
- await buildProgram(project, found.key, credentials, environment);
302
+ entries = [[options.only, ref]];
302
303
  }
303
- });
304
- }
305
- async function loadProjects() {
306
- try {
307
- const raw = await readFile2(
308
- resolve2(process.cwd(), PROJECTS_FILENAME),
309
- "utf-8"
310
- );
311
- return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
312
- } catch {
313
- return null;
314
- }
315
- }
316
-
317
- // src/commands/compare.ts
318
- import { execFileSync } from "child_process";
319
- import { dirname, resolve as resolve3 } from "path";
320
- function registerCompare(program2) {
321
- program2.command("compare").description("Compare programs using gt-compare").allowUnknownOption().argument("[args...]", "Arguments to pass to gt-compare").action((args) => {
322
- const gtPath = process.env.GT_COMPARE_PATH ?? findGtCompare();
323
- if (!gtPath) {
324
- console.error(
325
- "Could not find gt-compare. Set GT_COMPARE_PATH to the gt-compare directory."
326
- );
304
+ if (entries.length === 0) {
305
+ console.error("No programs in config. Run `gt init` first.");
327
306
  process.exit(1);
328
307
  }
329
- try {
330
- execFileSync("./gt-compare", args, {
331
- cwd: gtPath,
332
- stdio: "inherit"
333
- });
334
- } catch {
335
- process.exit(1);
308
+ for (const [key, ref] of entries) {
309
+ await buildProgram(getPushFile(ref), key, credentials, environment);
336
310
  }
337
311
  });
338
312
  }
339
- function findGtCompare() {
340
- try {
341
- const gtBin = execFileSync("which", ["gt"], { encoding: "utf-8" }).trim();
342
- return resolve3(dirname(gtBin), "gt-compare");
343
- } catch {
344
- return null;
345
- }
346
- }
347
313
 
348
314
  // src/commands/config.ts
349
315
  function registerConfig(program2) {
@@ -387,6 +353,9 @@ function registerCreate(program2) {
387
353
  process.exit(1);
388
354
  }
389
355
  console.log(`Creating programs in ${environment}...`);
356
+ const config = await loadConfig();
357
+ const programs = config.programs ?? {};
358
+ let configChanged = false;
390
359
  for (const filename of filenames) {
391
360
  process.stdout.write(`>> Creating "${filename}"... `);
392
361
  try {
@@ -398,7 +367,19 @@ function registerCreate(program2) {
398
367
  });
399
368
  const data = await response.json();
400
369
  if (data.job_id) {
401
- console.log("done");
370
+ await pollJob(data.job_id, credentials, {
371
+ environment,
372
+ intervalMs: 1e3
373
+ }).catch(() => {
374
+ });
375
+ const found = await findProgramByTitle(filename, credentials, environment);
376
+ if (found) {
377
+ console.log(`done (id: ${found.id}, key: ${found.key})`);
378
+ programs[found.key] = { file: filename, id: found.id };
379
+ configChanged = true;
380
+ } else {
381
+ console.log("done");
382
+ }
402
383
  } else {
403
384
  console.error("failed");
404
385
  console.error(`${JSON.stringify(data)} -- skipping`);
@@ -409,6 +390,10 @@ function registerCreate(program2) {
409
390
  process.exit(1);
410
391
  }
411
392
  }
393
+ if (configChanged) {
394
+ await saveConfig({ ...config, programs });
395
+ console.log("Updated gt.config.json");
396
+ }
412
397
  });
413
398
  }
414
399
 
@@ -417,10 +402,10 @@ import { createInterface as createInterface2 } from "readline";
417
402
  var rl = () => createInterface2({ input: process.stdin, output: process.stdout });
418
403
  async function ask(question) {
419
404
  const iface = rl();
420
- return new Promise((resolve7) => {
405
+ return new Promise((resolve5) => {
421
406
  iface.question(question, (answer) => {
422
407
  iface.close();
423
- resolve7(answer.trim());
408
+ resolve5(answer.trim());
424
409
  });
425
410
  });
426
411
  }
@@ -454,7 +439,7 @@ function registerInit(program2) {
454
439
  console.log("No .gt files found.");
455
440
  return;
456
441
  }
457
- const linkedFiles = new Set(Object.values(programs).map((p) => p.file));
442
+ const linkedFiles = new Set(Object.values(programs).map((p) => getPullFile(p)));
458
443
  for (const file of files) {
459
444
  if (linkedFiles.has(file)) {
460
445
  console.log(`
@@ -508,7 +493,7 @@ Found "${file}"`);
508
493
  console.log(`found! ("${found.name}")`);
509
494
  if (programs[found.key]) {
510
495
  console.log(
511
- `Program "${found.name}" is already linked to "${programs[found.key].file}", skipping.`
496
+ `Program "${found.name}" is already linked to "${getPullFile(programs[found.key])}", skipping.`
512
497
  );
513
498
  continue;
514
499
  }
@@ -526,7 +511,7 @@ Wrote ${CONFIG_FILENAME}`);
526
511
  // src/commands/program.ts
527
512
  import { exec } from "child_process";
528
513
  import { writeFile as writeFile2 } from "fs/promises";
529
- import { resolve as resolve4 } from "path";
514
+ import { resolve as resolve2 } from "path";
530
515
  import { createInterface as createInterface3 } from "readline";
531
516
  function registerProgram(parent) {
532
517
  const program2 = parent.command("program").description("Manage programs on the server");
@@ -693,7 +678,7 @@ function registerProgram(parent) {
693
678
  });
694
679
  const csv = await response.text();
695
680
  if (options.output) {
696
- await writeFile2(resolve4(process.cwd(), options.output), csv);
681
+ await writeFile2(resolve2(process.cwd(), options.output), csv);
697
682
  console.log(`Saved to ${options.output}`);
698
683
  } else {
699
684
  process.stdout.write(csv);
@@ -702,10 +687,10 @@ function registerProgram(parent) {
702
687
  }
703
688
  function confirm2(prompt2) {
704
689
  const rl2 = createInterface3({ input: process.stdin, output: process.stdout });
705
- return new Promise((resolve7) => {
690
+ return new Promise((resolve5) => {
706
691
  rl2.question(prompt2, (answer) => {
707
692
  rl2.close();
708
- resolve7(answer.toLowerCase() === "y");
693
+ resolve5(answer.toLowerCase() === "y");
709
694
  });
710
695
  });
711
696
  }
@@ -717,7 +702,7 @@ function openBrowser(url) {
717
702
 
718
703
  // src/commands/pull.ts
719
704
  import { writeFile as writeFile3 } from "fs/promises";
720
- import { resolve as resolve5 } from "path";
705
+ import { resolve as resolve3 } from "path";
721
706
  function registerPull(program2) {
722
707
  program2.command("pull").description("Download program source from the server").option("-o, --only <key>", "Pull only the specified program (by key)").action(async (options) => {
723
708
  const credentials = await resolveCredentials();
@@ -743,25 +728,26 @@ function registerPull(program2) {
743
728
  }
744
729
  console.log(`Pulling from ${environment}...`);
745
730
  for (const [, ref] of entries) {
731
+ const pullFile = getPullFile(ref);
746
732
  process.stdout.write(
747
- `>> Downloading "${ref.file}" (id: ${ref.id})... `
733
+ `>> Downloading "${pullFile}" (id: ${ref.id})... `
748
734
  );
749
735
  const source = await fetchProgramSource(
750
736
  ref.id,
751
737
  credentials,
752
738
  environment
753
739
  );
754
- await writeFile3(resolve5(process.cwd(), ref.file), source);
740
+ await writeFile3(resolve3(process.cwd(), pullFile), source);
755
741
  console.log("done");
756
742
  }
757
743
  });
758
744
  }
759
745
 
760
746
  // src/commands/push.ts
761
- import { readFile as readFile3 } from "fs/promises";
762
- import { resolve as resolve6 } from "path";
747
+ import { readFile as readFile2 } from "fs/promises";
748
+ import { resolve as resolve4 } from "path";
763
749
  function registerPush(program2) {
764
- program2.command("push").description("Upload local program files to the server").option("-o, --only <key>", "Push only the specified program (by key)").option("-b, --build", "Build after pushing").action(async (options) => {
750
+ program2.command("push").description("Upload local program files to the server").option("-o, --only <key>", "Push only the specified program (by key)").option("--no-build", "Skip building after push").action(async (options) => {
765
751
  const credentials = await resolveCredentials();
766
752
  const environment = getEnvironment();
767
753
  const config = await loadConfig();
@@ -786,11 +772,12 @@ function registerPush(program2) {
786
772
  console.log(`Pushing to ${environment}...`);
787
773
  const pushed = [];
788
774
  for (const [key, ref] of entries) {
775
+ const pushFile = getPushFile(ref);
789
776
  process.stdout.write(
790
- `>> Updating "${ref.file}" (id: ${ref.id})... `
777
+ `>> Updating "${pushFile}" (id: ${ref.id})... `
791
778
  );
792
- const contents = await readFile3(
793
- resolve6(process.cwd(), ref.file),
779
+ const contents = await readFile2(
780
+ resolve4(process.cwd(), pushFile),
794
781
  "utf-8"
795
782
  );
796
783
  await apiRequest(`/programs/${ref.id}.json`, {
@@ -800,7 +787,7 @@ function registerPush(program2) {
800
787
  body: { contents, program: { description: "" } }
801
788
  });
802
789
  console.log("done");
803
- pushed.push({ file: ref.file, key });
790
+ pushed.push({ file: pushFile, key });
804
791
  }
805
792
  if (options.build && pushed.length > 0) {
806
793
  console.log("\nBuilding pushed programs...");
@@ -865,7 +852,6 @@ registerPush(program);
865
852
  registerPull(program);
866
853
  registerCreate(program);
867
854
  registerBuild(program);
868
- registerCompare(program);
869
855
  registerInit(program);
870
856
  registerConfig(program);
871
857
  registerProgram(program);
package/package.json CHANGED
@@ -39,5 +39,5 @@
39
39
  "test:watch": "vitest"
40
40
  },
41
41
  "type": "module",
42
- "version": "0.1.4"
42
+ "version": "0.1.6"
43
43
  }