@kokimoki/kit 1.6.7 → 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 +4 -0
- package/dist/index.js +5 -0
- package/dist/kokimoki-kit-plugin.d.ts +47 -4
- package/dist/kokimoki-kit-plugin.js +383 -71
- package/dist/production-loading-screen.d.ts +20 -0
- package/dist/production-loading-screen.js +123 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/kokimoki-kit.instructions.md +341 -0
- package/llms.txt +46 -0
- package/package.json +10 -3
|
@@ -36,14 +36,89 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.getI18nMeta = void 0;
|
|
39
40
|
exports.kokimokiKitPlugin = kokimokiKitPlugin;
|
|
40
|
-
const bson_objectid_1 = __importDefault(require("bson-objectid"));
|
|
41
41
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
42
43
|
const yaml = __importStar(require("yaml"));
|
|
43
44
|
const v4_1 = require("zod/v4");
|
|
45
|
+
const api_1 = require("./api");
|
|
46
|
+
const credentials_1 = require("./credentials");
|
|
47
|
+
const dev_app_1 = require("./dev-app");
|
|
48
|
+
const dev_frame_1 = require("./dev-frame");
|
|
49
|
+
const dev_i18n_1 = require("./dev-i18n");
|
|
50
|
+
const dev_overlays_1 = require("./dev-overlays");
|
|
44
51
|
const preprocess_style_1 = require("./preprocess-style");
|
|
52
|
+
const production_loading_screen_1 = require("./production-loading-screen");
|
|
45
53
|
const version_1 = require("./version");
|
|
54
|
+
var dev_i18n_2 = require("./dev-i18n");
|
|
55
|
+
Object.defineProperty(exports, "getI18nMeta", { enumerable: true, get: function () { return dev_i18n_2.getI18nMeta; } });
|
|
46
56
|
function kokimokiKitPlugin(config) {
|
|
57
|
+
// Cache for loaded i18n resources
|
|
58
|
+
let cachedI18n = null;
|
|
59
|
+
// Cache dev app info for syncing
|
|
60
|
+
let devAppInfo = null;
|
|
61
|
+
// Initialization state
|
|
62
|
+
let initState = "pending";
|
|
63
|
+
let initPromise = null;
|
|
64
|
+
// Cached dev app result
|
|
65
|
+
let cachedDevAppResult = null;
|
|
66
|
+
/**
|
|
67
|
+
* Start initialization if not already started.
|
|
68
|
+
* Returns a promise that resolves when initialization is complete.
|
|
69
|
+
*/
|
|
70
|
+
function startInitialization() {
|
|
71
|
+
if (initPromise) {
|
|
72
|
+
return initPromise;
|
|
73
|
+
}
|
|
74
|
+
if (initState !== "pending") {
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
initState = "initializing";
|
|
78
|
+
initPromise = (async () => {
|
|
79
|
+
try {
|
|
80
|
+
// Get or create dev app
|
|
81
|
+
cachedDevAppResult = await (0, dev_app_1.getOrCreateDevApp)({
|
|
82
|
+
conceptId: config.conceptId,
|
|
83
|
+
endpoint: config.endpoint,
|
|
84
|
+
});
|
|
85
|
+
const { appId, buildUrl, organization, error: devAppError, } = cachedDevAppResult;
|
|
86
|
+
// Store dev app info for i18n syncing
|
|
87
|
+
if (organization && !devAppError) {
|
|
88
|
+
const credentials = await (0, credentials_1.readCredentials)();
|
|
89
|
+
const apiKey = credentials?.apiKeys?.[organization.id];
|
|
90
|
+
if (apiKey) {
|
|
91
|
+
devAppInfo = {
|
|
92
|
+
appId,
|
|
93
|
+
apiKey,
|
|
94
|
+
endpoint: config.endpoint ?? api_1.DEFAULT_ENDPOINT,
|
|
95
|
+
buildUrl,
|
|
96
|
+
};
|
|
97
|
+
// Sync i18n files to dev app on startup
|
|
98
|
+
const i18nResources = await getI18nResources();
|
|
99
|
+
await (0, dev_i18n_1.syncAllI18nToDevApp)(devAppInfo, i18nResources, config.i18nPrimaryLng ?? "en");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
initState = "ready";
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
console.error("[kokimoki-kit] Initialization error:", e);
|
|
106
|
+
initState = "error";
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
return initPromise;
|
|
110
|
+
}
|
|
111
|
+
async function getI18nResources() {
|
|
112
|
+
if (cachedI18n)
|
|
113
|
+
return cachedI18n;
|
|
114
|
+
if (config.i18nPath) {
|
|
115
|
+
cachedI18n = await (0, dev_i18n_1.loadI18nFromPath)(config.i18nPath);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
cachedI18n = {};
|
|
119
|
+
}
|
|
120
|
+
return cachedI18n;
|
|
121
|
+
}
|
|
47
122
|
return {
|
|
48
123
|
name: "kokimoki-kit",
|
|
49
124
|
async transform(code, id) {
|
|
@@ -54,9 +129,16 @@ function kokimokiKitPlugin(config) {
|
|
|
54
129
|
return code;
|
|
55
130
|
},
|
|
56
131
|
async transformIndexHtml(html) {
|
|
132
|
+
// Load i18n resources (from path or config)
|
|
133
|
+
const i18nResources = await getI18nResources();
|
|
134
|
+
// Extract namespace names and language codes from i18n config
|
|
135
|
+
const i18nNamespaces = Object.keys(Object.values(i18nResources)[0] ?? {});
|
|
136
|
+
const i18nLanguages = Object.keys(i18nResources);
|
|
57
137
|
if (process.env.NODE_ENV !== "development") {
|
|
138
|
+
// Remove any existing km-loading element from the HTML
|
|
139
|
+
const processedHtml = (0, production_loading_screen_1.removeExistingLoadingScreen)(html);
|
|
58
140
|
// NOTE: Try https://github.com/jsdom/jsdom instead of regex parsing
|
|
59
|
-
|
|
141
|
+
const result = processedHtml
|
|
60
142
|
.replace("<head>", `<head>
|
|
61
143
|
<base href="%KM_BASE%">
|
|
62
144
|
<script id="kokimoki-env" type="application/json">
|
|
@@ -68,6 +150,9 @@ function kokimokiKitPlugin(config) {
|
|
|
68
150
|
"code": "%KM_CODE%",
|
|
69
151
|
"clientContext": "%KM_CLIENT_CONTEXT%",
|
|
70
152
|
"config": "%KM_CONFIG%",
|
|
153
|
+
"i18nPath": "km-i18n",
|
|
154
|
+
"i18nNamespaces": ${JSON.stringify(i18nNamespaces)},
|
|
155
|
+
"i18nLanguages": ${JSON.stringify(i18nLanguages)},
|
|
71
156
|
"base": "%KM_BASE%",
|
|
72
157
|
"assets": "%KM_ASSETS%"
|
|
73
158
|
}
|
|
@@ -80,58 +165,67 @@ function kokimokiKitPlugin(config) {
|
|
|
80
165
|
|
|
81
166
|
return "%KM_ASSETS%/" + path;
|
|
82
167
|
};
|
|
83
|
-
</script
|
|
168
|
+
</script>${production_loading_screen_1.loadingScreenStyles}${production_loading_screen_1.loadingScreenScript}
|
|
84
169
|
`)
|
|
170
|
+
.replace(/<body([^>]*)>/, `<body$1>${production_loading_screen_1.loadingScreenElement}`)
|
|
85
171
|
.replace(/<link.*?href="(.*?)".*?>/g, (match, p1) => {
|
|
86
172
|
return match.replace(p1, `%KM_ASSETS%${p1.startsWith("/") ? "" : "/"}${p1}`);
|
|
87
173
|
})
|
|
88
174
|
.replace(/<script.*?src="(.*?)".*?>/g, (match, p1) => {
|
|
89
175
|
return match.replace(p1, `%KM_ASSETS%${p1.startsWith("/") ? "" : "/"}${p1}`);
|
|
90
176
|
});
|
|
177
|
+
return result;
|
|
91
178
|
}
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
179
|
+
// Development mode: ensure initialization is started and wait for it
|
|
180
|
+
if (initState === "pending" || initState === "initializing") {
|
|
181
|
+
// Start initialization if not already started
|
|
182
|
+
startInitialization();
|
|
183
|
+
// Return loading page - it will poll and reload when ready
|
|
184
|
+
return (0, dev_overlays_1.renderLoadingPage)();
|
|
96
185
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
186
|
+
// Get cached dev app result (should be available after initialization)
|
|
187
|
+
const { appId, buildUrl, organization, error: devAppError, } = cachedDevAppResult ?? {
|
|
188
|
+
appId: "",
|
|
189
|
+
buildUrl: undefined,
|
|
190
|
+
organization: undefined,
|
|
191
|
+
error: { code: "INIT_ERROR", message: "Initialization failed" },
|
|
192
|
+
};
|
|
193
|
+
// Check if stores configuration has changed
|
|
194
|
+
const currentStoresHash = (0, dev_app_1.computeStoresHash)(config.stores);
|
|
195
|
+
const previousStoresHash = await (0, dev_app_1.readStoresHash)();
|
|
196
|
+
const storesChanged = previousStoresHash !== null && previousStoresHash !== currentStoresHash;
|
|
197
|
+
// Write initial stores hash if it doesn't exist yet
|
|
198
|
+
if (previousStoresHash === null) {
|
|
199
|
+
await (0, dev_app_1.writeStoresHash)(currentStoresHash);
|
|
107
200
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
201
|
+
// Return full stores changed page if stores configuration has changed
|
|
202
|
+
if (storesChanged) {
|
|
203
|
+
return (0, dev_overlays_1.renderStoresChangedPage)(!!organization);
|
|
112
204
|
}
|
|
113
|
-
//
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
await promises_1.default.writeFile(".kokimoki/app-id", appId);
|
|
205
|
+
// Return full error page if there's a dev app error
|
|
206
|
+
if (devAppError) {
|
|
207
|
+
return (0, dev_overlays_1.renderErrorPage)(devAppError);
|
|
117
208
|
}
|
|
118
209
|
// Get default config
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
210
|
+
let defaultProjectConfig = {};
|
|
211
|
+
if (config.defaultProjectConfigPath && config.schema) {
|
|
212
|
+
const defaultProjectConfigFile = await promises_1.default.readFile(config.defaultProjectConfigPath, "utf8");
|
|
213
|
+
defaultProjectConfig = config.schema.parse(yaml.parse(defaultProjectConfigFile));
|
|
214
|
+
}
|
|
124
215
|
// Inject the app id into the index.html
|
|
125
216
|
html = html.replace("<head>", `<head>
|
|
126
217
|
<script id="kokimoki-env" type="application/json">
|
|
127
218
|
{
|
|
128
219
|
"dev": true,
|
|
129
220
|
"test": true,
|
|
130
|
-
"host": "y-wss.kokimoki.com",
|
|
221
|
+
"host": "${config.host ?? "y-wss.kokimoki.com"}",
|
|
131
222
|
"appId": "${appId}",
|
|
132
223
|
"configObject": ${JSON.stringify(defaultProjectConfig)},
|
|
224
|
+
"i18nNamespaces": ${JSON.stringify(i18nNamespaces)},
|
|
225
|
+
"i18nLanguages": ${JSON.stringify(i18nLanguages)},
|
|
133
226
|
"base": "/",
|
|
134
|
-
"assets": "/"
|
|
227
|
+
"assets": "/",
|
|
228
|
+
"buildUrl": ${JSON.stringify(buildUrl ?? null)}
|
|
135
229
|
}
|
|
136
230
|
</script>`);
|
|
137
231
|
// Inject default project style in development
|
|
@@ -153,6 +247,10 @@ function kokimokiKitPlugin(config) {
|
|
|
153
247
|
throw e;
|
|
154
248
|
}
|
|
155
249
|
}
|
|
250
|
+
// Get i18n metadata
|
|
251
|
+
const i18n = config.i18nPath
|
|
252
|
+
? await (0, dev_i18n_1.getI18nMeta)(config.i18nPath, config.i18nPrimaryLng ?? "en")
|
|
253
|
+
: null;
|
|
156
254
|
// write config
|
|
157
255
|
await promises_1.default.writeFile(".kokimoki/config.json", JSON.stringify({
|
|
158
256
|
kokimokiKitVersion: version_1.KOKIMOKI_KIT_VERSION,
|
|
@@ -161,49 +259,174 @@ function kokimokiKitPlugin(config) {
|
|
|
161
259
|
build: "dist",
|
|
162
260
|
defaultProjectConfigPath: config.defaultProjectConfigPath,
|
|
163
261
|
defaultProjectStylePath: config.defaultProjectStylePath,
|
|
262
|
+
i18n,
|
|
164
263
|
}, null, 2));
|
|
165
264
|
// write schema
|
|
166
|
-
const jsonSchema =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
265
|
+
const jsonSchema = config.schema
|
|
266
|
+
? v4_1.z.toJSONSchema(config.schema, {
|
|
267
|
+
override(ctx) {
|
|
268
|
+
// check if schema is discriminated union
|
|
269
|
+
if (ctx.zodSchema._zod.def.type === "union" &&
|
|
270
|
+
"discriminator" in ctx.zodSchema._zod.def) {
|
|
271
|
+
const discriminator = ctx.zodSchema._zod.def
|
|
272
|
+
.discriminator;
|
|
273
|
+
ctx.jsonSchema.type = "object";
|
|
274
|
+
ctx.jsonSchema.discriminator = { propertyName: discriminator };
|
|
275
|
+
ctx.jsonSchema.required = [discriminator];
|
|
276
|
+
ctx.jsonSchema.oneOf = ctx.jsonSchema.anyOf.map((objectSchema) => ({
|
|
277
|
+
properties: objectSchema.properties,
|
|
278
|
+
additionalProperties: objectSchema.additionalProperties,
|
|
279
|
+
required: (objectSchema.required ?? []).filter((prop) => prop !== discriminator),
|
|
280
|
+
}));
|
|
281
|
+
delete ctx.jsonSchema.anyOf;
|
|
282
|
+
}
|
|
283
|
+
// Remove fields that have a default from the required list
|
|
284
|
+
if (ctx.jsonSchema.properties && ctx.jsonSchema.required) {
|
|
285
|
+
const properties = ctx.jsonSchema.properties;
|
|
286
|
+
ctx.jsonSchema.required = ctx.jsonSchema.required.filter((field) => !("default" in properties[field]));
|
|
287
|
+
}
|
|
288
|
+
// Make sure property has description if set in zod schema
|
|
289
|
+
if ("description" in ctx.zodSchema) {
|
|
290
|
+
ctx.jsonSchema.description = ctx.zodSchema
|
|
291
|
+
.description;
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
})
|
|
295
|
+
: {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {},
|
|
298
|
+
};
|
|
194
299
|
await promises_1.default.writeFile(".kokimoki/schema.json", JSON.stringify(jsonSchema, null, 2));
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
300
|
+
// write stores config with JSON schemas to build output
|
|
301
|
+
if (config.stores) {
|
|
302
|
+
const storesWithJsonSchema = config.stores.map((store) => ({
|
|
303
|
+
pattern: store.pattern,
|
|
304
|
+
local: store.local,
|
|
305
|
+
schema: v4_1.z.toJSONSchema(store.schema),
|
|
306
|
+
}));
|
|
307
|
+
this.emitFile({
|
|
308
|
+
type: "asset",
|
|
309
|
+
fileName: "km-stores.json",
|
|
310
|
+
source: JSON.stringify(storesWithJsonSchema, null, 2),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// write i18n files to build output as individual files per lng/ns
|
|
314
|
+
const i18nResources = await getI18nResources();
|
|
315
|
+
if (Object.keys(i18nResources).length > 0) {
|
|
316
|
+
const i18nManifest = {};
|
|
317
|
+
for (const [lng, namespaces] of Object.entries(i18nResources)) {
|
|
318
|
+
i18nManifest[lng] = {};
|
|
319
|
+
for (const [ns, translations] of Object.entries(namespaces)) {
|
|
320
|
+
const fileName = `km-i18n/${lng}/${ns}.json`;
|
|
321
|
+
this.emitFile({
|
|
322
|
+
type: "asset",
|
|
323
|
+
fileName,
|
|
324
|
+
source: JSON.stringify(translations, null, 2),
|
|
325
|
+
});
|
|
326
|
+
// Store relative path - server can prepend assets base or modify URLs
|
|
327
|
+
i18nManifest[lng][ns] = fileName;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Emit manifest mapping lng/ns to URLs
|
|
331
|
+
this.emitFile({
|
|
332
|
+
type: "asset",
|
|
333
|
+
fileName: "km-i18n.json",
|
|
334
|
+
source: JSON.stringify(i18nManifest, null, 2),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
205
337
|
},
|
|
206
338
|
configureServer(server) {
|
|
339
|
+
// Start initialization as early as possible (don't wait for first request)
|
|
340
|
+
startInitialization();
|
|
341
|
+
// Helper to check stores changed status
|
|
342
|
+
async function checkStoresChanged() {
|
|
343
|
+
const currentStoresHash = (0, dev_app_1.computeStoresHash)(config.stores);
|
|
344
|
+
const previousStoresHash = await (0, dev_app_1.readStoresHash)();
|
|
345
|
+
return (previousStoresHash !== null &&
|
|
346
|
+
previousStoresHash !== currentStoresHash);
|
|
347
|
+
}
|
|
348
|
+
// Serve i18n JSON files in development
|
|
349
|
+
server.middlewares.use("/__kokimoki/i18n", async (req, res, next) => {
|
|
350
|
+
// Parse URL: /__kokimoki/i18n/{lng}/{ns}.json
|
|
351
|
+
const match = req.url?.match(/^\/([^/]+)\/([^/]+)\.json$/);
|
|
352
|
+
if (!match) {
|
|
353
|
+
return next();
|
|
354
|
+
}
|
|
355
|
+
const [, lng, ns] = match;
|
|
356
|
+
try {
|
|
357
|
+
const i18nResources = await getI18nResources();
|
|
358
|
+
const translations = i18nResources[lng]?.[ns];
|
|
359
|
+
if (!translations) {
|
|
360
|
+
res.statusCode = 404;
|
|
361
|
+
res.setHeader("Content-Type", "application/json");
|
|
362
|
+
res.end(JSON.stringify({ error: `Translation not found: ${lng}/${ns}` }));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
res.statusCode = 200;
|
|
366
|
+
res.setHeader("Content-Type", "application/json");
|
|
367
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
368
|
+
res.end(JSON.stringify(translations));
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
res.statusCode = 500;
|
|
372
|
+
res.setHeader("Content-Type", "application/json");
|
|
373
|
+
res.end(JSON.stringify({
|
|
374
|
+
error: e instanceof Error ? e.message : "Unknown error",
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
// API endpoint to check if initialization is complete
|
|
379
|
+
server.middlewares.use("/__kokimoki/ready", async (_req, res) => {
|
|
380
|
+
res.statusCode = 200;
|
|
381
|
+
res.setHeader("Content-Type", "application/json");
|
|
382
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
383
|
+
res.end(JSON.stringify({
|
|
384
|
+
ready: initState === "ready" || initState === "error",
|
|
385
|
+
}));
|
|
386
|
+
});
|
|
387
|
+
// API endpoint to acknowledge stores hash change (dismiss the popup)
|
|
388
|
+
server.middlewares.use("/__kokimoki/stores-hash/acknowledge", async (_req, res) => {
|
|
389
|
+
try {
|
|
390
|
+
const currentStoresHash = (0, dev_app_1.computeStoresHash)(config.stores);
|
|
391
|
+
await (0, dev_app_1.writeStoresHash)(currentStoresHash);
|
|
392
|
+
res.statusCode = 200;
|
|
393
|
+
res.setHeader("Content-Type", "application/json");
|
|
394
|
+
res.end(JSON.stringify({ success: true }));
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
res.statusCode = 500;
|
|
398
|
+
res.setHeader("Content-Type", "application/json");
|
|
399
|
+
res.end(JSON.stringify({
|
|
400
|
+
error: e instanceof Error ? e.message : "Unknown error",
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
// API endpoint to reset dev app state (deletes app-id to create fresh dev app)
|
|
405
|
+
server.middlewares.use("/__kokimoki/dev-app/reset", async (_req, res) => {
|
|
406
|
+
try {
|
|
407
|
+
// Delete the app-id file so a new dev app will be created
|
|
408
|
+
await (0, dev_app_1.deleteAppId)();
|
|
409
|
+
// Update the stores hash
|
|
410
|
+
const currentStoresHash = (0, dev_app_1.computeStoresHash)(config.stores);
|
|
411
|
+
await (0, dev_app_1.writeStoresHash)(currentStoresHash);
|
|
412
|
+
// Reset initialization state so next request triggers re-initialization
|
|
413
|
+
initState = "pending";
|
|
414
|
+
initPromise = null;
|
|
415
|
+
cachedDevAppResult = null;
|
|
416
|
+
// Start re-initialization (polling from loading page will detect when ready)
|
|
417
|
+
startInitialization();
|
|
418
|
+
res.statusCode = 200;
|
|
419
|
+
res.setHeader("Content-Type", "application/json");
|
|
420
|
+
res.end(JSON.stringify({ success: true }));
|
|
421
|
+
}
|
|
422
|
+
catch (e) {
|
|
423
|
+
res.statusCode = 500;
|
|
424
|
+
res.setHeader("Content-Type", "application/json");
|
|
425
|
+
res.end(JSON.stringify({
|
|
426
|
+
error: e instanceof Error ? e.message : "Unknown error",
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
});
|
|
207
430
|
// reload when defaultProjectConfigPath or defaultProjectStylePath changes
|
|
208
431
|
if (config.defaultProjectConfigPath) {
|
|
209
432
|
server.watcher.add(config.defaultProjectConfigPath);
|
|
@@ -211,7 +434,25 @@ function kokimokiKitPlugin(config) {
|
|
|
211
434
|
if (config.defaultProjectStylePath) {
|
|
212
435
|
server.watcher.add(config.defaultProjectStylePath);
|
|
213
436
|
}
|
|
214
|
-
|
|
437
|
+
// Watch i18n folder for changes
|
|
438
|
+
if (config.i18nPath) {
|
|
439
|
+
const i18nAbsolutePath = path_1.default.resolve(process.cwd(), config.i18nPath);
|
|
440
|
+
server.watcher.add(i18nAbsolutePath);
|
|
441
|
+
}
|
|
442
|
+
server.watcher.on("change", async (file) => {
|
|
443
|
+
// Invalidate i18n cache on i18n file changes and sync to dev app
|
|
444
|
+
if (config.i18nPath) {
|
|
445
|
+
const i18nAbsolutePath = path_1.default.resolve(process.cwd(), config.i18nPath);
|
|
446
|
+
if (file.startsWith(i18nAbsolutePath) && file.endsWith(".json")) {
|
|
447
|
+
cachedI18n = null;
|
|
448
|
+
// Sync only the changed file to dev app (only primary language)
|
|
449
|
+
if (devAppInfo) {
|
|
450
|
+
await (0, dev_i18n_1.syncI18nFile)(devAppInfo, config.i18nPath, file, config.i18nPrimaryLng ?? "en");
|
|
451
|
+
}
|
|
452
|
+
server.ws.send({ type: "full-reload" });
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
215
456
|
if (config.defaultProjectConfigPath?.match(file) ||
|
|
216
457
|
config.defaultProjectStylePath?.match(file)) {
|
|
217
458
|
server.ws.send({
|
|
@@ -219,6 +460,77 @@ function kokimokiKitPlugin(config) {
|
|
|
219
460
|
});
|
|
220
461
|
}
|
|
221
462
|
});
|
|
463
|
+
// Intercept all root HTML requests to show loading/error/stores-changed/dev-view at root level
|
|
464
|
+
// This runs BEFORE Vite's built-in middleware so we can intercept index.html requests
|
|
465
|
+
server.middlewares.use(async (req, res, next) => {
|
|
466
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
467
|
+
// Only intercept root path requests (with or without key param)
|
|
468
|
+
if (url.pathname !== "/") {
|
|
469
|
+
return next();
|
|
470
|
+
}
|
|
471
|
+
const hasKeyParam = url.searchParams.has("key");
|
|
472
|
+
// Show loading page while initializing (at root level only)
|
|
473
|
+
if (initState === "pending" || initState === "initializing") {
|
|
474
|
+
if (!hasKeyParam) {
|
|
475
|
+
res.statusCode = 200;
|
|
476
|
+
res.setHeader("Content-Type", "text/html");
|
|
477
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
478
|
+
res.end((0, dev_overlays_1.renderLoadingPage)());
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
// For iframe requests during init, let them wait via transformIndexHtml
|
|
482
|
+
return next();
|
|
483
|
+
}
|
|
484
|
+
const { organization, error: devAppError } = cachedDevAppResult ?? {
|
|
485
|
+
organization: undefined,
|
|
486
|
+
error: { code: "INIT_ERROR", message: "Initialization failed" },
|
|
487
|
+
};
|
|
488
|
+
// Show error page at root level only
|
|
489
|
+
if (devAppError && !hasKeyParam) {
|
|
490
|
+
res.statusCode = 200;
|
|
491
|
+
res.setHeader("Content-Type", "text/html");
|
|
492
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
493
|
+
res.end((0, dev_overlays_1.renderErrorPage)(devAppError));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
// Check if stores configuration has changed
|
|
497
|
+
const storesChanged = await checkStoresChanged();
|
|
498
|
+
if (storesChanged) {
|
|
499
|
+
if (!hasKeyParam) {
|
|
500
|
+
// Root page: show stores changed page
|
|
501
|
+
res.statusCode = 200;
|
|
502
|
+
res.setHeader("Content-Type", "text/html");
|
|
503
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
504
|
+
res.end((0, dev_overlays_1.renderStoresChangedPage)(!!organization));
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
// Iframe: reload parent to show stores changed page at root level
|
|
508
|
+
res.statusCode = 200;
|
|
509
|
+
res.setHeader("Content-Type", "text/html");
|
|
510
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
511
|
+
res.end(`<!DOCTYPE html>
|
|
512
|
+
<html><head><script>window.parent.location.reload();</script></head><body></body></html>`);
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
// For requests with key param, continue to normal handling (Vite will serve index.html)
|
|
517
|
+
if (hasKeyParam) {
|
|
518
|
+
return next();
|
|
519
|
+
}
|
|
520
|
+
// Dev view disabled or not configured, continue to normal handling
|
|
521
|
+
if (config.devView === false || config.devView === undefined) {
|
|
522
|
+
return next();
|
|
523
|
+
}
|
|
524
|
+
// Render dev view HTML at root
|
|
525
|
+
const devViewHtml = (0, dev_frame_1.renderDevFrame)({
|
|
526
|
+
title: "Dev View",
|
|
527
|
+
rows: config.devView,
|
|
528
|
+
});
|
|
529
|
+
res.statusCode = 200;
|
|
530
|
+
res.setHeader("Content-Type", "text/html");
|
|
531
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
532
|
+
res.end(devViewHtml);
|
|
533
|
+
});
|
|
222
534
|
},
|
|
223
535
|
};
|
|
224
536
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production loading screen HTML/CSS/JS for kokimoki-kit plugin.
|
|
3
|
+
* This screen is injected into production builds and removed when km:ready is received.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Loading screen styles
|
|
7
|
+
*/
|
|
8
|
+
export declare const loadingScreenStyles = "\n<style id=\"km-loading-style\">\n #km-loading {\n position: fixed;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 1.5rem;\n background: #fff;\n z-index: 9999;\n transition: opacity 0.3s ease-out;\n color: #1a1a2e;\n text-decoration: none;\n }\n #km-loading.km-ready {\n opacity: 0;\n pointer-events: none;\n }\n #km-loading .km-spinner {\n height: 2rem;\n animation: km-spin 1s linear infinite;\n }\n #km-loading .km-logo {\n height: 3rem;\n }\n #km-loading .km-powered {\n font-size: 0.75rem;\n font-weight: 600;\n opacity: 0.65;\n font-family: system-ui, -apple-system, sans-serif;\n }\n @keyframes km-spin {\n to { transform: rotate(360deg); }\n }\n</style>";
|
|
9
|
+
/**
|
|
10
|
+
* km:ready listener script with 3s minimum display time
|
|
11
|
+
*/
|
|
12
|
+
export declare const loadingScreenScript = "\n<script>\n (function() {\n var startTime = Date.now();\n var minDisplayTime = 3000;\n var appReady = false;\n \n function hideLoading() {\n var el = document.getElementById('km-loading');\n var style = document.getElementById('km-loading-style');\n if (el) {\n el.classList.add('km-ready');\n setTimeout(function() {\n el.remove();\n if (style) style.remove();\n }, 300);\n }\n }\n \n function tryHide() {\n if (!appReady) return;\n var elapsed = Date.now() - startTime;\n var remaining = Math.max(0, minDisplayTime - elapsed);\n setTimeout(hideLoading, remaining);\n }\n \n window.addEventListener('message', function(e) {\n if (e.data === 'km:ready' && !appReady) {\n appReady = true;\n tryHide();\n }\n });\n })();\n</script>";
|
|
13
|
+
/**
|
|
14
|
+
* Loading screen HTML element with Games for Crowds branding
|
|
15
|
+
*/
|
|
16
|
+
export declare const loadingScreenElement = "<a href=\"https://gamesforcrowds.com\" target=\"_blank\" id=\"km-loading\">\n <svg class=\"km-spinner\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M12 22q-2.05 0-3.875-.788t-3.187-2.15t-2.15-3.187T2 12q0-2.075.788-3.887t2.15-3.175t3.187-2.15T12 2q.425 0 .713.288T13 3t-.288.713T12 4Q8.675 4 6.337 6.338T4 12t2.338 5.663T12 20t5.663-2.337T20 12q0-.425.288-.712T21 11t.713.288T22 12q0 2.05-.788 3.875t-2.15 3.188t-3.175 2.15T12 22\"/>\n </svg>\n <svg class=\"km-logo\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 797.59 219.07\" fill=\"currentColor\">\n <g>\n <path d=\"M162.8,96.03c30.87-26.51,10.41-92.22-28.46-95.03-29.39-2.13-60.31-.26-90.99.07-.73,0-1.45-.02-2.17.09C20.3,4.26,3.91,21.33.05,41.8l-.05,123.54c2.6,4.28,5.58,6,10.54,3.94,6.74-3.82,26.58-29.17,31.77-30.22,1.69-.34,3.72.1,5.48-.03-1.15,26.53,19.87,47.52,45.62,49.88,10.24.94,31.34-1.93,39.43.57,8.71,2.7,26.57,28.84,33.53,29.55,3.29.34,7.74-2.14,8.38-5.52,1.72-9.15-2.16-26.7.23-34.77.33-1.1,7.39-8.47,9.02-10.98,16.21-24.98,7.36-61.07-21.22-71.74ZM50.83,121.59l-15.96,2.04-19.08,18.42V46.55c1.67-14.1,15.14-27.27,28.74-30.26,27.33,1.33,57.83-2.6,84.8-.28,20.06,1.73,36.5,34.92,31,55.08-7.53,27.65-45.72,17.08-67.02,18.97-18.52,1.65-36.21,13.88-42.47,31.53ZM170.32,159.08c-1.85,3-10.53,10.69-10.53,11.47v20.5c-1.75.44-2-.72-3.01-1.49-5.71-4.38-11.7-14.4-18.92-16.08-20.43-4.73-47.91,7.68-65.09-10.9-16.98-18.36-8.23-50.63,16.23-55.82,10.35-2.2,44.8-1.74,56.33-.74,24.82,2.14,38.05,31.97,25,53.07Z\"/>\n <polygon points=\"573.74 181.88 556.14 122.98 538.69 122.81 519.73 181.89 501.74 122.88 481.24 122.88 508.73 212.9 525.78 213.95 527.35 213.29 545.25 152.88 564.73 212.9 583.28 213.42 611.24 122.88 590.24 122.88 573.74 181.88\"/>\n <path d=\"M666.74,122.88c-13.74-1.63-30.44,1.21-44.5,0v91h40.5c2.92,0,11.75-2.85,14.86-4.14,38.57-16.14,31.3-81.86-10.86-86.86ZM678.08,187.22c-2.88,4.2-11.41,9.66-16.34,9.66h-20.5v-58c6.18.55,13.48-.75,19.5,0,22.38,2.79,28.85,31.55,17.34,48.34Z\"/>\n <path d=\"M425,121.14c-41.35,5.24-51.11,67.28-16.45,87.94,28.45,16.96,67.81.39,70.73-33.66,2.97-34.62-18.81-58.77-54.28-54.28ZM427.98,198.64c-27.94-4.46-28.04-57.3,1.02-61.5,40.78-5.9,43.14,68.55-1.02,61.5Z\"/>\n <path d=\"M363.48,127.14c-1.58-.84-10.61-4.26-11.74-4.26h-41.5v91h19v-33l11.58.92,18.92,32.08h21.5l-20.99-35.44c19.78-8.94,22.88-40.88,3.23-51.3ZM355.77,157.91c-1.32,4.05-6.98,7.97-11.02,7.97h-15.5v-27c5.36.62,12.39-.85,17.5,0,9.11,1.51,11.57,11.21,9.02,19.03Z\"/>\n <path d=\"M777.02,179.61c-3.95-19.44-32.26-17.78-42.74-26.26-5.4-4.37-4.24-12.34,1.69-15.23,9.47-4.61,20.58,1.49,27.6,7.63l12.65-9.36c-10.04-12.51-23.17-17.41-39.22-15.24-28.56,3.86-35.71,38.53-7.02,50.5,9.33,3.89,33.09,5.53,28.6,20.07-3.99,12.93-29.66,6.57-35.83-2.65l-12.44,11.18c19.81,25.44,74.88,19.65,66.7-20.64Z\"/>\n <path d=\"M267.5,198.64c-41.37,7.02-43.83-58.8-9.75-61.75,10.54-.91,17.28,3.56,24.13,10.88l14.35-7.92c-9.32-19.99-38.11-23.5-56.51-14.99-32.89,15.21-33.7,69.52-.86,85.89,19.28,9.61,48.03,5.78,58.38-14.86l-12.64-7.94c-5.5,3.74-10.09,9.5-17.1,10.69Z\"/>\n <path d=\"M433.4,44.09l21.29,35.46c2.72,2.8,9.85,1.78,13.55,1.07l22.47-37.65v59.55h21.35V4.76l-20.8,1.11-27.53,50.58-29.76-51.69h-20.79v97.76h20.23v-58.43Z\"/>\n <path d=\"M306.43,48.58h-42.7v16.85h21.35c-.27,13.22-7.04,21.88-20.74,22.52-41.54,1.94-38.23-76.19,5.07-66.97,7.84,1.67,10.69,7.85,16.21,11.88l15.05-8.01c-.07-9-13.02-17.18-20.95-19.79-43.1-14.17-73.97,23.73-63.98,65.1,11.75,48.67,85.6,47.68,90.69-4.17.54-5.55-.39-11.78,0-17.42Z\"/>\n <path d=\"M340.09,81.68l33.82-.02,6.67,20.85h23.6L368.81,5.87c-1.88-2.26-20.12-1.02-24.2-.56l-34.81,97.22h23.6l6.7-20.83ZM356.43,29.47l10.67,34.84h-21.35l10.67-34.84Z\"/>\n <polygon points=\"590.71 84.54 551.39 84.54 551.39 60.94 585.1 60.94 585.1 42.96 551.39 42.96 551.39 22.74 588.47 22.74 588.47 4.76 530.04 4.76 530.04 102.52 590.71 102.52 590.71 84.54\"/>\n <path d=\"M608.77,97.39c25.08,16.57,67.45,5.44,61.46-30-1.89-11.15-12.36-17.09-21.93-20.77-7.64-2.94-35.54-8.52-22.36-22.37,8.09-8.51,23.15-1.69,30.32,5.07,2.52-1.37,13.45-9.1,13.04-11.48-14.3-20.97-60.08-20.5-65.72,7.08-2.89,14.13,1.82,25,14.48,31.72,10.24,5.44,38.71,7.15,31.37,24.24-3.47,8.09-18.1,7.13-25,4.22-4.16-1.76-9.85-8.71-12.66-8.29-2.01.3-13.33,8.46-13.03,10.32.38,2.39,7.73,8.73,10.04,10.25Z\"/>\n </g>\n <g>\n <path fill=\"#3c63e7\" d=\"M735,55.98c-24.25,4.14-27.28,47.44,1.59,47.65,31.23.23,35.57-53.99-1.59-47.65ZM734.29,92.39c-9.51-2.94-5.29-24.14,2.49-26.5,16.88-5.12,14.38,31.73-2.49,26.5Z\"/>\n <path fill=\"#3c63e7\" d=\"M716.28,56.67c-3.34-.27-7.15.39-10.41,0-1.21-.14-1.64.47-1.24-1.25,1.77-7.6,5.49-10.69,13.44-7.79l.86-10.79c-14.12-3.91-26.67,3.85-26.53,18.98l-8.25,1.69-1.3,9.08,8.13.02-3.61,36.13,12.26-.85,4.05-35.24h11.1c1.99-.47,1.49-8.28,1.51-9.99Z\"/>\n <path fill=\"#3c63e7\" d=\"M796.72,56.17c-6.86-1.78-12.48,2-15.87,7.73l.45-7.22h-11.74s-4.51,46.06-4.51,46.06h12.65c2.16-13.01-1.47-38.8,18.96-34.32-.94-2.92,2.45-10.57.06-12.25Z\"/>\n </g>\n </svg>\n <p class=\"km-powered\">Powered by Kokimoki</p>\n</a>";
|
|
17
|
+
/**
|
|
18
|
+
* Removes any existing km-loading elements from HTML
|
|
19
|
+
*/
|
|
20
|
+
export declare function removeExistingLoadingScreen(html: string): string;
|