@hexclave/shared 1.0.36 → 1.0.37

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 (98) hide show
  1. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +2 -2
  2. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  3. package/dist/config/format.js +10 -0
  4. package/dist/config/format.js.map +1 -1
  5. package/dist/config/schema.d.ts +50 -50
  6. package/dist/config-eval.d.ts +31 -0
  7. package/dist/config-eval.d.ts.map +1 -0
  8. package/dist/config-eval.js +117 -0
  9. package/dist/config-eval.js.map +1 -0
  10. package/dist/config-rendering.d.ts +3 -6
  11. package/dist/config-rendering.d.ts.map +1 -1
  12. package/dist/config-rendering.js +21 -69
  13. package/dist/config-rendering.js.map +1 -1
  14. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +2 -2
  15. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  16. package/dist/esm/config/format.js +10 -0
  17. package/dist/esm/config/format.js.map +1 -1
  18. package/dist/esm/config/schema.d.ts +50 -50
  19. package/dist/esm/config-eval.d.ts +31 -0
  20. package/dist/esm/config-eval.d.ts.map +1 -0
  21. package/dist/esm/config-eval.js +112 -0
  22. package/dist/esm/config-eval.js.map +1 -0
  23. package/dist/esm/config-rendering.d.ts +3 -6
  24. package/dist/esm/config-rendering.d.ts.map +1 -1
  25. package/dist/esm/config-rendering.js +15 -58
  26. package/dist/esm/config-rendering.js.map +1 -1
  27. package/dist/esm/interface/admin-interface.d.ts +44 -2
  28. package/dist/esm/interface/admin-interface.d.ts.map +1 -1
  29. package/dist/esm/interface/admin-interface.js +56 -1
  30. package/dist/esm/interface/admin-interface.js.map +1 -1
  31. package/dist/esm/interface/admin-metrics.d.ts +12 -12
  32. package/dist/esm/interface/client-interface.js +1 -1
  33. package/dist/esm/interface/conversations.d.ts +4 -4
  34. package/dist/esm/interface/crud/current-user.d.ts +12 -12
  35. package/dist/esm/interface/crud/email-outbox.d.ts +180 -180
  36. package/dist/esm/interface/crud/products.d.ts +15 -15
  37. package/dist/esm/interface/crud/products.d.ts.map +1 -1
  38. package/dist/esm/interface/crud/project-api-keys.d.ts +2 -2
  39. package/dist/esm/interface/crud/projects.d.ts +28 -28
  40. package/dist/esm/interface/crud/team-member-profiles.d.ts +26 -26
  41. package/dist/esm/interface/crud/transactions.d.ts +20 -20
  42. package/dist/esm/interface/crud/transactions.d.ts.map +1 -1
  43. package/dist/esm/interface/crud/users.d.ts +16 -16
  44. package/dist/esm/interface/plan-usage.d.ts +2 -2
  45. package/dist/esm/interface/server-interface.js +1 -1
  46. package/dist/esm/interface/webhooks.d.ts +2 -2
  47. package/dist/esm/known-errors.d.ts +1 -1
  48. package/dist/esm/schema-fields.d.ts +35 -2
  49. package/dist/esm/schema-fields.d.ts.map +1 -1
  50. package/dist/esm/schema-fields.js +53 -2
  51. package/dist/esm/schema-fields.js.map +1 -1
  52. package/dist/esm/sessions.d.ts +7 -7
  53. package/dist/esm/utils/jsx-editable-transpiler.js +2 -2
  54. package/dist/esm/utils/promises.js +1 -1
  55. package/dist/interface/admin-interface.d.ts +44 -2
  56. package/dist/interface/admin-interface.d.ts.map +1 -1
  57. package/dist/interface/admin-interface.js +56 -1
  58. package/dist/interface/admin-interface.js.map +1 -1
  59. package/dist/interface/admin-metrics.d.ts +12 -12
  60. package/dist/interface/client-interface.js +1 -1
  61. package/dist/interface/conversations.d.ts +4 -4
  62. package/dist/interface/crud/current-user.d.ts +12 -12
  63. package/dist/interface/crud/email-outbox.d.ts +180 -180
  64. package/dist/interface/crud/products.d.ts +15 -15
  65. package/dist/interface/crud/products.d.ts.map +1 -1
  66. package/dist/interface/crud/project-api-keys.d.ts +2 -2
  67. package/dist/interface/crud/projects.d.ts +28 -28
  68. package/dist/interface/crud/team-member-profiles.d.ts +26 -26
  69. package/dist/interface/crud/transactions.d.ts +20 -20
  70. package/dist/interface/crud/transactions.d.ts.map +1 -1
  71. package/dist/interface/crud/users.d.ts +16 -16
  72. package/dist/interface/plan-usage.d.ts +2 -2
  73. package/dist/interface/server-interface.js +1 -1
  74. package/dist/interface/webhooks.d.ts +2 -2
  75. package/dist/known-errors.d.ts +1 -1
  76. package/dist/schema-fields.d.ts +35 -2
  77. package/dist/schema-fields.d.ts.map +1 -1
  78. package/dist/schema-fields.js +55 -1
  79. package/dist/schema-fields.js.map +1 -1
  80. package/dist/sessions.d.ts +7 -7
  81. package/dist/utils/jsx-editable-transpiler.js +4 -4
  82. package/dist/utils/promises.js +1 -1
  83. package/package.json +2 -1
  84. package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +2 -2
  85. package/src/config/format.ts +18 -0
  86. package/src/config-eval.ts +140 -0
  87. package/src/config-rendering.ts +23 -79
  88. package/src/interface/admin-interface.ts +85 -5
  89. package/src/schema-fields.ts +59 -1
  90. package/dist/esm/hexclave-config-file.d.ts +0 -38
  91. package/dist/esm/hexclave-config-file.d.ts.map +0 -1
  92. package/dist/esm/hexclave-config-file.js +0 -144
  93. package/dist/esm/hexclave-config-file.js.map +0 -1
  94. package/dist/hexclave-config-file.d.ts +0 -38
  95. package/dist/hexclave-config-file.d.ts.map +0 -1
  96. package/dist/hexclave-config-file.js +0 -153
  97. package/dist/hexclave-config-file.js.map +0 -1
  98. package/src/hexclave-config-file.ts +0 -215
