@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.
- package/README.md +33 -10
- package/dist/index.js +67 -81
- 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
|
|
41
|
-
gt push --only <
|
|
42
|
-
gt push --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 <
|
|
45
|
-
gt create [names...] # Create new programs
|
|
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
|
|
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
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
95
|
+
"abc1234": {
|
|
96
|
+
"file": "my-program.gt",
|
|
97
|
+
"id": 12345
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
```
|
|
100
102
|
|
|
101
|
-
|
|
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((
|
|
141
|
+
return new Promise((resolve5) => {
|
|
140
142
|
rl2.question(question, (answer) => {
|
|
141
143
|
rl2.close();
|
|
142
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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((
|
|
405
|
+
return new Promise((resolve5) => {
|
|
421
406
|
iface.question(question, (answer) => {
|
|
422
407
|
iface.close();
|
|
423
|
-
|
|
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
|
|
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]
|
|
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
|
|
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(
|
|
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((
|
|
690
|
+
return new Promise((resolve5) => {
|
|
706
691
|
rl2.question(prompt2, (answer) => {
|
|
707
692
|
rl2.close();
|
|
708
|
-
|
|
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
|
|
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 "${
|
|
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(
|
|
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
|
|
762
|
-
import { resolve as
|
|
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("-
|
|
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 "${
|
|
777
|
+
`>> Updating "${pushFile}" (id: ${ref.id})... `
|
|
791
778
|
);
|
|
792
|
-
const contents = await
|
|
793
|
-
|
|
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:
|
|
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