@ikas/component-cli 1.4.0-beta.23 → 1.4.0-beta.230
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-to-page.js +1 -1
- package/dist/commands/add-to-page.js.map +1 -1
- 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 +23 -170
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/config-texts.d.ts +39 -0
- package/dist/commands/config-texts.d.ts.map +1 -0
- package/dist/commands/config-texts.js +192 -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 +1205 -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 +328 -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 +43 -0
- package/dist/commands/get-section-values.js.map +1 -0
- package/dist/commands/import.js +1 -1
- package/dist/commands/import.js.map +1 -1
- 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-imported.js +1 -1
- package/dist/commands/list-imported.js.map +1 -1
- package/dist/commands/list-page-sections.d.ts +3 -0
- package/dist/commands/list-page-sections.d.ts.map +1 -0
- package/dist/commands/list-page-sections.js +31 -0
- package/dist/commands/list-page-sections.js.map +1 -0
- package/dist/commands/list-pages.js +1 -1
- package/dist/commands/list-pages.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 +72 -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 +42 -0
- package/dist/commands/update-page-sections.js.map +1 -0
- package/dist/commands/update-section-prop.d.ts +3 -0
- package/dist/commands/update-section-prop.d.ts.map +1 -0
- package/dist/commands/update-section-prop.js +81 -0
- package/dist/commands/update-section-prop.js.map +1 -0
- 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 +45 -0
- package/dist/commands/update-translations.js.map +1 -0
- package/dist/commands/upload-image.d.ts +3 -0
- package/dist/commands/upload-image.d.ts.map +1 -0
- package/dist/commands/upload-image.js +38 -0
- package/dist/commands/upload-image.js.map +1 -0
- 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 +66 -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 +742 -109
- package/dist/utils/compile.js.map +1 -1
- package/dist/utils/component-helpers.d.ts +236 -4
- package/dist/utils/component-helpers.d.ts.map +1 -1
- package/dist/utils/component-helpers.js +656 -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 +28 -0
- package/dist/utils/dev-server-auth.d.ts.map +1 -0
- package/dist/utils/dev-server-auth.js +79 -0
- package/dist/utils/dev-server-auth.js.map +1 -0
- package/dist/utils/editor-action-client.d.ts +20 -3
- package/dist/utils/editor-action-client.d.ts.map +1 -1
- package/dist/utils/editor-action-client.js +81 -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 +98 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +408 -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 +187 -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 +165 -7
- package/dist/utils/websocket-server.d.ts.map +1 -1
- package/dist/utils/websocket-server.js +326 -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 -176
- package/dist/commands/create.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, checkConfigTranslations, } 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,18 @@ 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;
|
|
30
|
+
// What the editor would otherwise not hear of: UI texts left out, translations the config holds wrong
|
|
31
|
+
let projectWarnings = [];
|
|
23
32
|
// Cached types list received from the editor via sync-types
|
|
24
33
|
let cachedEditorTypes = null;
|
|
25
34
|
// Cached custom type definitions received from the editor via sync-types (full IType objects)
|
|
26
35
|
let cachedCustomTypes = null;
|
|
36
|
+
// Cached theme global variables received from the editor via sync-types.
|
|
37
|
+
// Drives the ThemeSettingName union in src/global-types.ts.
|
|
38
|
+
let cachedThemeSettings = null;
|
|
27
39
|
const LIBRARY_TYPE_PREFIX = "@ikas/";
|
|
28
40
|
/**
|
|
29
41
|
* Match custom types for a component's props from the cached custom types.
|
|
@@ -77,24 +89,6 @@ function matchCustomTypesForProps(props, allCustomTypes) {
|
|
|
77
89
|
}
|
|
78
90
|
return Array.from(collected.values());
|
|
79
91
|
}
|
|
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
92
|
/**
|
|
99
93
|
* Compile all components using multi-entry code splitting.
|
|
100
94
|
* Updates compiledComponents and compiledSharedModules maps.
|
|
@@ -136,6 +130,18 @@ async function compileAllComponentsForDev(config) {
|
|
|
136
130
|
if (chunkIds.length > 0) {
|
|
137
131
|
console.log(chalk.cyan(` Extracted ${chunkIds.length} shared chunk(s)`));
|
|
138
132
|
}
|
|
133
|
+
// Nothing downstream can resolve a computed specifier, so it 404s on the first lazy path that
|
|
134
|
+
// reaches it. Same pass `publish` runs, on the same two maps, so the two cannot drift apart —
|
|
135
|
+
// and both only warn for now (see the note in publish.ts on why that gate is not yet an error).
|
|
136
|
+
// Warning here would be right regardless: killing the watch loop over it is worse than a canvas
|
|
137
|
+
// that half-works, and the developer is right here to read the line.
|
|
138
|
+
const computedImports = findComputedDynamicImportsInBundles(result.chunks, result.entries, new Map(config.components.map((c) => [c.id, c.name])));
|
|
139
|
+
if (computedImports.length > 0) {
|
|
140
|
+
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):`));
|
|
141
|
+
for (const offender of computedImports) {
|
|
142
|
+
console.warn(chalk.gray(` ${offender}`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
139
145
|
// Store global CSS (unscoped). Preserve `updatedAt` across rebuilds when the compiled
|
|
140
146
|
// global CSS is byte-identical so the editor only sees a fresh timestamp when the
|
|
141
147
|
// designer actually changed `global.css` — the publish UI watches this timestamp to
|
|
@@ -176,6 +182,12 @@ async function compileAllComponentsForDev(config) {
|
|
|
176
182
|
const canvasClientJs = transformEsmToWindowGlobals(entry.clientJs, {
|
|
177
183
|
chunkMapping,
|
|
178
184
|
});
|
|
185
|
+
// Config names a prop's visibility gate; the editor addresses props by id, so resolve the one
|
|
186
|
+
// against the other off the same index formula the ids come from.
|
|
187
|
+
const propIdByName = propIdsByName(component.props.map((prop, index) => ({
|
|
188
|
+
id: `${component.id}_prop_${index}`,
|
|
189
|
+
name: prop.name,
|
|
190
|
+
})));
|
|
179
191
|
const propsWithIds = component.props.map((prop, index) => ({
|
|
180
192
|
id: `${component.id}_prop_${index}`,
|
|
181
193
|
name: prop.name,
|
|
@@ -187,10 +199,15 @@ async function compileAllComponentsForDev(config) {
|
|
|
187
199
|
groupId: prop.groupId,
|
|
188
200
|
typeId: prop.typeId,
|
|
189
201
|
enumTypeId: prop.enumTypeId,
|
|
202
|
+
...(prop.numberRangeData
|
|
203
|
+
? { numberRangeData: prop.numberRangeData }
|
|
204
|
+
: {}),
|
|
190
205
|
...(prop.privateVarMap ? { privateVarMap: prop.privateVarMap } : {}),
|
|
191
206
|
...(prop.filteredComponentIds
|
|
192
207
|
? { filteredComponentIds: prop.filteredComponentIds }
|
|
193
208
|
: {}),
|
|
209
|
+
...resolveCondition(prop, propIdByName),
|
|
210
|
+
...translationRecords(prop.translations),
|
|
194
211
|
}));
|
|
195
212
|
// Build prop group hierarchy if groups are defined
|
|
196
213
|
const propGroups = component.propGroups && component.propGroups.length > 0
|
|
@@ -200,7 +217,7 @@ async function compileAllComponentsForDev(config) {
|
|
|
200
217
|
const componentCustomTypes = cachedCustomTypes
|
|
201
218
|
? matchCustomTypesForProps(propsWithIds, cachedCustomTypes)
|
|
202
219
|
: undefined;
|
|
203
|
-
const usedSharedModuleIds = (result.entryChunkIds.get(component.id) || []).map((id) => config.projectId ? `${config.projectId}-${id}` : id);
|
|
220
|
+
const usedSharedModuleIds = (result.entryChunkIds.get(component.id) || []).map((id) => (config.projectId ? `${config.projectId}-${id}` : id));
|
|
204
221
|
const data = {
|
|
205
222
|
id: component.id,
|
|
206
223
|
name: component.name,
|
|
@@ -215,6 +232,7 @@ async function compileAllComponentsForDev(config) {
|
|
|
215
232
|
type: component.type || "component",
|
|
216
233
|
...(component.isHeader ? { isHeader: true } : {}),
|
|
217
234
|
...(component.isFooter ? { isFooter: true } : {}),
|
|
235
|
+
...translationRecords(component.translations),
|
|
218
236
|
propGroups,
|
|
219
237
|
configPropGroups: component.propGroups && component.propGroups.length > 0
|
|
220
238
|
? component.propGroups
|
|
@@ -253,6 +271,10 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
|
|
|
253
271
|
const component = config.components.find((c) => c.id === componentId);
|
|
254
272
|
if (!component)
|
|
255
273
|
return null;
|
|
274
|
+
const propIdByName = propIdsByName(component.props.map((prop, index) => ({
|
|
275
|
+
id: `${component.id}_prop_${index}`,
|
|
276
|
+
name: prop.name,
|
|
277
|
+
})));
|
|
256
278
|
const propsWithIds = component.props.map((prop, index) => ({
|
|
257
279
|
id: `${component.id}_prop_${index}`,
|
|
258
280
|
name: prop.name,
|
|
@@ -264,10 +286,13 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
|
|
|
264
286
|
groupId: prop.groupId,
|
|
265
287
|
typeId: prop.typeId,
|
|
266
288
|
enumTypeId: prop.enumTypeId,
|
|
289
|
+
...(prop.numberRangeData ? { numberRangeData: prop.numberRangeData } : {}),
|
|
267
290
|
...(prop.privateVarMap ? { privateVarMap: prop.privateVarMap } : {}),
|
|
268
291
|
...(prop.filteredComponentIds
|
|
269
292
|
? { filteredComponentIds: prop.filteredComponentIds }
|
|
270
293
|
: {}),
|
|
294
|
+
...resolveCondition(prop, propIdByName),
|
|
295
|
+
...translationRecords(prop.translations),
|
|
271
296
|
}));
|
|
272
297
|
const propGroups = component.propGroups && component.propGroups.length > 0
|
|
273
298
|
? buildPropGroupHierarchy(component.id, component.propGroups, propsWithIds)
|
|
@@ -277,6 +302,7 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
|
|
|
277
302
|
: undefined;
|
|
278
303
|
const updated = {
|
|
279
304
|
...existing,
|
|
305
|
+
translations: translationRecords(component.translations).translations,
|
|
280
306
|
props: propsWithIds,
|
|
281
307
|
propGroups,
|
|
282
308
|
configPropGroups: component.propGroups && component.propGroups.length > 0
|
|
@@ -310,7 +336,13 @@ function sha256Hex(input) {
|
|
|
310
336
|
*/
|
|
311
337
|
function populateContentHashes() {
|
|
312
338
|
for (const mod of compiledSharedModules.values()) {
|
|
313
|
-
mod.contentHash = sha256Hex([
|
|
339
|
+
mod.contentHash = sha256Hex([
|
|
340
|
+
mod.id,
|
|
341
|
+
mod.compiledServerJs,
|
|
342
|
+
mod.compiledClientJs,
|
|
343
|
+
mod.canvasClientJs,
|
|
344
|
+
mod.compiledCss,
|
|
345
|
+
].join("\0"));
|
|
314
346
|
}
|
|
315
347
|
for (const comp of compiledComponents.values()) {
|
|
316
348
|
const referencedHashes = (comp.usedSharedModuleIds || [])
|
|
@@ -330,6 +362,7 @@ function populateContentHashes() {
|
|
|
330
362
|
comp.compiledCss,
|
|
331
363
|
comp.canvasClientJs,
|
|
332
364
|
JSON.stringify(comp.props),
|
|
365
|
+
JSON.stringify(comp.translations || null),
|
|
333
366
|
JSON.stringify(comp.propGroups || null),
|
|
334
367
|
JSON.stringify(comp.configPropGroups || null),
|
|
335
368
|
JSON.stringify(comp.customTypes || null),
|
|
@@ -425,9 +458,12 @@ function broadcastCompileDelta(wsServer) {
|
|
|
425
458
|
components: getAllComponents(),
|
|
426
459
|
sharedModules: getAllSharedModules(),
|
|
427
460
|
globalStyles: compiledGlobalStyles,
|
|
461
|
+
i18n: projectI18n ?? null,
|
|
462
|
+
warnings: projectWarnings,
|
|
428
463
|
timestamp: Date.now(),
|
|
429
464
|
},
|
|
430
465
|
});
|
|
466
|
+
lastBroadcastI18nHash = i18nHash();
|
|
431
467
|
lastBroadcastComponentHashes.clear();
|
|
432
468
|
for (const [id, comp] of compiledComponents) {
|
|
433
469
|
if (comp.contentHash)
|
|
@@ -442,6 +478,38 @@ function broadcastCompileDelta(wsServer) {
|
|
|
442
478
|
lastBroadcastGlobalStyleHash = currentGlobalHash;
|
|
443
479
|
return result;
|
|
444
480
|
}
|
|
481
|
+
const i18nHash = () => sha256Hex(JSON.stringify([projectI18n ?? null, projectWarnings]));
|
|
482
|
+
/**
|
|
483
|
+
* Reads the project's UI texts again (and writes their `index.d.ts`) and checks the config's translations; a problem
|
|
484
|
+
* is shown here and in the editor, the rest is kept.
|
|
485
|
+
*/
|
|
486
|
+
function reloadI18n(config) {
|
|
487
|
+
const { data, errors } = loadProjectI18n(config, projectI18n);
|
|
488
|
+
projectWarnings = [
|
|
489
|
+
...errors.map((error) => `i18n: ${error}`),
|
|
490
|
+
...Object.values(checkConfigTranslations(config.components))
|
|
491
|
+
.flat()
|
|
492
|
+
.map((problem) => `ikas.config.json: ${problem}`),
|
|
493
|
+
];
|
|
494
|
+
projectWarnings.forEach((warning) => console.warn(chalk.yellow(` ${warning}`)));
|
|
495
|
+
projectI18n = data;
|
|
496
|
+
}
|
|
497
|
+
/** Sends the UI texts to the editors when they changed since they were last sent. */
|
|
498
|
+
function broadcastI18n(wsServer) {
|
|
499
|
+
const hash = i18nHash();
|
|
500
|
+
if (hash === lastBroadcastI18nHash)
|
|
501
|
+
return;
|
|
502
|
+
wsServer.broadcast({
|
|
503
|
+
type: "i18n-update",
|
|
504
|
+
payload: {
|
|
505
|
+
i18n: projectI18n ?? null,
|
|
506
|
+
warnings: projectWarnings,
|
|
507
|
+
timestamp: Date.now(),
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
lastBroadcastI18nHash = hash;
|
|
511
|
+
console.log(chalk.cyan(" Broadcast UI texts"));
|
|
512
|
+
}
|
|
445
513
|
/**
|
|
446
514
|
* Read and return the current config from disk
|
|
447
515
|
*/
|
|
@@ -461,12 +529,20 @@ function writeConfig(configPath, config) {
|
|
|
461
529
|
function writeGlobalTypesFile(config) {
|
|
462
530
|
const globalTypesPath = path.resolve(process.cwd(), "src/global-types.ts");
|
|
463
531
|
const usedEnumIds = collectUsedEnumIds(config.components);
|
|
464
|
-
const content = generateGlobalTypesFile(cachedEditorTypes, cachedCustomTypes, usedEnumIds);
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
532
|
+
const content = generateGlobalTypesFile(cachedEditorTypes, cachedCustomTypes, usedEnumIds, cachedThemeSettings);
|
|
533
|
+
// Single read instead of existsSync + readFileSync; absent file reads as null.
|
|
534
|
+
let existing = null;
|
|
535
|
+
try {
|
|
536
|
+
existing = fs.readFileSync(globalTypesPath, "utf-8");
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
existing = null;
|
|
540
|
+
}
|
|
541
|
+
// Skip unnecessary writes (file-watcher loops) and, crucially, don't let the
|
|
542
|
+
// startup write clobber editor-synced theme globals before the editor has
|
|
543
|
+
// connected and re-synced them.
|
|
544
|
+
if (!shouldWriteGlobalTypes(existing, content, cachedThemeSettings != null)) {
|
|
545
|
+
return;
|
|
470
546
|
}
|
|
471
547
|
fs.writeFileSync(globalTypesPath, content);
|
|
472
548
|
}
|
|
@@ -475,10 +551,19 @@ function writeGlobalTypesFile(config) {
|
|
|
475
551
|
* Extracted from onSyncTypes to avoid duplication.
|
|
476
552
|
*/
|
|
477
553
|
function regenerateAllComponentTypes(config) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
554
|
+
config.components.forEach(regenerateComponentTypes);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Regenerate one component's types.ts (over its props). An unchanged file
|
|
558
|
+
* is not rewritten: the watcher recompiles every component on a write under `src`.
|
|
559
|
+
*/
|
|
560
|
+
function regenerateComponentTypes(component) {
|
|
561
|
+
const typesPath = path.join(path.resolve(process.cwd(), path.dirname(component.entry)), "types.ts");
|
|
562
|
+
const content = generateTypesFile(component.name, component.props, component.type, cachedEditorTypes, cachedCustomTypes);
|
|
563
|
+
if (fs.existsSync(typesPath) &&
|
|
564
|
+
fs.readFileSync(typesPath, "utf8") === content)
|
|
565
|
+
return;
|
|
566
|
+
fs.writeFileSync(typesPath, content);
|
|
482
567
|
}
|
|
483
568
|
/**
|
|
484
569
|
* Create handler functions for editor commands
|
|
@@ -501,7 +586,36 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
501
586
|
components: getAllComponents(),
|
|
502
587
|
sharedModules: getAllSharedModules(),
|
|
503
588
|
globalStyles: compiledGlobalStyles,
|
|
589
|
+
i18n: projectI18n ?? null,
|
|
590
|
+
warnings: projectWarnings,
|
|
504
591
|
}),
|
|
592
|
+
// The editor sends this after a partner publishes a design-asset update:
|
|
593
|
+
// zip this folder without node_modules and send the zip back; the editor
|
|
594
|
+
// hands it to the ikas backend. The working tree is untouched.
|
|
595
|
+
onArchiveProject: async (payload) => {
|
|
596
|
+
const rootDir = process.cwd();
|
|
597
|
+
console.log(chalk.cyan(` Archiving theme source (${rootDir}) without node_modules...`));
|
|
598
|
+
const archive = await archiveProject({
|
|
599
|
+
rootDir,
|
|
600
|
+
exclude: payload.exclude,
|
|
601
|
+
rewriteIds: payload.rewriteIds,
|
|
602
|
+
});
|
|
603
|
+
try {
|
|
604
|
+
const contentBase64 = fs
|
|
605
|
+
.readFileSync(archive.zipPath)
|
|
606
|
+
.toString("base64");
|
|
607
|
+
console.log(chalk.green(` Theme source archived: ${archive.fileCount} files, ${(archive.size / 1024).toFixed(0)} KB`));
|
|
608
|
+
return {
|
|
609
|
+
rootDir,
|
|
610
|
+
size: archive.size,
|
|
611
|
+
fileCount: archive.fileCount,
|
|
612
|
+
contentBase64,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
removeArchive(archive.zipPath);
|
|
617
|
+
}
|
|
618
|
+
},
|
|
505
619
|
onCreateComponent: async (name, type) => {
|
|
506
620
|
const pascalName = toPascalCase(name);
|
|
507
621
|
// Validate
|
|
@@ -512,6 +626,12 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
512
626
|
if (config.components.some((c) => c.name === pascalName)) {
|
|
513
627
|
throw new Error(`Component "${pascalName}" already exists`);
|
|
514
628
|
}
|
|
629
|
+
// No `!` assertion: the config may have been hot-reloaded since startup, and
|
|
630
|
+
// minting with a missing/malformed projectId writes a corrupt component id
|
|
631
|
+
// into ikas.config.json that later publishes verbatim.
|
|
632
|
+
if (!config.projectId || !isValidProjectId(config.projectId)) {
|
|
633
|
+
throw new Error("Invalid or missing projectId in ikas.config.json — restore the generated value and restart the dev server");
|
|
634
|
+
}
|
|
515
635
|
const componentId = generateComponentId(config.projectId);
|
|
516
636
|
const componentDir = path.resolve(process.cwd(), `src/components/${pascalName}`);
|
|
517
637
|
// Create directory and files
|
|
@@ -532,8 +652,7 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
532
652
|
writeConfig(configPath, config);
|
|
533
653
|
setConfig(config);
|
|
534
654
|
// Update barrel export
|
|
535
|
-
|
|
536
|
-
updateBarrelExport(process.cwd(), allNames);
|
|
655
|
+
updateBarrelExport(process.cwd(), config.components);
|
|
537
656
|
// Regenerate global types
|
|
538
657
|
writeGlobalTypesFile(config);
|
|
539
658
|
console.log(chalk.green(` Created component: ${pascalName}`));
|
|
@@ -553,7 +672,7 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
553
672
|
if (!prop.enumTypeId) {
|
|
554
673
|
throw new Error(`ENUM prop "${prop.name}" requires enumTypeId. Create one first with "config add-enum".`);
|
|
555
674
|
}
|
|
556
|
-
if (!prop.enumTypeId
|
|
675
|
+
if (!isBuiltInEnumTypeId(prop.enumTypeId)) {
|
|
557
676
|
const customTypes = config.customTypes || [];
|
|
558
677
|
const enumExists = customTypes.some((t) => t.id === prop.enumTypeId && t.type === "enum");
|
|
559
678
|
if (!enumExists) {
|
|
@@ -573,6 +692,9 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
573
692
|
`Known components: ${config.components.map((c) => `${c.name} (${c.id})`).join(", ")}.`);
|
|
574
693
|
}
|
|
575
694
|
}
|
|
695
|
+
const addGateError = visibilityGateError(component, prop, `prop "${prop.name}"`, { selfPropName: prop.name });
|
|
696
|
+
if (addGateError)
|
|
697
|
+
throw new Error(addGateError);
|
|
576
698
|
component.props.push(prop);
|
|
577
699
|
writeConfig(configPath, config);
|
|
578
700
|
setConfig(config);
|
|
@@ -601,6 +723,13 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
601
723
|
`Known components: ${config.components.map((c) => `${c.name} (${c.id})`).join(", ")}.`);
|
|
602
724
|
}
|
|
603
725
|
}
|
|
726
|
+
// Checked against the component as it will be, not as it is: a gate on the incoming prop can
|
|
727
|
+
// name another prop, and a rename in the same save changes what the chain resolves to.
|
|
728
|
+
const nextProps = component.props.slice();
|
|
729
|
+
nextProps[propIndex] = prop;
|
|
730
|
+
const updateGateError = visibilityGateError({ ...component, props: nextProps }, prop, `prop "${prop.name}"`, { selfPropName: prop.name });
|
|
731
|
+
if (updateGateError)
|
|
732
|
+
throw new Error(updateGateError);
|
|
604
733
|
component.props[propIndex] = prop;
|
|
605
734
|
writeConfig(configPath, config);
|
|
606
735
|
setConfig(config);
|
|
@@ -648,7 +777,21 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
648
777
|
...(propGroup.description
|
|
649
778
|
? { description: propGroup.description }
|
|
650
779
|
: {}),
|
|
780
|
+
...(propGroup.requiredProp
|
|
781
|
+
? {
|
|
782
|
+
requiredProp: propGroup.requiredProp,
|
|
783
|
+
...(propGroup.requiredEnumOptionValue !== undefined
|
|
784
|
+
? {
|
|
785
|
+
requiredEnumOptionId: propGroup.requiredEnumOptionId,
|
|
786
|
+
requiredEnumOptionValue: propGroup.requiredEnumOptionValue,
|
|
787
|
+
}
|
|
788
|
+
: { requiredPropValue: propGroup.requiredPropValue ?? true }),
|
|
789
|
+
}
|
|
790
|
+
: {}),
|
|
651
791
|
};
|
|
792
|
+
const gateError = visibilityGateError(component, newGroup, `group "${newGroup.id}"`, { group: newGroup });
|
|
793
|
+
if (gateError)
|
|
794
|
+
throw new Error(gateError);
|
|
652
795
|
if (propGroup.parentGroupId) {
|
|
653
796
|
const found = findPropGroup(component.propGroups, propGroup.parentGroupId);
|
|
654
797
|
if (!found)
|
|
@@ -681,11 +824,61 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
681
824
|
found.group.name = updates.name;
|
|
682
825
|
if (updates.description !== undefined)
|
|
683
826
|
found.group.description = updates.description || undefined;
|
|
827
|
+
if (updates.requiredProp !== undefined) {
|
|
828
|
+
if (!updates.requiredProp || updates.requiredProp === "none") {
|
|
829
|
+
// Both go together — a state with no prop behind it would read as a rule.
|
|
830
|
+
delete found.group.requiredProp;
|
|
831
|
+
delete found.group.requiredPropValue;
|
|
832
|
+
delete found.group.requiredEnumOptionId;
|
|
833
|
+
delete found.group.requiredEnumOptionValue;
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
found.group.requiredProp = updates.requiredProp;
|
|
837
|
+
if (updates.requiredEnumOptionValue !== undefined) {
|
|
838
|
+
found.group.requiredEnumOptionId = updates.requiredEnumOptionId;
|
|
839
|
+
found.group.requiredEnumOptionValue =
|
|
840
|
+
updates.requiredEnumOptionValue;
|
|
841
|
+
delete found.group.requiredPropValue;
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
found.group.requiredPropValue = updates.requiredPropValue ?? true;
|
|
845
|
+
delete found.group.requiredEnumOptionId;
|
|
846
|
+
delete found.group.requiredEnumOptionValue;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
const gateError = visibilityGateError(component, found.group, `group "${found.group.id}"`, { group: found.group });
|
|
851
|
+
if (gateError)
|
|
852
|
+
throw new Error(gateError);
|
|
684
853
|
writeConfig(configPath, config);
|
|
685
854
|
setConfig(config);
|
|
686
855
|
broadcastConfigChange(componentId);
|
|
687
856
|
console.log(chalk.green(` Updated prop group "${propGroupId}" on ${component.name}`));
|
|
688
857
|
},
|
|
858
|
+
onUpdateTranslations: async (componentId, texts) => {
|
|
859
|
+
const config = getConfig();
|
|
860
|
+
const component = config.components.find((c) => c.id === componentId);
|
|
861
|
+
if (!component)
|
|
862
|
+
throw new Error(`Component not found: ${componentId}`);
|
|
863
|
+
// The editor sends every component's texts after an undo: an unchanged one is not rewritten.
|
|
864
|
+
const before = JSON.stringify(component);
|
|
865
|
+
applyTechnicalTexts(component, texts);
|
|
866
|
+
if (JSON.stringify(component) === before)
|
|
867
|
+
return;
|
|
868
|
+
writeConfig(configPath, config);
|
|
869
|
+
setConfig(config);
|
|
870
|
+
broadcastConfigChange(componentId);
|
|
871
|
+
console.log(chalk.green(` Updated editor texts of ${component.name}`));
|
|
872
|
+
},
|
|
873
|
+
onUpdateI18n: async (projectId, texts) => {
|
|
874
|
+
const config = getConfig();
|
|
875
|
+
if (projectId !== config.projectId)
|
|
876
|
+
throw new Error(`Not this project's UI texts: ${projectId}`);
|
|
877
|
+
// The watcher then sends the files' texts back (the same as the editor's: no change there)
|
|
878
|
+
const written = writeEditorI18n(config, texts);
|
|
879
|
+
if (written.length)
|
|
880
|
+
console.log(chalk.green(` Updated UI texts: ${written.join(", ")}`));
|
|
881
|
+
},
|
|
689
882
|
onDeletePropGroup: async (componentId, propGroupId) => {
|
|
690
883
|
const config = getConfig();
|
|
691
884
|
const component = config.components.find((c) => c.id === componentId);
|
|
@@ -871,8 +1064,10 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
|
|
|
871
1064
|
regenerateAllComponentTypes(config);
|
|
872
1065
|
console.log(chalk.green(` Deleted enum: ${removed.name} (${enumId})`));
|
|
873
1066
|
},
|
|
874
|
-
onSyncTypes: (types, customTypes) => {
|
|
1067
|
+
onSyncTypes: (types, customTypes, themeSettings) => {
|
|
875
1068
|
cachedEditorTypes = types;
|
|
1069
|
+
if (themeSettings)
|
|
1070
|
+
cachedThemeSettings = themeSettings;
|
|
876
1071
|
// Merge incoming custom types with existing ones instead of replacing.
|
|
877
1072
|
// Custom types belong to the code component project and should not be
|
|
878
1073
|
// lost when a different editor project connects.
|
|
@@ -951,6 +1146,27 @@ async function startDevServer(opts = {}) {
|
|
|
951
1146
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
952
1147
|
console.log(chalk.yellow(` Auto-generated projectId: ${config.projectId}`));
|
|
953
1148
|
}
|
|
1149
|
+
else if (!isValidProjectId(config.projectId)) {
|
|
1150
|
+
// A malformed projectId (hand-edited config) breaks the editor's republish
|
|
1151
|
+
// stability: published/local id prefixes stop matching and every publish
|
|
1152
|
+
// INSERTs duplicate design elements. Fail fast instead of corrupting data.
|
|
1153
|
+
console.error(chalk.red(`Error: invalid projectId "${config.projectId}" in ikas.config.json`));
|
|
1154
|
+
console.error(chalk.gray("projectId must be at least 6 alphanumeric characters (no '-' or '_'). " +
|
|
1155
|
+
"Restore the original generated value — do not hand-edit it."));
|
|
1156
|
+
process.exit(1);
|
|
1157
|
+
}
|
|
1158
|
+
// Component ids carry the same prefix/suffix contract as projectId; the dev
|
|
1159
|
+
// server syncs them into the editor (and from there into themes), so a
|
|
1160
|
+
// hand-edited id must be rejected before the server starts.
|
|
1161
|
+
const invalidComponents = findInvalidComponentIds(config.components, config.projectId);
|
|
1162
|
+
if (invalidComponents.length > 0) {
|
|
1163
|
+
console.error(chalk.red("Error: invalid component id(s) in ikas.config.json:"));
|
|
1164
|
+
invalidComponents.forEach((c) => {
|
|
1165
|
+
console.error(chalk.red(` - "${c.id}"${c.name ? ` (${c.name})` : ""} — ${c.reason}`));
|
|
1166
|
+
});
|
|
1167
|
+
console.error(chalk.gray(COMPONENT_ID_HINT));
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
954
1170
|
console.log(chalk.cyan("\n ikas Component Dev Server\n"));
|
|
955
1171
|
console.log(chalk.gray(` Project: ${config.name}`));
|
|
956
1172
|
console.log(chalk.gray(` Components: ${config.components.length}\n`));
|
|
@@ -975,6 +1191,7 @@ async function startDevServer(opts = {}) {
|
|
|
975
1191
|
}
|
|
976
1192
|
// Generate global types file at startup
|
|
977
1193
|
writeGlobalTypesFile(config);
|
|
1194
|
+
reloadI18n(config);
|
|
978
1195
|
// Initial compilation of all components (multi-entry with code splitting)
|
|
979
1196
|
console.log(chalk.cyan(" Compiling components..."));
|
|
980
1197
|
await compileAllComponentsForDev(config);
|
|
@@ -990,25 +1207,42 @@ async function startDevServer(opts = {}) {
|
|
|
990
1207
|
lastBroadcastSharedHashes.set(id, mod.contentHash);
|
|
991
1208
|
}
|
|
992
1209
|
lastBroadcastGlobalStyleHash = computeGlobalStyleHash(compiledGlobalStyles);
|
|
1210
|
+
lastBroadcastI18nHash = i18nHash();
|
|
993
1211
|
console.log("");
|
|
994
1212
|
// Start WebSocket server
|
|
995
|
-
|
|
1213
|
+
// Control clients (the CLI's commands, the MCP) prove they run as this user with the token.
|
|
1214
|
+
const devServerToken = newDevServerToken();
|
|
1215
|
+
const wsServer = new DevServerWebSocket(DEV_SERVER_PORT, devServerToken);
|
|
996
1216
|
const handlers = createEditorHandlers(configPath, () => config, (newConfig) => {
|
|
997
1217
|
config = newConfig;
|
|
998
1218
|
}, wsServer);
|
|
999
1219
|
try {
|
|
1000
1220
|
await wsServer.start(handlers);
|
|
1221
|
+
// Written once this server owns the port: another dev server's token is not replaced.
|
|
1222
|
+
writeDevServerToken(DEV_SERVER_PORT, devServerToken);
|
|
1001
1223
|
}
|
|
1002
1224
|
catch (error) {
|
|
1003
1225
|
console.error(chalk.red(` Failed to start WebSocket server: ${error.message}`));
|
|
1004
1226
|
process.exit(1);
|
|
1005
1227
|
}
|
|
1228
|
+
console.log(chalk.green(` Dev server listening on 127.0.0.1:${DEV_SERVER_PORT}`));
|
|
1229
|
+
// Studio reaches this server only through the tunnel + websocket routing
|
|
1230
|
+
// that `ikas theme dev` registers; a directly-started server is invisible
|
|
1231
|
+
// to the editor. Hint only on a TTY — under `ikas theme dev` stdio is
|
|
1232
|
+
// piped and the wrapper already handles the connection.
|
|
1233
|
+
if (process.stdout.isTTY) {
|
|
1234
|
+
console.log(chalk.yellow("\n Note: ikas Studio cannot connect to this server directly."));
|
|
1235
|
+
console.log(chalk.yellow(" To develop with the editor, run `ikas theme dev` in this folder —"));
|
|
1236
|
+
console.log(chalk.yellow(" it starts this server, opens a tunnel and registers it with your store."));
|
|
1237
|
+
}
|
|
1006
1238
|
// Watch for file changes
|
|
1007
1239
|
const watchPaths = [
|
|
1008
1240
|
"src/**/*.tsx",
|
|
1009
1241
|
"src/**/*.ts",
|
|
1010
1242
|
"src/**/*.css",
|
|
1011
1243
|
"ikas.config.json",
|
|
1244
|
+
// The UI texts' files (`default.json`, `<locale>.json`)
|
|
1245
|
+
...(config.i18n ? [path.posix.join(config.i18n, "*.json")] : []),
|
|
1012
1246
|
];
|
|
1013
1247
|
const watcher = chokidar.watch(watchPaths, {
|
|
1014
1248
|
ignored: ["**/global-types.ts"],
|
|
@@ -1049,15 +1283,67 @@ async function startDevServer(opts = {}) {
|
|
|
1049
1283
|
}
|
|
1050
1284
|
}, DEBOUNCE_MS);
|
|
1051
1285
|
};
|
|
1286
|
+
// A UI texts file changed, came or went: the texts go to the editors (a changed base rewrites `index.d.ts`, whose
|
|
1287
|
+
// change recompiles)
|
|
1288
|
+
const onI18nFile = (filePath) => {
|
|
1289
|
+
const dir = i18nDir(config);
|
|
1290
|
+
const normalizedPath = path.resolve(filePath);
|
|
1291
|
+
if (!dir ||
|
|
1292
|
+
path.dirname(normalizedPath) !== dir ||
|
|
1293
|
+
!normalizedPath.endsWith(".json"))
|
|
1294
|
+
return false;
|
|
1295
|
+
console.log(chalk.yellow(` UI texts changed: ${path.basename(filePath)}`));
|
|
1296
|
+
reloadI18n(config);
|
|
1297
|
+
broadcastI18n(wsServer);
|
|
1298
|
+
return true;
|
|
1299
|
+
};
|
|
1052
1300
|
watcher.on("change", async (filePath) => {
|
|
1053
1301
|
const normalizedPath = path.resolve(filePath);
|
|
1302
|
+
if (onI18nFile(filePath))
|
|
1303
|
+
return;
|
|
1054
1304
|
// Handle config changes
|
|
1055
1305
|
if (normalizedPath === configPath) {
|
|
1056
1306
|
console.log(chalk.yellow("\n Config changed, reloading..."));
|
|
1057
1307
|
try {
|
|
1058
|
-
|
|
1308
|
+
const reloaded = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1309
|
+
// The startup guards validated projectId/component ids once; a hot-reload
|
|
1310
|
+
// must not smuggle a hand-edited value past them — a malformed id reaching
|
|
1311
|
+
// `generateComponentId` or chunk namespacing corrupts republish matching.
|
|
1312
|
+
// Keep the session's validated projectId if the key was removed; reject the
|
|
1313
|
+
// reload outright (previous config stays active) on invalid values.
|
|
1314
|
+
if (!reloaded.projectId) {
|
|
1315
|
+
reloaded.projectId = config.projectId;
|
|
1316
|
+
}
|
|
1317
|
+
else if (!isValidProjectId(reloaded.projectId)) {
|
|
1318
|
+
console.error(chalk.red(` Ignoring config reload: invalid projectId "${reloaded.projectId}". `) +
|
|
1319
|
+
chalk.gray("Restore the original generated value — previous config stays active."));
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
const reloadedInvalidComponents = findInvalidComponentIds(reloaded.components ?? [], reloaded.projectId);
|
|
1323
|
+
if (reloadedInvalidComponents.length > 0) {
|
|
1324
|
+
console.error(chalk.red(` Ignoring config reload: invalid component id(s): ${reloadedInvalidComponents
|
|
1325
|
+
.map((c) => `"${c.id}" (${c.reason})`)
|
|
1326
|
+
.join(", ")}. `) +
|
|
1327
|
+
chalk.gray("Restore the original generated values — previous config stays active."));
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
// Well-formed is not enough: an id that changes while the server runs is
|
|
1331
|
+
// broadcast as `component-deleted` for the old one, and the editor then
|
|
1332
|
+
// removes the imported component and its page placements from the theme.
|
|
1333
|
+
const identityChanges = findIdentityChanges(config, reloaded);
|
|
1334
|
+
if (identityChanges.length > 0) {
|
|
1335
|
+
console.error(chalk.red(` Ignoring config reload: generated ids were changed (${identityChanges.join(", ")}). `) +
|
|
1336
|
+
chalk.gray("Restore the original generated values — previous config stays active."));
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
// The first `set-text` names the UI texts' folder: watch it from then on
|
|
1340
|
+
if (reloaded.i18n && reloaded.i18n !== config.i18n)
|
|
1341
|
+
watcher.add(path.posix.join(reloaded.i18n, "*.json"));
|
|
1342
|
+
config = reloaded;
|
|
1059
1343
|
cachedCustomTypes = config.customTypes || [];
|
|
1060
1344
|
writeGlobalTypesFile(config);
|
|
1345
|
+
reloadI18n(config);
|
|
1346
|
+
broadcastI18n(wsServer);
|
|
1061
1347
|
await compileAllComponentsForDev(config);
|
|
1062
1348
|
const delta = broadcastCompileDelta(wsServer);
|
|
1063
1349
|
console.log(chalk.cyan(` Config reload delta: ${delta.changedComponents} changed, ${delta.deletedComponents} deleted, ${delta.changedSharedModules} shared chunks changed${delta.globalStylesChanged ? ", globals updated" : ""}`));
|
|
@@ -1073,12 +1359,14 @@ async function startDevServer(opts = {}) {
|
|
|
1073
1359
|
});
|
|
1074
1360
|
watcher.on("add", async (filePath) => {
|
|
1075
1361
|
const normalizedPath = path.resolve(filePath);
|
|
1076
|
-
if (normalizedPath === configPath)
|
|
1362
|
+
if (normalizedPath === configPath || onI18nFile(filePath))
|
|
1077
1363
|
return;
|
|
1078
1364
|
console.log(chalk.cyan(` New file: ${path.basename(filePath)}`));
|
|
1079
1365
|
scheduleFullRecompile(normalizedPath);
|
|
1080
1366
|
});
|
|
1081
1367
|
watcher.on("unlink", (filePath) => {
|
|
1368
|
+
if (onI18nFile(filePath))
|
|
1369
|
+
return;
|
|
1082
1370
|
const component = findComponentForFile(config, filePath);
|
|
1083
1371
|
if (component) {
|
|
1084
1372
|
console.log(chalk.yellow(` File removed: ${path.basename(filePath)}`));
|
|
@@ -1118,6 +1406,7 @@ async function startDevServer(opts = {}) {
|
|
|
1118
1406
|
console.log(chalk.yellow("\n Shutting down..."));
|
|
1119
1407
|
watcher.close();
|
|
1120
1408
|
await wsServer.stop();
|
|
1409
|
+
removeDevServerToken(DEV_SERVER_PORT);
|
|
1121
1410
|
process.exit(0);
|
|
1122
1411
|
};
|
|
1123
1412
|
process.on("SIGINT", shutdown);
|