@@ -1,144 +0,0 @@
1
- import * as parser from "@babel/parser";
2
- import * as t from "@babel/types";
3
- import { isValidConfig, normalize } from "./config/format.js";
4
-
5
- //#region src/hexclave-config-file.ts
6
- const showOnboardingHexclaveConfigValue = "show-onboarding";
7
- const DEFAULT_CONFIG_IMPORT_PACKAGE = "@hexclave/js";
8
- /**
9
- * Renders a config object into the source text of a `stack.config.ts` file.
10
- *
11
- * Browser-safe: kept here (next to `parseHexclaveConfigFileContent`) instead of in
12
- * `config-rendering.ts` so dashboard client code can render config files
13
- * without pulling in `fs` / `path`.
14
- */
15
- function renderConfigFileContent(config, importPackage) {
16
- if (!isValidConfig(config)) throw new Error("Invalid config: expected a plain object.");
17
- const droppedKeys = [];
18
- const normalizedConfig = normalize(config, {
19
- onDotIntoNonObject: "ignore",
20
- onDotIntoNull: "empty-object",
21
- droppedKeys
22
- });
23
- if (droppedKeys.length > 0) throw new Error(`Config has conflicting keys that would be dropped during normalization: ${droppedKeys.map((k) => JSON.stringify(k)).join(", ")}`);
24
- const pkg = importPackage ?? DEFAULT_CONFIG_IMPORT_PACKAGE;
25
- return `${`import type { HexclaveConfig } from "${pkg.startsWith("@hexclave/") ? `${pkg}/config` : pkg}";`}\n\nexport const config: HexclaveConfig = ${JSON.stringify(normalizedConfig, null, 2)};\n`;
26
- }
27
- function unwrapStaticConfigExpression(expression) {
28
- if (t.isTSAsExpression(expression) || t.isTSSatisfiesExpression(expression) || t.isTSTypeAssertion(expression) || t.isTSNonNullExpression(expression)) return unwrapStaticConfigExpression(expression.expression);
29
- return expression;
30
- }
31
- function evaluateStaticConfigExpression(expression) {
32
- const unwrapped = unwrapStaticConfigExpression(expression);
33
- if (t.isStringLiteral(unwrapped)) return unwrapped.value;
34
- if (t.isBooleanLiteral(unwrapped)) return unwrapped.value;
35
- if (t.isNumericLiteral(unwrapped)) return unwrapped.value;
36
- if (t.isNullLiteral(unwrapped)) return null;
37
- if (t.isIdentifier(unwrapped) && unwrapped.name === "undefined") return void 0;
38
- if (t.isUnaryExpression(unwrapped) && unwrapped.operator === "-" && t.isNumericLiteral(unwrapped.argument)) return -unwrapped.argument.value;
39
- if (t.isArrayExpression(unwrapped)) return unwrapped.elements.map((element) => {
40
- if (element == null || t.isSpreadElement(element)) throw new Error("Config arrays cannot contain holes or spreads.");
41
- return evaluateStaticConfigExpression(element);
42
- });
43
- if (t.isObjectExpression(unwrapped)) {
44
- const result = {};
45
- for (const property of unwrapped.properties) {
46
- if (t.isSpreadElement(property)) throw new Error("Config objects cannot contain spreads.");
47
- if (property.computed) throw new Error("Config object keys cannot be computed.");
48
- const key = t.isIdentifier(property.key) ? property.key.name : t.isStringLiteral(property.key) || t.isNumericLiteral(property.key) ? String(property.key.value) : null;
49
- if (key == null) throw new Error("Unsupported config object key.");
50
- if (t.isObjectMethod(property)) throw new Error("Config objects cannot contain methods.");
51
- if (!t.isExpression(property.value)) throw new Error("Unsupported config object value.");
52
- result[key] = evaluateStaticConfigExpression(property.value);
53
- }
54
- return result;
55
- }
56
- throw new Error(`Unsupported config expression: ${unwrapped.type}`);
57
- }
58
- /**
59
- * Like {@link parseHexclaveConfigFileContent}, but returns `null` instead of
60
- * throwing when the file is not a plain static config (e.g. it wraps the config
61
- * in a helper call, references imported values, or has a syntax error). Useful
62
- * for deciding whether a config file can be safely regenerated deterministically
63
- * or whether it has custom structure that must be preserved.
64
- */
65
- function tryParseHexclaveConfigFileContent(content, filePath) {
66
- try {
67
- return parseHexclaveConfigFileContent(content, filePath);
68
- } catch {
69
- return null;
70
- }
71
- }
72
- /**
73
- * Returns whether `content` parses as a module that exports a `config` binding.
74
- * Used as a lightweight structural sanity check after editing config files whose
75
- * values can't be evaluated by our loader (e.g. they import external text
76
- * files), where a full semantic comparison isn't possible.
77
- */
78
- function hexclaveConfigFileExportsConfig(content, filePath) {
79
- let ast;
80
- try {
81
- ast = parser.parse(content, {
82
- sourceType: "module",
83
- sourceFilename: filePath,
84
- plugins: ["typescript", "importAttributes"]
85
- });
86
- } catch {
87
- return false;
88
- }
89
- for (const statement of ast.program.body) {
90
- if (!t.isExportNamedDeclaration(statement)) continue;
91
- if (statement.exportKind === "type") continue;
92
- if (t.isVariableDeclaration(statement.declaration)) {
93
- for (const declaration of statement.declaration.declarations) if (t.isIdentifier(declaration.id) && declaration.id.name === "config" && declaration.init != null) return true;
94
- }
95
- for (const specifier of statement.specifiers) if (t.isExportSpecifier(specifier) && specifier.exportKind !== "type") {
96
- if ((t.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value) === "config") return true;
97
- }
98
- }
99
- return false;
100
- }
101
- /**
102
- * Returns the relative import sources (those starting with `./` or `../`)
103
- * declared in `content`. Used to discover the external files a config update may
104
- * touch — e.g. `import x from "./welcome-email.tsx" with { type: "text" }` — so
105
- * they can be snapshotted and rolled back if an in-place update fails. Returns
106
- * an empty array if the file can't be parsed.
107
- */
108
- function getRelativeImportSpecifiers(content) {
109
- let ast;
110
- try {
111
- ast = parser.parse(content, {
112
- sourceType: "module",
113
- plugins: ["typescript", "importAttributes"]
114
- });
115
- } catch {
116
- return [];
117
- }
118
- const sources = [];
119
- for (const statement of ast.program.body) if (t.isImportDeclaration(statement)) {
120
- const source = statement.source.value;
121
- if (source.startsWith("./") || source.startsWith("../")) sources.push(source);
122
- }
123
- return sources;
124
- }
125
- function parseHexclaveConfigFileContent(content, filePath) {
126
- if (content.trim() === "") return {};
127
- const ast = parser.parse(content, {
128
- sourceType: "module",
129
- plugins: ["typescript", "importAttributes"]
130
- });
131
- for (const statement of ast.program.body) {
132
- if (!t.isExportNamedDeclaration(statement) || !t.isVariableDeclaration(statement.declaration)) continue;
133
- for (const declaration of statement.declaration.declarations) {
134
- if (!t.isIdentifier(declaration.id) || declaration.id.name !== "config") continue;
135
- if (declaration.init == null || !t.isExpression(declaration.init)) throw new Error(`Config export in ${filePath} must have an initializer.`);
136
- return evaluateStaticConfigExpression(declaration.init);
137
- }
138
- }
139
- throw new Error(`Invalid config in ${filePath}. The file must export a plain \`config\` object or "show-onboarding".`);
140
- }
141
-
142
- //#endregion
143
- export { getRelativeImportSpecifiers, hexclaveConfigFileExportsConfig, parseHexclaveConfigFileContent, renderConfigFileContent, showOnboardingHexclaveConfigValue, tryParseHexclaveConfigFileContent };
144
- //# sourceMappingURL=hexclave-config-file.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hexclave-config-file.js","names":[],"sources":["../../src/hexclave-config-file.ts"],"sourcesContent":["import * as parser from \"@babel/parser\";\nimport * as t from \"@babel/types\";\nimport { isValidConfig, normalize } from \"./config/format\";\n\nexport const showOnboardingHexclaveConfigValue = \"show-onboarding\";\n\nconst DEFAULT_CONFIG_IMPORT_PACKAGE = \"@hexclave/js\";\n\n/**\n * Renders a config object into the source text of a `stack.config.ts` file.\n *\n * Browser-safe: kept here (next to `parseHexclaveConfigFileContent`) instead of in\n * `config-rendering.ts` so dashboard client code can render config files\n * without pulling in `fs` / `path`.\n */\nexport function renderConfigFileContent(config: unknown, importPackage?: string): string {\n if (!isValidConfig(config)) {\n throw new Error(\"Invalid config: expected a plain object.\");\n }\n\n const droppedKeys: string[] = [];\n const normalizedConfig = normalize(config, {\n onDotIntoNonObject: \"ignore\",\n onDotIntoNull: \"empty-object\",\n droppedKeys,\n });\n if (droppedKeys.length > 0) {\n throw new Error(`Config has conflicting keys that would be dropped during normalization: ${droppedKeys.map(k => JSON.stringify(k)).join(\", \")}`);\n }\n const pkg = importPackage ?? DEFAULT_CONFIG_IMPORT_PACKAGE;\n // Import the `HexclaveConfig` type from the package's lightweight `/config`\n // entrypoint, which is free of framework runtime code and therefore safe for\n // tooling (e.g. the local dashboard) to load in a plain Node context. Only the\n // Hexclave-branded packages expose this subpath; legacy `@stackframe/*`\n // releases predate it, so fall back to their package root.\n const importSpecifier = pkg.startsWith(\"@hexclave/\") ? `${pkg}/config` : pkg;\n const importLine = `import type { HexclaveConfig } from \"${importSpecifier}\";`;\n return `${importLine}\\n\\nexport const config: HexclaveConfig = ${JSON.stringify(normalizedConfig, null, 2)};\\n`;\n}\n\ntype ParsedStackConfig = Record<string, unknown> | typeof showOnboardingHexclaveConfigValue;\n\nfunction unwrapStaticConfigExpression(expression: t.Expression): t.Expression {\n if (\n t.isTSAsExpression(expression)\n || t.isTSSatisfiesExpression(expression)\n || t.isTSTypeAssertion(expression)\n || t.isTSNonNullExpression(expression)\n ) {\n return unwrapStaticConfigExpression(expression.expression);\n }\n return expression;\n}\n\nfunction evaluateStaticConfigExpression(expression: t.Expression): unknown {\n const unwrapped = unwrapStaticConfigExpression(expression);\n if (t.isStringLiteral(unwrapped)) return unwrapped.value;\n if (t.isBooleanLiteral(unwrapped)) return unwrapped.value;\n if (t.isNumericLiteral(unwrapped)) return unwrapped.value;\n if (t.isNullLiteral(unwrapped)) return null;\n if (t.isIdentifier(unwrapped) && unwrapped.name === \"undefined\") return undefined;\n if (t.isUnaryExpression(unwrapped) && unwrapped.operator === \"-\" && t.isNumericLiteral(unwrapped.argument)) {\n return -unwrapped.argument.value;\n }\n if (t.isArrayExpression(unwrapped)) {\n return unwrapped.elements.map((element) => {\n if (element == null || t.isSpreadElement(element)) {\n throw new Error(\"Config arrays cannot contain holes or spreads.\");\n }\n return evaluateStaticConfigExpression(element);\n });\n }\n if (t.isObjectExpression(unwrapped)) {\n const result: Record<string, unknown> = {};\n for (const property of unwrapped.properties) {\n if (t.isSpreadElement(property)) {\n throw new Error(\"Config objects cannot contain spreads.\");\n }\n if (property.computed) {\n throw new Error(\"Config object keys cannot be computed.\");\n }\n const key = t.isIdentifier(property.key)\n ? property.key.name\n : t.isStringLiteral(property.key) || t.isNumericLiteral(property.key)\n ? String(property.key.value)\n : null;\n if (key == null) {\n throw new Error(\"Unsupported config object key.\");\n }\n if (t.isObjectMethod(property)) {\n throw new Error(\"Config objects cannot contain methods.\");\n }\n if (!t.isExpression(property.value)) {\n throw new Error(\"Unsupported config object value.\");\n }\n result[key] = evaluateStaticConfigExpression(property.value);\n }\n return result;\n }\n throw new Error(`Unsupported config expression: ${unwrapped.type}`);\n}\n\n/**\n * Like {@link parseHexclaveConfigFileContent}, but returns `null` instead of\n * throwing when the file is not a plain static config (e.g. it wraps the config\n * in a helper call, references imported values, or has a syntax error). Useful\n * for deciding whether a config file can be safely regenerated deterministically\n * or whether it has custom structure that must be preserved.\n */\nexport function tryParseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig | null {\n try {\n return parseHexclaveConfigFileContent(content, filePath);\n } catch {\n return null;\n }\n}\n\n/**\n * Returns whether `content` parses as a module that exports a `config` binding.\n * Used as a lightweight structural sanity check after editing config files whose\n * values can't be evaluated by our loader (e.g. they import external text\n * files), where a full semantic comparison isn't possible.\n */\nexport function hexclaveConfigFileExportsConfig(content: string, filePath: string): boolean {\n let ast: parser.ParseResult<t.File>;\n try {\n ast = parser.parse(content, {\n sourceType: \"module\",\n sourceFilename: filePath,\n plugins: [\"typescript\", \"importAttributes\"],\n });\n } catch {\n return false;\n }\n for (const statement of ast.program.body) {\n if (!t.isExportNamedDeclaration(statement)) {\n continue;\n }\n // Ignore type-only exports (`export type { config }`): they don't produce a\n // runtime `config` value, so they must not satisfy the structural check.\n if (statement.exportKind === \"type\") {\n continue;\n }\n if (t.isVariableDeclaration(statement.declaration)) {\n for (const declaration of statement.declaration.declarations) {\n if (t.isIdentifier(declaration.id) && declaration.id.name === \"config\" && declaration.init != null) {\n return true;\n }\n }\n }\n for (const specifier of statement.specifiers) {\n if (t.isExportSpecifier(specifier) && specifier.exportKind !== \"type\") {\n const exportedName = t.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value;\n if (exportedName === \"config\") {\n return true;\n }\n }\n }\n }\n return false;\n}\n\n/**\n * Returns the relative import sources (those starting with `./` or `../`)\n * declared in `content`. Used to discover the external files a config update may\n * touch — e.g. `import x from \"./welcome-email.tsx\" with { type: \"text\" }` — so\n * they can be snapshotted and rolled back if an in-place update fails. Returns\n * an empty array if the file can't be parsed.\n */\nexport function getRelativeImportSpecifiers(content: string): string[] {\n let ast: parser.ParseResult<t.File>;\n try {\n ast = parser.parse(content, {\n sourceType: \"module\",\n plugins: [\"typescript\", \"importAttributes\"],\n });\n } catch {\n return [];\n }\n const sources: string[] = [];\n for (const statement of ast.program.body) {\n if (t.isImportDeclaration(statement)) {\n const source = statement.source.value;\n if (source.startsWith(\"./\") || source.startsWith(\"../\")) {\n sources.push(source);\n }\n }\n }\n return sources;\n}\n\nexport function parseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig {\n if (content.trim() === \"\") return {};\n const ast = parser.parse(content, {\n sourceType: \"module\",\n plugins: [\"typescript\", \"importAttributes\"],\n });\n\n for (const statement of ast.program.body) {\n if (!t.isExportNamedDeclaration(statement) || !t.isVariableDeclaration(statement.declaration)) {\n continue;\n }\n for (const declaration of statement.declaration.declarations) {\n if (!t.isIdentifier(declaration.id) || declaration.id.name !== \"config\") {\n continue;\n }\n if (declaration.init == null || !t.isExpression(declaration.init)) {\n throw new Error(`Config export in ${filePath} must have an initializer.`);\n }\n return evaluateStaticConfigExpression(declaration.init) as ParsedStackConfig;\n }\n }\n\n throw new Error(`Invalid config in ${filePath}. The file must export a plain \\`config\\` object or \"show-onboarding\".`);\n}\n"],"mappings":"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"}
@@ -1,38 +0,0 @@
1
- //#region src/hexclave-config-file.d.ts
2
- declare const showOnboardingHexclaveConfigValue = "show-onboarding";
3
- /**
4
- * Renders a config object into the source text of a `stack.config.ts` file.
5
- *
6
- * Browser-safe: kept here (next to `parseHexclaveConfigFileContent`) instead of in
7
- * `config-rendering.ts` so dashboard client code can render config files
8
- * without pulling in `fs` / `path`.
9
- */
10
- declare function renderConfigFileContent(config: unknown, importPackage?: string): string;
11
- type ParsedStackConfig = Record<string, unknown> | typeof showOnboardingHexclaveConfigValue;
12
- /**
13
- * Like {@link parseHexclaveConfigFileContent}, but returns `null` instead of
14
- * throwing when the file is not a plain static config (e.g. it wraps the config
15
- * in a helper call, references imported values, or has a syntax error). Useful
16
- * for deciding whether a config file can be safely regenerated deterministically
17
- * or whether it has custom structure that must be preserved.
18
- */
19
- declare function tryParseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig | null;
20
- /**
21
- * Returns whether `content` parses as a module that exports a `config` binding.
22
- * Used as a lightweight structural sanity check after editing config files whose
23
- * values can't be evaluated by our loader (e.g. they import external text
24
- * files), where a full semantic comparison isn't possible.
25
- */
26
- declare function hexclaveConfigFileExportsConfig(content: string, filePath: string): boolean;
27
- /**
28
- * Returns the relative import sources (those starting with `./` or `../`)
29
- * declared in `content`. Used to discover the external files a config update may
30
- * touch — e.g. `import x from "./welcome-email.tsx" with { type: "text" }` — so
31
- * they can be snapshotted and rolled back if an in-place update fails. Returns
32
- * an empty array if the file can't be parsed.
33
- */
34
- declare function getRelativeImportSpecifiers(content: string): string[];
35
- declare function parseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig;
36
- //#endregion
37
- export { getRelativeImportSpecifiers, hexclaveConfigFileExportsConfig, parseHexclaveConfigFileContent, renderConfigFileContent, showOnboardingHexclaveConfigValue, tryParseHexclaveConfigFileContent };
38
- //# sourceMappingURL=hexclave-config-file.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hexclave-config-file.d.ts","names":[],"sources":["../src/hexclave-config-file.ts"],"mappings":";cAIa,iCAAA;AAAb;;;;;AAWA;;AAXA,iBAWgB,uBAAA,CAAwB,MAAA,WAAiB,aAAA;AAAA,KAyBpD,iBAAA,GAAoB,MAAA,2BAAiC,iCAAA;;AAFzD;;;;;AAuED;iBAAgB,iCAAA,CAAkC,OAAA,UAAiB,QAAA,WAAmB,iBAAA;;;;;;;iBActE,+BAAA,CAAgC,OAAA,UAAiB,QAAA;AAAjE;;;;;AA8CA;;AA9CA,iBA8CgB,2BAAA,CAA4B,OAAA;AAAA,iBAsB5B,8BAAA,CAA+B,OAAA,UAAiB,QAAA,WAAmB,iBAAA"}
@@ -1,153 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_chunk = require('./chunk-BE-pF4vm.js');
3
- let _babel_parser = require("@babel/parser");
4
- _babel_parser = require_chunk.__toESM(_babel_parser);
5
- let _babel_types = require("@babel/types");
6
- _babel_types = require_chunk.__toESM(_babel_types);
7
- let __config_format_js = require("./config/format.js");
8
-
9
- //#region src/hexclave-config-file.ts
10
- const showOnboardingHexclaveConfigValue = "show-onboarding";
11
- const DEFAULT_CONFIG_IMPORT_PACKAGE = "@hexclave/js";
12
- /**
13
- * Renders a config object into the source text of a `stack.config.ts` file.
14
- *
15
- * Browser-safe: kept here (next to `parseHexclaveConfigFileContent`) instead of in
16
- * `config-rendering.ts` so dashboard client code can render config files
17
- * without pulling in `fs` / `path`.
18
- */
19
- function renderConfigFileContent(config, importPackage) {
20
- if (!(0, __config_format_js.isValidConfig)(config)) throw new Error("Invalid config: expected a plain object.");
21
- const droppedKeys = [];
22
- const normalizedConfig = (0, __config_format_js.normalize)(config, {
23
- onDotIntoNonObject: "ignore",
24
- onDotIntoNull: "empty-object",
25
- droppedKeys
26
- });
27
- if (droppedKeys.length > 0) throw new Error(`Config has conflicting keys that would be dropped during normalization: ${droppedKeys.map((k) => JSON.stringify(k)).join(", ")}`);
28
- const pkg = importPackage ?? DEFAULT_CONFIG_IMPORT_PACKAGE;
29
- return `${`import type { HexclaveConfig } from "${pkg.startsWith("@hexclave/") ? `${pkg}/config` : pkg}";`}\n\nexport const config: HexclaveConfig = ${JSON.stringify(normalizedConfig, null, 2)};\n`;
30
- }
31
- function unwrapStaticConfigExpression(expression) {
32
- if (_babel_types.isTSAsExpression(expression) || _babel_types.isTSSatisfiesExpression(expression) || _babel_types.isTSTypeAssertion(expression) || _babel_types.isTSNonNullExpression(expression)) return unwrapStaticConfigExpression(expression.expression);
33
- return expression;
34
- }
35
- function evaluateStaticConfigExpression(expression) {
36
- const unwrapped = unwrapStaticConfigExpression(expression);
37
- if (_babel_types.isStringLiteral(unwrapped)) return unwrapped.value;
38
- if (_babel_types.isBooleanLiteral(unwrapped)) return unwrapped.value;
39
- if (_babel_types.isNumericLiteral(unwrapped)) return unwrapped.value;
40
- if (_babel_types.isNullLiteral(unwrapped)) return null;
41
- if (_babel_types.isIdentifier(unwrapped) && unwrapped.name === "undefined") return void 0;
42
- if (_babel_types.isUnaryExpression(unwrapped) && unwrapped.operator === "-" && _babel_types.isNumericLiteral(unwrapped.argument)) return -unwrapped.argument.value;
43
- if (_babel_types.isArrayExpression(unwrapped)) return unwrapped.elements.map((element) => {
44
- if (element == null || _babel_types.isSpreadElement(element)) throw new Error("Config arrays cannot contain holes or spreads.");
45
- return evaluateStaticConfigExpression(element);
46
- });
47
- if (_babel_types.isObjectExpression(unwrapped)) {
48
- const result = {};
49
- for (const property of unwrapped.properties) {
50
- if (_babel_types.isSpreadElement(property)) throw new Error("Config objects cannot contain spreads.");
51
- if (property.computed) throw new Error("Config object keys cannot be computed.");
52
- const key = _babel_types.isIdentifier(property.key) ? property.key.name : _babel_types.isStringLiteral(property.key) || _babel_types.isNumericLiteral(property.key) ? String(property.key.value) : null;
53
- if (key == null) throw new Error("Unsupported config object key.");
54
- if (_babel_types.isObjectMethod(property)) throw new Error("Config objects cannot contain methods.");
55
- if (!_babel_types.isExpression(property.value)) throw new Error("Unsupported config object value.");
56
- result[key] = evaluateStaticConfigExpression(property.value);
57
- }
58
- return result;
59
- }
60
- throw new Error(`Unsupported config expression: ${unwrapped.type}`);
61
- }
62
- /**
63
- * Like {@link parseHexclaveConfigFileContent}, but returns `null` instead of
64
- * throwing when the file is not a plain static config (e.g. it wraps the config
65
- * in a helper call, references imported values, or has a syntax error). Useful
66
- * for deciding whether a config file can be safely regenerated deterministically
67
- * or whether it has custom structure that must be preserved.
68
- */
69
- function tryParseHexclaveConfigFileContent(content, filePath) {
70
- try {
71
- return parseHexclaveConfigFileContent(content, filePath);
72
- } catch {
73
- return null;
74
- }
75
- }
76
- /**
77
- * Returns whether `content` parses as a module that exports a `config` binding.
78
- * Used as a lightweight structural sanity check after editing config files whose
79
- * values can't be evaluated by our loader (e.g. they import external text
80
- * files), where a full semantic comparison isn't possible.
81
- */
82
- function hexclaveConfigFileExportsConfig(content, filePath) {
83
- let ast;
84
- try {
85
- ast = _babel_parser.parse(content, {
86
- sourceType: "module",
87
- sourceFilename: filePath,
88
- plugins: ["typescript", "importAttributes"]
89
- });
90
- } catch {
91
- return false;
92
- }
93
- for (const statement of ast.program.body) {
94
- if (!_babel_types.isExportNamedDeclaration(statement)) continue;
95
- if (statement.exportKind === "type") continue;
96
- if (_babel_types.isVariableDeclaration(statement.declaration)) {
97
- for (const declaration of statement.declaration.declarations) if (_babel_types.isIdentifier(declaration.id) && declaration.id.name === "config" && declaration.init != null) return true;
98
- }
99
- for (const specifier of statement.specifiers) if (_babel_types.isExportSpecifier(specifier) && specifier.exportKind !== "type") {
100
- if ((_babel_types.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value) === "config") return true;
101
- }
102
- }
103
- return false;
104
- }
105
- /**
106
- * Returns the relative import sources (those starting with `./` or `../`)
107
- * declared in `content`. Used to discover the external files a config update may
108
- * touch — e.g. `import x from "./welcome-email.tsx" with { type: "text" }` — so
109
- * they can be snapshotted and rolled back if an in-place update fails. Returns
110
- * an empty array if the file can't be parsed.
111
- */
112
- function getRelativeImportSpecifiers(content) {
113
- let ast;
114
- try {
115
- ast = _babel_parser.parse(content, {
116
- sourceType: "module",
117
- plugins: ["typescript", "importAttributes"]
118
- });
119
- } catch {
120
- return [];
121
- }
122
- const sources = [];
123
- for (const statement of ast.program.body) if (_babel_types.isImportDeclaration(statement)) {
124
- const source = statement.source.value;
125
- if (source.startsWith("./") || source.startsWith("../")) sources.push(source);
126
- }
127
- return sources;
128
- }
129
- function parseHexclaveConfigFileContent(content, filePath) {
130
- if (content.trim() === "") return {};
131
- const ast = _babel_parser.parse(content, {
132
- sourceType: "module",
133
- plugins: ["typescript", "importAttributes"]
134
- });
135
- for (const statement of ast.program.body) {
136
- if (!_babel_types.isExportNamedDeclaration(statement) || !_babel_types.isVariableDeclaration(statement.declaration)) continue;
137
- for (const declaration of statement.declaration.declarations) {
138
- if (!_babel_types.isIdentifier(declaration.id) || declaration.id.name !== "config") continue;
139
- if (declaration.init == null || !_babel_types.isExpression(declaration.init)) throw new Error(`Config export in ${filePath} must have an initializer.`);
140
- return evaluateStaticConfigExpression(declaration.init);
141
- }
142
- }
143
- throw new Error(`Invalid config in ${filePath}. The file must export a plain \`config\` object or "show-onboarding".`);
144
- }
145
-
146
- //#endregion
147
- exports.getRelativeImportSpecifiers = getRelativeImportSpecifiers;
148
- exports.hexclaveConfigFileExportsConfig = hexclaveConfigFileExportsConfig;
149
- exports.parseHexclaveConfigFileContent = parseHexclaveConfigFileContent;
150
- exports.renderConfigFileContent = renderConfigFileContent;
151
- exports.showOnboardingHexclaveConfigValue = showOnboardingHexclaveConfigValue;
152
- exports.tryParseHexclaveConfigFileContent = tryParseHexclaveConfigFileContent;
153
- //# sourceMappingURL=hexclave-config-file.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hexclave-config-file.js","names":["t","parser"],"sources":["../src/hexclave-config-file.ts"],"sourcesContent":["import * as parser from \"@babel/parser\";\nimport * as t from \"@babel/types\";\nimport { isValidConfig, normalize } from \"./config/format\";\n\nexport const showOnboardingHexclaveConfigValue = \"show-onboarding\";\n\nconst DEFAULT_CONFIG_IMPORT_PACKAGE = \"@hexclave/js\";\n\n/**\n * Renders a config object into the source text of a `stack.config.ts` file.\n *\n * Browser-safe: kept here (next to `parseHexclaveConfigFileContent`) instead of in\n * `config-rendering.ts` so dashboard client code can render config files\n * without pulling in `fs` / `path`.\n */\nexport function renderConfigFileContent(config: unknown, importPackage?: string): string {\n if (!isValidConfig(config)) {\n throw new Error(\"Invalid config: expected a plain object.\");\n }\n\n const droppedKeys: string[] = [];\n const normalizedConfig = normalize(config, {\n onDotIntoNonObject: \"ignore\",\n onDotIntoNull: \"empty-object\",\n droppedKeys,\n });\n if (droppedKeys.length > 0) {\n throw new Error(`Config has conflicting keys that would be dropped during normalization: ${droppedKeys.map(k => JSON.stringify(k)).join(\", \")}`);\n }\n const pkg = importPackage ?? DEFAULT_CONFIG_IMPORT_PACKAGE;\n // Import the `HexclaveConfig` type from the package's lightweight `/config`\n // entrypoint, which is free of framework runtime code and therefore safe for\n // tooling (e.g. the local dashboard) to load in a plain Node context. Only the\n // Hexclave-branded packages expose this subpath; legacy `@stackframe/*`\n // releases predate it, so fall back to their package root.\n const importSpecifier = pkg.startsWith(\"@hexclave/\") ? `${pkg}/config` : pkg;\n const importLine = `import type { HexclaveConfig } from \"${importSpecifier}\";`;\n return `${importLine}\\n\\nexport const config: HexclaveConfig = ${JSON.stringify(normalizedConfig, null, 2)};\\n`;\n}\n\ntype ParsedStackConfig = Record<string, unknown> | typeof showOnboardingHexclaveConfigValue;\n\nfunction unwrapStaticConfigExpression(expression: t.Expression): t.Expression {\n if (\n t.isTSAsExpression(expression)\n || t.isTSSatisfiesExpression(expression)\n || t.isTSTypeAssertion(expression)\n || t.isTSNonNullExpression(expression)\n ) {\n return unwrapStaticConfigExpression(expression.expression);\n }\n return expression;\n}\n\nfunction evaluateStaticConfigExpression(expression: t.Expression): unknown {\n const unwrapped = unwrapStaticConfigExpression(expression);\n if (t.isStringLiteral(unwrapped)) return unwrapped.value;\n if (t.isBooleanLiteral(unwrapped)) return unwrapped.value;\n if (t.isNumericLiteral(unwrapped)) return unwrapped.value;\n if (t.isNullLiteral(unwrapped)) return null;\n if (t.isIdentifier(unwrapped) && unwrapped.name === \"undefined\") return undefined;\n if (t.isUnaryExpression(unwrapped) && unwrapped.operator === \"-\" && t.isNumericLiteral(unwrapped.argument)) {\n return -unwrapped.argument.value;\n }\n if (t.isArrayExpression(unwrapped)) {\n return unwrapped.elements.map((element) => {\n if (element == null || t.isSpreadElement(element)) {\n throw new Error(\"Config arrays cannot contain holes or spreads.\");\n }\n return evaluateStaticConfigExpression(element);\n });\n }\n if (t.isObjectExpression(unwrapped)) {\n const result: Record<string, unknown> = {};\n for (const property of unwrapped.properties) {\n if (t.isSpreadElement(property)) {\n throw new Error(\"Config objects cannot contain spreads.\");\n }\n if (property.computed) {\n throw new Error(\"Config object keys cannot be computed.\");\n }\n const key = t.isIdentifier(property.key)\n ? property.key.name\n : t.isStringLiteral(property.key) || t.isNumericLiteral(property.key)\n ? String(property.key.value)\n : null;\n if (key == null) {\n throw new Error(\"Unsupported config object key.\");\n }\n if (t.isObjectMethod(property)) {\n throw new Error(\"Config objects cannot contain methods.\");\n }\n if (!t.isExpression(property.value)) {\n throw new Error(\"Unsupported config object value.\");\n }\n result[key] = evaluateStaticConfigExpression(property.value);\n }\n return result;\n }\n throw new Error(`Unsupported config expression: ${unwrapped.type}`);\n}\n\n/**\n * Like {@link parseHexclaveConfigFileContent}, but returns `null` instead of\n * throwing when the file is not a plain static config (e.g. it wraps the config\n * in a helper call, references imported values, or has a syntax error). Useful\n * for deciding whether a config file can be safely regenerated deterministically\n * or whether it has custom structure that must be preserved.\n */\nexport function tryParseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig | null {\n try {\n return parseHexclaveConfigFileContent(content, filePath);\n } catch {\n return null;\n }\n}\n\n/**\n * Returns whether `content` parses as a module that exports a `config` binding.\n * Used as a lightweight structural sanity check after editing config files whose\n * values can't be evaluated by our loader (e.g. they import external text\n * files), where a full semantic comparison isn't possible.\n */\nexport function hexclaveConfigFileExportsConfig(content: string, filePath: string): boolean {\n let ast: parser.ParseResult<t.File>;\n try {\n ast = parser.parse(content, {\n sourceType: \"module\",\n sourceFilename: filePath,\n plugins: [\"typescript\", \"importAttributes\"],\n });\n } catch {\n return false;\n }\n for (const statement of ast.program.body) {\n if (!t.isExportNamedDeclaration(statement)) {\n continue;\n }\n // Ignore type-only exports (`export type { config }`): they don't produce a\n // runtime `config` value, so they must not satisfy the structural check.\n if (statement.exportKind === \"type\") {\n continue;\n }\n if (t.isVariableDeclaration(statement.declaration)) {\n for (const declaration of statement.declaration.declarations) {\n if (t.isIdentifier(declaration.id) && declaration.id.name === \"config\" && declaration.init != null) {\n return true;\n }\n }\n }\n for (const specifier of statement.specifiers) {\n if (t.isExportSpecifier(specifier) && specifier.exportKind !== \"type\") {\n const exportedName = t.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value;\n if (exportedName === \"config\") {\n return true;\n }\n }\n }\n }\n return false;\n}\n\n/**\n * Returns the relative import sources (those starting with `./` or `../`)\n * declared in `content`. Used to discover the external files a config update may\n * touch — e.g. `import x from \"./welcome-email.tsx\" with { type: \"text\" }` — so\n * they can be snapshotted and rolled back if an in-place update fails. Returns\n * an empty array if the file can't be parsed.\n */\nexport function getRelativeImportSpecifiers(content: string): string[] {\n let ast: parser.ParseResult<t.File>;\n try {\n ast = parser.parse(content, {\n sourceType: \"module\",\n plugins: [\"typescript\", \"importAttributes\"],\n });\n } catch {\n return [];\n }\n const sources: string[] = [];\n for (const statement of ast.program.body) {\n if (t.isImportDeclaration(statement)) {\n const source = statement.source.value;\n if (source.startsWith(\"./\") || source.startsWith(\"../\")) {\n sources.push(source);\n }\n }\n }\n return sources;\n}\n\nexport function parseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig {\n if (content.trim() === \"\") return {};\n const ast = parser.parse(content, {\n sourceType: \"module\",\n plugins: [\"typescript\", \"importAttributes\"],\n });\n\n for (const statement of ast.program.body) {\n if (!t.isExportNamedDeclaration(statement) || !t.isVariableDeclaration(statement.declaration)) {\n continue;\n }\n for (const declaration of statement.declaration.declarations) {\n if (!t.isIdentifier(declaration.id) || declaration.id.name !== \"config\") {\n continue;\n }\n if (declaration.init == null || !t.isExpression(declaration.init)) {\n throw new Error(`Config export in ${filePath} must have an initializer.`);\n }\n return evaluateStaticConfigExpression(declaration.init) as ParsedStackConfig;\n }\n }\n\n throw new Error(`Invalid config in ${filePath}. The file must export a plain \\`config\\` object or \"show-onboarding\".`);\n}\n"],"mappings":";;;;;;;;;AAIA,MAAa,oCAAoC;AAEjD,MAAM,gCAAgC;;;;;;;;AAStC,SAAgB,wBAAwB,QAAiB,eAAgC;AACvF,KAAI,uCAAe,OAAO,CACxB,OAAM,IAAI,MAAM,2CAA2C;CAG7D,MAAM,cAAwB,EAAE;CAChC,MAAM,qDAA6B,QAAQ;EACzC,oBAAoB;EACpB,eAAe;EACf;EACD,CAAC;AACF,KAAI,YAAY,SAAS,EACvB,OAAM,IAAI,MAAM,2EAA2E,YAAY,KAAI,MAAK,KAAK,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,GAAG;CAElJ,MAAM,MAAM,iBAAiB;AAQ7B,QAAO,GADY,wCADK,IAAI,WAAW,aAAa,GAAG,GAAG,IAAI,WAAW,IACE,IACtD,4CAA4C,KAAK,UAAU,kBAAkB,MAAM,EAAE,CAAC;;AAK7G,SAAS,6BAA6B,YAAwC;AAC5E,KACEA,aAAE,iBAAiB,WAAW,IAC3BA,aAAE,wBAAwB,WAAW,IACrCA,aAAE,kBAAkB,WAAW,IAC/BA,aAAE,sBAAsB,WAAW,CAEtC,QAAO,6BAA6B,WAAW,WAAW;AAE5D,QAAO;;AAGT,SAAS,+BAA+B,YAAmC;CACzE,MAAM,YAAY,6BAA6B,WAAW;AAC1D,KAAIA,aAAE,gBAAgB,UAAU,CAAE,QAAO,UAAU;AACnD,KAAIA,aAAE,iBAAiB,UAAU,CAAE,QAAO,UAAU;AACpD,KAAIA,aAAE,iBAAiB,UAAU,CAAE,QAAO,UAAU;AACpD,KAAIA,aAAE,cAAc,UAAU,CAAE,QAAO;AACvC,KAAIA,aAAE,aAAa,UAAU,IAAI,UAAU,SAAS,YAAa,QAAO;AACxE,KAAIA,aAAE,kBAAkB,UAAU,IAAI,UAAU,aAAa,OAAOA,aAAE,iBAAiB,UAAU,SAAS,CACxG,QAAO,CAAC,UAAU,SAAS;AAE7B,KAAIA,aAAE,kBAAkB,UAAU,CAChC,QAAO,UAAU,SAAS,KAAK,YAAY;AACzC,MAAI,WAAW,QAAQA,aAAE,gBAAgB,QAAQ,CAC/C,OAAM,IAAI,MAAM,iDAAiD;AAEnE,SAAO,+BAA+B,QAAQ;GAC9C;AAEJ,KAAIA,aAAE,mBAAmB,UAAU,EAAE;EACnC,MAAM,SAAkC,EAAE;AAC1C,OAAK,MAAM,YAAY,UAAU,YAAY;AAC3C,OAAIA,aAAE,gBAAgB,SAAS,CAC7B,OAAM,IAAI,MAAM,yCAAyC;AAE3D,OAAI,SAAS,SACX,OAAM,IAAI,MAAM,yCAAyC;GAE3D,MAAM,MAAMA,aAAE,aAAa,SAAS,IAAI,GACpC,SAAS,IAAI,OACbA,aAAE,gBAAgB,SAAS,IAAI,IAAIA,aAAE,iBAAiB,SAAS,IAAI,GACjE,OAAO,SAAS,IAAI,MAAM,GAC1B;AACN,OAAI,OAAO,KACT,OAAM,IAAI,MAAM,iCAAiC;AAEnD,OAAIA,aAAE,eAAe,SAAS,CAC5B,OAAM,IAAI,MAAM,yCAAyC;AAE3D,OAAI,CAACA,aAAE,aAAa,SAAS,MAAM,CACjC,OAAM,IAAI,MAAM,mCAAmC;AAErD,UAAO,OAAO,+BAA+B,SAAS,MAAM;;AAE9D,SAAO;;AAET,OAAM,IAAI,MAAM,kCAAkC,UAAU,OAAO;;;;;;;;;AAUrE,SAAgB,kCAAkC,SAAiB,UAA4C;AAC7G,KAAI;AACF,SAAO,+BAA+B,SAAS,SAAS;SAClD;AACN,SAAO;;;;;;;;;AAUX,SAAgB,gCAAgC,SAAiB,UAA2B;CAC1F,IAAI;AACJ,KAAI;AACF,QAAMC,cAAO,MAAM,SAAS;GAC1B,YAAY;GACZ,gBAAgB;GAChB,SAAS,CAAC,cAAc,mBAAmB;GAC5C,CAAC;SACI;AACN,SAAO;;AAET,MAAK,MAAM,aAAa,IAAI,QAAQ,MAAM;AACxC,MAAI,CAACD,aAAE,yBAAyB,UAAU,CACxC;AAIF,MAAI,UAAU,eAAe,OAC3B;AAEF,MAAIA,aAAE,sBAAsB,UAAU,YAAY,EAChD;QAAK,MAAM,eAAe,UAAU,YAAY,aAC9C,KAAIA,aAAE,aAAa,YAAY,GAAG,IAAI,YAAY,GAAG,SAAS,YAAY,YAAY,QAAQ,KAC5F,QAAO;;AAIb,OAAK,MAAM,aAAa,UAAU,WAChC,KAAIA,aAAE,kBAAkB,UAAU,IAAI,UAAU,eAAe,QAE7D;QADqBA,aAAE,aAAa,UAAU,SAAS,GAAG,UAAU,SAAS,OAAO,UAAU,SAAS,WAClF,SACnB,QAAO;;;AAKf,QAAO;;;;;;;;;AAUT,SAAgB,4BAA4B,SAA2B;CACrE,IAAI;AACJ,KAAI;AACF,QAAMC,cAAO,MAAM,SAAS;GAC1B,YAAY;GACZ,SAAS,CAAC,cAAc,mBAAmB;GAC5C,CAAC;SACI;AACN,SAAO,EAAE;;CAEX,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,aAAa,IAAI,QAAQ,KAClC,KAAID,aAAE,oBAAoB,UAAU,EAAE;EACpC,MAAM,SAAS,UAAU,OAAO;AAChC,MAAI,OAAO,WAAW,KAAK,IAAI,OAAO,WAAW,MAAM,CACrD,SAAQ,KAAK,OAAO;;AAI1B,QAAO;;AAGT,SAAgB,+BAA+B,SAAiB,UAAqC;AACnG,KAAI,QAAQ,MAAM,KAAK,GAAI,QAAO,EAAE;CACpC,MAAM,MAAMC,cAAO,MAAM,SAAS;EAChC,YAAY;EACZ,SAAS,CAAC,cAAc,mBAAmB;EAC5C,CAAC;AAEF,MAAK,MAAM,aAAa,IAAI,QAAQ,MAAM;AACxC,MAAI,CAACD,aAAE,yBAAyB,UAAU,IAAI,CAACA,aAAE,sBAAsB,UAAU,YAAY,CAC3F;AAEF,OAAK,MAAM,eAAe,UAAU,YAAY,cAAc;AAC5D,OAAI,CAACA,aAAE,aAAa,YAAY,GAAG,IAAI,YAAY,GAAG,SAAS,SAC7D;AAEF,OAAI,YAAY,QAAQ,QAAQ,CAACA,aAAE,aAAa,YAAY,KAAK,CAC/D,OAAM,IAAI,MAAM,oBAAoB,SAAS,4BAA4B;AAE3E,UAAO,+BAA+B,YAAY,KAAK;;;AAI3D,OAAM,IAAI,MAAM,qBAAqB,SAAS,wEAAwE"}
@@ -1,215 +0,0 @@
1
- import * as parser from "@babel/parser";
2
- import * as t from "@babel/types";
3
- import { isValidConfig, normalize } from "./config/format";
4
-
5
- export const showOnboardingHexclaveConfigValue = "show-onboarding";
6
-
7
- const DEFAULT_CONFIG_IMPORT_PACKAGE = "@hexclave/js";
8
-
9
- /**
10
- * Renders a config object into the source text of a `stack.config.ts` file.
11
- *
12
- * Browser-safe: kept here (next to `parseHexclaveConfigFileContent`) instead of in
13
- * `config-rendering.ts` so dashboard client code can render config files
14
- * without pulling in `fs` / `path`.
15
- */
16
- export function renderConfigFileContent(config: unknown, importPackage?: string): string {
17
- if (!isValidConfig(config)) {
18
- throw new Error("Invalid config: expected a plain object.");
19
- }
20
-
21
- const droppedKeys: string[] = [];
22
- const normalizedConfig = normalize(config, {
23
- onDotIntoNonObject: "ignore",
24
- onDotIntoNull: "empty-object",
25
- droppedKeys,
26
- });
27
- if (droppedKeys.length > 0) {
28
- throw new Error(`Config has conflicting keys that would be dropped during normalization: ${droppedKeys.map(k => JSON.stringify(k)).join(", ")}`);
29
- }
30
- const pkg = importPackage ?? DEFAULT_CONFIG_IMPORT_PACKAGE;
31
- // Import the `HexclaveConfig` type from the package's lightweight `/config`
32
- // entrypoint, which is free of framework runtime code and therefore safe for
33
- // tooling (e.g. the local dashboard) to load in a plain Node context. Only the
34
- // Hexclave-branded packages expose this subpath; legacy `@stackframe/*`
35
- // releases predate it, so fall back to their package root.
36
- const importSpecifier = pkg.startsWith("@hexclave/") ? `${pkg}/config` : pkg;
37
- const importLine = `import type { HexclaveConfig } from "${importSpecifier}";`;
38
- return `${importLine}\n\nexport const config: HexclaveConfig = ${JSON.stringify(normalizedConfig, null, 2)};\n`;
39
- }
40
-
41
- type ParsedStackConfig = Record<string, unknown> | typeof showOnboardingHexclaveConfigValue;
42
-
43
- function unwrapStaticConfigExpression(expression: t.Expression): t.Expression {
44
- if (
45
- t.isTSAsExpression(expression)
46
- || t.isTSSatisfiesExpression(expression)
47
- || t.isTSTypeAssertion(expression)
48
- || t.isTSNonNullExpression(expression)
49
- ) {
50
- return unwrapStaticConfigExpression(expression.expression);
51
- }
52
- return expression;
53
- }
54
-
55
- function evaluateStaticConfigExpression(expression: t.Expression): unknown {
56
- const unwrapped = unwrapStaticConfigExpression(expression);
57
- if (t.isStringLiteral(unwrapped)) return unwrapped.value;
58
- if (t.isBooleanLiteral(unwrapped)) return unwrapped.value;
59
- if (t.isNumericLiteral(unwrapped)) return unwrapped.value;
60
- if (t.isNullLiteral(unwrapped)) return null;
61
- if (t.isIdentifier(unwrapped) && unwrapped.name === "undefined") return undefined;
62
- if (t.isUnaryExpression(unwrapped) && unwrapped.operator === "-" && t.isNumericLiteral(unwrapped.argument)) {
63
- return -unwrapped.argument.value;
64
- }
65
- if (t.isArrayExpression(unwrapped)) {
66
- return unwrapped.elements.map((element) => {
67
- if (element == null || t.isSpreadElement(element)) {
68
- throw new Error("Config arrays cannot contain holes or spreads.");
69
- }
70
- return evaluateStaticConfigExpression(element);
71
- });
72
- }
73
- if (t.isObjectExpression(unwrapped)) {
74
- const result: Record<string, unknown> = {};
75
- for (const property of unwrapped.properties) {
76
- if (t.isSpreadElement(property)) {
77
- throw new Error("Config objects cannot contain spreads.");
78
- }
79
- if (property.computed) {
80
- throw new Error("Config object keys cannot be computed.");
81
- }
82
- const key = t.isIdentifier(property.key)
83
- ? property.key.name
84
- : t.isStringLiteral(property.key) || t.isNumericLiteral(property.key)
85
- ? String(property.key.value)
86
- : null;
87
- if (key == null) {
88
- throw new Error("Unsupported config object key.");
89
- }
90
- if (t.isObjectMethod(property)) {
91
- throw new Error("Config objects cannot contain methods.");
92
- }
93
- if (!t.isExpression(property.value)) {
94
- throw new Error("Unsupported config object value.");
95
- }
96
- result[key] = evaluateStaticConfigExpression(property.value);
97
- }
98
- return result;
99
- }
100
- throw new Error(`Unsupported config expression: ${unwrapped.type}`);
101
- }
102
-
103
- /**
104
- * Like {@link parseHexclaveConfigFileContent}, but returns `null` instead of
105
- * throwing when the file is not a plain static config (e.g. it wraps the config
106
- * in a helper call, references imported values, or has a syntax error). Useful
107
- * for deciding whether a config file can be safely regenerated deterministically
108
- * or whether it has custom structure that must be preserved.
109
- */
110
- export function tryParseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig | null {
111
- try {
112
- return parseHexclaveConfigFileContent(content, filePath);
113
- } catch {
114
- return null;
115
- }
116
- }
117
-
118
- /**
119
- * Returns whether `content` parses as a module that exports a `config` binding.
120
- * Used as a lightweight structural sanity check after editing config files whose
121
- * values can't be evaluated by our loader (e.g. they import external text
122
- * files), where a full semantic comparison isn't possible.
123
- */
124
- export function hexclaveConfigFileExportsConfig(content: string, filePath: string): boolean {
125
- let ast: parser.ParseResult<t.File>;
126
- try {
127
- ast = parser.parse(content, {
128
- sourceType: "module",
129
- sourceFilename: filePath,
130
- plugins: ["typescript", "importAttributes"],
131
- });
132
- } catch {
133
- return false;
134
- }
135
- for (const statement of ast.program.body) {
136
- if (!t.isExportNamedDeclaration(statement)) {
137
- continue;
138
- }
139
- // Ignore type-only exports (`export type { config }`): they don't produce a
140
- // runtime `config` value, so they must not satisfy the structural check.
141
- if (statement.exportKind === "type") {
142
- continue;
143
- }
144
- if (t.isVariableDeclaration(statement.declaration)) {
145
- for (const declaration of statement.declaration.declarations) {
146
- if (t.isIdentifier(declaration.id) && declaration.id.name === "config" && declaration.init != null) {
147
- return true;
148
- }
149
- }
150
- }
151
- for (const specifier of statement.specifiers) {
152
- if (t.isExportSpecifier(specifier) && specifier.exportKind !== "type") {
153
- const exportedName = t.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value;
154
- if (exportedName === "config") {
155
- return true;
156
- }
157
- }
158
- }
159
- }
160
- return false;
161
- }
162
-
163
- /**
164
- * Returns the relative import sources (those starting with `./` or `../`)
165
- * declared in `content`. Used to discover the external files a config update may
166
- * touch — e.g. `import x from "./welcome-email.tsx" with { type: "text" }` — so
167
- * they can be snapshotted and rolled back if an in-place update fails. Returns
168
- * an empty array if the file can't be parsed.
169
- */
170
- export function getRelativeImportSpecifiers(content: string): string[] {
171
- let ast: parser.ParseResult<t.File>;
172
- try {
173
- ast = parser.parse(content, {
174
- sourceType: "module",
175
- plugins: ["typescript", "importAttributes"],
176
- });
177
- } catch {
178
- return [];
179
- }
180
- const sources: string[] = [];
181
- for (const statement of ast.program.body) {
182
- if (t.isImportDeclaration(statement)) {
183
- const source = statement.source.value;
184
- if (source.startsWith("./") || source.startsWith("../")) {
185
- sources.push(source);
186
- }
187
- }
188
- }
189
- return sources;
190
- }
191
-
192
- export function parseHexclaveConfigFileContent(content: string, filePath: string): ParsedStackConfig {
193
- if (content.trim() === "") return {};
194
- const ast = parser.parse(content, {
195
- sourceType: "module",
196
- plugins: ["typescript", "importAttributes"],
197
- });
198
-
199
- for (const statement of ast.program.body) {
200
- if (!t.isExportNamedDeclaration(statement) || !t.isVariableDeclaration(statement.declaration)) {
201
- continue;
202
- }
203
- for (const declaration of statement.declaration.declarations) {
204
- if (!t.isIdentifier(declaration.id) || declaration.id.name !== "config") {
205
- continue;
206
- }
207
- if (declaration.init == null || !t.isExpression(declaration.init)) {
208
- throw new Error(`Config export in ${filePath} must have an initializer.`);
209
- }
210
- return evaluateStaticConfigExpression(declaration.init) as ParsedStackConfig;
211
- }
212
- }
213
-
214
- throw new Error(`Invalid config in ${filePath}. The file must export a plain \`config\` object or "show-onboarding".`);
215
- }