@voobase/cli 0.1.1-nonprod.2039 → 0.1.1-nonprod.2050
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/dist-bundle/index.mjs +69 -61
- package/package.json +9 -6
- package/runtime-sdk/dist/auth/client.d.ts +14 -0
- package/runtime-sdk/dist/auth/client.js +28 -0
- package/runtime-sdk/dist/blob/client.d.ts +39 -0
- package/runtime-sdk/dist/blob/client.js +148 -0
- package/runtime-sdk/dist/blob/client.test.d.ts +1 -0
- package/runtime-sdk/dist/blob/client.test.js +494 -0
- package/runtime-sdk/dist/browser/app.d.ts +142 -0
- package/runtime-sdk/dist/browser/app.js +256 -0
- package/runtime-sdk/dist/browser/app.test.d.ts +1 -0
- package/runtime-sdk/dist/browser/app.test.js +81 -0
- package/runtime-sdk/dist/browser/errors.d.ts +39 -0
- package/runtime-sdk/dist/browser/errors.js +265 -0
- package/runtime-sdk/dist/browser/errors.test.d.ts +1 -0
- package/runtime-sdk/dist/browser/errors.test.js +41 -0
- package/runtime-sdk/dist/browser.d.ts +5 -0
- package/runtime-sdk/dist/browser.js +5 -0
- package/runtime-sdk/dist/client.d.ts +16 -0
- package/runtime-sdk/dist/client.js +22 -0
- package/runtime-sdk/dist/cms/client.d.ts +39 -0
- package/runtime-sdk/dist/cms/client.js +56 -0
- package/runtime-sdk/dist/cms/client.test.d.ts +1 -0
- package/runtime-sdk/dist/cms/client.test.js +56 -0
- package/runtime-sdk/dist/cms/media.d.ts +10 -0
- package/runtime-sdk/dist/cms/media.js +113 -0
- package/runtime-sdk/dist/cms/media.test.d.ts +1 -0
- package/runtime-sdk/dist/cms/media.test.js +76 -0
- package/runtime-sdk/dist/cms/taxonomy.d.ts +8 -0
- package/runtime-sdk/dist/cms/taxonomy.js +77 -0
- package/runtime-sdk/dist/cms/taxonomy.test.d.ts +1 -0
- package/runtime-sdk/dist/cms/taxonomy.test.js +65 -0
- package/runtime-sdk/dist/data-form/client.d.ts +19 -0
- package/runtime-sdk/dist/data-form/client.js +118 -0
- package/runtime-sdk/dist/data-form/client.test.d.ts +1 -0
- package/runtime-sdk/dist/data-form/client.test.js +210 -0
- package/runtime-sdk/dist/data-form/contracts.d.ts +10 -0
- package/runtime-sdk/dist/data-form/contracts.js +33 -0
- package/runtime-sdk/dist/data-form/index.d.ts +3 -0
- package/runtime-sdk/dist/data-form/index.js +2 -0
- package/runtime-sdk/dist/data-form/types.d.ts +110 -0
- package/runtime-sdk/dist/data-form/types.js +1 -0
- package/runtime-sdk/dist/http.d.ts +27 -0
- package/runtime-sdk/dist/http.js +126 -0
- package/runtime-sdk/dist/index.d.ts +19 -0
- package/runtime-sdk/dist/index.js +14 -0
- package/runtime-sdk/dist/notification/client.d.ts +16 -0
- package/runtime-sdk/dist/notification/client.js +39 -0
- package/runtime-sdk/dist/notification/index.d.ts +1 -0
- package/runtime-sdk/dist/notification/index.js +1 -0
- package/runtime-sdk/dist/react/context.d.ts +18 -0
- package/runtime-sdk/dist/react/context.js +28 -0
- package/runtime-sdk/dist/react/hooks.d.ts +90 -0
- package/runtime-sdk/dist/react/hooks.js +172 -0
- package/runtime-sdk/dist/react.d.ts +2 -0
- package/runtime-sdk/dist/react.js +2 -0
- package/runtime-sdk/dist/session/browser.d.ts +25 -0
- package/runtime-sdk/dist/session/browser.js +60 -0
- package/runtime-sdk/dist/types/auth.d.ts +38 -0
- package/runtime-sdk/dist/types/auth.js +1 -0
- package/runtime-sdk/dist/types/cms.d.ts +93 -0
- package/runtime-sdk/dist/types/cms.js +1 -0
- package/runtime-sdk/dist/types/notification.d.ts +37 -0
- package/runtime-sdk/dist/types/notification.js +1 -0
- package/runtime-sdk/dist/types/object-storage.d.ts +58 -0
- package/runtime-sdk/dist/types/object-storage.js +1 -0
- package/runtime-sdk/dist/types/workflow.d.ts +52 -0
- package/runtime-sdk/dist/types/workflow.js +1 -0
- package/runtime-sdk/dist/workflow/client.d.ts +30 -0
- package/runtime-sdk/dist/workflow/client.js +45 -0
- package/scaffold-assets-manifest.json +429 -0
- package/template-blocks/assets.ts +95 -0
- package/template-blocks/contract/capabilities.ts +33 -0
- package/template-blocks/contract/facade-contract.ts +59 -0
- package/template-blocks/contract/index.ts +4 -0
- package/template-blocks/contract/path-builders.ts +76 -0
- package/template-blocks/contract/route-path-patterns-source.ts +222 -0
- package/template-blocks/contract/route-path-patterns.ts +210 -0
- package/template-blocks/contract/runtime-config.ts +256 -0
- package/template-blocks/dev-support/index.ts +2 -0
- package/template-blocks/dev-support/node/auth-shell.ts +31 -0
- package/template-blocks/dev-support/node/index.ts +3 -0
- package/template-blocks/dev-support/node/proxy.ts +133 -0
- package/template-blocks/dev-support/node/runtime-config.ts +163 -0
- package/template-blocks/dev-support/vite/auth-shell.ts +23 -0
- package/template-blocks/dev-support/vite/index.ts +3 -0
- package/template-blocks/dev-support/vite/proxy.ts +451 -0
- package/template-blocks/dev-support/vite/runtime-config.ts +107 -0
- package/template-blocks/legacy/app-core.ts +153 -0
- package/template-blocks/legacy/index.ts +10 -0
- package/template-blocks/legacy/lifecycle.ts +38 -0
- package/template-blocks/legacy/metadata.ts +59 -0
- package/template-blocks/legacy/pages.ts +156 -0
- package/template-blocks/legacy/react-lock.ts +1599 -0
- package/template-blocks/legacy/react.ts +823 -0
- package/template-blocks/legacy/tailwind.ts +1138 -0
- package/template-blocks/legacy/wrappers/auth.ts +105 -0
- package/template-blocks/legacy/wrappers/proxy.ts +250 -0
- package/template-blocks/legacy/wrappers/runtime.ts +535 -0
- package/template-blocks/protocol/auth-routes.ts +82 -0
- package/template-blocks/protocol/cms-routes.ts +81 -0
- package/template-blocks/protocol/index.ts +4 -0
- package/template-blocks/protocol/public-app-routes.ts +48 -0
- package/template-blocks/protocol/route-map.ts +75 -0
- package/template-blocks/routing.ts +62 -0
- package/template-blocks/server/html-injection.ts +45 -0
- package/template-blocks/server/proxy-routing.ts +65 -0
- package/template-blocks/server/request-context.ts +103 -0
- package/template-blocks/server/route-strip.ts +47 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { RuntimeApiError } from "../http.js";
|
|
4
|
+
import { classifyBrowserWorkflowError } from "./errors.js";
|
|
5
|
+
test("classifyBrowserWorkflowError maps definition gaps to configuration guidance", () => {
|
|
6
|
+
const error = new RuntimeApiError("workflow definition was not found", 404, "workflow_definition_not_found", {
|
|
7
|
+
definition_key: "wf-demo"
|
|
8
|
+
}, "req-demo-1");
|
|
9
|
+
const resolved = classifyBrowserWorkflowError(error, {
|
|
10
|
+
appId: "app-demo",
|
|
11
|
+
operationLabel: "Workflow 启动"
|
|
12
|
+
});
|
|
13
|
+
assert.equal(resolved.workflowKind, "configuration");
|
|
14
|
+
assert.equal(resolved.isConfigurationGap, true);
|
|
15
|
+
assert.match(resolved.message, /Workflow 定义或绑定/);
|
|
16
|
+
assert.match(resolved.recommendation ?? "", /platform workflow diagnose --app app-demo/);
|
|
17
|
+
});
|
|
18
|
+
test("classifyBrowserWorkflowError maps forbidden actions to role-binding guidance", () => {
|
|
19
|
+
const error = new RuntimeApiError("principal is not allowed to perform this action", 403, "workflow_action_forbidden", {
|
|
20
|
+
action_key: "approve"
|
|
21
|
+
}, "req-demo-2");
|
|
22
|
+
const resolved = classifyBrowserWorkflowError(error, {
|
|
23
|
+
appId: "app-demo"
|
|
24
|
+
});
|
|
25
|
+
assert.equal(resolved.workflowKind, "permission");
|
|
26
|
+
assert.equal(resolved.isPermissionGap, true);
|
|
27
|
+
assert.match(resolved.message, /没有执行这个动作的权限/);
|
|
28
|
+
assert.match(resolved.recommendation ?? "", /role binding/);
|
|
29
|
+
});
|
|
30
|
+
test("classifyBrowserWorkflowError maps already-active instances to state guidance", () => {
|
|
31
|
+
const error = new RuntimeApiError("an active workflow instance already exists for this object", 409, "workflow_instance_already_active", {
|
|
32
|
+
object_id: "demo-request-001"
|
|
33
|
+
}, "req-demo-3");
|
|
34
|
+
const resolved = classifyBrowserWorkflowError(error, {
|
|
35
|
+
appId: "app-demo"
|
|
36
|
+
});
|
|
37
|
+
assert.equal(resolved.workflowKind, "state");
|
|
38
|
+
assert.equal(resolved.isStateGap, true);
|
|
39
|
+
assert.match(resolved.message, /已经有进行中的流程实例/);
|
|
40
|
+
assert.match(resolved.recommendation ?? "", /刷新当前对象状态/);
|
|
41
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createBrowserRuntimeApp, type BrowserRuntimeAppOptions } from "./browser/app.js";
|
|
2
|
+
export { normalizeCmsMediaUrl, resolveCmsCoverImageSet, resolveCmsCoverImageUrl, resolveCmsImageSet, resolveCmsImageUrl, type RuntimeCmsMediaUrlOptions } from "./cms/media.js";
|
|
3
|
+
export { buildCmsCategoryNameMap, resolveCmsCategoryName, resolveCmsItemCategoryName, resolveCmsItemTagLabels, resolveCmsTagLabels } from "./cms/taxonomy.js";
|
|
4
|
+
export { classifyBrowserRuntimeError, classifyBrowserWorkflowError, getBrowserRuntimeErrorMessage, getBrowserWorkflowErrorMessage, resolveEndUserAccess, type BrowserRuntimeAccessKind, type BrowserRuntimeAccessResolution, type BrowserRuntimeErrorKind, type BrowserRuntimeErrorResolution, type BrowserWorkflowErrorKind, type BrowserWorkflowErrorResolution, } from "./browser/errors.js";
|
|
5
|
+
export { buildEndUserSessionStorageKey, createBrowserRuntimeSdk, createBrowserSessionStore, type BrowserSessionStore, type BrowserSessionStoreOptions } from "./session/browser.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createBrowserRuntimeApp } from "./browser/app.js";
|
|
2
|
+
export { normalizeCmsMediaUrl, resolveCmsCoverImageSet, resolveCmsCoverImageUrl, resolveCmsImageSet, resolveCmsImageUrl } from "./cms/media.js";
|
|
3
|
+
export { buildCmsCategoryNameMap, resolveCmsCategoryName, resolveCmsItemCategoryName, resolveCmsItemTagLabels, resolveCmsTagLabels } from "./cms/taxonomy.js";
|
|
4
|
+
export { classifyBrowserRuntimeError, classifyBrowserWorkflowError, getBrowserRuntimeErrorMessage, getBrowserWorkflowErrorMessage, resolveEndUserAccess, } from "./browser/errors.js";
|
|
5
|
+
export { buildEndUserSessionStorageKey, createBrowserRuntimeSdk, createBrowserSessionStore } from "./session/browser.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RuntimeSdkOptions } from "./http.js";
|
|
2
|
+
import { RuntimeAuthClient } from "./auth/client.js";
|
|
3
|
+
import { RuntimeBlobClient } from "./blob/client.js";
|
|
4
|
+
import { RuntimeCmsClient } from "./cms/client.js";
|
|
5
|
+
import { RuntimeDataFormClient } from "./data-form/client.js";
|
|
6
|
+
import { RuntimeNotificationClient } from "./notification/client.js";
|
|
7
|
+
import { RuntimeWorkflowClient } from "./workflow/client.js";
|
|
8
|
+
export declare class PlatformRuntimeSdk {
|
|
9
|
+
readonly auth: RuntimeAuthClient;
|
|
10
|
+
readonly blob: RuntimeBlobClient;
|
|
11
|
+
readonly cms: RuntimeCmsClient;
|
|
12
|
+
readonly dataForm: RuntimeDataFormClient;
|
|
13
|
+
readonly notification: RuntimeNotificationClient;
|
|
14
|
+
readonly workflow: RuntimeWorkflowClient;
|
|
15
|
+
constructor(options: RuntimeSdkOptions);
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { RuntimeAuthClient } from "./auth/client.js";
|
|
2
|
+
import { RuntimeBlobClient } from "./blob/client.js";
|
|
3
|
+
import { RuntimeCmsClient } from "./cms/client.js";
|
|
4
|
+
import { RuntimeDataFormClient } from "./data-form/client.js";
|
|
5
|
+
import { RuntimeNotificationClient } from "./notification/client.js";
|
|
6
|
+
import { RuntimeWorkflowClient } from "./workflow/client.js";
|
|
7
|
+
export class PlatformRuntimeSdk {
|
|
8
|
+
auth;
|
|
9
|
+
blob;
|
|
10
|
+
cms;
|
|
11
|
+
dataForm;
|
|
12
|
+
notification;
|
|
13
|
+
workflow;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.auth = new RuntimeAuthClient(options);
|
|
16
|
+
this.blob = new RuntimeBlobClient(options);
|
|
17
|
+
this.cms = new RuntimeCmsClient(options);
|
|
18
|
+
this.dataForm = new RuntimeDataFormClient(options);
|
|
19
|
+
this.notification = new RuntimeNotificationClient(options);
|
|
20
|
+
this.workflow = new RuntimeWorkflowClient(options);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BaseRuntimeSdk, type RuntimeSdkOptions } from "../http.js";
|
|
2
|
+
import type { RuntimeCmsCategoryTreeResponse, RuntimeCmsCollectionListResponse, RuntimeCmsCollectionMetaResponse, RuntimeCmsDeliveryListResponse, RuntimeCmsItemDeliveryPayload } from "../types/cms.js";
|
|
3
|
+
export declare class RuntimeCmsClient extends BaseRuntimeSdk {
|
|
4
|
+
constructor(options: RuntimeSdkOptions);
|
|
5
|
+
private buildCmsPath;
|
|
6
|
+
listCollections(args: {
|
|
7
|
+
tenantId: string;
|
|
8
|
+
}): Promise<RuntimeCmsCollectionListResponse>;
|
|
9
|
+
getCollectionMeta(args: {
|
|
10
|
+
tenantId: string;
|
|
11
|
+
collection: string;
|
|
12
|
+
}): Promise<RuntimeCmsCollectionMetaResponse>;
|
|
13
|
+
listItems(args: {
|
|
14
|
+
tenantId: string;
|
|
15
|
+
collection: string;
|
|
16
|
+
page?: number;
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
categoryId?: string;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
featured?: boolean;
|
|
21
|
+
sortField?: string;
|
|
22
|
+
sortDirection?: "asc" | "desc";
|
|
23
|
+
q?: string;
|
|
24
|
+
}): Promise<RuntimeCmsDeliveryListResponse>;
|
|
25
|
+
listCategories(args: {
|
|
26
|
+
tenantId: string;
|
|
27
|
+
collection: string;
|
|
28
|
+
}): Promise<RuntimeCmsCategoryTreeResponse>;
|
|
29
|
+
getItemBySlug(args: {
|
|
30
|
+
tenantId: string;
|
|
31
|
+
collection: string;
|
|
32
|
+
slug: string;
|
|
33
|
+
}): Promise<RuntimeCmsItemDeliveryPayload>;
|
|
34
|
+
getItemById(args: {
|
|
35
|
+
tenantId: string;
|
|
36
|
+
collection: string;
|
|
37
|
+
itemId: string;
|
|
38
|
+
}): Promise<RuntimeCmsItemDeliveryPayload>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BaseRuntimeSdk } from "../http.js";
|
|
2
|
+
export class RuntimeCmsClient extends BaseRuntimeSdk {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
super(options);
|
|
5
|
+
}
|
|
6
|
+
buildCmsPath(path, tenantId, configure) {
|
|
7
|
+
const normalizedPath = typeof path === "string" ? path.replace(/^\/+/, "") : "";
|
|
8
|
+
const url = new URL(normalizedPath, "http://platform.local/");
|
|
9
|
+
url.searchParams.set("tenant_id", tenantId);
|
|
10
|
+
configure?.(url.searchParams);
|
|
11
|
+
return `${url.pathname}${url.search}`;
|
|
12
|
+
}
|
|
13
|
+
async listCollections(args) {
|
|
14
|
+
return this.request(this.buildCmsPath("/api/public/cms/collections", args.tenantId));
|
|
15
|
+
}
|
|
16
|
+
async getCollectionMeta(args) {
|
|
17
|
+
return this.request(this.buildCmsPath(`/api/public/cms/collections/${encodeURIComponent(args.collection)}/meta`, args.tenantId));
|
|
18
|
+
}
|
|
19
|
+
async listItems(args) {
|
|
20
|
+
return this.request(this.buildCmsPath(`/api/public/cms/collections/${encodeURIComponent(args.collection)}/items`, args.tenantId, (params) => {
|
|
21
|
+
if (args.page !== undefined) {
|
|
22
|
+
params.set("page", String(args.page));
|
|
23
|
+
}
|
|
24
|
+
if (args.pageSize !== undefined) {
|
|
25
|
+
params.set("page_size", String(args.pageSize));
|
|
26
|
+
}
|
|
27
|
+
if (args.categoryId) {
|
|
28
|
+
params.set("category_id", args.categoryId);
|
|
29
|
+
}
|
|
30
|
+
if (args.sortField) {
|
|
31
|
+
params.set("sort_field", args.sortField);
|
|
32
|
+
}
|
|
33
|
+
if (args.sortDirection) {
|
|
34
|
+
params.set("sort_direction", args.sortDirection);
|
|
35
|
+
}
|
|
36
|
+
if (args.q?.trim()) {
|
|
37
|
+
params.set("q", args.q.trim());
|
|
38
|
+
}
|
|
39
|
+
if (typeof args.featured === "boolean") {
|
|
40
|
+
params.set("featured", String(args.featured));
|
|
41
|
+
}
|
|
42
|
+
for (const tag of args.tags ?? []) {
|
|
43
|
+
params.append("tag", tag);
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
async listCategories(args) {
|
|
48
|
+
return this.request(this.buildCmsPath(`/api/public/cms/collections/${encodeURIComponent(args.collection)}/categories`, args.tenantId));
|
|
49
|
+
}
|
|
50
|
+
async getItemBySlug(args) {
|
|
51
|
+
return this.request(this.buildCmsPath(`/api/public/cms/collections/${encodeURIComponent(args.collection)}/items/by-slug/${encodeURIComponent(args.slug)}`, args.tenantId));
|
|
52
|
+
}
|
|
53
|
+
async getItemById(args) {
|
|
54
|
+
return this.request(this.buildCmsPath(`/api/public/cms/collections/${encodeURIComponent(args.collection)}/items/${encodeURIComponent(args.itemId)}`, args.tenantId));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { PlatformRuntimeSdk } from "../client.js";
|
|
4
|
+
test("runtime cms client keeps mounted preview path single when building requests", async () => {
|
|
5
|
+
const calls = [];
|
|
6
|
+
const sdk = new PlatformRuntimeSdk({
|
|
7
|
+
baseUrl: "http://127.0.0.1.nip.io/previews/app-demo/rel-demo/",
|
|
8
|
+
appId: "app-demo",
|
|
9
|
+
fetchImpl: async (input) => {
|
|
10
|
+
const url = String(input);
|
|
11
|
+
calls.push(url);
|
|
12
|
+
if (url === "http://127.0.0.1.nip.io/previews/app-demo/rel-demo/api/public/cms/collections?tenant_id=t-demo") {
|
|
13
|
+
return new Response(JSON.stringify({ collections: [] }), {
|
|
14
|
+
status: 200,
|
|
15
|
+
headers: { "Content-Type": "application/json" }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`Unexpected fetch url: ${url}`);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const result = await sdk.cms.listCollections({ tenantId: "t-demo" });
|
|
22
|
+
assert.deepEqual(result, { collections: [] });
|
|
23
|
+
assert.deepEqual(calls, [
|
|
24
|
+
"http://127.0.0.1.nip.io/previews/app-demo/rel-demo/api/public/cms/collections?tenant_id=t-demo"
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
test("runtime cms client forwards featured query when listing items", async () => {
|
|
28
|
+
const calls = [];
|
|
29
|
+
const sdk = new PlatformRuntimeSdk({
|
|
30
|
+
baseUrl: "http://127.0.0.1.nip.io/previews/app-demo/rel-demo/",
|
|
31
|
+
appId: "app-demo",
|
|
32
|
+
fetchImpl: async (input) => {
|
|
33
|
+
const url = String(input);
|
|
34
|
+
calls.push(url);
|
|
35
|
+
if (url ===
|
|
36
|
+
"http://127.0.0.1.nip.io/previews/app-demo/rel-demo/api/public/cms/collections/article/items?tenant_id=t-demo&page=1&page_size=10&featured=true") {
|
|
37
|
+
return new Response(JSON.stringify({ items: [], page: 1, page_size: 10, total: 0 }), {
|
|
38
|
+
status: 200,
|
|
39
|
+
headers: { "Content-Type": "application/json" }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Unexpected fetch url: ${url}`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const result = await sdk.cms.listItems({
|
|
46
|
+
tenantId: "t-demo",
|
|
47
|
+
collection: "article",
|
|
48
|
+
page: 1,
|
|
49
|
+
pageSize: 10,
|
|
50
|
+
featured: true
|
|
51
|
+
});
|
|
52
|
+
assert.deepEqual(result, { items: [], page: 1, page_size: 10, total: 0 });
|
|
53
|
+
assert.deepEqual(calls, [
|
|
54
|
+
"http://127.0.0.1.nip.io/previews/app-demo/rel-demo/api/public/cms/collections/article/items?tenant_id=t-demo&page=1&page_size=10&featured=true"
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RuntimeCmsImageSet, RuntimeCmsItemDeliveryPayload } from "../types/cms.js";
|
|
2
|
+
export type RuntimeCmsMediaUrlOptions = {
|
|
3
|
+
upgradeInsecureHttp?: boolean;
|
|
4
|
+
currentProtocol?: string | null;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveCmsImageSet(value: unknown, options?: RuntimeCmsMediaUrlOptions): RuntimeCmsImageSet | null;
|
|
7
|
+
export declare function resolveCmsImageUrl(value: unknown, options?: RuntimeCmsMediaUrlOptions): string | null;
|
|
8
|
+
export declare function resolveCmsCoverImageSet(item: RuntimeCmsItemDeliveryPayload, options?: RuntimeCmsMediaUrlOptions): RuntimeCmsImageSet | null;
|
|
9
|
+
export declare function resolveCmsCoverImageUrl(item: RuntimeCmsItemDeliveryPayload, options?: RuntimeCmsMediaUrlOptions): string | null;
|
|
10
|
+
export declare function normalizeCmsMediaUrl(value: unknown, options?: RuntimeCmsMediaUrlOptions): string | null;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const CMS_COVER_FIELD_NAMES = ["cover", "cover_image", "hero_image", "thumbnail", "image"];
|
|
2
|
+
export function resolveCmsImageSet(value, options = {}) {
|
|
3
|
+
const stringValue = asNonEmptyString(value);
|
|
4
|
+
if (stringValue) {
|
|
5
|
+
return buildImageSet({
|
|
6
|
+
original: normalizeCmsMediaUrl(stringValue, options),
|
|
7
|
+
thumbnail: null,
|
|
8
|
+
sm: null,
|
|
9
|
+
md: null,
|
|
10
|
+
lg: null
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const record = asObjectRecord(value);
|
|
14
|
+
if (!record) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const nestedCoverUrls = resolveCmsImageSet(record.cover_urls, options);
|
|
18
|
+
if (nestedCoverUrls) {
|
|
19
|
+
return nestedCoverUrls;
|
|
20
|
+
}
|
|
21
|
+
const variantRecord = asObjectRecord(record.variant_urls);
|
|
22
|
+
return buildImageSet({
|
|
23
|
+
original: normalizeCmsMediaUrl(asNonEmptyString(record.original_url) ?? asNonEmptyString(record.url), options),
|
|
24
|
+
thumbnail: normalizeCmsMediaUrl(asVariantString(variantRecord, "thumbnail"), options),
|
|
25
|
+
sm: normalizeCmsMediaUrl(asVariantString(variantRecord, "sm"), options),
|
|
26
|
+
md: normalizeCmsMediaUrl(asVariantString(variantRecord, "md"), options),
|
|
27
|
+
lg: normalizeCmsMediaUrl(asVariantString(variantRecord, "lg"), options)
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function resolveCmsImageUrl(value, options = {}) {
|
|
31
|
+
const imageSet = resolveCmsImageSet(value, options);
|
|
32
|
+
return pickPreferredImageUrl(imageSet);
|
|
33
|
+
}
|
|
34
|
+
export function resolveCmsCoverImageSet(item, options = {}) {
|
|
35
|
+
const presentationSet = resolveCmsImageSet(item.presentation?.cover_urls, options);
|
|
36
|
+
if (presentationSet) {
|
|
37
|
+
return presentationSet;
|
|
38
|
+
}
|
|
39
|
+
for (const fieldName of CMS_COVER_FIELD_NAMES) {
|
|
40
|
+
const fieldSet = resolveCmsImageSet(item.content[fieldName], options);
|
|
41
|
+
if (fieldSet) {
|
|
42
|
+
return fieldSet;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const presentationUrl = normalizeCmsMediaUrl(item.presentation?.cover_url, options);
|
|
46
|
+
if (!presentationUrl) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
original: presentationUrl,
|
|
51
|
+
thumbnail: null,
|
|
52
|
+
sm: null,
|
|
53
|
+
md: null,
|
|
54
|
+
lg: null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function resolveCmsCoverImageUrl(item, options = {}) {
|
|
58
|
+
const presentationUrl = normalizeCmsMediaUrl(item.presentation?.cover_url, options);
|
|
59
|
+
if (presentationUrl) {
|
|
60
|
+
return presentationUrl;
|
|
61
|
+
}
|
|
62
|
+
return resolveCmsImageUrl(item.presentation?.cover_urls, options) ?? resolveCmsImageUrlFromContent(item, options);
|
|
63
|
+
}
|
|
64
|
+
export function normalizeCmsMediaUrl(value, options = {}) {
|
|
65
|
+
const normalized = asNonEmptyString(value);
|
|
66
|
+
if (!normalized) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (!shouldUpgradeInsecureHttp(options) || !normalized.startsWith("http://")) {
|
|
70
|
+
return normalized;
|
|
71
|
+
}
|
|
72
|
+
return `https://${normalized.slice("http://".length)}`;
|
|
73
|
+
}
|
|
74
|
+
function resolveCmsImageUrlFromContent(item, options) {
|
|
75
|
+
for (const fieldName of CMS_COVER_FIELD_NAMES) {
|
|
76
|
+
const fieldUrl = resolveCmsImageUrl(item.content[fieldName], options);
|
|
77
|
+
if (fieldUrl) {
|
|
78
|
+
return fieldUrl;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function pickPreferredImageUrl(imageSet) {
|
|
84
|
+
if (!imageSet) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return imageSet.md ?? imageSet.lg ?? imageSet.sm ?? imageSet.thumbnail ?? imageSet.original ?? null;
|
|
88
|
+
}
|
|
89
|
+
function buildImageSet(imageSet) {
|
|
90
|
+
if (!imageSet.original && !imageSet.thumbnail && !imageSet.sm && !imageSet.md && !imageSet.lg) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return imageSet;
|
|
94
|
+
}
|
|
95
|
+
function shouldUpgradeInsecureHttp(options) {
|
|
96
|
+
if (options.upgradeInsecureHttp != null) {
|
|
97
|
+
return options.upgradeInsecureHttp;
|
|
98
|
+
}
|
|
99
|
+
return options.currentProtocol === "https:";
|
|
100
|
+
}
|
|
101
|
+
function asObjectRecord(value) {
|
|
102
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
103
|
+
}
|
|
104
|
+
function asVariantString(record, key) {
|
|
105
|
+
return record ? asNonEmptyString(record[key]) : null;
|
|
106
|
+
}
|
|
107
|
+
function asNonEmptyString(value) {
|
|
108
|
+
if (typeof value !== "string") {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const normalized = value.trim();
|
|
112
|
+
return normalized.length > 0 ? normalized : null;
|
|
113
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { normalizeCmsMediaUrl, resolveCmsCoverImageSet, resolveCmsCoverImageUrl, resolveCmsImageSet, resolveCmsImageUrl } from "./media.js";
|
|
4
|
+
test("resolveCmsImageUrl prefers md variant and upgrades insecure http on https pages", () => {
|
|
5
|
+
const url = resolveCmsImageUrl({
|
|
6
|
+
original_url: "http://assets.example.com/original.jpg",
|
|
7
|
+
variant_urls: {
|
|
8
|
+
thumbnail: "http://assets.example.com/thumbnail.jpg",
|
|
9
|
+
md: "http://assets.example.com/md.jpg",
|
|
10
|
+
lg: "http://assets.example.com/lg.jpg"
|
|
11
|
+
}
|
|
12
|
+
}, { currentProtocol: "https:" });
|
|
13
|
+
assert.equal(url, "https://assets.example.com/md.jpg");
|
|
14
|
+
});
|
|
15
|
+
test("resolveCmsImageSet supports direct string values", () => {
|
|
16
|
+
assert.deepEqual(resolveCmsImageSet("http://assets.example.com/file.jpg"), {
|
|
17
|
+
original: "http://assets.example.com/file.jpg",
|
|
18
|
+
thumbnail: null,
|
|
19
|
+
sm: null,
|
|
20
|
+
md: null,
|
|
21
|
+
lg: null
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
test("resolveCmsCoverImageUrl prefers presentation cover_url before nested content fields", () => {
|
|
25
|
+
const item = buildItem({
|
|
26
|
+
presentation: {
|
|
27
|
+
cover_url: "http://assets.example.com/presentation-md.jpg",
|
|
28
|
+
cover_urls: {
|
|
29
|
+
original: "http://assets.example.com/presentation-original.jpg",
|
|
30
|
+
lg: "http://assets.example.com/presentation-lg.jpg"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
content: {
|
|
34
|
+
cover: {
|
|
35
|
+
original_url: "http://assets.example.com/content-original.jpg",
|
|
36
|
+
variant_urls: {
|
|
37
|
+
md: "http://assets.example.com/content-md.jpg"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
assert.equal(resolveCmsCoverImageUrl(item, { currentProtocol: "https:" }), "https://assets.example.com/presentation-md.jpg");
|
|
43
|
+
});
|
|
44
|
+
test("resolveCmsCoverImageSet falls back to nested cover field when presentation is absent", () => {
|
|
45
|
+
const item = buildItem({
|
|
46
|
+
content: {
|
|
47
|
+
cover: {
|
|
48
|
+
original_url: "http://assets.example.com/content-original.jpg",
|
|
49
|
+
variant_urls: {
|
|
50
|
+
thumbnail: "http://assets.example.com/content-thumbnail.jpg",
|
|
51
|
+
md: "http://assets.example.com/content-md.jpg"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
assert.deepEqual(resolveCmsCoverImageSet(item, { upgradeInsecureHttp: true }), {
|
|
57
|
+
original: "https://assets.example.com/content-original.jpg",
|
|
58
|
+
thumbnail: "https://assets.example.com/content-thumbnail.jpg",
|
|
59
|
+
sm: null,
|
|
60
|
+
md: "https://assets.example.com/content-md.jpg",
|
|
61
|
+
lg: null
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
test("normalizeCmsMediaUrl upgrades only insecure http", () => {
|
|
65
|
+
assert.equal(normalizeCmsMediaUrl("http://assets.example.com/a.jpg", { upgradeInsecureHttp: true }), "https://assets.example.com/a.jpg");
|
|
66
|
+
assert.equal(normalizeCmsMediaUrl("https://assets.example.com/a.jpg", { upgradeInsecureHttp: true }), "https://assets.example.com/a.jpg");
|
|
67
|
+
assert.equal(normalizeCmsMediaUrl("/relative/path.jpg", { upgradeInsecureHttp: true }), "/relative/path.jpg");
|
|
68
|
+
});
|
|
69
|
+
function buildItem(overrides) {
|
|
70
|
+
return {
|
|
71
|
+
collection: "articles",
|
|
72
|
+
system: {},
|
|
73
|
+
content: {},
|
|
74
|
+
...overrides
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RuntimeCmsCategoryNode, RuntimeCmsCollectionMetaResponse, RuntimeCmsItemDeliveryPayload } from "../types/cms.js";
|
|
2
|
+
type RuntimeCmsCategoryLookupSource = RuntimeCmsCollectionMetaResponse | RuntimeCmsCategoryNode[] | Map<string, string> | null | undefined;
|
|
3
|
+
export declare function buildCmsCategoryNameMap(source: RuntimeCmsCategoryLookupSource): Map<string, string>;
|
|
4
|
+
export declare function resolveCmsCategoryName(categoryId: unknown, source: RuntimeCmsCategoryLookupSource, fallback?: string): string;
|
|
5
|
+
export declare function resolveCmsItemCategoryName(item: RuntimeCmsItemDeliveryPayload, source: RuntimeCmsCategoryLookupSource, fallback?: string): string;
|
|
6
|
+
export declare function resolveCmsTagLabels(value: unknown): string[];
|
|
7
|
+
export declare function resolveCmsItemTagLabels(item: RuntimeCmsItemDeliveryPayload): string[];
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export function buildCmsCategoryNameMap(source) {
|
|
2
|
+
if (source instanceof Map) {
|
|
3
|
+
return source;
|
|
4
|
+
}
|
|
5
|
+
const map = new Map();
|
|
6
|
+
for (const category of getCategoryNodes(source)) {
|
|
7
|
+
const id = asNonEmptyString(category?.id);
|
|
8
|
+
if (!id) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
map.set(id, asNonEmptyString(category?.name) ?? id);
|
|
12
|
+
}
|
|
13
|
+
return map;
|
|
14
|
+
}
|
|
15
|
+
export function resolveCmsCategoryName(categoryId, source, fallback = "未分类") {
|
|
16
|
+
const normalizedCategoryId = asNonEmptyString(categoryId);
|
|
17
|
+
if (!normalizedCategoryId) {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
return buildCmsCategoryNameMap(source).get(normalizedCategoryId) ?? normalizedCategoryId;
|
|
21
|
+
}
|
|
22
|
+
export function resolveCmsItemCategoryName(item, source, fallback = "未分类") {
|
|
23
|
+
return resolveCmsCategoryName(item?.system?.category_id, source, fallback);
|
|
24
|
+
}
|
|
25
|
+
export function resolveCmsTagLabels(value) {
|
|
26
|
+
const labels = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const candidate of getTagCandidates(value)) {
|
|
29
|
+
const normalized = asNonEmptyString(candidate);
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const label = humanizeTagLabel(normalized);
|
|
34
|
+
const dedupeKey = label.toLocaleLowerCase();
|
|
35
|
+
if (seen.has(dedupeKey)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
seen.add(dedupeKey);
|
|
39
|
+
labels.push(label);
|
|
40
|
+
}
|
|
41
|
+
return labels;
|
|
42
|
+
}
|
|
43
|
+
export function resolveCmsItemTagLabels(item) {
|
|
44
|
+
return resolveCmsTagLabels(item?.system?.tags);
|
|
45
|
+
}
|
|
46
|
+
function getCategoryNodes(source) {
|
|
47
|
+
if (source instanceof Map) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(source)) {
|
|
51
|
+
return source;
|
|
52
|
+
}
|
|
53
|
+
return Array.isArray(source?.categories) ? source.categories : [];
|
|
54
|
+
}
|
|
55
|
+
function getTagCandidates(value) {
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
return value.flatMap((item) => getTagCandidates(item));
|
|
58
|
+
}
|
|
59
|
+
const normalized = asNonEmptyString(value);
|
|
60
|
+
if (!normalized) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return normalized
|
|
64
|
+
.split(/[,\n,|]+/g)
|
|
65
|
+
.map((item) => item.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
function humanizeTagLabel(value) {
|
|
69
|
+
return value.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
70
|
+
}
|
|
71
|
+
function asNonEmptyString(value) {
|
|
72
|
+
if (typeof value !== "string") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const normalized = value.trim();
|
|
76
|
+
return normalized.length > 0 ? normalized : null;
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { buildCmsCategoryNameMap, resolveCmsCategoryName, resolveCmsItemCategoryName, resolveCmsItemTagLabels, resolveCmsTagLabels } from "./taxonomy.js";
|
|
4
|
+
test("buildCmsCategoryNameMap accepts collection meta and keeps id fallback", () => {
|
|
5
|
+
const meta = {
|
|
6
|
+
collection: "article",
|
|
7
|
+
label: "Article",
|
|
8
|
+
template: "article",
|
|
9
|
+
published_item_count: 2,
|
|
10
|
+
schema: {
|
|
11
|
+
schema_version: "1",
|
|
12
|
+
kind: "collection",
|
|
13
|
+
name: "article",
|
|
14
|
+
label: "Article",
|
|
15
|
+
description: "",
|
|
16
|
+
template: "article",
|
|
17
|
+
capabilities: {},
|
|
18
|
+
fields: []
|
|
19
|
+
},
|
|
20
|
+
categories: [
|
|
21
|
+
buildCategory({ id: "cat-a", name: "设计资讯" }),
|
|
22
|
+
buildCategory({ id: "cat-b", name: "" })
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
assert.deepEqual(Array.from(buildCmsCategoryNameMap(meta).entries()), [
|
|
26
|
+
["cat-a", "设计资讯"],
|
|
27
|
+
["cat-b", "cat-b"]
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
test("resolveCmsItemCategoryName falls back to category id or default label", () => {
|
|
31
|
+
const categories = [buildCategory({ id: "cat-a", name: "设计资讯" })];
|
|
32
|
+
assert.equal(resolveCmsCategoryName("cat-a", categories), "设计资讯");
|
|
33
|
+
assert.equal(resolveCmsCategoryName("cat-missing", categories), "cat-missing");
|
|
34
|
+
assert.equal(resolveCmsItemCategoryName(buildItem({ system: { category_id: null } }), categories), "未分类");
|
|
35
|
+
});
|
|
36
|
+
test("resolveCmsTagLabels trims, splits and humanizes tag values", () => {
|
|
37
|
+
assert.deepEqual(resolveCmsTagLabels(["featured_post", "design-news", " Featured Post ", "", null]), [
|
|
38
|
+
"featured post",
|
|
39
|
+
"design news"
|
|
40
|
+
]);
|
|
41
|
+
assert.deepEqual(resolveCmsTagLabels("alpha,beta,gamma"), ["alpha", "beta", "gamma"]);
|
|
42
|
+
});
|
|
43
|
+
test("resolveCmsItemTagLabels returns normalized tag labels from item system", () => {
|
|
44
|
+
const labels = resolveCmsItemTagLabels(buildItem({ system: { tags: ["ui_ux", "design-news"] } }));
|
|
45
|
+
assert.deepEqual(labels, ["ui ux", "design news"]);
|
|
46
|
+
});
|
|
47
|
+
function buildCategory(overrides) {
|
|
48
|
+
return {
|
|
49
|
+
id: "cat-1",
|
|
50
|
+
collection: "article",
|
|
51
|
+
name: "默认分类",
|
|
52
|
+
slug: "default",
|
|
53
|
+
parent_id: null,
|
|
54
|
+
order: 0,
|
|
55
|
+
...overrides
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildItem(overrides) {
|
|
59
|
+
return {
|
|
60
|
+
collection: "article",
|
|
61
|
+
system: {},
|
|
62
|
+
content: {},
|
|
63
|
+
...overrides
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DataFormRuntimeCreateRecordInput, DataFormRuntimeRecord, DataFormRuntimeRecordListResponse, DataFormRuntimeResourceListResponse, DataFormRuntimeSchemaResponse, DataFormRuntimeUpdateRecordInput } from "./types.js";
|
|
2
|
+
import { BaseRuntimeSdk, type RuntimeSdkOptions } from "../http.js";
|
|
3
|
+
export declare class RuntimeDataFormClient extends BaseRuntimeSdk {
|
|
4
|
+
constructor(options: RuntimeSdkOptions);
|
|
5
|
+
private resolveRuntimeEnv;
|
|
6
|
+
private withRuntimeEnv;
|
|
7
|
+
listResources(): Promise<DataFormRuntimeResourceListResponse>;
|
|
8
|
+
getSchema(resourceKey: string): Promise<DataFormRuntimeSchemaResponse>;
|
|
9
|
+
listRecords(args: {
|
|
10
|
+
resourceKey: string;
|
|
11
|
+
page?: number;
|
|
12
|
+
pageSize?: number;
|
|
13
|
+
ownerScope?: "me" | "all";
|
|
14
|
+
q?: string;
|
|
15
|
+
}): Promise<DataFormRuntimeRecordListResponse>;
|
|
16
|
+
getRecord(resourceKey: string, recordId: string): Promise<DataFormRuntimeRecord>;
|
|
17
|
+
createRecord(resourceKey: string, input: DataFormRuntimeCreateRecordInput): Promise<DataFormRuntimeRecord>;
|
|
18
|
+
updateRecord(resourceKey: string, recordId: string, input: DataFormRuntimeUpdateRecordInput): Promise<DataFormRuntimeRecord>;
|
|
19
|
+
}
|