@kokimoki/kit 1.6.6 → 1.7.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/README.md +76 -0
- package/dist/api.d.ts +63 -0
- package/dist/api.js +95 -0
- package/dist/credentials.d.ts +25 -0
- package/dist/credentials.js +44 -0
- package/dist/dev-app.d.ts +57 -0
- package/dist/dev-app.js +271 -0
- package/dist/dev-frame/index.d.ts +5 -0
- package/dist/dev-frame/index.js +9 -0
- package/dist/dev-frame/render-dev-frame.d.ts +30 -0
- package/dist/dev-frame/render-dev-frame.js +160 -0
- package/dist/dev-frame/styles.d.ts +4 -0
- package/dist/dev-frame/styles.js +191 -0
- package/dist/dev-i18n.d.ts +56 -0
- package/dist/dev-i18n.js +164 -0
- package/dist/dev-overlays.d.ts +19 -0
- package/dist/dev-overlays.js +300 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -0
- package/dist/kokimoki-kit-plugin.d.ts +47 -4
- package/dist/kokimoki-kit-plugin.js +403 -82
- package/dist/preprocess-style.js +13 -14
- package/dist/preprocess-style.spec.js +1 -1
- package/dist/production-loading-screen.d.ts +20 -0
- package/dist/production-loading-screen.js +123 -0
- package/dist/schema-builder.d.ts +89 -74
- package/dist/schema-builder.js +149 -132
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/zod.d.ts +1 -0
- package/dist/zod.js +5 -0
- package/docs/kokimoki-kit.instructions.md +341 -0
- package/llms.txt +46 -0
- package/package.json +10 -3
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @kokimoki/kit
|
|
2
|
+
|
|
3
|
+
Vite plugin and development tools for building Kokimoki apps.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @kokimoki/kit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
Add the plugin to your `vite.config.ts`:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { defineConfig } from "vite";
|
|
17
|
+
import { kokimokiKitPlugin } from "@kokimoki/kit";
|
|
18
|
+
import { z } from "@kokimoki/kit";
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
plugins: [
|
|
22
|
+
kokimokiKitPlugin({
|
|
23
|
+
conceptId: "your-concept-id",
|
|
24
|
+
deployCodes: [
|
|
25
|
+
{
|
|
26
|
+
name: "default",
|
|
27
|
+
description: "Default configuration",
|
|
28
|
+
clientContext: {},
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
schema: z.object({
|
|
32
|
+
title: z.string(),
|
|
33
|
+
maxPlayers: z.number(),
|
|
34
|
+
}),
|
|
35
|
+
stores: [
|
|
36
|
+
{ pattern: "game", schema: z.object({ status: z.string() }) },
|
|
37
|
+
{
|
|
38
|
+
pattern: "player-*",
|
|
39
|
+
schema: z.object({ name: z.string() }),
|
|
40
|
+
local: true,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
i18nPath: "./src/i18n",
|
|
44
|
+
i18nPrimaryLng: "en",
|
|
45
|
+
devView: [
|
|
46
|
+
[{ label: "host", clientContext: { mode: "host" } }],
|
|
47
|
+
[
|
|
48
|
+
{ label: "player1", clientContext: { mode: "player" } },
|
|
49
|
+
{ label: "player2", clientContext: { mode: "player" } },
|
|
50
|
+
],
|
|
51
|
+
],
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
- **Vite Plugin** - Integrates Kokimoki into your Vite build
|
|
60
|
+
- **Dev Frame** - Multi-window development view for testing different player modes
|
|
61
|
+
- **Schema Validation** - Zod-based validation for stores and project config
|
|
62
|
+
- **i18n Sync** - Automatic synchronization of translations during development
|
|
63
|
+
- **Style Preprocessing** - Theme color processing for CSS variables
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
See the [docs](./docs/) folder for detailed instructions:
|
|
68
|
+
|
|
69
|
+
| File | Description |
|
|
70
|
+
| ------------------------------------------------------------------- | ------------------------------ |
|
|
71
|
+
| [kokimoki-kit.instructions.md](./docs/kokimoki-kit.instructions.md) | Plugin configuration and usage |
|
|
72
|
+
|
|
73
|
+
## Related Packages
|
|
74
|
+
|
|
75
|
+
- **@kokimoki/app** - Core SDK for runtime (stores, transactions, AI, etc.)
|
|
76
|
+
- **@kokimoki/cli** - CLI for project creation and deployment
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kokimoki API utilities
|
|
3
|
+
* Shared between @kokimoki/cli and @kokimoki/kit
|
|
4
|
+
*/
|
|
5
|
+
import { Organization } from "./credentials";
|
|
6
|
+
export declare const DEFAULT_ENDPOINT = "https://api.kokimoki.com";
|
|
7
|
+
export interface KokimokiApiOptions {
|
|
8
|
+
endpoint: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateConceptRequest {
|
|
12
|
+
packageName: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CreateConceptResponse {
|
|
16
|
+
id: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CreateBuildRequest {
|
|
19
|
+
projectId: string;
|
|
20
|
+
schema: string;
|
|
21
|
+
deployCodes?: {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
clientContext: any;
|
|
25
|
+
}[];
|
|
26
|
+
defaultStyle?: string;
|
|
27
|
+
defaultConfig?: string;
|
|
28
|
+
version: string;
|
|
29
|
+
changelog?: string;
|
|
30
|
+
i18n?: {
|
|
31
|
+
path: string;
|
|
32
|
+
primaryLng: string;
|
|
33
|
+
namespaces: string[];
|
|
34
|
+
languages: string[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface CreateBuildResponse {
|
|
38
|
+
id: string;
|
|
39
|
+
publicPath: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a new concept via the Kokimoki API
|
|
43
|
+
*/
|
|
44
|
+
export declare function createConcept(options: KokimokiApiOptions, request: CreateConceptRequest): Promise<CreateConceptResponse>;
|
|
45
|
+
/**
|
|
46
|
+
* Get organization details via the Kokimoki API
|
|
47
|
+
* Used for authentication validation
|
|
48
|
+
*/
|
|
49
|
+
export declare function getOrganization(endpoint: string, apiKey: string): Promise<Organization>;
|
|
50
|
+
/**
|
|
51
|
+
* Get the organization that owns a concept (by concept ID)
|
|
52
|
+
*/
|
|
53
|
+
export declare function getConceptOrganization(endpoint: string, conceptId: string): Promise<Organization>;
|
|
54
|
+
/**
|
|
55
|
+
* Create a new build via the Kokimoki API
|
|
56
|
+
*/
|
|
57
|
+
export declare function createBuild(options: KokimokiApiOptions, request: CreateBuildRequest, cliVersion?: string): Promise<CreateBuildResponse>;
|
|
58
|
+
/**
|
|
59
|
+
* Get the deploy URL for a build
|
|
60
|
+
*/
|
|
61
|
+
export declare function getDeployUrl(endpoint: string, apiKey: string, buildId: string): Promise<{
|
|
62
|
+
url: string;
|
|
63
|
+
}>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Kokimoki API utilities
|
|
4
|
+
* Shared between @kokimoki/cli and @kokimoki/kit
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.DEFAULT_ENDPOINT = void 0;
|
|
8
|
+
exports.createConcept = createConcept;
|
|
9
|
+
exports.getOrganization = getOrganization;
|
|
10
|
+
exports.getConceptOrganization = getConceptOrganization;
|
|
11
|
+
exports.createBuild = createBuild;
|
|
12
|
+
exports.getDeployUrl = getDeployUrl;
|
|
13
|
+
exports.DEFAULT_ENDPOINT = "https://api.kokimoki.com";
|
|
14
|
+
/**
|
|
15
|
+
* Make a request to the Kokimoki API
|
|
16
|
+
*/
|
|
17
|
+
async function kokimokiApiRequest(path, options, init) {
|
|
18
|
+
const url = `${options.endpoint}${path}`;
|
|
19
|
+
const res = await fetch(url, {
|
|
20
|
+
...init,
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: options.apiKey,
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
...init?.headers,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const errorText = await res.text();
|
|
29
|
+
throw new Error(`Kokimoki API request failed: ${res.status} ${res.statusText} - ${errorText}`);
|
|
30
|
+
}
|
|
31
|
+
return await res.json();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a new concept via the Kokimoki API
|
|
35
|
+
*/
|
|
36
|
+
async function createConcept(options, request) {
|
|
37
|
+
return kokimokiApiRequest("/concepts", options, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
body: JSON.stringify(request),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get organization details via the Kokimoki API
|
|
44
|
+
* Used for authentication validation
|
|
45
|
+
*/
|
|
46
|
+
async function getOrganization(endpoint, apiKey) {
|
|
47
|
+
return kokimokiApiRequest("/auth", { endpoint, apiKey }, { method: "GET" });
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the organization that owns a concept (by concept ID)
|
|
51
|
+
*/
|
|
52
|
+
async function getConceptOrganization(endpoint, conceptId) {
|
|
53
|
+
const url = `${endpoint}/concepts/${conceptId}/organization`;
|
|
54
|
+
const res = await fetch(url, {
|
|
55
|
+
method: "GET",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
const errorData = await res.json().catch(() => ({}));
|
|
62
|
+
throw new Error(errorData.message || `Failed to get concept organization: ${res.status}`);
|
|
63
|
+
}
|
|
64
|
+
return await res.json();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a new build via the Kokimoki API
|
|
68
|
+
*/
|
|
69
|
+
async function createBuild(options, request, cliVersion) {
|
|
70
|
+
return kokimokiApiRequest("/builds", options, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
"X-KM-CLI-Version": cliVersion || "",
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify(request),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get the deploy URL for a build
|
|
80
|
+
*/
|
|
81
|
+
async function getDeployUrl(endpoint, apiKey, buildId) {
|
|
82
|
+
const url = `${endpoint}/deploy-urls/builds/${buildId}`;
|
|
83
|
+
const res = await fetch(url, {
|
|
84
|
+
method: "GET",
|
|
85
|
+
headers: {
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
Authorization: apiKey,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
const errorData = await res.json().catch(() => ({}));
|
|
92
|
+
throw new Error(errorData.message || `Failed to get deploy URL: ${res.status}`);
|
|
93
|
+
}
|
|
94
|
+
return await res.json();
|
|
95
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials management for Kokimoki CLI and Kit
|
|
3
|
+
* Shared between @kokimoki/cli and @kokimoki/kit
|
|
4
|
+
*/
|
|
5
|
+
export interface Credentials {
|
|
6
|
+
apiKeys: {
|
|
7
|
+
[organizationId: string]: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface Organization {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the path to the global credentials file (~/.kokimoki)
|
|
16
|
+
*/
|
|
17
|
+
export declare function getCredentialsPath(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Read credentials from the ~/.kokimoki file
|
|
20
|
+
*/
|
|
21
|
+
export declare function readCredentials(): Promise<Credentials | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Write credentials to the ~/.kokimoki file
|
|
24
|
+
*/
|
|
25
|
+
export declare function writeCredentials(credentials: Credentials): Promise<void>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Credentials management for Kokimoki CLI and Kit
|
|
4
|
+
* Shared between @kokimoki/cli and @kokimoki/kit
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.getCredentialsPath = getCredentialsPath;
|
|
11
|
+
exports.readCredentials = readCredentials;
|
|
12
|
+
exports.writeCredentials = writeCredentials;
|
|
13
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
14
|
+
const os_1 = require("os");
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
/**
|
|
17
|
+
* Get the path to the global credentials file (~/.kokimoki)
|
|
18
|
+
*/
|
|
19
|
+
function getCredentialsPath() {
|
|
20
|
+
return path_1.default.join((0, os_1.homedir)(), ".kokimoki");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read credentials from the ~/.kokimoki file
|
|
24
|
+
*/
|
|
25
|
+
async function readCredentials() {
|
|
26
|
+
try {
|
|
27
|
+
const credentialsPath = getCredentialsPath();
|
|
28
|
+
const credentialsJson = await promises_1.default.readFile(credentialsPath, "utf-8");
|
|
29
|
+
return JSON.parse(credentialsJson);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
if (e?.code === "ENOENT") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Write credentials to the ~/.kokimoki file
|
|
40
|
+
*/
|
|
41
|
+
async function writeCredentials(credentials) {
|
|
42
|
+
const credentialsPath = getCredentialsPath();
|
|
43
|
+
await promises_1.default.writeFile(credentialsPath, JSON.stringify(credentials, null, 2));
|
|
44
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Credentials, Organization } from "./credentials";
|
|
2
|
+
export type { Credentials, Organization };
|
|
3
|
+
export interface DevAppConfig {
|
|
4
|
+
conceptId: string;
|
|
5
|
+
endpoint?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DevAppError {
|
|
8
|
+
code: "CONCEPT_NOT_FOUND" | "NO_CREDENTIALS" | "DEV_APP_INIT_FAILED";
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DevAppResult {
|
|
12
|
+
appId: string;
|
|
13
|
+
buildUrl?: string;
|
|
14
|
+
organization?: Organization;
|
|
15
|
+
error?: DevAppError;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Delete the app ID file from .kokimoki/app-id
|
|
19
|
+
*/
|
|
20
|
+
export declare function deleteAppId(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Set i18n metadata for a dev app
|
|
23
|
+
*/
|
|
24
|
+
export declare function setDevAppI18n(endpoint: string, apiKey: string, appId: string, i18nMeta: {
|
|
25
|
+
path: string;
|
|
26
|
+
primaryLng: string;
|
|
27
|
+
namespaces: string[];
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Set translations for a specific language and namespace in a dev app
|
|
31
|
+
*/
|
|
32
|
+
export declare function setDevAppTranslation(endpoint: string, apiKey: string, appId: string, lng: string, namespace: string, translations: Record<string, unknown>): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Compute a hash of the stores configuration
|
|
35
|
+
*/
|
|
36
|
+
export declare function computeStoresHash(stores: unknown): string;
|
|
37
|
+
/**
|
|
38
|
+
* Read the stored stores hash from .kokimoki/stores-hash
|
|
39
|
+
*/
|
|
40
|
+
export declare function readStoresHash(): Promise<string | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Write the stores hash to .kokimoki/stores-hash
|
|
43
|
+
*/
|
|
44
|
+
export declare function writeStoresHash(hash: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Get or create a dev app for local development.
|
|
47
|
+
*
|
|
48
|
+
* This function will:
|
|
49
|
+
* 1. Verify the concept ID and get the organization it belongs to
|
|
50
|
+
* 2. Read credentials from ~/.kokimoki
|
|
51
|
+
* 3. If an app ID exists locally, verify it with the API
|
|
52
|
+
* 4. If no app ID exists or verification fails, initialize a new dev app
|
|
53
|
+
* 5. Store the app ID in .kokimoki/app-id
|
|
54
|
+
*
|
|
55
|
+
* If credentials are not available, falls back to generating a local-only ObjectId.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getOrCreateDevApp(config: DevAppConfig): Promise<DevAppResult>;
|
package/dist/dev-app.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.deleteAppId = deleteAppId;
|
|
7
|
+
exports.setDevAppI18n = setDevAppI18n;
|
|
8
|
+
exports.setDevAppTranslation = setDevAppTranslation;
|
|
9
|
+
exports.computeStoresHash = computeStoresHash;
|
|
10
|
+
exports.readStoresHash = readStoresHash;
|
|
11
|
+
exports.writeStoresHash = writeStoresHash;
|
|
12
|
+
exports.getOrCreateDevApp = getOrCreateDevApp;
|
|
13
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
14
|
+
const bson_objectid_1 = __importDefault(require("bson-objectid"));
|
|
15
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const api_1 = require("./api");
|
|
18
|
+
const credentials_1 = require("./credentials");
|
|
19
|
+
const KOKIMOKI_DIR = ".kokimoki";
|
|
20
|
+
const APP_ID_FILE = "app-id";
|
|
21
|
+
const STORES_HASH_FILE = "stores-hash";
|
|
22
|
+
/**
|
|
23
|
+
* Initialize a new dev app via the Kokimoki API
|
|
24
|
+
*/
|
|
25
|
+
async function initializeDevApp(endpoint, apiKey, conceptId) {
|
|
26
|
+
const url = `${endpoint}/dev-apps`;
|
|
27
|
+
const res = await fetch(url, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
Authorization: apiKey,
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({ conceptId }),
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
const errorData = await res.json().catch(() => ({}));
|
|
37
|
+
throw new Error(errorData.message || `Failed to initialize dev app: ${res.status}`);
|
|
38
|
+
}
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
return { appId: data.appId, buildUrl: data.buildUrl };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Verify an existing dev app ID via the Kokimoki API
|
|
44
|
+
* Returns buildUrl if valid, null if invalid
|
|
45
|
+
*/
|
|
46
|
+
async function verifyDevApp(endpoint, apiKey, appId, conceptId) {
|
|
47
|
+
const url = `${endpoint}/dev-apps/${appId}/verify`;
|
|
48
|
+
const res = await fetch(url, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
Authorization: apiKey,
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({ conceptId }),
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
// If verification fails, the app ID may be invalid or belong to a different concept
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
return data.valid === true ? data.buildUrl : null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Ensure the .kokimoki directory exists
|
|
65
|
+
*/
|
|
66
|
+
async function ensureKokimokiDir() {
|
|
67
|
+
try {
|
|
68
|
+
await promises_1.default.mkdir(KOKIMOKI_DIR);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
if (e?.code !== "EEXIST") {
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Read the local app ID from .kokimoki/app-id
|
|
78
|
+
*/
|
|
79
|
+
async function readLocalAppId() {
|
|
80
|
+
try {
|
|
81
|
+
const appId = await promises_1.default.readFile(path_1.default.join(KOKIMOKI_DIR, APP_ID_FILE), "utf8");
|
|
82
|
+
return appId.trim() || null;
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
if (e?.code === "ENOENT") {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
throw e;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Write the app ID to .kokimoki/app-id
|
|
93
|
+
*/
|
|
94
|
+
async function writeLocalAppId(appId) {
|
|
95
|
+
await ensureKokimokiDir();
|
|
96
|
+
await promises_1.default.writeFile(path_1.default.join(KOKIMOKI_DIR, APP_ID_FILE), appId);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Delete the app ID file from .kokimoki/app-id
|
|
100
|
+
*/
|
|
101
|
+
async function deleteAppId() {
|
|
102
|
+
try {
|
|
103
|
+
await promises_1.default.unlink(path_1.default.join(KOKIMOKI_DIR, APP_ID_FILE));
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
if (e?.code !== "ENOENT") {
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Set i18n metadata for a dev app
|
|
113
|
+
*/
|
|
114
|
+
async function setDevAppI18n(endpoint, apiKey, appId, i18nMeta) {
|
|
115
|
+
const url = `${endpoint}/dev-apps/${appId}/i18n`;
|
|
116
|
+
const res = await fetch(url, {
|
|
117
|
+
method: "PUT",
|
|
118
|
+
headers: {
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
Authorization: apiKey,
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify(i18nMeta),
|
|
123
|
+
});
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
const errorText = await res.text().catch(() => "");
|
|
126
|
+
throw new Error(`Failed to set dev app i18n: ${res.status} ${errorText}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Set translations for a specific language and namespace in a dev app
|
|
131
|
+
*/
|
|
132
|
+
async function setDevAppTranslation(endpoint, apiKey, appId, lng, namespace, translations) {
|
|
133
|
+
const url = `${endpoint}/dev-apps/${appId}/translations/${lng}/${namespace}`;
|
|
134
|
+
const res = await fetch(url, {
|
|
135
|
+
method: "PUT",
|
|
136
|
+
headers: {
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
Authorization: apiKey,
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify(translations),
|
|
141
|
+
});
|
|
142
|
+
if (!res.ok) {
|
|
143
|
+
const errorText = await res.text().catch(() => "");
|
|
144
|
+
throw new Error(`Failed to set dev app translation: ${res.status} ${errorText}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Compute a hash of the stores configuration
|
|
149
|
+
*/
|
|
150
|
+
function computeStoresHash(stores) {
|
|
151
|
+
const content = JSON.stringify(stores ?? []);
|
|
152
|
+
return crypto_1.default.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Read the stored stores hash from .kokimoki/stores-hash
|
|
156
|
+
*/
|
|
157
|
+
async function readStoresHash() {
|
|
158
|
+
try {
|
|
159
|
+
const hash = await promises_1.default.readFile(path_1.default.join(KOKIMOKI_DIR, STORES_HASH_FILE), "utf8");
|
|
160
|
+
return hash.trim() || null;
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
if (e?.code === "ENOENT") {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
throw e;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Write the stores hash to .kokimoki/stores-hash
|
|
171
|
+
*/
|
|
172
|
+
async function writeStoresHash(hash) {
|
|
173
|
+
await ensureKokimokiDir();
|
|
174
|
+
await promises_1.default.writeFile(path_1.default.join(KOKIMOKI_DIR, STORES_HASH_FILE), hash);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get or create a dev app for local development.
|
|
178
|
+
*
|
|
179
|
+
* This function will:
|
|
180
|
+
* 1. Verify the concept ID and get the organization it belongs to
|
|
181
|
+
* 2. Read credentials from ~/.kokimoki
|
|
182
|
+
* 3. If an app ID exists locally, verify it with the API
|
|
183
|
+
* 4. If no app ID exists or verification fails, initialize a new dev app
|
|
184
|
+
* 5. Store the app ID in .kokimoki/app-id
|
|
185
|
+
*
|
|
186
|
+
* If credentials are not available, falls back to generating a local-only ObjectId.
|
|
187
|
+
*/
|
|
188
|
+
async function getOrCreateDevApp(config) {
|
|
189
|
+
const endpoint = config.endpoint ?? api_1.DEFAULT_ENDPOINT;
|
|
190
|
+
await ensureKokimokiDir();
|
|
191
|
+
// Try to read existing app ID
|
|
192
|
+
let appId = await readLocalAppId();
|
|
193
|
+
// Try to verify concept and get organization
|
|
194
|
+
let organization;
|
|
195
|
+
try {
|
|
196
|
+
organization = await (0, api_1.getConceptOrganization)(endpoint, config.conceptId);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
const errorMessage = e instanceof Error ? e.message : "unknown error";
|
|
200
|
+
console.warn(`[kokimoki-kit] Could not verify concept: ${errorMessage}`);
|
|
201
|
+
// Fall back to local-only mode but report error
|
|
202
|
+
if (!appId) {
|
|
203
|
+
appId = new bson_objectid_1.default().toHexString();
|
|
204
|
+
await writeLocalAppId(appId);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
appId,
|
|
208
|
+
error: {
|
|
209
|
+
code: "CONCEPT_NOT_FOUND",
|
|
210
|
+
message: `Could not verify concept "${config.conceptId}": ${errorMessage}. Make sure the conceptId in your vite.config is correct.`,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Try to read credentials
|
|
215
|
+
const credentials = await (0, credentials_1.readCredentials)();
|
|
216
|
+
const apiKey = credentials?.apiKeys?.[organization.id];
|
|
217
|
+
if (!apiKey) {
|
|
218
|
+
const errorMessage = `No credentials found for organization ${organization.name} (${organization.id}). Run "kokimoki login" to authenticate.`;
|
|
219
|
+
console.warn(`[kokimoki-kit] ${errorMessage}`);
|
|
220
|
+
// Fall back to local-only mode but report error
|
|
221
|
+
if (!appId) {
|
|
222
|
+
appId = new bson_objectid_1.default().toHexString();
|
|
223
|
+
await writeLocalAppId(appId);
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
appId,
|
|
227
|
+
organization,
|
|
228
|
+
error: {
|
|
229
|
+
code: "NO_CREDENTIALS",
|
|
230
|
+
message: errorMessage,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// If we have an existing app ID, verify it
|
|
235
|
+
if (appId) {
|
|
236
|
+
try {
|
|
237
|
+
const buildUrl = await verifyDevApp(endpoint, apiKey, appId, config.conceptId);
|
|
238
|
+
if (buildUrl) {
|
|
239
|
+
return { appId, buildUrl, organization };
|
|
240
|
+
}
|
|
241
|
+
console.warn(`[kokimoki-kit] Existing dev app ID is invalid or belongs to a different concept. Creating new one.`);
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
console.warn(`[kokimoki-kit] Could not verify dev app: ${e instanceof Error ? e.message : "unknown error"}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Initialize a new dev app
|
|
248
|
+
try {
|
|
249
|
+
const { appId: newAppId, buildUrl } = await initializeDevApp(endpoint, apiKey, config.conceptId);
|
|
250
|
+
appId = newAppId;
|
|
251
|
+
await writeLocalAppId(appId);
|
|
252
|
+
return { appId, buildUrl, organization };
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
const errorMessage = e instanceof Error ? e.message : "unknown error";
|
|
256
|
+
console.warn(`[kokimoki-kit] Could not initialize dev app: ${errorMessage}`);
|
|
257
|
+
// Fall back to local-only mode but report error
|
|
258
|
+
if (!appId) {
|
|
259
|
+
appId = new bson_objectid_1.default().toHexString();
|
|
260
|
+
await writeLocalAppId(appId);
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
appId,
|
|
264
|
+
organization,
|
|
265
|
+
error: {
|
|
266
|
+
code: "DEV_APP_INIT_FAILED",
|
|
267
|
+
message: `Could not initialize dev app: ${errorMessage}`,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dev frame module - provides a multi-window development view
|
|
4
|
+
* for testing host, presenter, and player modes simultaneously
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.renderDevFrame = void 0;
|
|
8
|
+
var render_dev_frame_1 = require("./render-dev-frame");
|
|
9
|
+
Object.defineProperty(exports, "renderDevFrame", { enumerable: true, get: function () { return render_dev_frame_1.renderDevFrame; } });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the dev frame HTML page - a multi-window view for development
|
|
3
|
+
*/
|
|
4
|
+
export interface DevFrameCell {
|
|
5
|
+
/** Label shown in the frame header */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Client context passed to the app */
|
|
8
|
+
clientContext: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface DevFrameConfig {
|
|
11
|
+
/** Title shown in browser tab */
|
|
12
|
+
title?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Grid layout for the dev frame. Each inner array is a row,
|
|
15
|
+
* and each item in the row is a cell/frame.
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
* ```
|
|
19
|
+
* [
|
|
20
|
+
* [{ label: 'host', clientContext: {...} }, { label: 'presenter', clientContext: {...} }],
|
|
21
|
+
* [{ label: 'player1', clientContext: {...} }, { label: 'player2', clientContext: {...} }]
|
|
22
|
+
* ]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
rows: readonly (readonly DevFrameCell[])[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render the complete dev frame HTML page
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderDevFrame(config: DevFrameConfig): string;
|