@moloodev/cli 0.3.0 → 0.4.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/src/publish.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { publishCredentials } from "./auth";
1
2
  import { createHash } from "node:crypto";
2
3
  import { putScriptUpload } from "@moloodev/editor-store/upload";
3
4
  import { getApiOrigin } from "@moloodev/platform";
@@ -20,13 +21,14 @@ export async function publishEditor(input: {
20
21
  token: string;
21
22
  apiUrl?: string;
22
23
  service?: string;
24
+ bearer?: boolean;
23
25
  }): Promise<{ status: number; body: string }> {
24
26
  const uploadUrl = uploadEndpoint(input.apiUrl);
25
27
  const publishUrl = publishEndpoint(input.apiUrl);
26
28
  const service = input.service ?? DEFAULT_SERVICE;
27
29
  const issued = await fetch(uploadUrl, {
28
30
  method: "POST",
29
- headers: serviceHeaders(input.token, service),
31
+ headers: input.bearer ? { authorization: `Bearer ${input.token}` } : serviceHeaders(input.token, service),
30
32
  });
31
33
  const issuedBody = await issued.text();
32
34
  if (issued.status !== 201) return { status: issued.status, body: issuedBody };
@@ -43,7 +45,7 @@ export async function publishEditor(input: {
43
45
  const response = await fetch(publishUrl, {
44
46
  method: "POST",
45
47
  headers: {
46
- ...serviceHeaders(input.token, service),
48
+ ...(input.bearer ? { authorization: `Bearer ${input.token}` } : serviceHeaders(input.token, service)),
47
49
  "content-type": "application/json",
48
50
  },
49
51
  body: JSON.stringify({
@@ -62,13 +64,12 @@ export async function publishEditorPackage(
62
64
  packageRoot: string,
63
65
  options: { token?: string; apiUrl?: string; service?: string } = {},
64
66
  ): Promise<{ id: string; status: number; body: string }> {
65
- const token = (options.token ?? process.env.MOLOO_SERVICE_TOKEN ?? "").trim();
66
- if (token.length === 0) throw new Error("MOLOO_SERVICE_TOKEN is empty");
67
+ const credentials = await publishCredentials(options.token ?? process.env.MOLOO_SERVICE_TOKEN);
67
68
  const built = await buildEditorPackage(packageRoot);
68
69
  const result = await publishEditor({
69
70
  manifest: built.manifest,
70
71
  script: built.script,
71
- token,
72
+ ...credentials,
72
73
  apiUrl: options.apiUrl,
73
74
  service: options.service,
74
75
  });
@@ -0,0 +1,68 @@
1
+ import { cliIdentity } from "./auth";
2
+ import { readConfig, writeConfig } from "./config";
3
+ import type { CredentialStore } from "./credentials";
4
+
5
+ export type WorkspaceOptions = {
6
+ store?: CredentialStore;
7
+ apiOrigin?: string;
8
+ configPath?: string;
9
+ };
10
+
11
+ export async function listWorkspaces(options: WorkspaceOptions = {}): Promise<void> {
12
+ const { workspaces: available } = await cliIdentity(options.store, options.apiOrigin);
13
+ const config = await readConfig(options.configPath);
14
+ for (const workspace of available) {
15
+ console.log(`${workspace.slug}\t${workspace.name}${workspace.slug === config.workspace ? " (default)" : ""}`);
16
+ }
17
+ if (!available.length) console.log("No workspaces.");
18
+ }
19
+
20
+ export async function useWorkspace(slug: string, options: WorkspaceOptions = {}): Promise<void> {
21
+ const { workspaces: available } = await cliIdentity(options.store, options.apiOrigin);
22
+ const workspace = available.find((item) => item.slug === slug);
23
+ if (!workspace) {
24
+ throw new Error(`Workspace "${slug}" is not in your memberships. Run moloo workspace use <slug>. Available: ${available.map((item) => item.slug).join(", ") || "none"}.`);
25
+ }
26
+ await writeConfig({ workspace: workspace.slug }, options.configPath);
27
+ console.log(`Default workspace: ${workspace.slug} (${workspace.name})`);
28
+ }
29
+
30
+ /** Resolve against current memberships even when the slug came from saved config. */
31
+ export async function resolveWorkspace({
32
+ flag,
33
+ interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY),
34
+ choose = chooseWorkspace,
35
+ ...options
36
+ }: WorkspaceOptions & {
37
+ flag?: string;
38
+ interactive?: boolean;
39
+ choose?: (workspaces: { slug: string; name: string }[]) => Promise<string>;
40
+ } = {}): Promise<string> {
41
+ const { workspaces } = await cliIdentity(options.store, options.apiOrigin);
42
+ const selected = flag ?? (await readConfig(options.configPath)).workspace;
43
+ const available = workspaces.map((workspace) => workspace.slug).join(", ") || "none";
44
+ const guidance = `Run moloo workspace use <slug>. Available: ${available}.`;
45
+ if (selected !== undefined) {
46
+ if (workspaces.some((workspace) => workspace.slug === selected)) return selected;
47
+ throw new Error(`Workspace "${selected}" is not in your memberships. ${guidance}`);
48
+ }
49
+ if (workspaces.length === 1 && workspaces[0]) return workspaces[0].slug;
50
+ if (!workspaces.length || !interactive) throw new Error(`Select a workspace. ${guidance}`);
51
+ const slug = await choose(workspaces);
52
+ if (!workspaces.some((workspace) => workspace.slug === slug)) {
53
+ throw new Error(`Invalid workspace selection. ${guidance}`);
54
+ }
55
+ return slug;
56
+ }
57
+
58
+ async function chooseWorkspace(workspaces: { slug: string; name: string }[]): Promise<string> {
59
+ const { createInterface } = await import("node:readline/promises");
60
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
61
+ try {
62
+ const choices = workspaces.map((workspace, index) => `${index + 1}. ${workspace.slug} (${workspace.name})`).join("\n");
63
+ const answer = (await prompt.question(`${choices}\nWorkspace number: `)).trim();
64
+ return /^[1-9][0-9]*$/.test(answer) ? workspaces[Number(answer) - 1]?.slug ?? "" : "";
65
+ } finally {
66
+ prompt.close();
67
+ }
68
+ }