@ikas/component-cli 1.4.0-beta.21 → 1.4.0-beta.212

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.
Files changed (176) hide show
  1. package/dist/build-tools.d.ts +37 -0
  2. package/dist/build-tools.d.ts.map +1 -0
  3. package/dist/build-tools.js +36 -0
  4. package/dist/build-tools.js.map +1 -0
  5. package/dist/commands/add-sections-to-page.d.ts +3 -0
  6. package/dist/commands/add-sections-to-page.d.ts.map +1 -0
  7. package/dist/commands/add-sections-to-page.js +39 -0
  8. package/dist/commands/add-sections-to-page.js.map +1 -0
  9. package/dist/commands/add.d.ts.map +1 -1
  10. package/dist/commands/add.js +11 -2
  11. package/dist/commands/add.js.map +1 -1
  12. package/dist/commands/build.d.ts.map +1 -1
  13. package/dist/commands/build.js +5 -165
  14. package/dist/commands/build.js.map +1 -1
  15. package/dist/commands/config.d.ts +35 -0
  16. package/dist/commands/config.d.ts.map +1 -1
  17. package/dist/commands/config.js +1082 -147
  18. package/dist/commands/config.js.map +1 -1
  19. package/dist/commands/create-design-tokens.d.ts +24 -0
  20. package/dist/commands/create-design-tokens.d.ts.map +1 -0
  21. package/dist/commands/create-design-tokens.js +388 -0
  22. package/dist/commands/create-design-tokens.js.map +1 -0
  23. package/dist/commands/create-global-variable.d.ts +3 -0
  24. package/dist/commands/create-global-variable.d.ts.map +1 -0
  25. package/dist/commands/create-global-variable.js +73 -0
  26. package/dist/commands/create-global-variable.js.map +1 -0
  27. package/dist/commands/create-page.d.ts +3 -0
  28. package/dist/commands/create-page.d.ts.map +1 -0
  29. package/dist/commands/create-page.js +31 -0
  30. package/dist/commands/create-page.js.map +1 -0
  31. package/dist/commands/delete-page.d.ts +3 -0
  32. package/dist/commands/delete-page.d.ts.map +1 -0
  33. package/dist/commands/delete-page.js +28 -0
  34. package/dist/commands/delete-page.js.map +1 -0
  35. package/dist/commands/delete-theme-globals.d.ts +4 -0
  36. package/dist/commands/delete-theme-globals.d.ts.map +1 -0
  37. package/dist/commands/delete-theme-globals.js +48 -0
  38. package/dist/commands/delete-theme-globals.js.map +1 -0
  39. package/dist/commands/dev.d.ts.map +1 -1
  40. package/dist/commands/dev.js +218 -31
  41. package/dist/commands/dev.js.map +1 -1
  42. package/dist/commands/enable-builtin-pages.d.ts +3 -0
  43. package/dist/commands/enable-builtin-pages.d.ts.map +1 -0
  44. package/dist/commands/enable-builtin-pages.js +27 -0
  45. package/dist/commands/enable-builtin-pages.js.map +1 -0
  46. package/dist/commands/get-component-props.d.ts +3 -0
  47. package/dist/commands/get-component-props.d.ts.map +1 -0
  48. package/dist/commands/get-component-props.js +32 -0
  49. package/dist/commands/get-component-props.js.map +1 -0
  50. package/dist/commands/get-page-by-type.d.ts +3 -0
  51. package/dist/commands/get-page-by-type.d.ts.map +1 -0
  52. package/dist/commands/get-page-by-type.js +25 -0
  53. package/dist/commands/get-page-by-type.js.map +1 -0
  54. package/dist/commands/get-section-values.d.ts +3 -0
  55. package/dist/commands/get-section-values.d.ts.map +1 -0
  56. package/dist/commands/get-section-values.js +39 -0
  57. package/dist/commands/get-section-values.js.map +1 -0
  58. package/dist/commands/list-builtin-pages.d.ts +3 -0
  59. package/dist/commands/list-builtin-pages.d.ts.map +1 -0
  60. package/dist/commands/list-builtin-pages.js +28 -0
  61. package/dist/commands/list-builtin-pages.js.map +1 -0
  62. package/dist/commands/list-entities.d.ts +3 -0
  63. package/dist/commands/list-entities.d.ts.map +1 -0
  64. package/dist/commands/list-entities.js +32 -0
  65. package/dist/commands/list-entities.js.map +1 -0
  66. package/dist/commands/list-page-sections.d.ts.map +1 -1
  67. package/dist/commands/list-page-sections.js +8 -2
  68. package/dist/commands/list-page-sections.js.map +1 -1
  69. package/dist/commands/list-theme-globals.d.ts +3 -0
  70. package/dist/commands/list-theme-globals.d.ts.map +1 -0
  71. package/dist/commands/list-theme-globals.js +22 -0
  72. package/dist/commands/list-theme-globals.js.map +1 -0
  73. package/dist/commands/move-page-section.d.ts +3 -0
  74. package/dist/commands/move-page-section.d.ts.map +1 -0
  75. package/dist/commands/move-page-section.js +62 -0
  76. package/dist/commands/move-page-section.js.map +1 -0
  77. package/dist/commands/publish-theme.d.ts +3 -0
  78. package/dist/commands/publish-theme.d.ts.map +1 -0
  79. package/dist/commands/publish-theme.js +29 -0
  80. package/dist/commands/publish-theme.js.map +1 -0
  81. package/dist/commands/publish.d.ts.map +1 -1
  82. package/dist/commands/publish.js +54 -3
  83. package/dist/commands/publish.js.map +1 -1
  84. package/dist/commands/remove-page-section.d.ts +3 -0
  85. package/dist/commands/remove-page-section.d.ts.map +1 -0
  86. package/dist/commands/remove-page-section.js +40 -0
  87. package/dist/commands/remove-page-section.js.map +1 -0
  88. package/dist/commands/search-products.d.ts +3 -0
  89. package/dist/commands/search-products.d.ts.map +1 -0
  90. package/dist/commands/search-products.js +40 -0
  91. package/dist/commands/search-products.js.map +1 -0
  92. package/dist/commands/update-builtin-page-prop.d.ts +3 -0
  93. package/dist/commands/update-builtin-page-prop.d.ts.map +1 -0
  94. package/dist/commands/update-builtin-page-prop.js +49 -0
  95. package/dist/commands/update-builtin-page-prop.js.map +1 -0
  96. package/dist/commands/update-global-variable.d.ts +3 -0
  97. package/dist/commands/update-global-variable.d.ts.map +1 -0
  98. package/dist/commands/update-global-variable.js +70 -0
  99. package/dist/commands/update-global-variable.js.map +1 -0
  100. package/dist/commands/update-page-sections.d.ts +3 -0
  101. package/dist/commands/update-page-sections.d.ts.map +1 -0
  102. package/dist/commands/update-page-sections.js +39 -0
  103. package/dist/commands/update-page-sections.js.map +1 -0
  104. package/dist/commands/update-section-prop.d.ts.map +1 -1
  105. package/dist/commands/update-section-prop.js +25 -8
  106. package/dist/commands/update-section-prop.js.map +1 -1
  107. package/dist/commands/upload-image.d.ts.map +1 -1
  108. package/dist/commands/upload-image.js +5 -46
  109. package/dist/commands/upload-image.js.map +1 -1
  110. package/dist/commands/upload-images.d.ts +3 -0
  111. package/dist/commands/upload-images.d.ts.map +1 -0
  112. package/dist/commands/upload-images.js +48 -0
  113. package/dist/commands/upload-images.js.map +1 -0
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +54 -1
  116. package/dist/index.js.map +1 -1
  117. package/dist/types.d.ts +51 -0
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/utils/archive-project.d.ts +69 -0
  120. package/dist/utils/archive-project.d.ts.map +1 -0
  121. package/dist/utils/archive-project.js +202 -0
  122. package/dist/utils/archive-project.js.map +1 -0
  123. package/dist/utils/compile.d.ts +17 -2
  124. package/dist/utils/compile.d.ts.map +1 -1
  125. package/dist/utils/compile.js +750 -117
  126. package/dist/utils/compile.js.map +1 -1
  127. package/dist/utils/component-helpers.d.ts +167 -2
  128. package/dist/utils/component-helpers.d.ts.map +1 -1
  129. package/dist/utils/component-helpers.js +452 -29
  130. package/dist/utils/component-helpers.js.map +1 -1
  131. package/dist/utils/css-import-resolver.d.ts +4 -1
  132. package/dist/utils/css-import-resolver.d.ts.map +1 -1
  133. package/dist/utils/css-import-resolver.js +9 -1
  134. package/dist/utils/css-import-resolver.js.map +1 -1
  135. package/dist/utils/editor-action-client.d.ts +4 -3
  136. package/dist/utils/editor-action-client.d.ts.map +1 -1
  137. package/dist/utils/editor-action-client.js +55 -19
  138. package/dist/utils/editor-action-client.js.map +1 -1
  139. package/dist/utils/esm-transform.d.ts +30 -0
  140. package/dist/utils/esm-transform.d.ts.map +1 -1
  141. package/dist/utils/esm-transform.js +169 -2
  142. package/dist/utils/esm-transform.js.map +1 -1
  143. package/dist/utils/find-component-for-file.d.ts +12 -0
  144. package/dist/utils/find-component-for-file.d.ts.map +1 -0
  145. package/dist/utils/find-component-for-file.js +24 -0
  146. package/dist/utils/find-component-for-file.js.map +1 -0
  147. package/dist/utils/forbid-deep-imports.d.ts +3 -0
  148. package/dist/utils/forbid-deep-imports.d.ts.map +1 -0
  149. package/dist/utils/forbid-deep-imports.js +54 -0
  150. package/dist/utils/forbid-deep-imports.js.map +1 -0
  151. package/dist/utils/forbid-external-packages.d.ts +26 -0
  152. package/dist/utils/forbid-external-packages.d.ts.map +1 -0
  153. package/dist/utils/forbid-external-packages.js +99 -0
  154. package/dist/utils/forbid-external-packages.js.map +1 -0
  155. package/dist/utils/load-image.d.ts +16 -0
  156. package/dist/utils/load-image.d.ts.map +1 -0
  157. package/dist/utils/load-image.js +50 -0
  158. package/dist/utils/load-image.js.map +1 -0
  159. package/dist/utils/vendor-storefront.d.ts +13 -0
  160. package/dist/utils/vendor-storefront.d.ts.map +1 -0
  161. package/dist/utils/vendor-storefront.js +91 -0
  162. package/dist/utils/vendor-storefront.js.map +1 -0
  163. package/dist/utils/websocket-server.d.ts +119 -5
  164. package/dist/utils/websocket-server.d.ts.map +1 -1
  165. package/dist/utils/websocket-server.js +255 -44
  166. package/dist/utils/websocket-server.js.map +1 -1
  167. package/package.json +19 -2
  168. package/templates/add/section.css +11 -0
  169. package/dist/commands/create.d.ts +0 -4
  170. package/dist/commands/create.d.ts.map +0 -1
  171. package/dist/commands/create.js +0 -228
  172. package/dist/commands/create.js.map +0 -1
  173. package/dist/commands/proxy.d.ts +0 -39
  174. package/dist/commands/proxy.d.ts.map +0 -1
  175. package/dist/commands/proxy.js +0 -210
  176. package/dist/commands/proxy.js.map +0 -1
