@trayai/tray-sync-cli 1.0.2 → 1.0.4
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 +1 -2
- package/dist/commands/project.js +2 -50
- package/dist/commands/pull.js +15 -3
- package/dist/lib/slug.js +3 -0
- package/dist/views/project/project.v2.js +2 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,6 @@ npm install -g @trayai/tray-sync-cli
|
|
|
19
19
|
| `tray pull` | Export tracked projects from Tray and materialise them on disk |
|
|
20
20
|
| `tray promote <env>` | Reconstruct local projects and import them into a target Tray environment |
|
|
21
21
|
| `tray status` | Local checksum-based drift detection (zero API calls) |
|
|
22
|
-
| `tray project add <project-id>` | Track a project (project scope only) |
|
|
23
22
|
| `tray project remove <project-id>` | Untrack a project and delete its local directory (project scope only) |
|
|
24
23
|
| `tray project list` | List managed projects with directory names and last-pulled timestamps |
|
|
25
24
|
| `tray env add <name>` | Add a target environment |
|
|
@@ -77,7 +76,7 @@ lists every project in your source workspace — id + name — so you don't have
|
|
|
77
76
|
|
|
78
77
|
A few notes on the flags:
|
|
79
78
|
|
|
80
|
-
- **`-p <project-id>`** pulls that project immediately after scaffolding. Omit it and `init` just scaffolds an empty repository — you can pull specific projects later with
|
|
79
|
+
- **`-p <project-id>`** pulls that project immediately after scaffolding. Omit it and `init` just scaffolds an empty repository — you can pull specific projects later with `tray pull -p <project_id>`, which tracks and pulls it in one step.
|
|
81
80
|
- **`-s workspace`** (i.e. `tray init -s workspace -r <region> -w <id> -t <token>`, omitting `-p`) switches to workspace scope: every project in the workspace is pulled automatically, and `tray pull` keeps that mirror in sync going forward (including removing projects deleted from Tray). `-p` is ignored in this scope.
|
|
82
81
|
- **`-t <token>`** is optional at `init` time. If you skip it (or need to replace it later — tokens expire), set or refresh it any time with:
|
|
83
82
|
```bash
|
package/dist/commands/project.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { readdir as fsReaddir, rm as fsRm } from "node:fs/promises";
|
|
3
3
|
import { Command, InvalidArgumentError } from "commander";
|
|
4
|
-
import { createTrayClientForRegion } from "../api/trayClient.js";
|
|
5
|
-
import { readCredentials } from "../config/credentials.js";
|
|
6
4
|
import { readProjectState } from "../config/state.js";
|
|
7
5
|
import { readTrayYaml, writeTrayYaml } from "../config/trayYaml.js";
|
|
8
6
|
import { Scopes } from "../config/types.js";
|
|
9
|
-
import { resolveToken } from "../lib/tokenResolution.js";
|
|
10
7
|
import { isUuid } from "../lib/uuid.js";
|
|
11
8
|
import { OutputFormats, parseFormat } from "../lib/outputFormat.js";
|
|
12
|
-
import {
|
|
9
|
+
import { printNoProjectsTracked, printNotTracked, printProjectListTable, printRemovedProject, printTrayYamlNotFound, printWorkspaceScopeDenied, } from "../views/project/project.v2.js";
|
|
13
10
|
import { printProjectListJson } from "../views/project/shared.js";
|
|
14
11
|
function findProjectDir(existingDirs, projectId) {
|
|
15
12
|
return existingDirs.find((name) => name.split("--")[0] === projectId);
|
|
@@ -36,37 +33,6 @@ async function listProjectDirs(readdir, projectsRoot) {
|
|
|
36
33
|
return [];
|
|
37
34
|
}
|
|
38
35
|
}
|
|
39
|
-
export async function runProjectAdd(deps, options) {
|
|
40
|
-
const { cwd, env, createClient, stdout, stderr } = deps;
|
|
41
|
-
const trayYaml = await requireTrayYaml(deps);
|
|
42
|
-
if (!trayYaml)
|
|
43
|
-
return 1;
|
|
44
|
-
if (refuseIfWorkspaceScope(trayYaml, stderr))
|
|
45
|
-
return 1;
|
|
46
|
-
if (trayYaml.projects.includes(options.projectId)) {
|
|
47
|
-
printAlreadyTracked(stdout, options.projectId);
|
|
48
|
-
return 0;
|
|
49
|
-
}
|
|
50
|
-
const credentials = await readCredentials(env);
|
|
51
|
-
try {
|
|
52
|
-
const token = resolveToken({
|
|
53
|
-
tokenFlag: options.token,
|
|
54
|
-
credentials,
|
|
55
|
-
workspaceId: trayYaml.workspace_id,
|
|
56
|
-
env,
|
|
57
|
-
});
|
|
58
|
-
const client = createClient(trayYaml.region, token);
|
|
59
|
-
await client.request("GET", `/v2/projects/${options.projectId}`);
|
|
60
|
-
printValidationSucceeded(stdout, options.projectId);
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
printValidationSkippedOrFailed(stdout, err.message);
|
|
64
|
-
}
|
|
65
|
-
const projects = [...trayYaml.projects, options.projectId].sort();
|
|
66
|
-
await writeTrayYaml({ ...trayYaml, projects }, cwd);
|
|
67
|
-
printAddedProject(stdout, options.projectId);
|
|
68
|
-
return 0;
|
|
69
|
-
}
|
|
70
36
|
export async function runProjectRemove(deps, options) {
|
|
71
37
|
const { cwd, readdir, rm, stdout, stderr } = deps;
|
|
72
38
|
const trayYaml = await requireTrayYaml(deps);
|
|
@@ -136,27 +102,13 @@ function parseUuid(value, flagName) {
|
|
|
136
102
|
function realDeps() {
|
|
137
103
|
return {
|
|
138
104
|
cwd: process.cwd(),
|
|
139
|
-
env: process.env,
|
|
140
|
-
createClient: (region, token) => createTrayClientForRegion(region, token),
|
|
141
105
|
readdir: (dir) => fsReaddir(dir),
|
|
142
106
|
rm: (dir) => fsRm(dir, { recursive: true, force: true }),
|
|
143
107
|
stdout: (line) => console.log(line),
|
|
144
108
|
stderr: (line) => console.error(line),
|
|
145
109
|
};
|
|
146
110
|
}
|
|
147
|
-
export const projectCommand = new Command("project").description("Manage tray.yaml.projects (project scope only for add
|
|
148
|
-
projectCommand
|
|
149
|
-
.command("add")
|
|
150
|
-
.description("Track a project (project scope only)")
|
|
151
|
-
.argument("<project-id>", "Tray project UUID", (value) => parseUuid(value, "project-id"))
|
|
152
|
-
.option("-t, --token <token>", "Tray API token, for best-effort validation")
|
|
153
|
-
.action(async (projectId, opts) => {
|
|
154
|
-
const exitCode = await runProjectAdd(realDeps(), {
|
|
155
|
-
projectId,
|
|
156
|
-
token: opts.token,
|
|
157
|
-
});
|
|
158
|
-
process.exitCode = exitCode;
|
|
159
|
-
});
|
|
111
|
+
export const projectCommand = new Command("project").description("Manage tray.yaml.projects (project scope only for remove; use `tray pull -p <id>` to add)");
|
|
160
112
|
projectCommand
|
|
161
113
|
.command("remove")
|
|
162
114
|
.description("Untrack a project and delete its local directory (project scope only)")
|
package/dist/commands/pull.js
CHANGED
|
@@ -9,7 +9,7 @@ import { readPullProgress, writePullProgress } from "../config/pullProgress.js";
|
|
|
9
9
|
import { readProjectState, writeProjectState } from "../config/state.js";
|
|
10
10
|
import { readTrayYaml, writeTrayYaml } from "../config/trayYaml.js";
|
|
11
11
|
import { Scopes, TrayTypes, } from "../config/types.js";
|
|
12
|
-
import { entityDirName } from "../lib/slug.js";
|
|
12
|
+
import { entityDirName, findDirsWithStaleSlug } from "../lib/slug.js";
|
|
13
13
|
import { entityDisplayName, parseExportStructure, } from "../lib/exportStructure.js";
|
|
14
14
|
import { extractScripts } from "../lib/scriptExtraction.js";
|
|
15
15
|
import { sha256 } from "../lib/checksum.js";
|
|
@@ -74,10 +74,22 @@ export async function writeExportedProject(projectDirPath, exportStructure, stdo
|
|
|
74
74
|
const resources = {};
|
|
75
75
|
for (const { field, dirName, fileName, trayType } of ASSET_DIRS) {
|
|
76
76
|
const entities = exportStructure[field] ?? [];
|
|
77
|
+
const entityParentDir = path.join(projectDirPath, dirName);
|
|
77
78
|
for (const entity of entities) {
|
|
78
79
|
const entityId = requireUuid(entity.id, `exportProject: ${field}[].id`);
|
|
79
80
|
const entityDirName_ = entityDirName(entityId, entityDisplayName(entity));
|
|
80
|
-
const entityDir = resolveContained(
|
|
81
|
+
const entityDir = resolveContained(entityParentDir, entityDirName_);
|
|
82
|
+
let staleDirNames;
|
|
83
|
+
try {
|
|
84
|
+
staleDirNames = findDirsWithStaleSlug(await fsReaddir(entityParentDir), entityId, entityDirName_);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
staleDirNames = [];
|
|
88
|
+
}
|
|
89
|
+
for (const staleDirName of staleDirNames) {
|
|
90
|
+
const staleDir = await resolveContainedSafe(entityParentDir, staleDirName);
|
|
91
|
+
await fsRm(staleDir, { recursive: true, force: true });
|
|
92
|
+
}
|
|
81
93
|
const entityJsonPath = path.join(entityDir, fileName);
|
|
82
94
|
await assertNoSymlinkInPath(projectDirPath, entityJsonPath);
|
|
83
95
|
await writeJson(entityJsonPath, entity);
|
|
@@ -195,7 +207,7 @@ export async function runPull(deps, options) {
|
|
|
195
207
|
const dir = await resolveContainedSafe(projectsRoot, newDirName);
|
|
196
208
|
let existingDirName;
|
|
197
209
|
try {
|
|
198
|
-
existingDirName = (await readdir(projectsRoot)
|
|
210
|
+
existingDirName = findDirsWithStaleSlug(await readdir(projectsRoot), validatedProjectId, newDirName)[0];
|
|
199
211
|
}
|
|
200
212
|
catch {
|
|
201
213
|
existingDirName = undefined;
|
package/dist/lib/slug.js
CHANGED
|
@@ -11,4 +11,7 @@ export function slugify(name) {
|
|
|
11
11
|
export function entityDirName(uuid, name) {
|
|
12
12
|
return `${uuid}--${slugify(name)}`;
|
|
13
13
|
}
|
|
14
|
+
export function findDirsWithStaleSlug(dirNames, uuid, currentDirName) {
|
|
15
|
+
return dirNames.filter((name) => name.split("--")[0] === uuid && name !== currentDirName);
|
|
16
|
+
}
|
|
14
17
|
//# sourceMappingURL=slug.js.map
|
|
@@ -5,20 +5,8 @@ export function printTrayYamlNotFound(stderr) {
|
|
|
5
5
|
stderr(`${FAILURE_ICON} tray.yaml not found. Run \`${ansis.bold("tray init")}\` first.`);
|
|
6
6
|
}
|
|
7
7
|
export function printWorkspaceScopeDenied(stderr) {
|
|
8
|
-
stderr(`${INFO_ICON} Operation denied. Project
|
|
9
|
-
stderr(` Create your project with ${ansis.bold("--scope project")} (or omit --scope) in a new directory to use \`${ansis.bold("tray project
|
|
10
|
-
}
|
|
11
|
-
export function printAlreadyTracked(stdout, projectId) {
|
|
12
|
-
stdout(`${ansis.bold(projectId)} is already tracked.`);
|
|
13
|
-
}
|
|
14
|
-
export function printValidationSucceeded(stdout, projectId) {
|
|
15
|
-
stdout(`Validation: ✓ (${ansis.bold(projectId)} found in Tray)`);
|
|
16
|
-
}
|
|
17
|
-
export function printValidationSkippedOrFailed(stdout, message) {
|
|
18
|
-
stdout(`Validation: skipped or failed (${message})`);
|
|
19
|
-
}
|
|
20
|
-
export function printAddedProject(stdout, projectId) {
|
|
21
|
-
stdout(`${ansis.bold("Added")} ${ansis.bold(projectId)} to tray.yaml.projects.`);
|
|
8
|
+
stderr(`${INFO_ICON} Operation denied. Project removal is not allowed in workspace scope.`);
|
|
9
|
+
stderr(` Create your project with ${ansis.bold("--scope project")} (or omit --scope) in a new directory to use \`${ansis.bold("tray project remove")}\`.`);
|
|
22
10
|
}
|
|
23
11
|
export function printNotTracked(stdout, projectId) {
|
|
24
12
|
stdout(`${ansis.bold(projectId)} is not tracked.`);
|
package/package.json
CHANGED