@dreamlake/dreamlake-cli 0.1.0
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/LICENSE +21 -0
- package/README.md +191 -0
- package/bin/dreamlake.js +41 -0
- package/dist/cli/auth/commands.js +240 -0
- package/dist/cli/auth/constants.js +16 -0
- package/dist/cli/auth/credentials.js +157 -0
- package/dist/cli/auth/device-flow.js +134 -0
- package/dist/cli/auth/device-secret.js +34 -0
- package/dist/cli/client.js +99 -0
- package/dist/cli/config.js +81 -0
- package/dist/cli/create/index.js +204 -0
- package/dist/cli/delete/index.js +228 -0
- package/dist/cli/download/index.js +128 -0
- package/dist/cli/glob.js +45 -0
- package/dist/cli/graphql-helpers.js +42 -0
- package/dist/cli/graphql.js +47 -0
- package/dist/cli/helpers.js +106 -0
- package/dist/cli/index.js +97 -0
- package/dist/cli/list/index.js +254 -0
- package/dist/cli/org/index.js +348 -0
- package/dist/cli/pipeline/index.js +481 -0
- package/dist/cli/progress.js +65 -0
- package/dist/cli/prompt.js +17 -0
- package/dist/cli/resources.js +134 -0
- package/dist/cli/target.js +85 -0
- package/dist/cli/team/index.js +411 -0
- package/dist/cli/update/index.js +256 -0
- package/dist/cli/upload/index.js +263 -0
- package/dist/cli/upload/kinds.js +85 -0
- package/dist/cli/upload/multipart.js +211 -0
- package/package.json +58 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// `dreamlake` CLI entry. Wires Commander to subcommand modules. Mirrors
|
|
2
|
+
// the command surface of dreamlake-py's `dreamlake.cli` (the data-warehouse
|
|
3
|
+
// CLI), implemented in lakeshore's TypeScript conventions.
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { authFilePath, BUILTIN_ENVS } from "./auth/credentials.js";
|
|
7
|
+
import { runLogin, runLogout, runProfile, runEnvList, runEnvUse, runEnvRemove, } from "./auth/commands.js";
|
|
8
|
+
import { registerUploadCommand } from "./upload/index.js";
|
|
9
|
+
import { registerDownloadCommand } from "./download/index.js";
|
|
10
|
+
import { registerListCommand } from "./list/index.js";
|
|
11
|
+
import { registerCreateCommand } from "./create/index.js";
|
|
12
|
+
import { registerUpdateCommand } from "./update/index.js";
|
|
13
|
+
import { registerDeleteCommand } from "./delete/index.js";
|
|
14
|
+
import { registerOrgCommand } from "./org/index.js";
|
|
15
|
+
import { registerTeamCommand } from "./team/index.js";
|
|
16
|
+
import { registerPipelineCommand } from "./pipeline/index.js";
|
|
17
|
+
// Resolves to the package root from both src/cli/ (dev) and dist/cli/ (published).
|
|
18
|
+
const { version } = createRequire(import.meta.url)("../../package.json");
|
|
19
|
+
async function main(argv) {
|
|
20
|
+
const program = new Command();
|
|
21
|
+
program
|
|
22
|
+
.name("dreamlake")
|
|
23
|
+
.description("DreamLake CLI — upload/download assets and manage episodes, bindrs, and datasets.")
|
|
24
|
+
.version(version)
|
|
25
|
+
.showHelpAfterError();
|
|
26
|
+
// ─── auth ────────────────────────────────────────────────────────
|
|
27
|
+
//
|
|
28
|
+
// Credentials are stored at `$XDG_CONFIG_HOME/dreamlake/auth.yml`
|
|
29
|
+
// (default `~/.config/dreamlake/auth.yml`) with chmod 600.
|
|
30
|
+
program
|
|
31
|
+
.command("login")
|
|
32
|
+
.description(`authenticate with DreamLake (built-in envs: ${Object.keys(BUILTIN_ENVS).join(", ")})`)
|
|
33
|
+
.option("--env <name>", "environment to log into (built-in or custom; default: active, else staging)")
|
|
34
|
+
.option("--url <url>", "DreamLake server URL (for a custom env, or to override)")
|
|
35
|
+
.option("--bss <url>", "BSS server URL (for a custom env, or to override)")
|
|
36
|
+
.option("--auth <url>", "vuer-auth server URL (must match what the target server trusts)")
|
|
37
|
+
.option("--no-browser", "don't open the browser automatically")
|
|
38
|
+
.option("--token <jwt>", "save a token directly instead of the device flow")
|
|
39
|
+
.action(async (opts) => {
|
|
40
|
+
const rc = await runLogin({
|
|
41
|
+
env: opts.env,
|
|
42
|
+
url: opts.url,
|
|
43
|
+
bss: opts.bss,
|
|
44
|
+
auth: opts.auth,
|
|
45
|
+
noBrowser: opts.browser === false,
|
|
46
|
+
token: opts.token,
|
|
47
|
+
});
|
|
48
|
+
process.exit(rc);
|
|
49
|
+
});
|
|
50
|
+
program
|
|
51
|
+
.command("logout")
|
|
52
|
+
.description("log out of the active environment (removes its saved token)")
|
|
53
|
+
.action(async () => {
|
|
54
|
+
process.exit(await runLogout());
|
|
55
|
+
});
|
|
56
|
+
program
|
|
57
|
+
.command("profile")
|
|
58
|
+
.description("show the current authenticated user")
|
|
59
|
+
.option("--url <url>", "override the saved server URL")
|
|
60
|
+
.action(async (opts) => {
|
|
61
|
+
process.exit(await runProfile({ url: opts.url }));
|
|
62
|
+
});
|
|
63
|
+
// ─── env (switch between logged-in environments) ─────────────────
|
|
64
|
+
const env = program
|
|
65
|
+
.command("env")
|
|
66
|
+
.description(`switch between environments (saved at ${authFilePath()})`);
|
|
67
|
+
env
|
|
68
|
+
.command("list")
|
|
69
|
+
.description("list logged-in environments (* = active)")
|
|
70
|
+
.option("--json", "emit JSON")
|
|
71
|
+
.action(async (opts) => process.exit(await runEnvList(opts)));
|
|
72
|
+
env
|
|
73
|
+
.command("use")
|
|
74
|
+
.description("switch the active environment")
|
|
75
|
+
.argument("<name>", "environment name")
|
|
76
|
+
.action(async (name) => process.exit(await runEnvUse(name)));
|
|
77
|
+
env
|
|
78
|
+
.command("remove")
|
|
79
|
+
.description("remove a saved environment")
|
|
80
|
+
.argument("<name>", "environment name")
|
|
81
|
+
.action(async (name) => process.exit(await runEnvRemove(name)));
|
|
82
|
+
// ─── data-warehouse commands ─────────────────────────────────────
|
|
83
|
+
registerUploadCommand(program);
|
|
84
|
+
registerDownloadCommand(program);
|
|
85
|
+
registerListCommand(program);
|
|
86
|
+
registerCreateCommand(program);
|
|
87
|
+
registerUpdateCommand(program);
|
|
88
|
+
registerDeleteCommand(program);
|
|
89
|
+
registerOrgCommand(program);
|
|
90
|
+
registerTeamCommand(program);
|
|
91
|
+
registerPipelineCommand(program);
|
|
92
|
+
await program.parseAsync(argv);
|
|
93
|
+
}
|
|
94
|
+
main(process.argv).catch((err) => {
|
|
95
|
+
process.stderr.write(`${err.stack ?? err}\n`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
});
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// `dreamlake list ...` — assets (default), bindrs, datasets, episodes.
|
|
2
|
+
// Port of dreamlake-py's cli/commands/list.py. Where the Python CLI used
|
|
3
|
+
// an interactive 20-per-page pager, we fetch all pages and render one
|
|
4
|
+
// table (more script-friendly); `--json` emits raw JSON.
|
|
5
|
+
import { resolveNamespace, resolveRemote, resolveToken } from "../config.js";
|
|
6
|
+
import { emitJson, fail, humanSize, renderTable } from "../helpers.js";
|
|
7
|
+
import { fetchAllContents, fetchAllEpisodes, fetchAllPages, fetchProjects, nodePathToCommaPrefix, relPathUnder, resolveProjectCtx, } from "../resources.js";
|
|
8
|
+
import { formatProject, formatTarget, parseTarget } from "../target.js";
|
|
9
|
+
export async function runListAssets(opts) {
|
|
10
|
+
if (!opts.episode) {
|
|
11
|
+
fail("--episode is required");
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
let t;
|
|
15
|
+
try {
|
|
16
|
+
t = parseTarget(opts.episode);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
fail(err.message);
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
const remote = resolveRemote();
|
|
23
|
+
const token = resolveToken();
|
|
24
|
+
if (!token) {
|
|
25
|
+
fail("not authenticated. run 'dreamlake login' first");
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
const namespace = await resolveNamespace(t.namespace, { token, remote });
|
|
29
|
+
if (!namespace) {
|
|
30
|
+
fail("namespace not specified and no authenticated user found. run 'dreamlake login'");
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
t.namespace = namespace;
|
|
34
|
+
// Resolve the container node (episode root, or project root when the
|
|
35
|
+
// target has no episode), then list its files via /nodes/:id/contents.
|
|
36
|
+
let containerId;
|
|
37
|
+
let containerPrefix;
|
|
38
|
+
try {
|
|
39
|
+
if (t.episode) {
|
|
40
|
+
const episodes = await fetchAllEpisodes(remote, t.namespace, t.project, token);
|
|
41
|
+
const ep = episodes.find((e) => e.name === t.episode);
|
|
42
|
+
if (!ep) {
|
|
43
|
+
fail(`episode '${t.episode}' not found in ${t.project}@${t.namespace}`);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
containerId = ep.id;
|
|
47
|
+
containerPrefix = nodePathToCommaPrefix(ep.nodePath ?? `/${t.project}/${t.episode}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const projects = await fetchProjects(remote, token, t.namespace);
|
|
51
|
+
const proj = projects.find((p) => p.slug === t.project);
|
|
52
|
+
if (!proj) {
|
|
53
|
+
fail(`project '${t.project}' not found in namespace ${t.namespace}`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
containerId = proj.id;
|
|
57
|
+
containerPrefix = `,${t.project},`;
|
|
58
|
+
}
|
|
59
|
+
const files = await fetchAllContents(remote, token, containerId, opts.type);
|
|
60
|
+
let rows = files.map((f) => ({
|
|
61
|
+
path: relPathUnder(f, containerPrefix),
|
|
62
|
+
kind: f.kind,
|
|
63
|
+
size: f.metadata?.size != null ? humanSize(f.metadata.size) : "—",
|
|
64
|
+
created: String(f.createdAt ?? "").slice(0, 10),
|
|
65
|
+
}));
|
|
66
|
+
if (opts.prefix) {
|
|
67
|
+
const want = opts.prefix.replace(/^\/+/, "");
|
|
68
|
+
rows = rows.filter((r) => r.path.startsWith(want));
|
|
69
|
+
}
|
|
70
|
+
if (opts.json) {
|
|
71
|
+
emitJson(rows);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
process.stdout.write(`Listing ${opts.type ?? "all"} assets in ${formatTarget(t)}\n\n`);
|
|
75
|
+
if (rows.length === 0) {
|
|
76
|
+
process.stdout.write(" (no assets found)\n");
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
process.stdout.write(renderTable(rows, ["path", "kind", "size", "created"]));
|
|
80
|
+
process.stdout.write(`\n ${rows.length} asset(s)\n`);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
fail(err.message);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ─── projects ────────────────────────────────────────────────────────
|
|
89
|
+
export async function runListProjects(opts) {
|
|
90
|
+
const remote = resolveRemote();
|
|
91
|
+
const token = resolveToken();
|
|
92
|
+
if (!token) {
|
|
93
|
+
fail("not authenticated. run 'dreamlake login' first");
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
const namespace = await resolveNamespace(opts.namespace, { token, remote });
|
|
97
|
+
if (!namespace) {
|
|
98
|
+
fail("namespace not specified and no authenticated user found. run 'dreamlake login'");
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const projects = await fetchProjects(remote, token, namespace);
|
|
102
|
+
if (opts.json) {
|
|
103
|
+
emitJson(projects);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
process.stdout.write(`Listing projects in namespace ${namespace}\n\n`);
|
|
107
|
+
const rows = projects.map((p) => ({
|
|
108
|
+
slug: p.slug,
|
|
109
|
+
name: p.name ?? "",
|
|
110
|
+
visibility: p.visibility ?? "",
|
|
111
|
+
bindrs: p.bindrCount ?? 0,
|
|
112
|
+
datasets: p.datasetCount ?? 0,
|
|
113
|
+
created: String(p.createdAt ?? "").slice(0, 10),
|
|
114
|
+
}));
|
|
115
|
+
process.stdout.write(renderTable(rows, ["slug", "name", "visibility", "bindrs", "datasets", "created"]));
|
|
116
|
+
process.stdout.write(`\n ${projects.length} project(s)\n`);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
// ─── bindrs / datasets / episodes ────────────────────────────────────
|
|
120
|
+
export async function runListBindrs(opts) {
|
|
121
|
+
if (!opts.project) {
|
|
122
|
+
fail("--project is required for listing bindrs");
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
const ctx = await resolveProjectCtx(opts.project);
|
|
126
|
+
if (!ctx)
|
|
127
|
+
return 1;
|
|
128
|
+
const { s, token, remote } = ctx;
|
|
129
|
+
const result = await fetchAllPages(remote, `/namespaces/${s.namespace}/projects/${s.project}/bindrs`, token, "bindrs");
|
|
130
|
+
if (result === "not-found") {
|
|
131
|
+
process.stdout.write(" (project not found)\n");
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (opts.json) {
|
|
135
|
+
emitJson(result.items);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
process.stdout.write(`Listing bindrs in ${formatProject(s)}\n\n`);
|
|
139
|
+
const rows = result.items.map((d) => ({
|
|
140
|
+
name: String(d.name ?? ""),
|
|
141
|
+
files: Array.isArray(d.members) ? d.members.length : 0,
|
|
142
|
+
tags: Array.isArray(d.tags) ? d.tags.join(", ") : "",
|
|
143
|
+
description: String(d.description ?? "").slice(0, 40),
|
|
144
|
+
created: String(d.createdAt ?? "").slice(0, 10),
|
|
145
|
+
}));
|
|
146
|
+
process.stdout.write(renderTable(rows, ["name", "files", "tags", "description", "created"]));
|
|
147
|
+
process.stdout.write(`\n ${result.total} bindr(s)\n`);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
export async function runListDatasets(opts) {
|
|
151
|
+
if (!opts.project) {
|
|
152
|
+
fail("--project is required for listing datasets");
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
const ctx = await resolveProjectCtx(opts.project);
|
|
156
|
+
if (!ctx)
|
|
157
|
+
return 1;
|
|
158
|
+
const { s, token, remote } = ctx;
|
|
159
|
+
const result = await fetchAllPages(remote, `/namespaces/${s.namespace}/projects/${s.project}/datasets`, token, "datasets");
|
|
160
|
+
if (result === "not-found") {
|
|
161
|
+
process.stdout.write(" (project not found)\n");
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
if (opts.json) {
|
|
165
|
+
emitJson(result.items);
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
process.stdout.write(`Listing datasets in ${formatProject(s)}\n\n`);
|
|
169
|
+
const rows = result.items.map((d) => ({
|
|
170
|
+
name: String(d.name ?? ""),
|
|
171
|
+
bindrs: Array.isArray(d.bindrs) ? d.bindrs.length : 0,
|
|
172
|
+
tags: Array.isArray(d.tags) ? d.tags.join(", ") : "",
|
|
173
|
+
description: String(d.description ?? "").slice(0, 40),
|
|
174
|
+
created: String(d.createdAt ?? "").slice(0, 10),
|
|
175
|
+
}));
|
|
176
|
+
process.stdout.write(renderTable(rows, ["name", "bindrs", "tags", "description", "created"]));
|
|
177
|
+
process.stdout.write(`\n ${result.total} dataset(s)\n`);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
export async function runListEpisodes(opts) {
|
|
181
|
+
if (!opts.project) {
|
|
182
|
+
fail("--project is required for listing episodes");
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
const ctx = await resolveProjectCtx(opts.project);
|
|
186
|
+
if (!ctx)
|
|
187
|
+
return 1;
|
|
188
|
+
const { s, token, remote } = ctx;
|
|
189
|
+
const result = await fetchAllPages(remote, `/namespaces/${s.namespace}/projects/${s.project}/episodes`, token, "episodes");
|
|
190
|
+
if (result === "not-found") {
|
|
191
|
+
process.stdout.write(" (project not found)\n");
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
if (opts.json) {
|
|
195
|
+
emitJson(result.items);
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
process.stdout.write(`Listing episodes in ${formatProject(s)}\n\n`);
|
|
199
|
+
const rows = result.items.map((ep) => ({
|
|
200
|
+
name: String(ep.name ?? ""),
|
|
201
|
+
path: String(ep.nodePath ?? ""),
|
|
202
|
+
status: String(ep.status ?? ""),
|
|
203
|
+
tags: Array.isArray(ep.tags) ? ep.tags.join(", ") : "",
|
|
204
|
+
created: String(ep.createdAt ?? "").slice(0, 10),
|
|
205
|
+
}));
|
|
206
|
+
process.stdout.write(renderTable(rows, ["name", "path", "status", "tags", "created"]));
|
|
207
|
+
process.stdout.write(`\n ${result.total} episode(s)\n`);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
// ─── registration ────────────────────────────────────────────────────
|
|
211
|
+
export function registerListCommand(program) {
|
|
212
|
+
const list = program
|
|
213
|
+
.command("list")
|
|
214
|
+
.description("list assets (default), or projects / bindrs / datasets / episodes")
|
|
215
|
+
.option("--episode <target>", "episode scope: space[@namespace][:episode]")
|
|
216
|
+
.option("--prefix <path>", "filter assets by path prefix")
|
|
217
|
+
.option("--type <category>", "filter assets by category")
|
|
218
|
+
.option("--json", "emit JSON instead of a table")
|
|
219
|
+
.action(async (opts) => {
|
|
220
|
+
process.exit(await runListAssets(opts));
|
|
221
|
+
});
|
|
222
|
+
list
|
|
223
|
+
.command("project")
|
|
224
|
+
.description("list projects in a namespace")
|
|
225
|
+
.option("--namespace <slug>", "namespace (default: your own)")
|
|
226
|
+
.option("--json", "emit JSON instead of a table")
|
|
227
|
+
.action(async (opts) => {
|
|
228
|
+
process.exit(await runListProjects(opts));
|
|
229
|
+
});
|
|
230
|
+
list
|
|
231
|
+
.command("bindr")
|
|
232
|
+
.description("list bindrs in a project")
|
|
233
|
+
.requiredOption("--project <target>", "project scope: space[@namespace]")
|
|
234
|
+
.option("--json", "emit JSON instead of a table")
|
|
235
|
+
.action(async (opts) => {
|
|
236
|
+
process.exit(await runListBindrs(opts));
|
|
237
|
+
});
|
|
238
|
+
list
|
|
239
|
+
.command("dataset")
|
|
240
|
+
.description("list datasets in a project")
|
|
241
|
+
.requiredOption("--project <target>", "project scope: space[@namespace]")
|
|
242
|
+
.option("--json", "emit JSON instead of a table")
|
|
243
|
+
.action(async (opts) => {
|
|
244
|
+
process.exit(await runListDatasets(opts));
|
|
245
|
+
});
|
|
246
|
+
list
|
|
247
|
+
.command("episode")
|
|
248
|
+
.description("list episodes in a project")
|
|
249
|
+
.requiredOption("--project <target>", "project scope: space[@namespace]")
|
|
250
|
+
.option("--json", "emit JSON instead of a table")
|
|
251
|
+
.action(async (opts) => {
|
|
252
|
+
process.exit(await runListEpisodes(opts));
|
|
253
|
+
});
|
|
254
|
+
}
|