@@ -0,0 +1,48 @@
1
+ import { Command } from "commander";
2
+ import { printErrorAndExit, printResultAndExit, runEditorAction } from "../utils/editor-action-client.js";
3
+ const TOKEN_TYPES = ["color", "typography", "breakpoint", "keyframe", "colorScheme", "colorSchemeSlot"];
4
+ function portOpts(port) {
5
+ const p = port ? parseInt(port, 10) : undefined;
6
+ return p ? { port: p } : {};
7
+ }
8
+ export function createDeleteGlobalVariableCommand() {
9
+ const cmd = new Command("delete-global-variable");
10
+ cmd
11
+ .description("Delete a global variable from the connected editor")
12
+ .requiredOption("--name <key>", "The variable's runtime key (from `list-theme-globals`)")
13
+ .option("--port <port>", "Dev server WebSocket port", "5201")
14
+ .action(async (options) => {
15
+ if (!options.name)
16
+ printErrorAndExit(new Error("--name is required"));
17
+ try {
18
+ printResultAndExit(await runEditorAction("delete-global-variable", { name: options.name }, portOpts(options.port)));
19
+ }
20
+ catch (e) {
21
+ printErrorAndExit(e);
22
+ }
23
+ });
24
+ return cmd;
25
+ }
26
+ export function createDeleteDesignTokenCommand() {
27
+ const cmd = new Command("delete-design-token");
28
+ cmd
29
+ .description("Delete a design token (color, typography, breakpoint, keyframe, color scheme, or color scheme slot) from the connected editor")
30
+ .requiredOption("--token-type <type>", TOKEN_TYPES.join(" | "))
31
+ .requiredOption("--id <id>", "The token id (from `list-theme-globals`). For colorSchemeSlot use the slot id from colorSchemes.schemes")
32
+ .option("--port <port>", "Dev server WebSocket port", "5201")
33
+ .action(async (options) => {
34
+ if (!options.tokenType || !TOKEN_TYPES.includes(options.tokenType)) {
35
+ printErrorAndExit(new Error(`--token-type must be one of: ${TOKEN_TYPES.join(", ")}`));
36
+ }
37
+ if (!options.id)
38
+ printErrorAndExit(new Error("--id is required"));
39
+ try {
40
+ printResultAndExit(await runEditorAction("delete-design-token", { tokenType: options.tokenType, id: options.id }, portOpts(options.port)));
41
+ }
42
+ catch (e) {
43
+ printErrorAndExit(e);
44
+ }
45
+ });
46
+ return cmd;
47
+ }
48
+ //# sourceMappingURL=delete-theme-globals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-theme-globals.js","sourceRoot":"","sources":["../../src/commands/delete-theme-globals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAE1G,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAAC;AAExG,SAAS,QAAQ,CAAC,IAAa;IAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChD,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,iCAAiC;IAC/C,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAClD,GAAG;SACA,WAAW,CAAC,oDAAoD,CAAC;SACjE,cAAc,CAAC,cAAc,EAAE,wDAAwD,CAAC;SACxF,MAAM,CAAC,eAAe,EAAE,2BAA2B,EAAE,MAAM,CAAC;SAC5D,MAAM,CAAC,KAAK,EAAE,OAAyC,EAAE,EAAE;QAC1D,IAAI,CAAC,OAAO,CAAC,IAAI;YAAE,iBAAiB,CAAC,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC;YACH,kBAAkB,CAAC,MAAM,eAAe,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IACL,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,8BAA8B;IAC5C,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/C,GAAG;SACA,WAAW,CAAC,+HAA+H,CAAC;SAC5I,cAAc,CAAC,qBAAqB,EAAE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SAC9D,cAAc,CACb,WAAW,EACX,yGAAyG,CAC1G;SACA,MAAM,CAAC,eAAe,EAAE,2BAA2B,EAAE,MAAM,CAAC;SAC5D,MAAM,CAAC,KAAK,EAAE,OAA2D,EAAE,EAAE;QAC5E,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACnE,iBAAiB,CAAC,IAAI,KAAK,CAAC,gCAAgC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,iBAAiB,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC;YACH,kBAAkB,CAChB,MAAM,eAAe,CACnB,qBAAqB,EACrB,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,EAChD,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CACvB,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IACL,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmlDpC,wBAAgB,gBAAgB,IAAI,OAAO,CAI1C"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAk5DpC,wBAAgB,gBAAgB,IAAI,OAAO,CAI1C"}
@@ -4,9 +4,11 @@ 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, 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, uploadArchive, } 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";
11
13
  const DEV_SERVER_PORT = 5201;
