@trayai/tray-sync-cli 1.0.5 ā 1.0.7
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 +4 -1
- package/dist/cli.js +23 -8
- package/dist/commands/api/api.js +3 -0
- package/dist/commands/pull.js +25 -0
- package/dist/lib/apiAuth.js +27 -0
- package/dist/views/help.js +25 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# tray-sync-cli
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
#### Tray CLI tool allows users to
|
|
4
|
+
- General-purpose, authenticated proxy to the Tray API
|
|
5
|
+
- Clone, review and promote Tray projects and related assets on a local machine or a version controlled pipeline
|
|
3
6
|
|
|
4
7
|
## Requirements
|
|
5
8
|
|
package/dist/cli.js
CHANGED
|
@@ -10,22 +10,37 @@ import { projectCommand } from "./commands/project.js";
|
|
|
10
10
|
import { envCommand } from "./commands/env.js";
|
|
11
11
|
import { authCommand } from "./commands/auth.js";
|
|
12
12
|
import { promoteCommand } from "./commands/promote.js";
|
|
13
|
+
import { apiCommand } from "./commands/api/api.js";
|
|
14
|
+
import { isApiEnabled } from "./lib/apiAuth.js";
|
|
13
15
|
import { BANNER } from "./views/banner.js";
|
|
16
|
+
import { TrayHelp } from "./views/help.js";
|
|
14
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
18
|
const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
19
|
+
Command.prototype.createHelp = () => new TrayHelp();
|
|
16
20
|
const program = new Command();
|
|
21
|
+
const description = `ā Mirror Tray projects to local files, promote them between Tray environments${isApiEnabled() ? ', and run general-purpose API requests' : ''}\nš Readme available at https://www.npmjs.com/package/@trayai/tray-sync-cli`;
|
|
17
22
|
program
|
|
18
23
|
.name("tray")
|
|
19
|
-
.description(
|
|
24
|
+
.description(description)
|
|
20
25
|
.version(pkg.version, "-v, --version")
|
|
26
|
+
.helpCommand(false)
|
|
21
27
|
.addHelpText("beforeAll", (context) => (context.command === program ? BANNER : ""));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
for (const cmd of [
|
|
29
|
+
initCommand,
|
|
30
|
+
pullCommand,
|
|
31
|
+
statusCommand,
|
|
32
|
+
projectCommand,
|
|
33
|
+
envCommand,
|
|
34
|
+
authCommand,
|
|
35
|
+
promoteCommand,
|
|
36
|
+
]) {
|
|
37
|
+
cmd.helpGroup("Local project management");
|
|
38
|
+
program.addCommand(cmd);
|
|
39
|
+
}
|
|
40
|
+
if (isApiEnabled()) {
|
|
41
|
+
apiCommand.helpGroup("General-purpose API (growing)");
|
|
42
|
+
program.addCommand(apiCommand);
|
|
43
|
+
}
|
|
29
44
|
program.parseAsync(process.argv).catch((err) => {
|
|
30
45
|
const message = err instanceof Error ? err.message : String(err);
|
|
31
46
|
console.error(message);
|
package/dist/commands/pull.js
CHANGED
|
@@ -59,6 +59,25 @@ export async function listProjectIds(client, workspaceId) {
|
|
|
59
59
|
const result = (await client.request("GET", `/v2/projects/workspaces/${workspaceId}`));
|
|
60
60
|
return result.projects.map((p) => requireUuid(p.id, "listProjects: projects[].id"));
|
|
61
61
|
}
|
|
62
|
+
async function removeOrphanedEntityDirs(projectDirPath, entityParentDir, currentIds) {
|
|
63
|
+
await assertNoSymlinkInPath(projectDirPath, entityParentDir);
|
|
64
|
+
let existingDirNames;
|
|
65
|
+
try {
|
|
66
|
+
existingDirNames = await fsReaddir(entityParentDir);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
existingDirNames = [];
|
|
70
|
+
}
|
|
71
|
+
for (const dirName_ of existingDirNames) {
|
|
72
|
+
const prefix = dirName_.split("--")[0];
|
|
73
|
+
if (!isUuid(prefix) || currentIds.has(prefix)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const staleDir = resolveContained(entityParentDir, dirName_);
|
|
77
|
+
await assertNoSymlinkInPath(projectDirPath, staleDir);
|
|
78
|
+
await fsRm(staleDir, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
62
81
|
export async function writeExportedProject(projectDirPath, exportStructure, stdout) {
|
|
63
82
|
const project = exportStructure.projects[0];
|
|
64
83
|
const projectJsonPath = path.join(projectDirPath, "project.json");
|
|
@@ -75,6 +94,7 @@ export async function writeExportedProject(projectDirPath, exportStructure, stdo
|
|
|
75
94
|
for (const { field, dirName, fileName, trayType } of ASSET_DIRS) {
|
|
76
95
|
const entities = exportStructure[field] ?? [];
|
|
77
96
|
const entityParentDir = path.join(projectDirPath, dirName);
|
|
97
|
+
const currentIds = new Set(entities.map((entity) => entity.id));
|
|
78
98
|
for (const entity of entities) {
|
|
79
99
|
const entityId = requireUuid(entity.id, `exportProject: ${field}[].id`);
|
|
80
100
|
const entityDirName_ = entityDirName(entityId, entityDisplayName(entity));
|
|
@@ -113,6 +133,7 @@ export async function writeExportedProject(projectDirPath, exportStructure, stdo
|
|
|
113
133
|
}
|
|
114
134
|
}
|
|
115
135
|
}
|
|
136
|
+
await removeOrphanedEntityDirs(projectDirPath, entityParentDir, currentIds);
|
|
116
137
|
}
|
|
117
138
|
const solutionDropped = Boolean(exportStructure.solution);
|
|
118
139
|
if (solutionDropped) {
|
|
@@ -252,6 +273,10 @@ export async function runPull(deps, options) {
|
|
|
252
273
|
}
|
|
253
274
|
}
|
|
254
275
|
if (unchanged) {
|
|
276
|
+
for (const { field, dirName } of ASSET_DIRS) {
|
|
277
|
+
const entities = exportStructure[field] ?? [];
|
|
278
|
+
await removeOrphanedEntityDirs(dir, path.join(dir, dirName), new Set(entities.map((e) => e.id)));
|
|
279
|
+
}
|
|
255
280
|
await writeProjectState(dir, { ...existingState, last_sync: new Date().toISOString() });
|
|
256
281
|
if (asJson) {
|
|
257
282
|
events.push({ project_id: projectId, project_name: projectName, action: "pulled_unchanged", renamed_dir: renamedDir });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { REGIONS, PUBLIC_REGIONS } from "./regionParsing.js";
|
|
2
|
+
import { describeRegion } from "./regionDisplay.js";
|
|
3
|
+
export function resolveApiToken(options) {
|
|
4
|
+
const { tokenFlag, env = process.env } = options;
|
|
5
|
+
if (tokenFlag)
|
|
6
|
+
return tokenFlag;
|
|
7
|
+
if (env.TRAY_API_TOKEN)
|
|
8
|
+
return env.TRAY_API_TOKEN;
|
|
9
|
+
throw new Error("No API token provided. Pass -t/--token <token> or set TRAY_API_TOKEN.");
|
|
10
|
+
}
|
|
11
|
+
export function resolveApiRegion(options) {
|
|
12
|
+
const { regionFlag, env = process.env } = options;
|
|
13
|
+
if (regionFlag)
|
|
14
|
+
return regionFlag;
|
|
15
|
+
const envRegion = env.TRAY_API_REGION;
|
|
16
|
+
if (envRegion) {
|
|
17
|
+
if (!REGIONS.includes(envRegion)) {
|
|
18
|
+
throw new Error(`TRAY_API_REGION="${envRegion}" is not a supported region. Must be one of: ${PUBLIC_REGIONS.map(describeRegion).join(", ")}.`);
|
|
19
|
+
}
|
|
20
|
+
return envRegion;
|
|
21
|
+
}
|
|
22
|
+
throw new Error("No region provided. Pass -r/--region <region> or set TRAY_API_REGION.");
|
|
23
|
+
}
|
|
24
|
+
export function isApiEnabled(env = process.env) {
|
|
25
|
+
return env.TRAY_CLI_API_ENABLE === "true";
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=apiAuth.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Help } from "commander";
|
|
2
|
+
import ansis from "ansis";
|
|
3
|
+
export const COMMAND_COLOR = "#5FD4D4";
|
|
4
|
+
export class TrayHelp extends Help {
|
|
5
|
+
styleTitle(str) {
|
|
6
|
+
return ansis.bold(str.replace(/:$/, "").toUpperCase());
|
|
7
|
+
}
|
|
8
|
+
styleSubcommandText(str) {
|
|
9
|
+
return str === "[command]" ? str : ansis.hex(COMMAND_COLOR)(str);
|
|
10
|
+
}
|
|
11
|
+
styleCommandText(str) {
|
|
12
|
+
return ansis.hex(COMMAND_COLOR)(str);
|
|
13
|
+
}
|
|
14
|
+
styleUsage(str) {
|
|
15
|
+
return `\n $ ${super.styleUsage(str)}`;
|
|
16
|
+
}
|
|
17
|
+
formatHelp(cmd, helper) {
|
|
18
|
+
const full = super.formatHelp(cmd, helper).replace(/^(.*?) \n/, "$1\n");
|
|
19
|
+
if (helper.commandDescription(cmd).length === 0)
|
|
20
|
+
return full;
|
|
21
|
+
const [usageBlock, descriptionBlock, ...rest] = full.split("\n\n");
|
|
22
|
+
return [descriptionBlock, usageBlock, ...rest].join("\n\n");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=help.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trayai/tray-sync-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "CLI tool to clone Tray projects and related assets to a local directory, and promote them between Tray environments",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tray": "dist/cli.js"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build": "rm -rf dist && tsc -p tsconfig.json && chmod +x dist/cli.js",
|
|
23
23
|
"prepack": "npm run build",
|
|
24
24
|
"pretest": "tsc -p tsconfig.test.json",
|
|
25
|
-
"test": "node --test dist-test/test/**/*.test.js",
|
|
25
|
+
"test": "node --test \"dist-test/test/**/*.test.js\"",
|
|
26
26
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
27
27
|
"build-and-reinstall": "npm uninstall -g @trayai/tray-sync-cli --silent || true; npm run build && npm pack --silent | xargs -I{} sh -c 'npm install -g ./{} && rm ./{}'"
|
|
28
28
|
},
|