@ikas/component-cli 1.4.0-beta.22 → 1.4.0-beta.220
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/build-tools.d.ts +37 -0
- package/dist/build-tools.d.ts.map +1 -0
- package/dist/build-tools.js +36 -0
- package/dist/build-tools.js.map +1 -0
- package/dist/commands/add-sections-to-page.d.ts +3 -0
- package/dist/commands/add-sections-to-page.d.ts.map +1 -0
- package/dist/commands/add-sections-to-page.js +39 -0
- package/dist/commands/add-sections-to-page.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +11 -2
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +15 -167
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/config-texts.d.ts +38 -0
- package/dist/commands/config-texts.d.ts.map +1 -0
- package/dist/commands/config-texts.js +186 -0
- package/dist/commands/config-texts.js.map +1 -0
- package/dist/commands/config.d.ts +45 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +1148 -154
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/create-design-tokens.d.ts +24 -0
- package/dist/commands/create-design-tokens.d.ts.map +1 -0
- package/dist/commands/create-design-tokens.js +388 -0
- package/dist/commands/create-design-tokens.js.map +1 -0
- package/dist/commands/create-global-variable.d.ts +3 -0
- package/dist/commands/create-global-variable.d.ts.map +1 -0
- package/dist/commands/create-global-variable.js +73 -0
- package/dist/commands/create-global-variable.js.map +1 -0
- package/dist/commands/create-page.d.ts +3 -0
- package/dist/commands/create-page.d.ts.map +1 -0
- package/dist/commands/create-page.js +31 -0
- package/dist/commands/create-page.js.map +1 -0
- package/dist/commands/delete-page.d.ts +3 -0
- package/dist/commands/delete-page.d.ts.map +1 -0
- package/dist/commands/delete-page.js +28 -0
- package/dist/commands/delete-page.js.map +1 -0
- package/dist/commands/delete-theme-globals.d.ts +4 -0
- package/dist/commands/delete-theme-globals.d.ts.map +1 -0
- package/dist/commands/delete-theme-globals.js +50 -0
- package/dist/commands/delete-theme-globals.js.map +1 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +308 -39
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/enable-builtin-pages.d.ts +3 -0
- package/dist/commands/enable-builtin-pages.d.ts.map +1 -0
- package/dist/commands/enable-builtin-pages.js +28 -0
- package/dist/commands/enable-builtin-pages.js.map +1 -0
- package/dist/commands/get-component-props.d.ts +3 -0
- package/dist/commands/get-component-props.d.ts.map +1 -0
- package/dist/commands/get-component-props.js +32 -0
- package/dist/commands/get-component-props.js.map +1 -0
- package/dist/commands/get-page-by-type.d.ts +3 -0
- package/dist/commands/get-page-by-type.d.ts.map +1 -0
- package/dist/commands/get-page-by-type.js +25 -0
- package/dist/commands/get-page-by-type.js.map +1 -0
- package/dist/commands/get-section-values.d.ts +3 -0
- package/dist/commands/get-section-values.d.ts.map +1 -0
- package/dist/commands/get-section-values.js +39 -0
- package/dist/commands/get-section-values.js.map +1 -0
- package/dist/commands/list-builtin-pages.d.ts +3 -0
- package/dist/commands/list-builtin-pages.d.ts.map +1 -0
- package/dist/commands/list-builtin-pages.js +28 -0
- package/dist/commands/list-builtin-pages.js.map +1 -0
- package/dist/commands/list-entities.d.ts +3 -0
- package/dist/commands/list-entities.d.ts.map +1 -0
- package/dist/commands/list-entities.js +32 -0
- package/dist/commands/list-entities.js.map +1 -0
- package/dist/commands/list-page-sections.d.ts.map +1 -1
- package/dist/commands/list-page-sections.js +8 -2
- package/dist/commands/list-page-sections.js.map +1 -1
- package/dist/commands/list-theme-globals.d.ts +3 -0
- package/dist/commands/list-theme-globals.d.ts.map +1 -0
- package/dist/commands/list-theme-globals.js +22 -0
- package/dist/commands/list-theme-globals.js.map +1 -0
- package/dist/commands/list-translations.d.ts +3 -0
- package/dist/commands/list-translations.d.ts.map +1 -0
- package/dist/commands/list-translations.js +33 -0
- package/dist/commands/list-translations.js.map +1 -0
- package/dist/commands/move-page-section.d.ts +3 -0
- package/dist/commands/move-page-section.d.ts.map +1 -0
- package/dist/commands/move-page-section.js +62 -0
- package/dist/commands/move-page-section.js.map +1 -0
- package/dist/commands/publish-theme.d.ts +3 -0
- package/dist/commands/publish-theme.d.ts.map +1 -0
- package/dist/commands/publish-theme.js +29 -0
- package/dist/commands/publish-theme.js.map +1 -0
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +65 -3
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/remove-page-section.d.ts +3 -0
- package/dist/commands/remove-page-section.d.ts.map +1 -0
- package/dist/commands/remove-page-section.js +40 -0
- package/dist/commands/remove-page-section.js.map +1 -0
- package/dist/commands/search-products.d.ts +3 -0
- package/dist/commands/search-products.d.ts.map +1 -0
- package/dist/commands/search-products.js +40 -0
- package/dist/commands/search-products.js.map +1 -0
- package/dist/commands/translation-languages.d.ts +3 -0
- package/dist/commands/translation-languages.d.ts.map +1 -0
- package/dist/commands/translation-languages.js +43 -0
- package/dist/commands/translation-languages.js.map +1 -0
- package/dist/commands/update-builtin-page-prop.d.ts +3 -0
- package/dist/commands/update-builtin-page-prop.d.ts.map +1 -0
- package/dist/commands/update-builtin-page-prop.js +49 -0
- package/dist/commands/update-builtin-page-prop.js.map +1 -0
- package/dist/commands/update-global-variable.d.ts +3 -0
- package/dist/commands/update-global-variable.d.ts.map +1 -0
- package/dist/commands/update-global-variable.js +70 -0
- package/dist/commands/update-global-variable.js.map +1 -0
- package/dist/commands/update-page-sections.d.ts +3 -0
- package/dist/commands/update-page-sections.d.ts.map +1 -0
- package/dist/commands/update-page-sections.js +39 -0
- package/dist/commands/update-page-sections.js.map +1 -0
- package/dist/commands/update-section-prop.d.ts.map +1 -1
- package/dist/commands/update-section-prop.js +25 -8
- package/dist/commands/update-section-prop.js.map +1 -1
- package/dist/commands/update-translations.d.ts +3 -0
- package/dist/commands/update-translations.d.ts.map +1 -0
- package/dist/commands/update-translations.js +37 -0
- package/dist/commands/update-translations.js.map +1 -0
- package/dist/commands/upload-image.d.ts.map +1 -1
- package/dist/commands/upload-image.js +5 -46
- package/dist/commands/upload-image.js.map +1 -1
- package/dist/commands/upload-images.d.ts +3 -0
- package/dist/commands/upload-images.d.ts.map +1 -0
- package/dist/commands/upload-images.js +48 -0
- package/dist/commands/upload-images.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/archive-project.d.ts +50 -0
- package/dist/utils/archive-project.d.ts.map +1 -0
- package/dist/utils/archive-project.js +178 -0
- package/dist/utils/archive-project.js.map +1 -0
- package/dist/utils/compile.d.ts +17 -2
- package/dist/utils/compile.d.ts.map +1 -1
- package/dist/utils/compile.js +759 -117
- package/dist/utils/compile.js.map +1 -1
- package/dist/utils/component-helpers.d.ts +222 -4
- package/dist/utils/component-helpers.d.ts.map +1 -1
- package/dist/utils/component-helpers.js +558 -37
- package/dist/utils/component-helpers.js.map +1 -1
- package/dist/utils/css-import-resolver.d.ts +4 -1
- package/dist/utils/css-import-resolver.d.ts.map +1 -1
- package/dist/utils/css-import-resolver.js +9 -1
- package/dist/utils/css-import-resolver.js.map +1 -1
- package/dist/utils/dev-server-auth.d.ts +19 -0
- package/dist/utils/dev-server-auth.d.ts.map +1 -0
- package/dist/utils/dev-server-auth.js +60 -0
- package/dist/utils/dev-server-auth.js.map +1 -0
- package/dist/utils/editor-action-client.d.ts +4 -3
- package/dist/utils/editor-action-client.d.ts.map +1 -1
- package/dist/utils/editor-action-client.js +56 -19
- package/dist/utils/editor-action-client.js.map +1 -1
- package/dist/utils/esm-transform.d.ts +30 -0
- package/dist/utils/esm-transform.d.ts.map +1 -1
- package/dist/utils/esm-transform.js +169 -2
- package/dist/utils/esm-transform.js.map +1 -1
- package/dist/utils/find-component-for-file.d.ts +12 -0
- package/dist/utils/find-component-for-file.d.ts.map +1 -0
- package/dist/utils/find-component-for-file.js +24 -0
- package/dist/utils/find-component-for-file.js.map +1 -0
- package/dist/utils/forbid-deep-imports.d.ts +3 -0
- package/dist/utils/forbid-deep-imports.d.ts.map +1 -0
- package/dist/utils/forbid-deep-imports.js +54 -0
- package/dist/utils/forbid-deep-imports.js.map +1 -0
- package/dist/utils/forbid-external-packages.d.ts +26 -0
- package/dist/utils/forbid-external-packages.d.ts.map +1 -0
- package/dist/utils/forbid-external-packages.js +99 -0
- package/dist/utils/forbid-external-packages.js.map +1 -0
- package/dist/utils/i18n.d.ts +80 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +265 -0
- package/dist/utils/i18n.js.map +1 -0
- package/dist/utils/load-image.d.ts +16 -0
- package/dist/utils/load-image.d.ts.map +1 -0
- package/dist/utils/load-image.js +50 -0
- package/dist/utils/load-image.js.map +1 -0
- package/dist/utils/storefront-t.d.ts +17 -0
- package/dist/utils/storefront-t.d.ts.map +1 -0
- package/dist/utils/storefront-t.js +186 -0
- package/dist/utils/storefront-t.js.map +1 -0
- package/dist/utils/vendor-storefront.d.ts +13 -0
- package/dist/utils/vendor-storefront.d.ts.map +1 -0
- package/dist/utils/vendor-storefront.js +91 -0
- package/dist/utils/vendor-storefront.js.map +1 -0
- package/dist/utils/websocket-server.d.ts +161 -7
- package/dist/utils/websocket-server.d.ts.map +1 -1
- package/dist/utils/websocket-server.js +324 -45
- package/dist/utils/websocket-server.js.map +1 -1
- package/package.json +19 -2
- package/templates/add/section.css +11 -0
- package/dist/commands/create.d.ts +0 -4
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -228
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/proxy.d.ts +0 -39
- package/dist/commands/proxy.d.ts.map +0 -1
- package/dist/commands/proxy.js +0 -210
- package/dist/commands/proxy.js.map +0 -1
package/dist/commands/dev.js
CHANGED
|
@@ -4,10 +4,14 @@ import { Command } from "commander";
|
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import * as path from "path";
|
|
7
|
-
import { toPascalCase, generateTypesFile, generateGlobalTypesFile, collectUsedEnumIds, collectUsedCustomTypeIds, generateComponentFile, generateStylesFile, updateBarrelExport, generateProjectId, generateComponentId, generateUniqueId, buildPropGroupHierarchy, findPropGroup, collectPropGroupIds, movePropGroupInTree, validateFilteredComponentIds, } from "../utils/component-helpers.js";
|
|
7
|
+
import { toPascalCase, generateTypesFile, generateGlobalTypesFile, collectUsedEnumIds, collectUsedCustomTypeIds, shouldWriteGlobalTypes, generateComponentFile, generateStylesFile, updateBarrelExport, generateProjectId, isValidProjectId, findInvalidComponentIds, findIdentityChanges, COMPONENT_ID_HINT, generateComponentId, generateUniqueId, buildPropGroupHierarchy, propIdsByName, resolveCondition, applyTechnicalTexts, translationRecords, visibilityGateError, findPropGroup, collectPropGroupIds, movePropGroupInTree, validateFilteredComponentIds, isBuiltInEnumTypeId, } from "../utils/component-helpers.js";
|
|
8
|
+
import { findComponentForFile } from "../utils/find-component-for-file.js";
|
|
9
|
+
import { archiveProject, removeArchive } from "../utils/archive-project.js";
|
|
8
10
|
import { compileAllComponents as compileAllComponentsMulti } from "../utils/compile.js";
|
|
9
|
-
import { transformEsmToWindowGlobals, transformChunkForCanvas, buildChunkMapping, } from "../utils/esm-transform.js";
|
|
11
|
+
import { transformEsmToWindowGlobals, transformChunkForCanvas, buildChunkMapping, findComputedDynamicImportsInBundles, } from "../utils/esm-transform.js";
|
|
10
12
|
import { DevServerWebSocket, } from "../utils/websocket-server.js";
|
|
13
|
+
import { i18nDir, loadProjectI18n, writeEditorI18n, } from "../utils/i18n.js";
|
|
14
|
+
import { newDevServerToken, removeDevServerToken, writeDevServerToken } from "../utils/dev-server-auth.js";
|
|
11
15
|
const DEV_SERVER_PORT = 5201;
|
|
12
16
|
// Store compiled components, shared modules, and global styles
|
|
13
17
|
const compiledComponents = new Map();
|
|
@@ -20,10 +24,16 @@ let compiledGlobalStyles;
|
|
|
20
24
|
const lastBroadcastComponentHashes = new Map();
|
|
21
25
|
const lastBroadcastSharedHashes = new Map();
|
|
22
26
|
let lastBroadcastGlobalStyleHash;
|
|
27
|
+
// The project's UI texts (`config.i18n`), as last read, and what the editors were last sent
|
|
28
|
+
let projectI18n;
|
|
29
|
+
let lastBroadcastI18nHash;
|
|
23
30
|
// Cached types list received from the editor via sync-types
|
|
24
31
|
let cachedEditorTypes = null;
|
|
25
32
|
// Cached custom type definitions received from the editor via sync-types (full IType objects)
|
|
26
33
|
let cachedCustomTypes = null;
|
|
34
|
+
// Cached theme global variables received from the editor via sync-types.
|
|
35
|
+
// Drives the ThemeSettingName union in src/global-types.ts.
|
|
36
|
+
let cachedThemeSettings = null;
|
|
27
37
|
const LIBRARY_TYPE_PREFIX = "@ikas/";
|
|
28
38
|
/**
|
|
29
39
|
* Match custom types for a component's props from the cached custom types.
|
|
@@ -77,24 +87,6 @@ function matchCustomTypesForProps(props, allCustomTypes) {
|
|
|
77
87
|
}
|
|
78
88
|
return Array.from(collected.values());
|
|
79
89
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Get the component definition that contains a file path
|
|
82
|
-
*/
|
|
83
|
-
function findComponentForFile(config, filePath) {
|
|
84
|
-
const normalizedPath = path.normalize(filePath);
|
|
85
|
-
for (const component of config.components) {
|
|
86
|
-
const entryPath = path.resolve(process.cwd(), component.entry);
|
|
87
|
-
const stylesPath = path.resolve(process.cwd(), component.styles);
|
|
88
|
-
const componentDir = path.dirname(entryPath);
|
|
89
|
-
// Check if the file is the entry, styles, or in the component directory
|
|
90
|
-
if (normalizedPath === entryPath ||
|
|
91
|
-
normalizedPath === stylesPath ||
|
|
92
|
-
normalizedPath.startsWith(componentDir + path.sep)) {
|
|
93
|
-
return component;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
90
|
/**
|
|
99
91
|
* Compile all components using multi-entry code splitting.
|
|
100
92
|
* Updates compiledComponents and compiledSharedModules maps.
|
|
@@ -136,6 +128,18 @@ async function compileAllComponentsForDev(config) {
|
|
|
136
128
|
if (chunkIds.length > 0) {
|
|
137
129
|
console.log(chalk.cyan(` Extracted ${chunkIds.length} shared chunk(s)`));
|
|
138
130
|
}
|
|
131
|
+
// Nothing downstream can resolve a computed specifier, so it 404s on the first lazy path that
|
|
132
|
+
// reaches it. Same pass `publish` runs, on the same two maps, so the two cannot drift apart —
|
|
133
|
+
// and both only warn for now (see the note in publish.ts on why that gate is not yet an error).
|
|
134
|
+
// Warning here would be right regardless: killing the watch loop over it is worse than a canvas
|
|
135
|
+
// that half-works, and the developer is right here to read the line.
|
|
136
|
+
const computedImports = findComputedDynamicImportsInBundles(result.chunks, result.entries, new Map(config.components.map((c) => [c.id, c.name])));
|
|
137
|
+
if (computedImports.length > 0) {
|
|
138
|
+
console.warn(chalk.yellow(` ${computedImports.length} dynamic import(s) with a computed specifier — these will 404 in the canvas. Use a literal, e.g. import("./thing.js") not import(name):`));
|
|
139
|
+
for (const offender of computedImports) {
|
|
140
|
+
console.warn(chalk.gray(` ${offender}`));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
139
143
|
// Store global CSS (unscoped). Preserve `updatedAt` across rebuilds when the compiled
|
|
140
144
|
// global CSS is byte-identical so the editor only sees a fresh timestamp when the
|
|
141
145
|
// designer actually changed `global.css` — the publish UI watches this timestamp to
|
|
@@ -176,6 +180,12 @@ async function compileAllComponentsForDev(config) {
|
|
|
176
180
|
const canvasClientJs = transformEsmToWindowGlobals(entry.clientJs, {
|
|
177
181
|
chunkMapping,
|
|
178
182
|
});
|
|
183
|
+
// Config names a prop's visibility gate; the editor addresses props by id, so resolve the one
|
|
184
|
+
// against the other off the same index formula the ids come from.
|
|
185
|
+
const propIdByName = propIdsByName(component.props.map((prop, index) => ({
|
|
186
|
+
id: `${component.id}_prop_${index}`,
|
|
187
|
+
name: prop.name,
|
|
188
|
+
})));
|
|
179
189
|
const propsWithIds = component.props.map((prop, index) => ({
|
|
180
190
|
id: `${component.id}_prop_${index}`,
|
|
181
191
|
name: prop.name,
|
|
@@ -187,10 +197,15 @@ async function compileAllComponentsForDev(config) {
|
|
|
187
197
|
groupId: prop.groupId,
|
|
188
198
|
typeId: prop.typeId,
|
|
189
199
|
enumTypeId: prop.enumTypeId,
|
|
200
|
+
...(prop.numberRangeData
|
|
201
|
+
? { numberRangeData: prop.numberRangeData }
|
|
202
|
+
: {}),
|
|
190
203
|
...(prop.privateVarMap ? { privateVarMap: prop.privateVarMap } : {}),
|
|
191
204
|
...(prop.filteredComponentIds
|
|
192
205
|
? { filteredComponentIds: prop.filteredComponentIds }
|
|
193
206
|
: {}),
|
|
207
|
+
...resolveCondition(prop, propIdByName),
|
|
208
|
+
...translationRecords(prop.translations),
|
|
194
209
|
}));
|
|
195
210
|
// Build prop group hierarchy if groups are defined
|
|
196
211
|
const propGroups = component.propGroups && component.propGroups.length > 0
|
|
@@ -200,7 +215,7 @@ async function compileAllComponentsForDev(config) {
|
|
|
200
215
|
const componentCustomTypes = cachedCustomTypes
|
|
201
216
|
? matchCustomTypesForProps(propsWithIds, cachedCustomTypes)
|
|
202
217
|
: undefined;
|
|
203
|
-
const usedSharedModuleIds = (result.entryChunkIds.get(component.id) || []).map((id) => config.projectId ? `${config.projectId}-${id}` : id);
|
|
218
|
+
const usedSharedModuleIds = (result.entryChunkIds.get(component.id) || []).map((id) => (config.projectId ? `${config.projectId}-${id}` : id));
|
|
204
219
|
const data = {
|
|
205
220
|
id: component.id,
|
|
206
221
|
name: component.name,
|
|
@@ -215,6 +230,7 @@ async function compileAllComponentsForDev(config) {
|
|
|
215
230
|
type: component.type || "component",
|
|
216
231
|
...(component.isHeader ? { isHeader: true } : {}),
|
|
217
232
|
...(component.isFooter ? { isFooter: true } : {}),
|
|
233
|
+
...translationRecords(component.translations),
|
|
218
234
|
propGroups,
|
|
219
235
|
configPropGroups: component.propGroups && component.propGroups.length > 0
|
|
220
236
|
? component.propGroups
|
|
@@ -253,6 +269,10 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
|
|
|
253
269
|
const component = config.components.find((c) => c.id === componentId);
|
|
254
270
|
if (!component)
|
|
255
271
|
return null;
|
|
272
|
+
const propIdByName = propIdsByName(component.props.map((prop, index) => ({
|
|
273
|
+
id: `${component.id}_prop_${index}`,
|
|
274
|
+
name: prop.name,
|
|
275
|
+
})));
|
|
256
276
|
const propsWithIds = component.props.map((prop, index) => ({
|
|
257
277
|
id: `${component.id}_prop_${index}`,
|
|
258
278
|
name: prop.name,
|
|
@@ -264,10 +284,13 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
|
|
|
264
284
|
groupId: prop.groupId,
|
|
265
285
|
typeId: prop.typeId,
|
|
266
286
|
enumTypeId: prop.enumTypeId,
|
|
287
|
+
...(prop.numberRangeData ? { numberRangeData: prop.numberRangeData } : {}),
|
|
267
288
|
...(prop.privateVarMap ? { privateVarMap: prop.privateVarMap } : {}),
|
|
268
289
|
...(prop.filteredComponentIds
|
|
269
290
|
? { filteredComponentIds: prop.filteredComponentIds }
|
|
270
291
|
: {}),
|
|
292
|
+
...resolveCondition(prop, propIdByName),
|
|
293
|
+
...translationRecords(prop.translations),
|
|
271
294
|
}));
|
|
272
295
|
const propGroups = component.propGroups && component.propGroups.length > 0
|
|
273
296
|
? buildPropGroupHierarchy(component.id, component.propGroups, propsWithIds)
|
|
@@ -277,6 +300,7 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
|
|
|
277
300
|
: undefined;
|
|
278
301
|
const updated = {
|
|
279
302
|
...existing,
|
|
303
|
+
translations: translationRecords(component.translations).translations,
|
|
280
304
|
props: propsWithIds,
|
|
281
305
|
propGroups,
|
|
282
306
|
configPropGroups: component.propGroups && component.propGroups.length > 0
|
|
@@ -310,7 +334,13 @@ function sha256Hex(input) {
|
|
|
310
334
|
*/
|
|
311
335
|
function populateContentHashes() {
|
|
312
336
|
for (const mod of compiledSharedModules.values()) {
|
|
313
|
-
mod.contentHash = sha256Hex([
|
|
337
|
+
mod.contentHash = sha256Hex([
|
|
338
|
+
mod.id,
|
|
339
|
+
mod.compiledServerJs,
|
|
340
|
+
mod.compiledClientJs,
|
|
341
|
+
mod.canvasClientJs,
|
|
342
|
+
mod.compiledCss,
|
|
343
|
+
].join("\0"));
|
|
314
344
|
}
|
|
315
345
|
for (const comp of compiledComponents.values()) {
|
|
316
346
|
const referencedHashes = (comp.usedSharedModuleIds || [])
|
|
@@ -330,6 +360,7 @@ function populateContentHashes() {
|
|
|
330
360
|
comp.compiledCss,
|
|
331
361
|
comp.canvasClientJs,
|
|
332
362
|
JSON.stringify(comp.props),
|
|
363
|
+
JSON.stringify(comp.translations || null),
|
|
333
364
|
JSON.stringify(comp.propGroups || null),
|
|
334
365
|
JSON.stringify(comp.configPropGroups || null),
|
|
335
366
|
JSON.stringify(comp.customTypes || null),
|
|
@@ -425,9 +456,11 @@ function broadcastCompileDelta(wsServer) {
|
|
|
425
456
|
components: getAllComponents(),
|
|
426
457
|
sharedModules: getAllSharedModules(),
|
|
427
458
|
globalStyles: compiledGlobalStyles,
|
|
459
|
+
i18n: projectI18n ?? null,
|
|
428
460
|
timestamp: Date.now(),
|
|
429
461
|
},
|
|
430
462
|
});
|
|
463
|
+
lastBroadcastI18nHash = i18nHash();
|
|
431
464
|
lastBroadcastComponentHashes.clear();
|
|
432
465
|
for (const [id, comp] of compiledComponents) {
|
|
433
466
|
if (comp.contentHash)
|
|
@@ -442,6 +475,25 @@ function broadcastCompileDelta(wsServer) {
|
|
|
442
475
|
lastBroadcastGlobalStyleHash = currentGlobalHash;
|
|
443
476
|
return result;
|
|
444
477
|
}
|
|
478
|
+
const i18nHash = () => sha256Hex(JSON.stringify(projectI18n ?? null));
|
|
479
|
+
/** Reads the project's UI texts again (and writes their `index.d.ts`); a problem is shown, the rest is kept. */
|
|
480
|
+
function reloadI18n(config) {
|
|
481
|
+
const { data, errors } = loadProjectI18n(config, projectI18n);
|
|
482
|
+
errors.forEach((error) => console.warn(chalk.yellow(` i18n: ${error}`)));
|
|
483
|
+
projectI18n = data;
|
|
484
|
+
}
|
|
485
|
+
/** Sends the UI texts to the editors when they changed since they were last sent. */
|
|
486
|
+
function broadcastI18n(wsServer) {
|
|
487
|
+
const hash = i18nHash();
|
|
488
|
+
if (hash === lastBroadcastI18nHash)
|
|
489
|
+
return;
|
|
490
|
+
wsServer.broadcast({
|
|
491
|
+
type: "i18n-update",
|
|
492
|
+
payload: { i18n: projectI18n ?? null, timestamp: Date.now() },
|
|
493
|
+
});
|
|
494
|
+
lastBroadcastI18nHash = hash;
|
|
495
|
+
console.log(chalk.cyan(" Broadcast UI texts"));
|
|
496
|
+
}
|
|
445
497
|
/**
|
|
446
498
|
* Read and return the current config from disk
|
|
447
499
|
*/
|
|
@@ -461,12 +513,20 @@ function writeConfig(configPath, config) {
|
|
|
461
513
|
function writeGlobalTypesFile(config) {
|
|
462
514
|
const globalTypesPath = path.resolve(process.cwd(), "src/global-types.ts");
|
|
463
515
|
const usedEnumIds = collectUsedEnumIds(config.components);
|
|
464
|
-
const content = generateGlobalTypesFile(cachedEditorTypes, cachedCustomTypes, usedEnumIds);
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
516
|
+
const content = generateGlobalTypesFile(cachedEditorTypes, cachedCustomTypes, usedEnumIds, cachedThemeSettings);
|
|
517
|
+
// Single read instead of existsSync + readFileSync; absent file reads as null.
|
|
518
|
+
let existing = null;
|
|
519
|
+
try {
|
|
520
|
+
existing = fs.readFileSync(globalTypesPath, "utf-8");
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
existing = null;
|
|
524
|
+
}
|
|
525
|
+
// Skip unnecessary writes (file-watcher loops) and, crucially, don't let the
|
|
526
|
+
// startup write clobber editor-synced theme globals before the editor has
|
|
527
|
+
// connected and re-synced them.
|
|
528
|
+
if (!shouldWriteGlobalTypes(existing, content, cachedThemeSettings != null)) {
|
|
529
|
+
return;
|
|
470
530
|
}
|
|
471
531
|
fs.writeFileSync(globalTypesPath, content);
|
|
472
532
|
}
|
|
@@ -475,10 +535,19 @@ function writeGlobalTypesFile(config) {
|
|
|
475
535
|
* Extracted from onSyncTypes to avoid duplication.
|
|
476
536
|
*/
|
|
477
537
|
function regenerateAllComponentTypes(config) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
538
|
+
config.components.forEach(regenerateComponentTypes);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Regenerate one component's types.ts (over its props). An unchanged file
|
|
542
|
+
* is not rewritten: the watcher recompiles every component on a write under `src`.
|
|
543
|
+
*/
|
|
544
|
+
function regenerateComponentTypes(component) {
|
|
545
|
+
const typesPath = path.join(path.resolve(process.cwd(), path.dirname(component.entry)), "types.ts");
|
|
546
|
+
const content = generateTypesFile(component.name, component.props, component.type, cachedEditorTypes, cachedCustomTypes);
|
|
547
|
+
if (fs.existsSync(typesPath) &&
|
|
548
|
+
fs.readFileSync(typesPath, "utf8") === content)
|
|
549
|
+
return;
|
|
550
|
+
fs.writeFileSync(typesPath, content);
|
|
482
551
|
}
|
|
483
552
|
/**
|
|
484
553
|
* Create handler functions for editor commands
|
|
@@ -501,7 +570,35 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
501
570
|
components: getAllComponents(),
|
|
502
571
|
sharedModules: getAllSharedModules(),
|
|
503
572
|
globalStyles: compiledGlobalStyles,
|
|
573
|
+
i18n: projectI18n ?? null,
|
|
504
574
|
}),
|
|
575
|
+
// The editor sends this after a partner publishes a design-asset update:
|
|
576
|
+
// zip this folder without node_modules and send the zip back; the editor
|
|
577
|
+
// hands it to the ikas backend. The working tree is untouched.
|
|
578
|
+
onArchiveProject: async (payload) => {
|
|
579
|
+
const rootDir = process.cwd();
|
|
580
|
+
console.log(chalk.cyan(` Archiving theme source (${rootDir}) without node_modules...`));
|
|
581
|
+
const archive = await archiveProject({
|
|
582
|
+
rootDir,
|
|
583
|
+
exclude: payload.exclude,
|
|
584
|
+
rewriteIds: payload.rewriteIds,
|
|
585
|
+
});
|
|
586
|
+
try {
|
|
587
|
+
const contentBase64 = fs
|
|
588
|
+
.readFileSync(archive.zipPath)
|
|
589
|
+
.toString("base64");
|
|
590
|
+
console.log(chalk.green(` Theme source archived: ${archive.fileCount} files, ${(archive.size / 1024).toFixed(0)} KB`));
|
|
591
|
+
return {
|
|
592
|
+
rootDir,
|
|
593
|
+
size: archive.size,
|
|
594
|
+
fileCount: archive.fileCount,
|
|
595
|
+
contentBase64,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
finally {
|
|
599
|
+
removeArchive(archive.zipPath);
|
|
600
|
+
}
|
|
601
|
+
},
|
|
505
602
|
onCreateComponent: async (name, type) => {
|
|
506
603
|
const pascalName = toPascalCase(name);
|
|
507
604
|
// Validate
|
|
@@ -512,6 +609,12 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
512
609
|
if (config.components.some((c) => c.name === pascalName)) {
|
|
513
610
|
throw new Error(`Component "${pascalName}" already exists`);
|
|
514
611
|
}
|
|
612
|
+
// No `!` assertion: the config may have been hot-reloaded since startup, and
|
|
613
|
+
// minting with a missing/malformed projectId writes a corrupt component id
|
|
614
|
+
// into ikas.config.json that later publishes verbatim.
|
|
615
|
+
if (!config.projectId || !isValidProjectId(config.projectId)) {
|
|
616
|
+
throw new Error("Invalid or missing projectId in ikas.config.json — restore the generated value and restart the dev server");
|
|
617
|
+
}
|
|
515
618
|
const componentId = generateComponentId(config.projectId);
|
|
516
619
|
const componentDir = path.resolve(process.cwd(), `src/components/${pascalName}`);
|
|
517
620
|
// Create directory and files
|
|
@@ -532,8 +635,7 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
532
635
|
writeConfig(configPath, config);
|
|
533
636
|
setConfig(config);
|
|
534
637
|
// Update barrel export
|
|
535
|
-
|
|
536
|
-
updateBarrelExport(process.cwd(), allNames);
|
|
638
|
+
updateBarrelExport(process.cwd(), config.components);
|
|
537
639
|
// Regenerate global types
|
|
538
640
|
writeGlobalTypesFile(config);
|
|
539
641
|
console.log(chalk.green(` Created component: ${pascalName}`));
|
|
@@ -553,7 +655,7 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
553
655
|
if (!prop.enumTypeId) {
|
|
554
656
|
throw new Error(`ENUM prop "${prop.name}" requires enumTypeId. Create one first with "config add-enum".`);
|
|
555
657
|
}
|
|
556
|
-
if (!prop.enumTypeId
|
|
658
|
+
if (!isBuiltInEnumTypeId(prop.enumTypeId)) {
|
|
557
659
|
const customTypes = config.customTypes || [];
|
|
558
660
|
const enumExists = customTypes.some((t) => t.id === prop.enumTypeId && t.type === "enum");
|
|
559
661
|
if (!enumExists) {
|
|
@@ -573,6 +675,9 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
573
675
|
`Known components: ${config.components.map((c) => `${c.name} (${c.id})`).join(", ")}.`);
|
|
574
676
|
}
|
|
575
677
|
}
|
|
678
|
+
const addGateError = visibilityGateError(component, prop, `prop "${prop.name}"`, { selfPropName: prop.name });
|
|
679
|
+
if (addGateError)
|
|
680
|
+
throw new Error(addGateError);
|
|
576
681
|
component.props.push(prop);
|
|
577
682
|
writeConfig(configPath, config);
|
|
578
683
|
setConfig(config);
|
|
@@ -601,6 +706,13 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
601
706
|
`Known components: ${config.components.map((c) => `${c.name} (${c.id})`).join(", ")}.`);
|
|
602
707
|
}
|
|
603
708
|
}
|
|
709
|
+
// Checked against the component as it will be, not as it is: a gate on the incoming prop can
|
|
710
|
+
// name another prop, and a rename in the same save changes what the chain resolves to.
|
|
711
|
+
const nextProps = component.props.slice();
|
|
712
|
+
nextProps[propIndex] = prop;
|
|
713
|
+
const updateGateError = visibilityGateError({ ...component, props: nextProps }, prop, `prop "${prop.name}"`, { selfPropName: prop.name });
|
|
714
|
+
if (updateGateError)
|
|
715
|
+
throw new Error(updateGateError);
|
|
604
716
|
component.props[propIndex] = prop;
|
|
605
717
|
writeConfig(configPath, config);
|
|
606
718
|
setConfig(config);
|
|
@@ -648,7 +760,21 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
648
760
|
...(propGroup.description
|
|
649
761
|
? { description: propGroup.description }
|
|
650
762
|
: {}),
|
|
763
|
+
...(propGroup.requiredProp
|
|
764
|
+
? {
|
|
765
|
+
requiredProp: propGroup.requiredProp,
|
|
766
|
+
...(propGroup.requiredEnumOptionValue !== undefined
|
|
767
|
+
? {
|
|
768
|
+
requiredEnumOptionId: propGroup.requiredEnumOptionId,
|
|
769
|
+
requiredEnumOptionValue: propGroup.requiredEnumOptionValue,
|
|
770
|
+
}
|
|
771
|
+
: { requiredPropValue: propGroup.requiredPropValue ?? true }),
|
|
772
|
+
}
|
|
773
|
+
: {}),
|
|
651
774
|
};
|
|
775
|
+
const gateError = visibilityGateError(component, newGroup, `group "${newGroup.id}"`, { group: newGroup });
|
|
776
|
+
if (gateError)
|
|
777
|
+
throw new Error(gateError);
|
|
652
778
|
if (propGroup.parentGroupId) {
|
|
653
779
|
const found = findPropGroup(component.propGroups, propGroup.parentGroupId);
|
|
654
780
|
if (!found)
|
|
@@ -681,11 +807,61 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
681
807
|
found.group.name = updates.name;
|
|
682
808
|
if (updates.description !== undefined)
|
|
683
809
|
found.group.description = updates.description || undefined;
|
|
810
|
+
if (updates.requiredProp !== undefined) {
|
|
811
|
+
if (!updates.requiredProp || updates.requiredProp === "none") {
|
|
812
|
+
// Both go together — a state with no prop behind it would read as a rule.
|
|
813
|
+
delete found.group.requiredProp;
|
|
814
|
+
delete found.group.requiredPropValue;
|
|
815
|
+
delete found.group.requiredEnumOptionId;
|
|
816
|
+
delete found.group.requiredEnumOptionValue;
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
found.group.requiredProp = updates.requiredProp;
|
|
820
|
+
if (updates.requiredEnumOptionValue !== undefined) {
|
|
821
|
+
found.group.requiredEnumOptionId = updates.requiredEnumOptionId;
|
|
822
|
+
found.group.requiredEnumOptionValue =
|
|
823
|
+
updates.requiredEnumOptionValue;
|
|
824
|
+
delete found.group.requiredPropValue;
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
found.group.requiredPropValue = updates.requiredPropValue ?? true;
|
|
828
|
+
delete found.group.requiredEnumOptionId;
|
|
829
|
+
delete found.group.requiredEnumOptionValue;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
const gateError = visibilityGateError(component, found.group, `group "${found.group.id}"`, { group: found.group });
|
|
834
|
+
if (gateError)
|
|
835
|
+
throw new Error(gateError);
|
|
684
836
|
writeConfig(configPath, config);
|
|
685
837
|
setConfig(config);
|
|
686
838
|
broadcastConfigChange(componentId);
|
|
687
839
|
console.log(chalk.green(` Updated prop group "${propGroupId}" on ${component.name}`));
|
|
688
840
|
},
|
|
841
|
+
onUpdateTranslations: async (componentId, texts) => {
|
|
842
|
+
const config = getConfig();
|
|
843
|
+
const component = config.components.find((c) => c.id === componentId);
|
|
844
|
+
if (!component)
|
|
845
|
+
throw new Error(`Component not found: ${componentId}`);
|
|
846
|
+
// The editor sends every component's texts after an undo: an unchanged one is not rewritten.
|
|
847
|
+
const before = JSON.stringify(component);
|
|
848
|
+
applyTechnicalTexts(component, texts);
|
|
849
|
+
if (JSON.stringify(component) === before)
|
|
850
|
+
return;
|
|
851
|
+
writeConfig(configPath, config);
|
|
852
|
+
setConfig(config);
|
|
853
|
+
broadcastConfigChange(componentId);
|
|
854
|
+
console.log(chalk.green(` Updated editor texts of ${component.name}`));
|
|
855
|
+
},
|
|
856
|
+
onUpdateI18n: async (projectId, texts) => {
|
|
857
|
+
const config = getConfig();
|
|
858
|
+
if (projectId !== config.projectId)
|
|
859
|
+
throw new Error(`Not this project's UI texts: ${projectId}`);
|
|
860
|
+
// The watcher then sends the files' texts back (the same as the editor's: no change there)
|
|
861
|
+
const written = writeEditorI18n(config, texts);
|
|
862
|
+
if (written.length)
|
|
863
|
+
console.log(chalk.green(` Updated UI texts: ${written.join(", ")}`));
|
|
864
|
+
},
|
|
689
865
|
onDeletePropGroup: async (componentId, propGroupId) => {
|
|
690
866
|
const config = getConfig();
|
|
691
867
|
const component = config.components.find((c) => c.id === componentId);
|
|
@@ -871,8 +1047,10 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
871
1047
|
regenerateAllComponentTypes(config);
|
|
872
1048
|
console.log(chalk.green(` Deleted enum: ${removed.name} (${enumId})`));
|
|
873
1049
|
},
|
|
874
|
-
onSyncTypes: (types, customTypes) => {
|
|
1050
|
+
onSyncTypes: (types, customTypes, themeSettings) => {
|
|
875
1051
|
cachedEditorTypes = types;
|
|
1052
|
+
if (themeSettings)
|
|
1053
|
+
cachedThemeSettings = themeSettings;
|
|
876
1054
|
// Merge incoming custom types with existing ones instead of replacing.
|
|
877
1055
|
// Custom types belong to the code component project and should not be
|
|
878
1056
|
// lost when a different editor project connects.
|
|
@@ -951,6 +1129,27 @@ async function startDevServer(opts = {}) {
|
|
|
951
1129
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
952
1130
|
console.log(chalk.yellow(` Auto-generated projectId: ${config.projectId}`));
|
|
953
1131
|
}
|
|
1132
|
+
else if (!isValidProjectId(config.projectId)) {
|
|
1133
|
+
// A malformed projectId (hand-edited config) breaks the editor's republish
|
|
1134
|
+
// stability: published/local id prefixes stop matching and every publish
|
|
1135
|
+
// INSERTs duplicate design elements. Fail fast instead of corrupting data.
|
|
1136
|
+
console.error(chalk.red(`Error: invalid projectId "${config.projectId}" in ikas.config.json`));
|
|
1137
|
+
console.error(chalk.gray("projectId must be at least 6 alphanumeric characters (no '-' or '_'). " +
|
|
1138
|
+
"Restore the original generated value — do not hand-edit it."));
|
|
1139
|
+
process.exit(1);
|
|
1140
|
+
}
|
|
1141
|
+
// Component ids carry the same prefix/suffix contract as projectId; the dev
|
|
1142
|
+
// server syncs them into the editor (and from there into themes), so a
|
|
1143
|
+
// hand-edited id must be rejected before the server starts.
|
|
1144
|
+
const invalidComponents = findInvalidComponentIds(config.components, config.projectId);
|
|
1145
|
+
if (invalidComponents.length > 0) {
|
|
1146
|
+
console.error(chalk.red("Error: invalid component id(s) in ikas.config.json:"));
|
|
1147
|
+
invalidComponents.forEach((c) => {
|
|
1148
|
+
console.error(chalk.red(` - "${c.id}"${c.name ? ` (${c.name})` : ""} — ${c.reason}`));
|
|
1149
|
+
});
|
|
1150
|
+
console.error(chalk.gray(COMPONENT_ID_HINT));
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
954
1153
|
console.log(chalk.cyan("\n ikas Component Dev Server\n"));
|
|
955
1154
|
console.log(chalk.gray(` Project: ${config.name}`));
|
|
956
1155
|
console.log(chalk.gray(` Components: ${config.components.length}\n`));
|
|
@@ -975,6 +1174,7 @@ async function startDevServer(opts = {}) {
|
|
|
975
1174
|
}
|
|
976
1175
|
// Generate global types file at startup
|
|
977
1176
|
writeGlobalTypesFile(config);
|
|
1177
|
+
reloadI18n(config);
|
|
978
1178
|
// Initial compilation of all components (multi-entry with code splitting)
|
|
979
1179
|
console.log(chalk.cyan(" Compiling components..."));
|
|
980
1180
|
await compileAllComponentsForDev(config);
|
|
@@ -990,25 +1190,42 @@ async function startDevServer(opts = {}) {
|
|
|
990
1190
|
lastBroadcastSharedHashes.set(id, mod.contentHash);
|
|
991
1191
|
}
|
|
992
1192
|
lastBroadcastGlobalStyleHash = computeGlobalStyleHash(compiledGlobalStyles);
|
|
1193
|
+
lastBroadcastI18nHash = i18nHash();
|
|
993
1194
|
console.log("");
|
|
994
1195
|
// Start WebSocket server
|
|
995
|
-
|
|
1196
|
+
// Control clients (the CLI's commands, the MCP) prove they run as this user with the token.
|
|
1197
|
+
const devServerToken = newDevServerToken();
|
|
1198
|
+
const wsServer = new DevServerWebSocket(DEV_SERVER_PORT, devServerToken);
|
|
996
1199
|
const handlers = createEditorHandlers(configPath, () => config, (newConfig) => {
|
|
997
1200
|
config = newConfig;
|
|
998
1201
|
}, wsServer);
|
|
999
1202
|
try {
|
|
1000
1203
|
await wsServer.start(handlers);
|
|
1204
|
+
// Written once this server owns the port: another dev server's token is not replaced.
|
|
1205
|
+
writeDevServerToken(DEV_SERVER_PORT, devServerToken);
|
|
1001
1206
|
}
|
|
1002
1207
|
catch (error) {
|
|
1003
1208
|
console.error(chalk.red(` Failed to start WebSocket server: ${error.message}`));
|
|
1004
1209
|
process.exit(1);
|
|
1005
1210
|
}
|
|
1211
|
+
console.log(chalk.green(` Dev server listening on 127.0.0.1:${DEV_SERVER_PORT}`));
|
|
1212
|
+
// Studio reaches this server only through the tunnel + websocket routing
|
|
1213
|
+
// that `ikas theme dev` registers; a directly-started server is invisible
|
|
1214
|
+
// to the editor. Hint only on a TTY — under `ikas theme dev` stdio is
|
|
1215
|
+
// piped and the wrapper already handles the connection.
|
|
1216
|
+
if (process.stdout.isTTY) {
|
|
1217
|
+
console.log(chalk.yellow("\n Note: ikas Studio cannot connect to this server directly."));
|
|
1218
|
+
console.log(chalk.yellow(" To develop with the editor, run `ikas theme dev` in this folder —"));
|
|
1219
|
+
console.log(chalk.yellow(" it starts this server, opens a tunnel and registers it with your store."));
|
|
1220
|
+
}
|
|
1006
1221
|
// Watch for file changes
|
|
1007
1222
|
const watchPaths = [
|
|
1008
1223
|
"src/**/*.tsx",
|
|
1009
1224
|
"src/**/*.ts",
|
|
1010
1225
|
"src/**/*.css",
|
|
1011
1226
|
"ikas.config.json",
|
|
1227
|
+
// The UI texts' files (`default.json`, `<locale>.json`)
|
|
1228
|
+
...(config.i18n ? [path.posix.join(config.i18n, "*.json")] : []),
|
|
1012
1229
|
];
|
|
1013
1230
|
const watcher = chokidar.watch(watchPaths, {
|
|
1014
1231
|
ignored: ["**/global-types.ts"],
|
|
@@ -1049,15 +1266,64 @@ async function startDevServer(opts = {}) {
|
|
|
1049
1266
|
}
|
|
1050
1267
|
}, DEBOUNCE_MS);
|
|
1051
1268
|
};
|
|
1269
|
+
// A UI texts file changed, came or went: the texts go to the editors (a changed base rewrites `index.d.ts`, whose
|
|
1270
|
+
// change recompiles)
|
|
1271
|
+
const onI18nFile = (filePath) => {
|
|
1272
|
+
const dir = i18nDir(config);
|
|
1273
|
+
const normalizedPath = path.resolve(filePath);
|
|
1274
|
+
if (!dir ||
|
|
1275
|
+
path.dirname(normalizedPath) !== dir ||
|
|
1276
|
+
!normalizedPath.endsWith(".json"))
|
|
1277
|
+
return false;
|
|
1278
|
+
console.log(chalk.yellow(` UI texts changed: ${path.basename(filePath)}`));
|
|
1279
|
+
reloadI18n(config);
|
|
1280
|
+
broadcastI18n(wsServer);
|
|
1281
|
+
return true;
|
|
1282
|
+
};
|
|
1052
1283
|
watcher.on("change", async (filePath) => {
|
|
1053
1284
|
const normalizedPath = path.resolve(filePath);
|
|
1285
|
+
if (onI18nFile(filePath))
|
|
1286
|
+
return;
|
|
1054
1287
|
// Handle config changes
|
|
1055
1288
|
if (normalizedPath === configPath) {
|
|
1056
1289
|
console.log(chalk.yellow("\n Config changed, reloading..."));
|
|
1057
1290
|
try {
|
|
1058
|
-
|
|
1291
|
+
const reloaded = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1292
|
+
// The startup guards validated projectId/component ids once; a hot-reload
|
|
1293
|
+
// must not smuggle a hand-edited value past them — a malformed id reaching
|
|
1294
|
+
// `generateComponentId` or chunk namespacing corrupts republish matching.
|
|
1295
|
+
// Keep the session's validated projectId if the key was removed; reject the
|
|
1296
|
+
// reload outright (previous config stays active) on invalid values.
|
|
1297
|
+
if (!reloaded.projectId) {
|
|
1298
|
+
reloaded.projectId = config.projectId;
|
|
1299
|
+
}
|
|
1300
|
+
else if (!isValidProjectId(reloaded.projectId)) {
|
|
1301
|
+
console.error(chalk.red(` Ignoring config reload: invalid projectId "${reloaded.projectId}". `) +
|
|
1302
|
+
chalk.gray("Restore the original generated value — previous config stays active."));
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const reloadedInvalidComponents = findInvalidComponentIds(reloaded.components ?? [], reloaded.projectId);
|
|
1306
|
+
if (reloadedInvalidComponents.length > 0) {
|
|
1307
|
+
console.error(chalk.red(` Ignoring config reload: invalid component id(s): ${reloadedInvalidComponents
|
|
1308
|
+
.map((c) => `"${c.id}" (${c.reason})`)
|
|
1309
|
+
.join(", ")}. `) +
|
|
1310
|
+
chalk.gray("Restore the original generated values — previous config stays active."));
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
// Well-formed is not enough: an id that changes while the server runs is
|
|
1314
|
+
// broadcast as `component-deleted` for the old one, and the editor then
|
|
1315
|
+
// removes the imported component and its page placements from the theme.
|
|
1316
|
+
const identityChanges = findIdentityChanges(config, reloaded);
|
|
1317
|
+
if (identityChanges.length > 0) {
|
|
1318
|
+
console.error(chalk.red(` Ignoring config reload: generated ids were changed (${identityChanges.join(", ")}). `) +
|
|
1319
|
+
chalk.gray("Restore the original generated values — previous config stays active."));
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
config = reloaded;
|
|
1059
1323
|
cachedCustomTypes = config.customTypes || [];
|
|
1060
1324
|
writeGlobalTypesFile(config);
|
|
1325
|
+
reloadI18n(config);
|
|
1326
|
+
broadcastI18n(wsServer);
|
|
1061
1327
|
await compileAllComponentsForDev(config);
|
|
1062
1328
|
const delta = broadcastCompileDelta(wsServer);
|
|
1063
1329
|
console.log(chalk.cyan(` Config reload delta: ${delta.changedComponents} changed, ${delta.deletedComponents} deleted, ${delta.changedSharedModules} shared chunks changed${delta.globalStylesChanged ? ", globals updated" : ""}`));
|
|
@@ -1073,12 +1339,14 @@ async function startDevServer(opts = {}) {
|
|
|
1073
1339
|
});
|
|
1074
1340
|
watcher.on("add", async (filePath) => {
|
|
1075
1341
|
const normalizedPath = path.resolve(filePath);
|
|
1076
|
-
if (normalizedPath === configPath)
|
|
1342
|
+
if (normalizedPath === configPath || onI18nFile(filePath))
|
|
1077
1343
|
return;
|
|
1078
1344
|
console.log(chalk.cyan(` New file: ${path.basename(filePath)}`));
|
|
1079
1345
|
scheduleFullRecompile(normalizedPath);
|
|
1080
1346
|
});
|
|
1081
1347
|
watcher.on("unlink", (filePath) => {
|
|
1348
|
+
if (onI18nFile(filePath))
|
|
1349
|
+
return;
|
|
1082
1350
|
const component = findComponentForFile(config, filePath);
|
|
1083
1351
|
if (component) {
|
|
1084
1352
|
console.log(chalk.yellow(` File removed: ${path.basename(filePath)}`));
|
|
@@ -1118,6 +1386,7 @@ async function startDevServer(opts = {}) {
|
|
|
1118
1386
|
console.log(chalk.yellow("\n Shutting down..."));
|
|
1119
1387
|
watcher.close();
|
|
1120
1388
|
await wsServer.stop();
|
|
1389
|
+
removeDevServerToken(DEV_SERVER_PORT);
|
|
1121
1390
|
process.exit(0);
|
|
1122
1391
|
};
|
|
1123
1392
|
process.on("SIGINT", shutdown);
|