12
14
  // Store compiled components, shared modules, and global styles
@@ -24,6 +26,9 @@ let lastBroadcastGlobalStyleHash;
24
26
  let cachedEditorTypes = null;
25
27
  // Cached custom type definitions received from the editor via sync-types (full IType objects)
26
28
  let cachedCustomTypes = null;
29
+ // Cached theme global variables received from the editor via sync-types.
30
+ // Drives the ThemeSettingName union in src/global-types.ts.
31
+ let cachedThemeSettings = null;
27
32
  const LIBRARY_TYPE_PREFIX = "@ikas/";
28
33
  /**
29
34
  * Match custom types for a component's props from the cached custom types.
@@ -77,24 +82,6 @@ function matchCustomTypesForProps(props, allCustomTypes) {
77
82
  }
78
83
  return Array.from(collected.values());
79
84
  }
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
85
  /**
99
86
  * Compile all components using multi-entry code splitting.
100
87
  * Updates compiledComponents and compiledSharedModules maps.
@@ -136,6 +123,18 @@ async function compileAllComponentsForDev(config) {
136
123
  if (chunkIds.length > 0) {
137
124
  console.log(chalk.cyan(` Extracted ${chunkIds.length} shared chunk(s)`));
138
125
  }
126
+ // Nothing downstream can resolve a computed specifier, so it 404s on the first lazy path that
127
+ // reaches it. Same pass `publish` runs, on the same two maps, so the two cannot drift apart —
128
+ // and both only warn for now (see the note in publish.ts on why that gate is not yet an error).
129
+ // Warning here would be right regardless: killing the watch loop over it is worse than a canvas
130
+ // that half-works, and the developer is right here to read the line.
131
+ const computedImports = findComputedDynamicImportsInBundles(result.chunks, result.entries, new Map(config.components.map((c) => [c.id, c.name])));
132
+ if (computedImports.length > 0) {
133
+ 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):`));
134
+ for (const offender of computedImports) {
135
+ console.warn(chalk.gray(` ${offender}`));
136
+ }
137
+ }
139
138
  // Store global CSS (unscoped). Preserve `updatedAt` across rebuilds when the compiled
140
139
  // global CSS is byte-identical so the editor only sees a fresh timestamp when the
141
140
  // designer actually changed `global.css` — the publish UI watches this timestamp to
@@ -176,6 +175,12 @@ async function compileAllComponentsForDev(config) {
176
175
  const canvasClientJs = transformEsmToWindowGlobals(entry.clientJs, {
177
176
  chunkMapping,
178
177
  });
178
+ // Config names a prop's visibility gate; the editor addresses props by id, so resolve the one
179
+ // against the other off the same index formula the ids come from.
180
+ const propIdByName = propIdsByName(component.props.map((prop, index) => ({
181
+ id: `${component.id}_prop_${index}`,
182
+ name: prop.name,
183
+ })));
179
184
  const propsWithIds = component.props.map((prop, index) => ({
180
185
  id: `${component.id}_prop_${index}`,
181
186
  name: prop.name,
@@ -187,10 +192,14 @@ async function compileAllComponentsForDev(config) {
187
192
  groupId: prop.groupId,
188
193
  typeId: prop.typeId,
189
194
  enumTypeId: prop.enumTypeId,
195
+ ...(prop.numberRangeData
196
+ ? { numberRangeData: prop.numberRangeData }
197
+ : {}),
190
198
  ...(prop.privateVarMap ? { privateVarMap: prop.privateVarMap } : {}),
191
199
  ...(prop.filteredComponentIds
192
200
  ? { filteredComponentIds: prop.filteredComponentIds }
193
201
  : {}),
202
+ ...resolveCondition(prop, propIdByName),
194
203
  }));
195
204
  // Build prop group hierarchy if groups are defined
196
205
  const propGroups = component.propGroups && component.propGroups.length > 0
@@ -200,7 +209,7 @@ async function compileAllComponentsForDev(config) {
200
209
  const componentCustomTypes = cachedCustomTypes
201
210
  ? matchCustomTypesForProps(propsWithIds, cachedCustomTypes)
202
211
  : undefined;
203
- const usedSharedModuleIds = (result.entryChunkIds.get(component.id) || []).map((id) => config.projectId ? `${config.projectId}-${id}` : id);
212
+ const usedSharedModuleIds = (result.entryChunkIds.get(component.id) || []).map((id) => (config.projectId ? `${config.projectId}-${id}` : id));
204
213
  const data = {
205
214
  id: component.id,
206
215
  name: component.name,
@@ -253,6 +262,10 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
253
262
  const component = config.components.find((c) => c.id === componentId);
254
263
  if (!component)
255
264
  return null;
265
+ const propIdByName = propIdsByName(component.props.map((prop, index) => ({
266
+ id: `${component.id}_prop_${index}`,
267
+ name: prop.name,
268
+ })));
256
269
  const propsWithIds = component.props.map((prop, index) => ({
257
270
  id: `${component.id}_prop_${index}`,
258
271
  name: prop.name,
@@ -264,10 +277,12 @@ function rebuildCompiledComponentFromConfig(config, componentId) {
264
277
  groupId: prop.groupId,
265
278
  typeId: prop.typeId,
266
279
  enumTypeId: prop.enumTypeId,
280
+ ...(prop.numberRangeData ? { numberRangeData: prop.numberRangeData } : {}),
267
281
  ...(prop.privateVarMap ? { privateVarMap: prop.privateVarMap } : {}),
268
282
  ...(prop.filteredComponentIds
269
283
  ? { filteredComponentIds: prop.filteredComponentIds }
270
284
  : {}),
285
+ ...resolveCondition(prop, propIdByName),
271
286
  }));
272
287
  const propGroups = component.propGroups && component.propGroups.length > 0
273
288
  ? buildPropGroupHierarchy(component.id, component.propGroups, propsWithIds)
@@ -310,7 +325,13 @@ function sha256Hex(input) {
310
325
  */
