@moloodev/cli 0.4.0 → 0.5.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/package.json +6 -6
- package/src/auth.ts +15 -14
- package/src/cli.ts +14 -3
- package/src/index.ts +0 -1
- package/src/publish.ts +33 -52
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moloodev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"moloo": "./bin/moloo.mjs"
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
"tw-animate-css": "^1.4.0",
|
|
23
23
|
"tsx": "^4.23.13",
|
|
24
24
|
"vite": "^8.2.2",
|
|
25
|
-
"@moloodev/editor-sdk": "0.
|
|
26
|
-
"@moloodev/
|
|
27
|
-
"@moloodev/
|
|
28
|
-
"@moloodev/ui": "0.
|
|
25
|
+
"@moloodev/editor-sdk": "0.5.0",
|
|
26
|
+
"@moloodev/editor-store": "0.5.0",
|
|
27
|
+
"@moloodev/platform": "0.5.0",
|
|
28
|
+
"@moloodev/ui": "0.5.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^24.13.3",
|
|
32
32
|
"typescript": "^7.0.2",
|
|
33
|
-
"@moloodev/typescript-config": "0.
|
|
33
|
+
"@moloodev/typescript-config": "0.5.0"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
package/src/auth.ts
CHANGED
|
@@ -72,7 +72,7 @@ export async function login(
|
|
|
72
72
|
|
|
73
73
|
export async function loginToken(store = credentialStore(ISSUER), issuer = ISSUER) {
|
|
74
74
|
const tokens = await store.read();
|
|
75
|
-
if (!tokens) throw new Error("Not logged in. Run moloo login
|
|
75
|
+
if (!tokens) throw new Error("Not logged in. Run moloo login.");
|
|
76
76
|
if (tokens.expiresAt > Date.now() + 60_000) return tokens.accessToken;
|
|
77
77
|
const updated = await readTokens(
|
|
78
78
|
await oauthPost(issuer, "/oauth2/token", {
|
|
@@ -100,22 +100,23 @@ export async function logout(store: CredentialStore = credentialStore(ISSUER), i
|
|
|
100
100
|
const tokens = await store.read();
|
|
101
101
|
if (tokens) await revokeTokens(issuer, tokens);
|
|
102
102
|
await store.delete();
|
|
103
|
-
console.log(tokens ? "Logged out." : "
|
|
103
|
+
console.log(tokens ? "Logged out." : "No valid credentials; local entry cleared.");
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
async function authorizePublish(issuer: string, scope: string): Promise<Tokens> {
|
|
107
|
+
if (process.env.CI || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
108
|
+
throw new Error("Publish scope required. Run moloo editor publish interactively to authorize editor:publish.");
|
|
109
|
+
}
|
|
110
|
+
return authorizeLogin(issuer, scope);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function publishToken(
|
|
108
114
|
store = credentialStore(ISSUER),
|
|
109
115
|
issuer = ISSUER,
|
|
110
|
-
authorize =
|
|
111
|
-
) {
|
|
112
|
-
if (serviceToken !== undefined) {
|
|
113
|
-
const token = serviceToken.trim();
|
|
114
|
-
if (!token) throw new Error("MOLOO_SERVICE_TOKEN is empty");
|
|
115
|
-
return { token, bearer: false };
|
|
116
|
-
}
|
|
116
|
+
authorize = authorizePublish,
|
|
117
|
+
): Promise<string> {
|
|
117
118
|
const previous = await store.read();
|
|
118
|
-
if (!previous) throw new Error("Not logged in. Run moloo login
|
|
119
|
+
if (!previous) throw new Error("Not logged in. Run moloo login.");
|
|
119
120
|
if (!previous.scopes.includes("editor:publish")) {
|
|
120
121
|
const scopes = [...new Set([...previous.scopes, "editor:publish"])];
|
|
121
122
|
const tokens = await authorize(issuer, scopes.join(" "));
|
|
@@ -124,9 +125,9 @@ export async function publishCredentials(
|
|
|
124
125
|
throw new Error("OAuth authorization did not grant the requested publish scopes.");
|
|
125
126
|
}
|
|
126
127
|
await replaceTokens(store, issuer, previous, tokens);
|
|
127
|
-
return
|
|
128
|
+
return tokens.accessToken;
|
|
128
129
|
}
|
|
129
|
-
return
|
|
130
|
+
return loginToken(store, issuer);
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
export async function cliIdentity(
|
package/src/cli.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { publishEditorPackage } from "./publish";
|
|
|
6
6
|
import { listWorkspaces, useWorkspace, type WorkspaceOptions } from "./workspace";
|
|
7
7
|
|
|
8
8
|
const EDITOR_USAGE =
|
|
9
|
-
"Usage: moloo editor <dev|build|publish> [--dir <path>] [--out-dir <path>] [--view-port <port>] [--workspace <slug>]
|
|
9
|
+
"Usage: moloo editor <dev|build|publish> [--dir <path>] [--out-dir <path>] [--view-port <port>] [--workspace <slug>]\nDev view port: 4310 (default)";
|
|
10
10
|
const WORKSPACE_USAGE = "Usage: moloo workspace list | use <slug>";
|
|
11
11
|
const USAGE = `Usage: moloo <command>\n\nCommands:\n login [--device]\n logout\n whoami\n workspace list | use <slug>\n editor dev | build | publish\n\n${WORKSPACE_USAGE}\n${EDITOR_USAGE}`;
|
|
12
12
|
|
|
@@ -40,10 +40,21 @@ const editorCommands: Record<string, Command> = {
|
|
|
40
40
|
);
|
|
41
41
|
return 0;
|
|
42
42
|
},
|
|
43
|
-
async publish(args) {
|
|
43
|
+
async publish(args, options) {
|
|
44
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
45
|
+
if (!["--dir", "--workspace"].includes(args[index] ?? "") ||
|
|
46
|
+
!args[index + 1] || args[index + 1]?.startsWith("--")) {
|
|
47
|
+
throw new Error(EDITOR_USAGE);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
44
50
|
const result = await publishEditorPackage(packageRootFlag(args), {
|
|
45
|
-
|
|
51
|
+
...options, workspace: readFlag(args, "--workspace"),
|
|
46
52
|
});
|
|
53
|
+
if (result.status === 202) {
|
|
54
|
+
const receipt = JSON.parse(result.body) as { data: { id: string } };
|
|
55
|
+
console.log(`${result.id} 제출됨·심사 필요 ${receipt.data.id}`);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
47
58
|
console.log(`${result.id} ${String(result.status)} ${result.body}`);
|
|
48
59
|
return result.status === 200 || result.status === 201 ? 0 : 1;
|
|
49
60
|
},
|
package/src/index.ts
CHANGED
package/src/publish.ts
CHANGED
|
@@ -1,34 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { publishToken } from "./auth";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { putScriptUpload } from "@moloodev/editor-store/upload";
|
|
4
|
-
import { getApiOrigin } from "@moloodev/platform";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { getApiOrigin, parseQualifiedName, type PublishTarget } from "@moloodev/platform";
|
|
5
|
+
import { buildEditorPackage, readPackageEditorView } from "./editor-views";
|
|
6
|
+
import { resolveWorkspace, type WorkspaceOptions } from "./workspace";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export function publishEndpoint(apiUrl?: string): string {
|
|
11
|
-
return `${apiBase(apiUrl)}/v1/revisions/publish`;
|
|
8
|
+
export function publishEndpoint(): string {
|
|
9
|
+
return `${getApiOrigin("production")}/v1/revisions/publish`;
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
export function uploadEndpoint(
|
|
15
|
-
return `${
|
|
12
|
+
export function uploadEndpoint(): string {
|
|
13
|
+
return `${getApiOrigin("production")}/v1/revisions/uploads`;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
export async function publishEditor(input: {
|
|
19
17
|
manifest: Record<string, unknown>;
|
|
20
18
|
script: string;
|
|
21
19
|
token: string;
|
|
22
|
-
|
|
23
|
-
service?: string;
|
|
24
|
-
bearer?: boolean;
|
|
20
|
+
target: PublishTarget;
|
|
25
21
|
}): Promise<{ status: number; body: string }> {
|
|
26
|
-
const uploadUrl = uploadEndpoint(
|
|
27
|
-
const publishUrl = publishEndpoint(
|
|
28
|
-
const service = input.service ?? DEFAULT_SERVICE;
|
|
22
|
+
const uploadUrl = uploadEndpoint();
|
|
23
|
+
const publishUrl = publishEndpoint();
|
|
29
24
|
const issued = await fetch(uploadUrl, {
|
|
30
25
|
method: "POST",
|
|
31
|
-
|
|
26
|
+
redirect: "error",
|
|
27
|
+
headers: { authorization: `Bearer ${input.token}`, "content-type": "application/json" },
|
|
28
|
+
body: JSON.stringify({ target: input.target, manifestId: input.manifest.id }),
|
|
32
29
|
});
|
|
33
30
|
const issuedBody = await issued.text();
|
|
34
31
|
if (issued.status !== 201) return { status: issued.status, body: issuedBody };
|
|
@@ -44,13 +41,14 @@ export async function publishEditor(input: {
|
|
|
44
41
|
|
|
45
42
|
const response = await fetch(publishUrl, {
|
|
46
43
|
method: "POST",
|
|
44
|
+
redirect: "error",
|
|
47
45
|
headers: {
|
|
48
|
-
|
|
46
|
+
authorization: `Bearer ${input.token}`,
|
|
49
47
|
"content-type": "application/json",
|
|
50
48
|
},
|
|
51
49
|
body: JSON.stringify({
|
|
50
|
+
target: input.target,
|
|
52
51
|
manifest: input.manifest,
|
|
53
|
-
editorId: reservedEditorId(input.manifest),
|
|
54
52
|
upload: {
|
|
55
53
|
pathname: upload.pathname,
|
|
56
54
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
@@ -62,27 +60,31 @@ export async function publishEditor(input: {
|
|
|
62
60
|
|
|
63
61
|
export async function publishEditorPackage(
|
|
64
62
|
packageRoot: string,
|
|
65
|
-
options:
|
|
63
|
+
options: Omit<WorkspaceOptions, "apiOrigin"> & { workspace?: string } = {},
|
|
66
64
|
): Promise<{ id: string; status: number; body: string }> {
|
|
67
|
-
const
|
|
65
|
+
const view = readPackageEditorView(packageRoot);
|
|
66
|
+
const name = parseQualifiedName(view.id);
|
|
67
|
+
if (!name) throw new Error("Invalid editor manifest id");
|
|
68
|
+
let target: PublishTarget;
|
|
69
|
+
if (name.publisher === "moloo") {
|
|
70
|
+
if (options.workspace !== undefined) throw new Error("Built-in editors do not accept --workspace.");
|
|
71
|
+
target = { kind: "builtin" };
|
|
72
|
+
} else {
|
|
73
|
+
target = { kind: "workspace", workspaceSlug: await resolveWorkspace({
|
|
74
|
+
flag: options.workspace, store: options.store, configPath: options.configPath,
|
|
75
|
+
}) };
|
|
76
|
+
}
|
|
77
|
+
const token = await publishToken(options.store);
|
|
68
78
|
const built = await buildEditorPackage(packageRoot);
|
|
69
79
|
const result = await publishEditor({
|
|
70
80
|
manifest: built.manifest,
|
|
71
81
|
script: built.script,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
service: options.service,
|
|
82
|
+
token,
|
|
83
|
+
target,
|
|
75
84
|
});
|
|
76
85
|
return { id: built.id, ...result };
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
function serviceHeaders(token: string, service: string): Record<string, string> {
|
|
80
|
-
return {
|
|
81
|
-
"x-moloo-service-token": token,
|
|
82
|
-
"x-moloo-service": service,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
88
|
function readUpload(text: string): { pathname: string; token: string } | undefined {
|
|
87
89
|
let value: unknown;
|
|
88
90
|
try {
|
|
@@ -99,27 +101,6 @@ function readUpload(text: string): { pathname: string; token: string } | undefin
|
|
|
99
101
|
return { pathname, token };
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
function apiBase(apiUrl: string | undefined): string {
|
|
103
|
-
const raw = apiUrl?.trim() ?? "";
|
|
104
|
-
const base = raw.length === 0 ? getApiOrigin("production") : raw;
|
|
105
|
-
let url: URL;
|
|
106
|
-
try {
|
|
107
|
-
url = new URL(base);
|
|
108
|
-
} catch {
|
|
109
|
-
throw new Error("publish API origin is invalid");
|
|
110
|
-
}
|
|
111
|
-
if (url.protocol !== "https:") throw new Error("publish API origin must be https");
|
|
112
|
-
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function reservedEditorId(manifest: Record<string, unknown>): string {
|
|
116
|
-
const id = manifest.id;
|
|
117
|
-
if (typeof id !== "string") throw new Error("builtin manifest id is missing");
|
|
118
|
-
const row = builtinEditors.find((item) => `${item.publisherHandle}.${item.slug}` === id);
|
|
119
|
-
if (row === undefined) throw new Error(`builtin editor ${id} is missing`);
|
|
120
|
-
return row.id;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
104
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
124
105
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
125
106
|
}
|