311
326
  function populateContentHashes() {
312
327
  for (const mod of compiledSharedModules.values()) {
313
- mod.contentHash = sha256Hex([mod.id, mod.compiledServerJs, mod.compiledClientJs, mod.canvasClientJs, mod.compiledCss].join("\0"));
328
+ mod.contentHash = sha256Hex([
329
+ mod.id,
330
+ mod.compiledServerJs,
331
+ mod.compiledClientJs,
332
+ mod.canvasClientJs,
333
+ mod.compiledCss,
334
+ ].join("\0"));
314
335
  }
315
336
  for (const comp of compiledComponents.values()) {
316
337
  const referencedHashes = (comp.usedSharedModuleIds || [])
@@ -461,12 +482,20 @@ function writeConfig(configPath, config) {
461
482
  function writeGlobalTypesFile(config) {
462
483
  const globalTypesPath = path.resolve(process.cwd(), "src/global-types.ts");
463
484
  const usedEnumIds = collectUsedEnumIds(config.components);
464
- const content = generateGlobalTypesFile(cachedEditorTypes, cachedCustomTypes, usedEnumIds);
465
- // Avoid unnecessary writes (prevents file watcher loops)
466
- if (fs.existsSync(globalTypesPath)) {
467
- const existing = fs.readFileSync(globalTypesPath, "utf-8");
468
- if (existing === content)
469
- return;
485
+ const content = generateGlobalTypesFile(cachedEditorTypes, cachedCustomTypes, usedEnumIds, cachedThemeSettings);
486
+ // Single read instead of existsSync + readFileSync; absent file reads as null.
487
+ let existing = null;
488
+ try {
489
+ existing = fs.readFileSync(globalTypesPath, "utf-8");
490
+ }
491
+ catch {
492
+ existing = null;
493
+ }
494
+ // Skip unnecessary writes (file-watcher loops) and, crucially, don't let the
495
+ // startup write clobber editor-synced theme globals before the editor has
496
+ // connected and re-synced them.
497
+ if (!shouldWriteGlobalTypes(existing, content, cachedThemeSettings != null)) {
498
+ return;
470
499
  }
471
500
  fs.writeFileSync(globalTypesPath, content);
472
501
  }
@@ -502,6 +531,44 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
502
531
  sharedModules: getAllSharedModules(),
503
532
  globalStyles: compiledGlobalStyles,
504
533
  }),
534
+ // The editor sends this after a partner publishes a new design-asset
535
+ // version: zip this folder without node_modules and PUT it to the
536
+ // presigned URL the ikas backend issued. The working tree is untouched.
537
+ onArchiveProject: async (payload) => {
538
+ const rootDir = process.cwd();
539
+ console.log(chalk.cyan(` Archiving theme source (${rootDir}) without node_modules...`));
540
+ const archive = await archiveProject({
541
+ rootDir,
542
+ exclude: payload.exclude,
543
+ rewriteIds: payload.rewriteIds,
544
+ });
545
+ try {
546
+ let uploaded = false;
547
+ let etag;
548
+ if (payload.uploadUrl) {
549
+ const result = await uploadArchive({
550
+ zipPath: archive.zipPath,
551
+ uploadUrl: payload.uploadUrl,
552
+ method: payload.method,
553
+ headers: payload.headers,
554
+ });
555
+ uploaded = true;
556
+ etag = result.etag;
557
+ }
558
+ console.log(chalk.green(` Theme source archived: ${archive.fileCount} files, ${(archive.size / 1024).toFixed(0)} KB` +
559
+ (uploaded ? " — uploaded" : " — not uploaded (no upload URL)")));
560
+ return {
561
+ rootDir,
562
+ size: archive.size,
563
+ fileCount: archive.fileCount,
564
+ uploaded,
565
+ etag,
566
+ };
567
+ }
568
+ finally {
569
+ removeArchive(archive.zipPath);
570
+ }
571
+ },
505
572
  onCreateComponent: async (name, type) => {
506
573
  const pascalName = toPascalCase(name);
507
574
  // Validate
@@ -512,6 +579,12 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
512
579
  if (config.components.some((c) => c.name === pascalName)) {
513
580
  throw new Error(`Component "${pascalName}" already exists`);
514
581
  }
582
+ // No `!` assertion: the config may have been hot-reloaded since startup, and
583
+ // minting with a missing/malformed projectId writes a corrupt component id
584
+ // into ikas.config.json that later publishes verbatim.
585
+ if (!config.projectId || !isValidProjectId(config.projectId)) {
586
+ throw new Error("Invalid or missing projectId in ikas.config.json — restore the generated value and restart the dev server");
587
+ }
515
588
  const componentId = generateComponentId(config.projectId);
516
589
  const componentDir = path.resolve(process.cwd(), `src/components/${pascalName}`);
517
590
  // Create directory and files
@@ -553,7 +626,7 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
553
626
  if (!prop.enumTypeId) {
554
627
  throw new Error(`ENUM prop "${prop.name}" requires enumTypeId. Create one first with "config add-enum".`);
555
628
  }
556
- if (!prop.enumTypeId.startsWith("@ikas/")) {
629
+ if (!isBuiltInEnumTypeId(prop.enumTypeId)) {
557
630
  const customTypes = config.customTypes || [];
558
631
  const enumExists = customTypes.some((t) => t.id === prop.enumTypeId && t.type === "enum");
559
632
  if (!enumExists) {
@@ -573,6 +646,9 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
573
646
  `Known components: ${config.components.map((c) => `${c.name} (${c.id})`).join(", ")}.`);
574
647
  }
575
648
  }
649
+ const addGateError = visibilityGateError(component, prop, `prop "${prop.name}"`, { selfPropName: prop.name });
650
+ if (addGateError)
651
+ throw new Error(addGateError);
576
652
  component.props.push(prop);
577
653
  writeConfig(configPath, config);
578
654
  setConfig(config);
@@ -601,6 +677,13 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
601
677
  `Known components: ${config.components.map((c) => `${c.name} (${c.id})`).join(", ")}.`);
602
678
  }
603
679
  }
680
+ // Checked against the component as it will be, not as it is: a gate on the incoming prop can
681
+ // name another prop, and a rename in the same save changes what the chain resolves to.
682
+ const nextProps = component.props.slice();
683
+ nextProps[propIndex] = prop;
684
+ const updateGateError = visibilityGateError({ ...component, props: nextProps }, prop, `prop "${prop.name}"`, { selfPropName: prop.name });
685
+ if (updateGateError)
686
+ throw new Error(updateGateError);
604
687
  component.props[propIndex] = prop;
605
688
  writeConfig(configPath, config);
606
689
  setConfig(config);
@@ -648,7 +731,21 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
648
731
  ...(propGroup.description
649
732
  ? { description: propGroup.description }
650
733
  : {}),
734
+ ...(propGroup.requiredProp
735
+ ? {
736
+ requiredProp: propGroup.requiredProp,
737
+ ...(propGroup.requiredEnumOptionValue !== undefined
738
+ ? {
739
+ requiredEnumOptionId: propGroup.requiredEnumOptionId,
740
+ requiredEnumOptionValue: propGroup.requiredEnumOptionValue,
741
+ }
742
+ : { requiredPropValue: propGroup.requiredPropValue ?? true }),
743
+ }
744
+ : {}),
651
745
  };
746
+ const gateError = visibilityGateError(component, newGroup, `group "${newGroup.id}"`, { group: newGroup });
747
+ if (gateError)
748
+ throw new Error(gateError);
652
749
  if (propGroup.parentGroupId) {
653
750
  const found = findPropGroup(component.propGroups, propGroup.parentGroupId);
654
751
  if (!found)
@@ -681,6 +778,32 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
681
778
  found.group.name = updates.name;
682
779
  if (updates.description !== undefined)
683
780
  found.group.description = updates.description || undefined;
781
+ if (updates.requiredProp !== undefined) {
782
+ if (!updates.requiredProp || updates.requiredProp === "none") {
783
+ // Both go together — a state with no prop behind it would read as a rule.
784
+ delete found.group.requiredProp;
785
+ delete found.group.requiredPropValue;
786
+ delete found.group.requiredEnumOptionId;
787
+ delete found.group.requiredEnumOptionValue;
788
+ }
789
+ else {
790
+ found.group.requiredProp = updates.requiredProp;
791
+ if (updates.requiredEnumOptionValue !== undefined) {
792
+ found.group.requiredEnumOptionId = updates.requiredEnumOptionId;
793
+ found.group.requiredEnumOptionValue =
794
+ updates.requiredEnumOptionValue;
795
+ delete found.group.requiredPropValue;
796
+ }
797
+ else {
798
+ found.group.requiredPropValue = updates.requiredPropValue ?? true;
799
+ delete found.group.requiredEnumOptionId;
800
+ delete found.group.requiredEnumOptionValue;
801
+ }
802
+ }
803
+ }
804
+ const gateError = visibilityGateError(component, found.group, `group "${found.group.id}"`, { group: found.group });
805
+ if (gateError)
806
+ throw new Error(gateError);
684
807
  writeConfig(configPath, config);
685
808
  setConfig(config);
686
809
  broadcastConfigChange(componentId);
@@ -871,8 +994,10 @@ function createEditorHandlers(configPath, getConfig, setConfig, wsServer) {
871
994
  regenerateAllComponentTypes(config);
872
995
  console.log(chalk.green(` Deleted enum: ${removed.name} (${enumId})`));
873
996
  },
874
- onSyncTypes: (types, customTypes) => {
997
+ onSyncTypes: (types, customTypes, themeSettings) => {
875
998
  cachedEditorTypes = types;
999
+ if (themeSettings)
1000
+ cachedThemeSettings = themeSettings;
876
1001
  // Merge incoming custom types with existing ones instead of replacing.
877
1002
  // Custom types belong to the code component project and should not be
878
1003
  // lost when a different editor project connects.
@@ -951,6 +1076,27 @@ async function startDevServer(opts = {}) {
951
1076
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
952
1077
  console.log(chalk.yellow(` Auto-generated projectId: ${config.projectId}`));
953
1078
  }
1079
+ else if (!isValidProjectId(config.projectId)) {
1080
+ // A malformed projectId (hand-edited config) breaks the editor's republish
1081
+ // stability: published/local id prefixes stop matching and every publish
1082
+ // INSERTs duplicate design elements. Fail fast instead of corrupting data.
1083
+ console.error(chalk.red(`Error: invalid projectId "${config.projectId}" in ikas.config.json`));
1084
+ console.error(chalk.gray("projectId must be at least 6 alphanumeric characters (no '-' or '_'). " +
1085
+ "Restore the original generated value — do not hand-edit it."));
1086
+ process.exit(1);
1087
+ }
1088
+ // Component ids carry the same prefix/suffix contract as projectId; the dev
1089
+ // server syncs them into the editor (and from there into themes), so a
1090
+ // hand-edited id must be rejected before the server starts.
1091
+ const invalidComponents = findInvalidComponentIds(config.components, config.projectId);
1092
+ if (invalidComponents.length > 0) {
1093
+ console.error(chalk.red("Error: invalid component id(s) in ikas.config.json:"));
1094
+ invalidComponents.forEach((c) => {
1095
+ console.error(chalk.red(` - "${c.id}"${c.name ? ` (${c.name})` : ""} — ${c.reason}`));
1096
+ });
1097
+ console.error(chalk.gray(COMPONENT_ID_HINT));
1098
+ process.exit(1);
1099
+ }
954
1100
  console.log(chalk.cyan("\n ikas Component Dev Server\n"));
955
1101
  console.log(chalk.gray(` Project: ${config.name}`));
956
1102
  console.log(chalk.gray(` Components: ${config.components.length}\n`));
@@ -1003,6 +1149,16 @@ async function startDevServer(opts = {}) {
1003
1149
  console.error(chalk.red(` Failed to start WebSocket server: ${error.message}`));
1004
1150
  process.exit(1);
1005
1151
  }
1152
+ console.log(chalk.green(` Dev server listening on 127.0.0.1:${DEV_SERVER_PORT}`));
1153
+ // Studio reaches this server only through the tunnel + websocket routing
1154
+ // that `ikas theme dev` registers; a directly-started server is invisible
1155
+ // to the editor. Hint only on a TTY — under `ikas theme dev` stdio is
1156
+ // piped and the wrapper already handles the connection.
1157
+ if (process.stdout.isTTY) {
1158
+ console.log(chalk.yellow("\n Note: ikas Studio cannot connect to this server directly."));
1159
+ console.log(chalk.yellow(" To develop with the editor, run `ikas theme dev` in this folder —"));
1160
+ console.log(chalk.yellow(" it starts this server, opens a tunnel and registers it with your store."));
1161
+ }
1006
1162
  // Watch for file changes
1007
1163
  const watchPaths = [
1008
1164
  "src/**/*.tsx",
@@ -1055,7 +1211,38 @@ async function startDevServer(opts = {}) {
1055
1211
  if (normalizedPath === configPath) {
1056
1212
  console.log(chalk.yellow("\n Config changed, reloading..."));
1057
1213
  try {
1058
- config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1214
+ const reloaded = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1215
+ // The startup guards validated projectId/component ids once; a hot-reload
1216
+ // must not smuggle a hand-edited value past them — a malformed id reaching
1217
+ // `generateComponentId` or chunk namespacing corrupts republish matching.
1218
+ // Keep the session's validated projectId if the key was removed; reject the
1219
+ // reload outright (previous config stays active) on invalid values.
1220
+ if (!reloaded.projectId) {
1221
+ reloaded.projectId = config.projectId;
1222
+ }
1223
+ else if (!isValidProjectId(reloaded.projectId)) {
1224
+ console.error(chalk.red(` Ignoring config reload: invalid projectId "${reloaded.projectId}". `) +
1225
+ chalk.gray("Restore the original generated value — previous config stays active."));
1226
+ return;
1227
+ }
1228
+ const reloadedInvalidComponents = findInvalidComponentIds(reloaded.components ?? [], reloaded.projectId);
1229
+ if (reloadedInvalidComponents.length > 0) {
1230
+ console.error(chalk.red(` Ignoring config reload: invalid component id(s): ${reloadedInvalidComponents
1231
+ .map((c) => `"${c.id}" (${c.reason})`)
1232
+ .join(", ")}. `) +
1233
+ chalk.gray("Restore the original generated values — previous config stays active."));
1234
+ return;
1235
+ }
1236
+ // Well-formed is not enough: an id that changes while the server runs is
1237
+ // broadcast as `component-deleted` for the old one, and the editor then
1238
+ // removes the imported component and its page placements from the theme.
1239
+ const identityChanges = findIdentityChanges(config, reloaded);
1240
+ if (identityChanges.length > 0) {
1241
+ console.error(chalk.red(` Ignoring config reload: generated ids were changed (${identityChanges.join(", ")}). `) +
1242
+ chalk.gray("Restore the original generated values — previous config stays active."));
1243
+ return;
1244
+ }
1245
+ config = reloaded;
1059
1246
  cachedCustomTypes = config.customTypes || [];
1060
1247
  writeGlobalTypesFile(config);
1061
1248
  await compileAllComponentsForDev(config);