@anvil-works/anvil-cli 0.5.14 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -33721,6 +33721,7 @@ var __webpack_exports__ = {};
33721
33721
  var commander = __webpack_require__("./node_modules/.pnpm/commander@14.0.2/node_modules/commander/index.js");
33722
33722
  const { program: esm_program, createCommand, createArgument, createOption, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Command, Argument, Option, Help } = commander;
33723
33723
  var external_fs_ = __webpack_require__("fs");
33724
+ var external_fs_default = /*#__PURE__*/ __webpack_require__.n(external_fs_);
33724
33725
  const external_path_namespaceObject = require("path");
33725
33726
  var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
33726
33727
  var semver = __webpack_require__("./node_modules/.pnpm/semver@7.7.3/node_modules/semver/index.js");
@@ -41840,18 +41841,27 @@ var __webpack_exports__ = {};
41840
41841
  if (list.length > 1) return `(${list.join(` ${separator} `)})`;
41841
41842
  return list[0] ?? "never";
41842
41843
  }
41843
- function minValue(requirement, message$1) {
41844
+ function getDotPath(issue) {
41845
+ if (issue.path) {
41846
+ let key = "";
41847
+ for (const item of issue.path)if ("string" != typeof item.key && "number" != typeof item.key) return null;
41848
+ else if (key) key += `.${item.key}`;
41849
+ else key += item.key;
41850
+ return key;
41851
+ }
41852
+ return null;
41853
+ }
41854
+ function dist_nonEmpty(message$1) {
41844
41855
  return {
41845
41856
  kind: "validation",
41846
- type: "min_value",
41847
- reference: minValue,
41857
+ type: "non_empty",
41858
+ reference: dist_nonEmpty,
41848
41859
  async: false,
41849
- expects: `>=${requirement instanceof Date ? requirement.toJSON() : /* @__PURE__ */ _stringify(requirement)}`,
41850
- requirement,
41860
+ expects: "!0",
41851
41861
  message: message$1,
41852
41862
  "~run" (dataset, config$1) {
41853
- if (dataset.typed && !(dataset.value >= this.requirement)) _addIssue(this, "value", dataset, config$1, {
41854
- received: dataset.value instanceof Date ? dataset.value.toJSON() : /* @__PURE__ */ _stringify(dataset.value)
41863
+ if (dataset.typed && 0 === dataset.value.length) _addIssue(this, "length", dataset, config$1, {
41864
+ received: "0"
41855
41865
  });
41856
41866
  return dataset;
41857
41867
  }
@@ -41961,53 +41971,30 @@ var __webpack_exports__ = {};
41961
41971
  }
41962
41972
  };
41963
41973
  }
41964
- function nullable(wrapped, default_) {
41965
- return {
41966
- kind: "schema",
41967
- type: "nullable",
41968
- reference: nullable,
41969
- expects: `(${wrapped.expects} | null)`,
41970
- async: false,
41971
- wrapped,
41972
- default: default_,
41973
- get "~standard" () {
41974
- return /* @__PURE__ */ _getStandardProps(this);
41975
- },
41976
- "~run" (dataset, config$1) {
41977
- if (null === dataset.value) {
41978
- if (void 0 !== this.default) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config$1);
41979
- if (null === dataset.value) {
41980
- dataset.typed = true;
41981
- return dataset;
41982
- }
41983
- }
41984
- return this.wrapped["~run"](dataset, config$1);
41985
- }
41986
- };
41987
- }
41988
- function dist_number(message$1) {
41974
+ function custom(check$1, message$1) {
41989
41975
  return {
41990
41976
  kind: "schema",
41991
- type: "number",
41992
- reference: dist_number,
41993
- expects: "number",
41977
+ type: "custom",
41978
+ reference: custom,
41979
+ expects: "unknown",
41994
41980
  async: false,
41981
+ check: check$1,
41995
41982
  message: message$1,
41996
41983
  get "~standard" () {
41997
41984
  return /* @__PURE__ */ _getStandardProps(this);
41998
41985
  },
41999
41986
  "~run" (dataset, config$1) {
42000
- if ("number" != typeof dataset.value || isNaN(dataset.value)) _addIssue(this, "type", dataset, config$1);
42001
- else dataset.typed = true;
41987
+ if (this.check(dataset.value)) dataset.typed = true;
41988
+ else _addIssue(this, "type", dataset, config$1);
42002
41989
  return dataset;
42003
41990
  }
42004
41991
  };
42005
41992
  }
42006
- function dist_object(entries$1, message$1) {
41993
+ function looseObject(entries$1, message$1) {
42007
41994
  return {
42008
41995
  kind: "schema",
42009
- type: "object",
42010
- reference: dist_object,
41996
+ type: "loose_object",
41997
+ reference: looseObject,
42011
41998
  expects: "Object",
42012
41999
  async: false,
42013
42000
  entries: entries$1,
@@ -42068,17 +42055,20 @@ var __webpack_exports__ = {};
42068
42055
  if (config$1.abortEarly) break;
42069
42056
  }
42070
42057
  }
42058
+ if (!dataset.issues || !config$1.abortEarly) {
42059
+ for(const key in input)if (/* @__PURE__ */ _isValidObjectKey(input, key) && !(key in this.entries)) dataset.value[key] = input[key];
42060
+ }
42071
42061
  } else _addIssue(this, "type", dataset, config$1);
42072
42062
  return dataset;
42073
42063
  }
42074
42064
  };
42075
42065
  }
42076
- function optional(wrapped, default_) {
42066
+ function nullable(wrapped, default_) {
42077
42067
  return {
42078
42068
  kind: "schema",
42079
- type: "optional",
42080
- reference: optional,
42081
- expects: `(${wrapped.expects} | undefined)`,
42069
+ type: "nullable",
42070
+ reference: nullable,
42071
+ expects: `(${wrapped.expects} | null)`,
42082
42072
  async: false,
42083
42073
  wrapped,
42084
42074
  default: default_,
@@ -42086,9 +42076,9 @@ var __webpack_exports__ = {};
42086
42076
  return /* @__PURE__ */ _getStandardProps(this);
42087
42077
  },
42088
42078
  "~run" (dataset, config$1) {
42089
- if (void 0 === dataset.value) {
42079
+ if (null === dataset.value) {
42090
42080
  if (void 0 !== this.default) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config$1);
42091
- if (void 0 === dataset.value) {
42081
+ if (null === dataset.value) {
42092
42082
  dataset.typed = true;
42093
42083
  return dataset;
42094
42084
  }
@@ -42097,151 +42087,81 @@ var __webpack_exports__ = {};
42097
42087
  }
42098
42088
  };
42099
42089
  }
42100
- function record(key, value$1, message$1) {
42090
+ function dist_number(message$1) {
42101
42091
  return {
42102
42092
  kind: "schema",
42103
- type: "record",
42104
- reference: record,
42105
- expects: "Object",
42093
+ type: "number",
42094
+ reference: dist_number,
42095
+ expects: "number",
42106
42096
  async: false,
42107
- key,
42108
- value: value$1,
42109
42097
  message: message$1,
42110
42098
  get "~standard" () {
42111
42099
  return /* @__PURE__ */ _getStandardProps(this);
42112
42100
  },
42113
42101
  "~run" (dataset, config$1) {
42114
- const input = dataset.value;
42115
- if (input && "object" == typeof input) {
42116
- dataset.typed = true;
42117
- dataset.value = {};
42118
- for(const entryKey in input)if (/* @__PURE__ */ _isValidObjectKey(input, entryKey)) {
42119
- const entryValue = input[entryKey];
42120
- const keyDataset = this.key["~run"]({
42121
- value: entryKey
42122
- }, config$1);
42123
- if (keyDataset.issues) {
42124
- const pathItem = {
42125
- type: "object",
42126
- origin: "key",
42127
- input,
42128
- key: entryKey,
42129
- value: entryValue
42130
- };
42131
- for (const issue of keyDataset.issues){
42132
- issue.path = [
42133
- pathItem
42134
- ];
42135
- dataset.issues?.push(issue);
42136
- }
42137
- if (!dataset.issues) dataset.issues = keyDataset.issues;
42138
- if (config$1.abortEarly) {
42139
- dataset.typed = false;
42140
- break;
42141
- }
42142
- }
42143
- const valueDataset = this.value["~run"]({
42144
- value: entryValue
42145
- }, config$1);
42146
- if (valueDataset.issues) {
42147
- const pathItem = {
42148
- type: "object",
42149
- origin: "value",
42150
- input,
42151
- key: entryKey,
42152
- value: entryValue
42153
- };
42154
- for (const issue of valueDataset.issues){
42155
- if (issue.path) issue.path.unshift(pathItem);
42156
- else issue.path = [
42157
- pathItem
42158
- ];
42159
- dataset.issues?.push(issue);
42160
- }
42161
- if (!dataset.issues) dataset.issues = valueDataset.issues;
42162
- if (config$1.abortEarly) {
42163
- dataset.typed = false;
42164
- break;
42165
- }
42166
- }
42167
- if (!keyDataset.typed || !valueDataset.typed) dataset.typed = false;
42168
- if (keyDataset.typed) dataset.value[keyDataset.value] = valueDataset.value;
42169
- }
42170
- } else _addIssue(this, "type", dataset, config$1);
42102
+ if ("number" != typeof dataset.value || isNaN(dataset.value)) _addIssue(this, "type", dataset, config$1);
42103
+ else dataset.typed = true;
42171
42104
  return dataset;
42172
42105
  }
42173
42106
  };
42174
42107
  }
42175
- function dist_string(message$1) {
42108
+ function optional(wrapped, default_) {
42176
42109
  return {
42177
42110
  kind: "schema",
42178
- type: "string",
42179
- reference: dist_string,
42180
- expects: "string",
42111
+ type: "optional",
42112
+ reference: optional,
42113
+ expects: `(${wrapped.expects} | undefined)`,
42114
+ async: false,
42115
+ wrapped,
42116
+ default: default_,
42117
+ get "~standard" () {
42118
+ return /* @__PURE__ */ _getStandardProps(this);
42119
+ },
42120
+ "~run" (dataset, config$1) {
42121
+ if (void 0 === dataset.value) {
42122
+ if (void 0 !== this.default) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config$1);
42123
+ if (void 0 === dataset.value) {
42124
+ dataset.typed = true;
42125
+ return dataset;
42126
+ }
42127
+ }
42128
+ return this.wrapped["~run"](dataset, config$1);
42129
+ }
42130
+ };
42131
+ }
42132
+ function picklist(options, message$1) {
42133
+ return {
42134
+ kind: "schema",
42135
+ type: "picklist",
42136
+ reference: picklist,
42137
+ expects: /* @__PURE__ */ _joinExpects(options.map(_stringify), "|"),
42181
42138
  async: false,
42139
+ options,
42182
42140
  message: message$1,
42183
42141
  get "~standard" () {
42184
42142
  return /* @__PURE__ */ _getStandardProps(this);
42185
42143
  },
42186
42144
  "~run" (dataset, config$1) {
42187
- if ("string" == typeof dataset.value) dataset.typed = true;
42145
+ if (this.options.includes(dataset.value)) dataset.typed = true;
42188
42146
  else _addIssue(this, "type", dataset, config$1);
42189
42147
  return dataset;
42190
42148
  }
42191
42149
  };
42192
42150
  }
42193
- function _subIssues(datasets) {
42194
- let issues;
42195
- if (datasets) for (const dataset of datasets)if (issues) issues.push(...dataset.issues);
42196
- else issues = dataset.issues;
42197
- return issues;
42198
- }
42199
- function union(options, message$1) {
42151
+ function dist_string(message$1) {
42200
42152
  return {
42201
42153
  kind: "schema",
42202
- type: "union",
42203
- reference: union,
42204
- expects: /* @__PURE__ */ _joinExpects(options.map((option)=>option.expects), "|"),
42154
+ type: "string",
42155
+ reference: dist_string,
42156
+ expects: "string",
42205
42157
  async: false,
42206
- options,
42207
42158
  message: message$1,
42208
42159
  get "~standard" () {
42209
42160
  return /* @__PURE__ */ _getStandardProps(this);
42210
42161
  },
42211
42162
  "~run" (dataset, config$1) {
42212
- let validDataset;
42213
- let typedDatasets;
42214
- let untypedDatasets;
42215
- for (const schema of this.options){
42216
- const optionDataset = schema["~run"]({
42217
- value: dataset.value
42218
- }, config$1);
42219
- if (optionDataset.typed) if (optionDataset.issues) if (typedDatasets) typedDatasets.push(optionDataset);
42220
- else typedDatasets = [
42221
- optionDataset
42222
- ];
42223
- else {
42224
- validDataset = optionDataset;
42225
- break;
42226
- }
42227
- else if (untypedDatasets) untypedDatasets.push(optionDataset);
42228
- else untypedDatasets = [
42229
- optionDataset
42230
- ];
42231
- }
42232
- if (validDataset) return validDataset;
42233
- if (typedDatasets) {
42234
- if (1 === typedDatasets.length) return typedDatasets[0];
42235
- _addIssue(this, "type", dataset, config$1, {
42236
- issues: /* @__PURE__ */ _subIssues(typedDatasets)
42237
- });
42238
- dataset.typed = true;
42239
- } else {
42240
- if (untypedDatasets?.length === 1) return untypedDatasets[0];
42241
- _addIssue(this, "type", dataset, config$1, {
42242
- issues: /* @__PURE__ */ _subIssues(untypedDatasets)
42243
- });
42244
- }
42163
+ if ("string" == typeof dataset.value) dataset.typed = true;
42164
+ else _addIssue(this, "type", dataset, config$1);
42245
42165
  return dataset;
42246
42166
  }
42247
42167
  };
@@ -42584,7 +42504,7 @@ var __webpack_exports__ = {};
42584
42504
  if (!preferredEditors.includes(normalized)) throw new Error(`✖ Error: preferredEditor must be one of: ${preferredEditors.join(", ")} (alias: code -> vscode)`);
42585
42505
  return normalized;
42586
42506
  }
42587
- if ("anvilUrl" === key) return value.trim().replace(/\/+$/, "");
42507
+ if ("anvilUrl" === key) return normalizeAnvilUrl(value);
42588
42508
  return coerceConfigValue(key, value);
42589
42509
  }
42590
42510
  function getPreferredEditorCommand(preferredEditorValue) {
@@ -48029,91 +47949,753 @@ var __webpack_exports__ = {};
48029
47949
  return filesToRemove;
48030
47950
  }
48031
47951
  var external_crypto_ = __webpack_require__("crypto");
48032
- const FormTemplateSchema = dist_object({
48033
- is_package: optional(dist_boolean()),
48034
- container: optional(dist_object({
48035
- type: dist_string(),
48036
- properties: optional(any())
48037
- })),
48038
- components: optional(dist_array(any())),
48039
- layout: optional(dist_object({
48040
- type: dist_string(),
48041
- properties: optional(any())
48042
- })),
48043
- components_by_slot: optional(record(dist_string(), dist_array(any()))),
48044
- custom_component: optional(dist_boolean()),
48045
- custom_component_container: optional(dist_boolean()),
48046
- properties: optional(dist_array(any())),
48047
- events: optional(dist_array(any())),
48048
- toolbox_item: optional(any()),
48049
- layout_metadata: optional(any()),
48050
- slots: optional(any()),
48051
- item_type: optional(dist_object({
48052
- table_id: dist_number()
48053
- }))
48054
- });
47952
+ const BUILTIN_COMPONENT_TYPES = new Set([
47953
+ "Label",
47954
+ "RichText",
47955
+ "Link",
47956
+ "Button",
47957
+ "TextBox",
47958
+ "TextArea",
47959
+ "CheckBox",
47960
+ "RadioButton",
47961
+ "DropDown",
47962
+ "DatePicker",
47963
+ "Timer",
47964
+ "Canvas",
47965
+ "Image",
47966
+ "YouTubeVideo",
47967
+ "GoogleMap",
47968
+ "Plot",
47969
+ "FileLoader",
47970
+ "Spacer",
47971
+ "GridPanel",
47972
+ "LinearPanel",
47973
+ "XYPanel",
47974
+ "ColumnPanel",
47975
+ "HtmlPanel",
47976
+ "RepeatingPanel",
47977
+ "DataGrid",
47978
+ "DataRowPanel",
47979
+ "CustomComponent",
47980
+ "LayoutSlot",
47981
+ "FlowPanel",
47982
+ "HtmlTemplate"
47983
+ ]);
47984
+ const KNOWN_RUNTIME_SERVICE_SOURCES = new Set([
47985
+ "/runtime/services/tables.yml",
47986
+ "/runtime/services/google.yml",
47987
+ "/runtime/services/uplink.yml",
47988
+ "/runtime/services/stripe.yml",
47989
+ "/runtime/services/segment.yml",
47990
+ "/runtime/services/facebook.yml",
47991
+ "/runtime/services/anvil/users.yml",
47992
+ "/runtime/services/anvil/secrets.yml",
47993
+ "/runtime/services/anvil/saml.yml",
47994
+ "/runtime/services/anvil/microsoft.yml",
47995
+ "/runtime/services/anvil/email.yml",
47996
+ "/runtime/services/anvil/files.yml"
47997
+ ]);
47998
+ const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
47999
+ const DEP_ID_RE = /^dep_[A-Za-z0-9]+$/;
48000
+ const DB_ACCESS_LEVELS = new Set([
48001
+ "none",
48002
+ "search",
48003
+ "full"
48004
+ ]);
48005
+ const DB_BASIC_COLUMN_TYPES = new Set([
48006
+ "string",
48007
+ "number",
48008
+ "bool",
48009
+ "date",
48010
+ "datetime",
48011
+ "media",
48012
+ "simpleObject"
48013
+ ]);
48014
+ const DB_LINK_COLUMN_TYPES = new Set([
48015
+ "link_single",
48016
+ "link_multiple"
48017
+ ]);
48018
+ const DB_TABLE_INDEX_TYPES = new Set([
48019
+ "b_tree",
48020
+ "trigram",
48021
+ "full_text"
48022
+ ]);
48023
+ const SCHEDULE_EVERY_VALUES = new Set([
48024
+ "minute",
48025
+ "hour",
48026
+ "day",
48027
+ "week",
48028
+ "month"
48029
+ ]);
48030
+ const CLIENT_VERSIONS = new Set([
48031
+ "2",
48032
+ "3"
48033
+ ]);
48034
+ const PYTHON_KEYWORDS = new Set([
48035
+ "False",
48036
+ "None",
48037
+ "True",
48038
+ "and",
48039
+ "as",
48040
+ "assert",
48041
+ "async",
48042
+ "await",
48043
+ "break",
48044
+ "class",
48045
+ "continue",
48046
+ "def",
48047
+ "del",
48048
+ "elif",
48049
+ "else",
48050
+ "except",
48051
+ "finally",
48052
+ "for",
48053
+ "from",
48054
+ "global",
48055
+ "if",
48056
+ "import",
48057
+ "in",
48058
+ "is",
48059
+ "lambda",
48060
+ "nonlocal",
48061
+ "not",
48062
+ "or",
48063
+ "pass",
48064
+ "raise",
48065
+ "return",
48066
+ "try",
48067
+ "while",
48068
+ "with",
48069
+ "yield",
48070
+ "match",
48071
+ "case"
48072
+ ]);
48073
+ const PROPERTY_TYPES = new Set([
48074
+ "string",
48075
+ "number",
48076
+ "color",
48077
+ "boolean",
48078
+ "text[]",
48079
+ "enum",
48080
+ "icon",
48081
+ "form",
48082
+ "object",
48083
+ "uri",
48084
+ "margin",
48085
+ "padding",
48086
+ "spacing"
48087
+ ]);
48088
+ function isValidFormReference(typeName) {
48089
+ if (!typeName.startsWith("form:")) return false;
48090
+ const rest = typeName.slice(5);
48091
+ if (0 === rest.length) return false;
48092
+ let classPath = rest;
48093
+ if (rest.startsWith("dep_")) {
48094
+ const splitIndex = rest.indexOf(":");
48095
+ if (splitIndex <= 0) return false;
48096
+ const depId = rest.slice(0, splitIndex);
48097
+ if (!DEP_ID_RE.test(depId)) return false;
48098
+ classPath = rest.slice(splitIndex + 1);
48099
+ }
48100
+ if (0 === classPath.length) return false;
48101
+ return classPath.split(".").every((part)=>IDENTIFIER_RE.test(part));
48102
+ }
48103
+ function isBuiltinComponentType(typeName) {
48104
+ return BUILTIN_COMPONENT_TYPES.has(typeName);
48105
+ }
48106
+ function validators_isPlainObject(value) {
48107
+ return "object" == typeof value && null !== value && !Array.isArray(value);
48108
+ }
48109
+ function pushIssue(issues, path, message) {
48110
+ issues.push({
48111
+ path,
48112
+ message
48113
+ });
48114
+ }
48115
+ function formatValidationPath(path) {
48116
+ return path || "root";
48117
+ }
48118
+ function validateIdentifier(value, path, issues) {
48119
+ if ("string" != typeof value || 0 === value.length) return void pushIssue(issues, path, "must be a non-empty string");
48120
+ if (!IDENTIFIER_RE.test(value)) return void pushIssue(issues, path, "must be a valid identifier");
48121
+ if (PYTHON_KEYWORDS.has(value)) pushIssue(issues, path, "must be a valid Python identifier (not a keyword)");
48122
+ }
48123
+ function validateComponentType(value, path, issues) {
48124
+ if ("string" != typeof value || 0 === value.length) return void pushIssue(issues, path, "must be a non-empty string");
48125
+ const typeName = value;
48126
+ if (typeName.startsWith("form:")) {
48127
+ if (!isValidFormReference(typeName)) pushIssue(issues, path, "must be a valid form: reference (form:Module.Form, form:dep_xxx:Package.Form)");
48128
+ return;
48129
+ }
48130
+ if (!IDENTIFIER_RE.test(typeName)) return void pushIssue(issues, path, "must be a valid identifier");
48131
+ if (PYTHON_KEYWORDS.has(typeName)) return void pushIssue(issues, path, "must be a valid Python identifier (not a keyword)");
48132
+ if (!isBuiltinComponentType(typeName)) pushIssue(issues, path, "must be a known Anvil component type (see componentIcons.ts) or a form: reference");
48133
+ }
48134
+ function validatePropertiesObject(value, path, issues) {
48135
+ if (void 0 !== value && !validators_isPlainObject(value)) pushIssue(issues, path, "must be an object");
48136
+ }
48137
+ function validateEventBindingsObject(value, path, issues) {
48138
+ if (void 0 === value) return;
48139
+ if (!validators_isPlainObject(value)) return void pushIssue(issues, path, "must be an object");
48140
+ for (const [eventName, handlerName] of Object.entries(value)){
48141
+ validateIdentifier(eventName, `${path}.${eventName}.name`, issues);
48142
+ validateIdentifier(handlerName, `${path}.${eventName}`, issues);
48143
+ }
48144
+ }
48145
+ function validateDataBindings(value, path, issues) {
48146
+ if (void 0 === value) return;
48147
+ if (!Array.isArray(value)) return void pushIssue(issues, path, "must be an array");
48148
+ value.forEach((binding, index)=>{
48149
+ const bindingPath = `${path}[${index}]`;
48150
+ if (!validators_isPlainObject(binding)) return void pushIssue(issues, bindingPath, "must be an object");
48151
+ if ("string" != typeof binding.property || 0 === binding.property.length) pushIssue(issues, `${bindingPath}.property`, "must be a non-empty string");
48152
+ if ("string" != typeof binding.code || 0 === binding.code.length) pushIssue(issues, `${bindingPath}.code`, "must be a non-empty string");
48153
+ if (void 0 !== binding.writeback && "boolean" != typeof binding.writeback) pushIssue(issues, `${bindingPath}.writeback`, "must be a boolean");
48154
+ });
48155
+ }
48156
+ function validateComponentArray(components, pathPrefix, issues, seenNames) {
48157
+ components.forEach((component, index)=>{
48158
+ const childPath = `${pathPrefix}[${index}]`;
48159
+ validateComponentNode(component, childPath, issues, seenNames);
48160
+ if (validators_isPlainObject(component) && "string" == typeof component.name && component.name.length > 0) {
48161
+ if (seenNames.has(component.name)) pushIssue(issues, `${childPath}.name`, "must be unique across form components");
48162
+ seenNames.add(component.name);
48163
+ }
48164
+ });
48165
+ }
48166
+ function validateComponentNode(value, path, issues, seenNames) {
48167
+ if (!validators_isPlainObject(value)) return void pushIssue(issues, path, "must be an object");
48168
+ validateComponentType(value.type, `${path}.type`, issues);
48169
+ if (void 0 === value.name || "string" != typeof value.name || 0 === value.name.length) pushIssue(issues, `${path}.name`, "must be a non-empty string");
48170
+ else validateIdentifier(value.name, `${path}.name`, issues);
48171
+ validatePropertiesObject(value.properties, `${path}.properties`, issues);
48172
+ validatePropertiesObject(value.layout_properties, `${path}.layout_properties`, issues);
48173
+ validateEventBindingsObject(value.event_bindings, `${path}.event_bindings`, issues);
48174
+ validateDataBindings(value.data_bindings, `${path}.data_bindings`, issues);
48175
+ if (void 0 !== value.components) if (Array.isArray(value.components)) validateComponentArray(value.components, `${path}.components`, issues, seenNames);
48176
+ else pushIssue(issues, `${path}.components`, "must be an array");
48177
+ }
48178
+ function validatePropertyDefinition(value, path, issues) {
48179
+ if (!validators_isPlainObject(value)) return void pushIssue(issues, path, "must be an object");
48180
+ validateIdentifier(value.name, `${path}.name`, issues);
48181
+ if ("string" != typeof value.type || !PROPERTY_TYPES.has(value.type)) pushIssue(issues, `${path}.type`, "must be a supported property type");
48182
+ if (void 0 !== value.description && "string" != typeof value.description) pushIssue(issues, `${path}.description`, "must be a string");
48183
+ if (void 0 !== value.default_binding_prop && "boolean" != typeof value.default_binding_prop) pushIssue(issues, `${path}.default_binding_prop`, "must be a boolean");
48184
+ if (void 0 !== value.allow_binding_writeback && "boolean" != typeof value.allow_binding_writeback) pushIssue(issues, `${path}.allow_binding_writeback`, "must be a boolean");
48185
+ if (void 0 !== value.binding_writeback_events) if (Array.isArray(value.binding_writeback_events)) value.binding_writeback_events.forEach((eventName, index)=>{
48186
+ if ("string" != typeof eventName || 0 === eventName.length) pushIssue(issues, `${path}.binding_writeback_events[${index}]`, "must be a non-empty string");
48187
+ });
48188
+ else pushIssue(issues, `${path}.binding_writeback_events`, "must be an array");
48189
+ if ("enum" === value.type) {
48190
+ if (!Array.isArray(value.options) || value.options.some((option)=>"string" != typeof option)) pushIssue(issues, `${path}.options`, "must be an array of strings for enum properties");
48191
+ }
48192
+ }
48193
+ function validateEventDefinition(value, path, issues) {
48194
+ if (!validators_isPlainObject(value)) return void pushIssue(issues, path, "must be an object");
48195
+ validateIdentifier(value.name, `${path}.name`, issues);
48196
+ if (void 0 !== value.description && "string" != typeof value.description) pushIssue(issues, `${path}.description`, "must be a string");
48197
+ if (void 0 !== value.default_event && "boolean" != typeof value.default_event) pushIssue(issues, `${path}.default_event`, "must be a boolean");
48198
+ if (void 0 !== value.parameters) if (Array.isArray(value.parameters)) value.parameters.forEach((parameter, index)=>{
48199
+ if (!validators_isPlainObject(parameter)) return void pushIssue(issues, `${path}.parameters[${index}]`, "must be an object");
48200
+ validateIdentifier(parameter.name, `${path}.parameters[${index}].name`, issues);
48201
+ if (void 0 !== parameter.description && "string" != typeof parameter.description) pushIssue(issues, `${path}.parameters[${index}].description`, "must be a string");
48202
+ });
48203
+ else pushIssue(issues, `${path}.parameters`, "must be an array");
48204
+ }
48205
+ function validateSlotDefinition(value, path, issues, seenNames) {
48206
+ if (!validators_isPlainObject(value)) return void pushIssue(issues, path, "must be an object");
48207
+ if ("number" != typeof value.index || !Number.isInteger(value.index) || value.index < 0) pushIssue(issues, `${path}.index`, "must be a non-negative integer");
48208
+ validatePropertiesObject(value.set_layout_properties, `${path}.set_layout_properties`, issues);
48209
+ if (validators_isPlainObject(value.target)) {
48210
+ if ("container" !== value.target.type && "slot" !== value.target.type) pushIssue(issues, `${path}.target.type`, "must be 'container' or 'slot'");
48211
+ validateIdentifier(value.target.name, `${path}.target.name`, issues);
48212
+ } else pushIssue(issues, `${path}.target`, "must be an object");
48213
+ if (void 0 !== value.template) validateComponentNode(value.template, `${path}.template`, issues, seenNames);
48214
+ }
48215
+ function validateFormTemplateData(value) {
48216
+ const issues = [];
48217
+ const componentNames = new Set();
48218
+ if (!validators_isPlainObject(value)) {
48219
+ pushIssue(issues, "", "must be a YAML object");
48220
+ return issues;
48221
+ }
48222
+ const hasContainer = void 0 !== value.container;
48223
+ const hasLayout = void 0 !== value.layout;
48224
+ const hasComponents = void 0 !== value.components;
48225
+ const hasComponentsBySlot = void 0 !== value.components_by_slot;
48226
+ if (hasContainer && hasLayout) pushIssue(issues, "", "cannot define both container and layout");
48227
+ if (!hasContainer && !hasLayout) pushIssue(issues, "", "must define either container or layout");
48228
+ if (void 0 !== value.is_package && "boolean" != typeof value.is_package) pushIssue(issues, "is_package", "must be a boolean");
48229
+ if (hasContainer) if (validators_isPlainObject(value.container)) {
48230
+ validateComponentType(value.container.type, "container.type", issues);
48231
+ validatePropertiesObject(value.container.properties, "container.properties", issues);
48232
+ validatePropertiesObject(value.container.layout_properties, "container.layout_properties", issues);
48233
+ validateEventBindingsObject(value.container.event_bindings, "container.event_bindings", issues);
48234
+ validateDataBindings(value.container.data_bindings, "container.data_bindings", issues);
48235
+ } else pushIssue(issues, "container", "must be an object");
48236
+ if (hasLayout) if (validators_isPlainObject(value.layout)) {
48237
+ validateComponentType(value.layout.type, "layout.type", issues);
48238
+ validatePropertiesObject(value.layout.properties, "layout.properties", issues);
48239
+ validateEventBindingsObject(value.layout.event_bindings, "layout.event_bindings", issues);
48240
+ validateEventBindingsObject(value.layout.form_event_bindings, "layout.form_event_bindings", issues);
48241
+ validateDataBindings(value.layout.data_bindings, "layout.data_bindings", issues);
48242
+ } else pushIssue(issues, "layout", "must be an object");
48243
+ if (hasComponents && hasLayout) pushIssue(issues, "components", "can only be used with container-based forms");
48244
+ else if (hasComponents && !hasContainer) pushIssue(issues, "components", "can only be used with container-based forms");
48245
+ else if (hasComponents) if (Array.isArray(value.components)) validateComponentArray(value.components, "components", issues, componentNames);
48246
+ else pushIssue(issues, "components", "must be an array");
48247
+ if (hasComponentsBySlot && hasContainer) pushIssue(issues, "components_by_slot", "can only be used with layout-based forms");
48248
+ else if (hasComponentsBySlot && !hasLayout) pushIssue(issues, "components_by_slot", "can only be used with layout-based forms");
48249
+ else if (hasComponentsBySlot) if (validators_isPlainObject(value.components_by_slot)) for (const [slotName, slotComponents] of Object.entries(value.components_by_slot)){
48250
+ if (!Array.isArray(slotComponents)) {
48251
+ pushIssue(issues, `components_by_slot.${slotName}`, "must be an array");
48252
+ continue;
48253
+ }
48254
+ validateComponentArray(slotComponents, `components_by_slot.${slotName}`, issues, componentNames);
48255
+ }
48256
+ else pushIssue(issues, "components_by_slot", "must be an object");
48257
+ if (void 0 !== value.custom_component && "boolean" != typeof value.custom_component) pushIssue(issues, "custom_component", "must be a boolean");
48258
+ if (void 0 !== value.custom_component_container && "boolean" != typeof value.custom_component_container) pushIssue(issues, "custom_component_container", "must be a boolean");
48259
+ if (true === value.custom_component_container && true !== value.custom_component) pushIssue(issues, "custom_component_container", "requires custom_component: true");
48260
+ const properties = value.properties;
48261
+ if (void 0 !== properties) if (Array.isArray(properties)) {
48262
+ const seenNames = new Set();
48263
+ let defaultBindingPropCount = 0;
48264
+ properties.forEach((property, index)=>{
48265
+ validatePropertyDefinition(property, `properties[${index}]`, issues);
48266
+ if (!validators_isPlainObject(property) || "string" != typeof property.name) return;
48267
+ if (seenNames.has(property.name)) pushIssue(issues, `properties[${index}].name`, "must be unique");
48268
+ seenNames.add(property.name);
48269
+ if (property.default_binding_prop) defaultBindingPropCount++;
48270
+ });
48271
+ if (defaultBindingPropCount > 1) pushIssue(issues, "properties", "can only define one default_binding_prop");
48272
+ } else pushIssue(issues, "properties", "must be an array");
48273
+ const events = value.events;
48274
+ const eventNames = new Set();
48275
+ if (void 0 !== events) if (Array.isArray(events)) {
48276
+ let defaultEventCount = 0;
48277
+ events.forEach((event, index)=>{
48278
+ validateEventDefinition(event, `events[${index}]`, issues);
48279
+ if (!validators_isPlainObject(event) || "string" != typeof event.name) return;
48280
+ if (eventNames.has(event.name)) pushIssue(issues, `events[${index}].name`, "must be unique");
48281
+ eventNames.add(event.name);
48282
+ if (event.default_event) defaultEventCount++;
48283
+ });
48284
+ if (defaultEventCount > 1) pushIssue(issues, "events", "can only define one default_event");
48285
+ } else pushIssue(issues, "events", "must be an array");
48286
+ if (Array.isArray(properties)) properties.forEach((property, index)=>{
48287
+ if (!validators_isPlainObject(property) || !Array.isArray(property.binding_writeback_events)) return;
48288
+ property.binding_writeback_events.forEach((eventName, eventIndex)=>{
48289
+ if ("string" == typeof eventName && !eventNames.has(eventName)) pushIssue(issues, `properties[${index}].binding_writeback_events[${eventIndex}]`, "must reference a defined event");
48290
+ });
48291
+ });
48292
+ if (void 0 !== value.slots) if (validators_isPlainObject(value.slots)) for (const [slotName, slotDef] of Object.entries(value.slots)){
48293
+ validateIdentifier(slotName, `slots.${slotName}.name`, issues);
48294
+ validateSlotDefinition(slotDef, `slots.${slotName}`, issues, componentNames);
48295
+ }
48296
+ else pushIssue(issues, "slots", "must be an object");
48297
+ if (void 0 !== value.item_type) if (validators_isPlainObject(value.item_type)) {
48298
+ if ("number" != typeof value.item_type.table_id) pushIssue(issues, "item_type.table_id", "must be a number");
48299
+ } else pushIssue(issues, "item_type", "must be an object");
48300
+ return issues;
48301
+ }
48055
48302
  function validateFormTemplate(yamlContent) {
48056
48303
  try {
48057
48304
  const data = js_yaml_load(yamlContent);
48058
- const result = safeParse(FormTemplateSchema, data);
48059
- if (!result.success) {
48060
- logger_logger.error("form_template.yaml: Validation failed");
48061
- for (const issue of result.issues)logger_logger.error(` - ${issue.path?.map((p)=>p.key).join(".") || "root"}: ${issue.message}`);
48062
- return false;
48063
- }
48064
- return true;
48305
+ const issues = validateFormTemplateData(data);
48306
+ if (issues.length > 0) return {
48307
+ ok: false,
48308
+ message: "form_template.yaml is invalid for Anvil",
48309
+ issues
48310
+ };
48311
+ return {
48312
+ ok: true
48313
+ };
48065
48314
  } catch (error) {
48066
- logger_logger.error(`form_template.yaml: YAML parse error: ${error}`);
48067
- return false;
48315
+ return {
48316
+ ok: false,
48317
+ message: "form_template.yaml is not valid YAML",
48318
+ issues: [
48319
+ {
48320
+ path: "",
48321
+ message: error instanceof Error ? error.message : String(error)
48322
+ }
48323
+ ]
48324
+ };
48325
+ }
48326
+ }
48327
+ const serviceConfigObject = custom((input)=>validators_isPlainObject(input), "must be an object");
48328
+ const AnvilServiceEntrySchema = looseObject({
48329
+ source: pipe(dist_string(), dist_nonEmpty()),
48330
+ client_config: optional(serviceConfigObject),
48331
+ server_config: optional(serviceConfigObject)
48332
+ });
48333
+ const RuntimeOptionsSchema = looseObject({
48334
+ version: dist_number(),
48335
+ client_version: optional(picklist([
48336
+ "2",
48337
+ "3"
48338
+ ])),
48339
+ preview_v3: optional(dist_boolean()),
48340
+ server_version: optional(dist_string()),
48341
+ server_persist: optional(dist_boolean()),
48342
+ legacy_features: optional(looseObject({
48343
+ class_names: optional(dist_boolean()),
48344
+ bootstrap3: optional(dist_boolean()),
48345
+ __dict__: optional(dist_boolean()),
48346
+ root_container: optional(dist_boolean())
48347
+ })),
48348
+ server_spec: optional(nullable(looseObject({
48349
+ base: optional(dist_string())
48350
+ })))
48351
+ });
48352
+ function validateRuntimeOptions(value, path) {
48353
+ const issues = [];
48354
+ if (void 0 === value) return issues;
48355
+ if (!validators_isPlainObject(value)) return issues;
48356
+ if (void 0 !== value.client_version) {
48357
+ if ("string" != typeof value.client_version || !CLIENT_VERSIONS.has(value.client_version)) pushIssue(issues, `${path}.client_version`, 'must be "2" or "3"');
48358
+ }
48359
+ if (void 0 !== value.server_spec && null !== value.server_spec) {
48360
+ if (!validators_isPlainObject(value.server_spec)) return issues;
48361
+ if ("requirements" in value.server_spec) pushIssue(issues, `${path}.server_spec.requirements`, "is not a valid anvil.yaml field; requirements come from server_code/requirements.txt");
48362
+ if (void 0 !== value.server_spec.base && "string" != typeof value.server_spec.base) pushIssue(issues, `${path}.server_spec.base`, "must be a string");
48363
+ }
48364
+ return issues;
48365
+ }
48366
+ function validateDbSchema(value, path) {
48367
+ const issues = [];
48368
+ if (void 0 === value) return issues;
48369
+ if (null === value) {
48370
+ pushIssue(issues, path, "must be an object, an empty array, or omitted");
48371
+ return issues;
48372
+ }
48373
+ if (Array.isArray(value)) {
48374
+ if (0 === value.length) return issues;
48375
+ pushIssue(issues, path, "must be an object mapping table names to schemas, or an empty array");
48376
+ return issues;
48377
+ }
48378
+ if (!validators_isPlainObject(value)) {
48379
+ pushIssue(issues, path, "must be an object mapping table names to schemas");
48380
+ return issues;
48381
+ }
48382
+ for (const tableName of Object.keys(value)){
48383
+ const tablePath = `${path}.${tableName}`;
48384
+ if (!IDENTIFIER_RE.test(tableName)) {
48385
+ pushIssue(issues, tablePath, "must be a valid Python identifier");
48386
+ continue;
48387
+ }
48388
+ const table = value[tableName];
48389
+ if (!validators_isPlainObject(table)) {
48390
+ pushIssue(issues, tablePath, "must be an object");
48391
+ continue;
48392
+ }
48393
+ for (const req of [
48394
+ "client",
48395
+ "server",
48396
+ "title",
48397
+ "columns"
48398
+ ])if (void 0 === table[req]) pushIssue(issues, `${tablePath}.${req}`, "is required");
48399
+ if (void 0 !== table.client) {
48400
+ if ("string" != typeof table.client || !DB_ACCESS_LEVELS.has(table.client)) pushIssue(issues, `${tablePath}.client`, 'must be "none", "search", or "full"');
48401
+ }
48402
+ if (void 0 !== table.server) {
48403
+ if ("string" != typeof table.server || !DB_ACCESS_LEVELS.has(table.server)) pushIssue(issues, `${tablePath}.server`, 'must be "none", "search", or "full"');
48404
+ }
48405
+ if (void 0 !== table.title) {
48406
+ if ("string" != typeof table.title || 0 === table.title.length) pushIssue(issues, `${tablePath}.title`, "must be a non-empty string");
48407
+ }
48408
+ if (void 0 !== table.columns) if (Array.isArray(table.columns)) table.columns.forEach((column, index)=>{
48409
+ validateDbSchemaColumn(column, `${tablePath}.columns[${index}]`, issues);
48410
+ });
48411
+ else pushIssue(issues, `${tablePath}.columns`, "must be an array");
48412
+ if (void 0 !== table.indexes) if (Array.isArray(table.indexes)) table.indexes.forEach((indexEntry, index)=>{
48413
+ validateDbTableIndex(indexEntry, `${tablePath}.indexes[${index}]`, issues);
48414
+ });
48415
+ else pushIssue(issues, `${tablePath}.indexes`, "must be an array");
48068
48416
  }
48417
+ return issues;
48418
+ }
48419
+ function validateDbTableIndex(value, path, issues) {
48420
+ if (!validators_isPlainObject(value)) return void pushIssue(issues, path, "must be an object");
48421
+ if (!Array.isArray(value.columns) || value.columns.some((c)=>"string" != typeof c)) pushIssue(issues, `${path}.columns`, "must be an array of strings");
48422
+ if (void 0 === value.type) pushIssue(issues, `${path}.type`, "is required");
48423
+ else if ("string" != typeof value.type || !DB_TABLE_INDEX_TYPES.has(value.type)) pushIssue(issues, `${path}.type`, 'must be "b_tree", "trigram", or "full_text"');
48424
+ }
48425
+ function validateDbSchemaColumn(value, path, issues) {
48426
+ if (!validators_isPlainObject(value)) return void pushIssue(issues, path, "must be an object");
48427
+ if (void 0 === value.name || "string" != typeof value.name || 0 === value.name.length) pushIssue(issues, `${path}.name`, "must be a non-empty string");
48428
+ else if (!IDENTIFIER_RE.test(value.name)) pushIssue(issues, `${path}.name`, "must be a valid Python identifier");
48429
+ if (void 0 === value.type) return void pushIssue(issues, `${path}.type`, "is required");
48430
+ if ("string" != typeof value.type) return void pushIssue(issues, `${path}.type`, "must be a string");
48431
+ const colType = value.type;
48432
+ if (DB_LINK_COLUMN_TYPES.has(colType)) {
48433
+ if (void 0 === value.target || "string" != typeof value.target || 0 === value.target.length) pushIssue(issues, `${path}.target`, "must be a non-empty string (target table python name)");
48434
+ else if (!IDENTIFIER_RE.test(value.target)) pushIssue(issues, `${path}.target`, "must be a valid Python identifier");
48435
+ } else if (!DB_BASIC_COLUMN_TYPES.has(colType)) pushIssue(issues, `${path}.type`, `must be a basic column type (${[
48436
+ ...DB_BASIC_COLUMN_TYPES
48437
+ ].join(", ")}) or ${[
48438
+ ...DB_LINK_COLUMN_TYPES
48439
+ ].join(", ")}`);
48440
+ if (void 0 !== value.client_hidden && "boolean" != typeof value.client_hidden) pushIssue(issues, `${path}.client_hidden`, "must be a boolean");
48441
+ if (void 0 !== value.admin_ui && null !== value.admin_ui) if (validators_isPlainObject(value.admin_ui)) {
48442
+ const ui = value.admin_ui;
48443
+ if (void 0 !== ui.width && "number" != typeof ui.width) pushIssue(issues, `${path}.admin_ui.width`, "must be a number");
48444
+ if (void 0 !== ui.order && "number" != typeof ui.order) pushIssue(issues, `${path}.admin_ui.order`, "must be a number");
48445
+ } else pushIssue(issues, `${path}.admin_ui`, "must be an object or null");
48446
+ if (void 0 !== value.indexes) {
48447
+ if (!Array.isArray(value.indexes)) return void pushIssue(issues, `${path}.indexes`, "must be an array");
48448
+ value.indexes.forEach((entry, i)=>{
48449
+ const idxPath = `${path}.indexes[${i}]`;
48450
+ if (!validators_isPlainObject(entry)) return void pushIssue(issues, idxPath, "must be an object");
48451
+ if (void 0 === entry.type) pushIssue(issues, `${idxPath}.type`, "is required");
48452
+ else if ("string" != typeof entry.type || !DB_TABLE_INDEX_TYPES.has(entry.type)) pushIssue(issues, `${idxPath}.type`, 'must be "b_tree", "trigram", or "full_text"');
48453
+ });
48454
+ }
48455
+ }
48456
+ function validateKnownServiceSources(services, path) {
48457
+ const issues = [];
48458
+ if (void 0 === services) return issues;
48459
+ if (!Array.isArray(services)) return issues;
48460
+ services.forEach((service, index)=>{
48461
+ if (!validators_isPlainObject(service) || "string" != typeof service.source) return;
48462
+ if (!KNOWN_RUNTIME_SERVICE_SOURCES.has(service.source)) pushIssue(issues, `${path}[${index}].source`, "must be a known runtime service source path");
48463
+ });
48464
+ return issues;
48069
48465
  }
48070
- const AnvilYamlSchema = dist_object({
48466
+ function validateServiceConfigs(services, path) {
48467
+ const issues = [];
48468
+ if (void 0 === services || !Array.isArray(services)) return issues;
48469
+ services.forEach((service, index)=>{
48470
+ if (!validators_isPlainObject(service) || "/runtime/services/anvil/users.yml" !== service.source) return;
48471
+ if (!validators_isPlainObject(service.client_config)) return;
48472
+ if (true === service.client_config.allow_remember_me && void 0 === service.client_config.remember_me_days) pushIssue(issues, `${path}[${index}].client_config.remember_me_days`, "must be set if allow_remember_me is true");
48473
+ });
48474
+ return issues;
48475
+ }
48476
+ function validateScheduledTasks(value, path) {
48477
+ const issues = [];
48478
+ if (void 0 === value) return issues;
48479
+ if (!Array.isArray(value)) {
48480
+ pushIssue(issues, path, "must be an array");
48481
+ return issues;
48482
+ }
48483
+ value.forEach((task, index)=>{
48484
+ const taskPath = `${path}[${index}]`;
48485
+ if (!validators_isPlainObject(task)) return void pushIssue(issues, taskPath, "must be an object");
48486
+ if ("string" != typeof task.job_id || 0 === task.job_id.length) pushIssue(issues, `${taskPath}.job_id`, "must be a non-empty string");
48487
+ validateIdentifier(task.task_name, `${taskPath}.task_name`, issues);
48488
+ if (!validators_isPlainObject(task.time_spec)) return void pushIssue(issues, `${taskPath}.time_spec`, "must be an object");
48489
+ const timeSpec = task.time_spec;
48490
+ if ("string" != typeof timeSpec.every || !SCHEDULE_EVERY_VALUES.has(timeSpec.every)) pushIssue(issues, `${taskPath}.time_spec.every`, 'must be one of "minute", "hour", "day", "week", or "month"');
48491
+ const interval = timeSpec.n;
48492
+ if (void 0 !== interval && ("number" != typeof interval || !Number.isInteger(interval) || interval <= 0)) pushIssue(issues, `${taskPath}.time_spec.n`, "must be a positive integer");
48493
+ if (void 0 !== timeSpec.at) if (validators_isPlainObject(timeSpec.at)) {
48494
+ const at = timeSpec.at;
48495
+ if (void 0 !== at.minute && !Number.isInteger(at.minute)) pushIssue(issues, `${taskPath}.time_spec.at.minute`, "must be an integer");
48496
+ if (void 0 !== at.hour && !Number.isInteger(at.hour)) pushIssue(issues, `${taskPath}.time_spec.at.hour`, "must be an integer");
48497
+ if (void 0 !== at.day && !Number.isInteger(at.day)) pushIssue(issues, `${taskPath}.time_spec.at.day`, "must be an integer");
48498
+ } else pushIssue(issues, `${taskPath}.time_spec.at`, "must be an object");
48499
+ });
48500
+ return issues;
48501
+ }
48502
+ const AnvilYamlSchema = looseObject({
48071
48503
  name: dist_string(),
48072
- package_name: dist_string(),
48504
+ package_name: optional(dist_string()),
48073
48505
  allow_embedding: optional(dist_boolean()),
48074
48506
  renamed: optional(dist_boolean()),
48075
48507
  dependencies: optional(dist_array(any())),
48076
- runtime_options: dist_object({
48077
- version: pipe(dist_number(), minValue(2)),
48078
- client_version: optional(dist_string()),
48079
- server_version: optional(dist_string()),
48080
- server_spec: optional(any())
48081
- }),
48082
- services: optional(dist_array(dist_object({
48083
- source: dist_string(),
48084
- client_config: optional(any()),
48085
- server_config: optional(any())
48086
- }))),
48087
- startup: optional(union([
48088
- dist_object({
48089
- module: dist_string(),
48090
- type: dist_string()
48091
- }),
48092
- any()
48093
- ])),
48508
+ runtime_options: RuntimeOptionsSchema,
48509
+ services: optional(dist_array(AnvilServiceEntrySchema)),
48510
+ startup: optional(looseObject({
48511
+ type: picklist([
48512
+ "module",
48513
+ "form"
48514
+ ]),
48515
+ module: optional(dist_string())
48516
+ })),
48094
48517
  startup_form: optional(nullable(dist_string())),
48095
48518
  db_schema: optional(any()),
48519
+ scheduled_tasks: optional(any()),
48096
48520
  secrets: optional(any()),
48097
- metadata: optional(any())
48521
+ metadata: optional(looseObject({
48522
+ title: optional(dist_string()),
48523
+ description: optional(dist_string()),
48524
+ logo_img: optional(dist_string())
48525
+ })),
48526
+ client_init_module: optional(dist_string())
48098
48527
  });
48528
+ function formatValibotIssuePath(issue) {
48529
+ return getDotPath(issue) ?? "";
48530
+ }
48099
48531
  function validateAnvilYaml(yamlContent) {
48100
- if (yamlContent.includes("<<<<<<<") || yamlContent.includes(">>>>>>>")) {
48101
- logger_logger.error("anvil.yaml contains git merge conflict markers. Please resolve conflicts manually.");
48102
- return false;
48103
- }
48532
+ if (yamlContent.includes("<<<<<<<") || yamlContent.includes(">>>>>>>")) return {
48533
+ ok: false,
48534
+ message: "anvil.yaml contains merge conflict markers",
48535
+ issues: [
48536
+ {
48537
+ path: "",
48538
+ message: "contains git merge conflict markers"
48539
+ }
48540
+ ]
48541
+ };
48104
48542
  try {
48105
48543
  const data = js_yaml_load(yamlContent);
48106
48544
  const result = safeParse(AnvilYamlSchema, data);
48107
- if (!result.success) {
48108
- logger_logger.error("anvil.yaml: Validation failed");
48109
- for (const issue of result.issues)logger_logger.error(` - ${issue.path?.map((p)=>p.key).join(".") || "root"}: ${issue.message}`);
48110
- return false;
48111
- }
48112
- return true;
48545
+ if (!result.success) return {
48546
+ ok: false,
48547
+ message: "anvil.yaml is invalid for Anvil",
48548
+ issues: result.issues.map((issue)=>({
48549
+ path: formatValibotIssuePath(issue),
48550
+ message: issue.message
48551
+ }))
48552
+ };
48553
+ const runtimeOptionIssues = validateRuntimeOptions(result.output.runtime_options, "runtime_options");
48554
+ if (runtimeOptionIssues.length > 0) return {
48555
+ ok: false,
48556
+ message: "anvil.yaml is invalid for Anvil",
48557
+ issues: runtimeOptionIssues
48558
+ };
48559
+ const serviceIssues = validateKnownServiceSources(result.output.services, "services");
48560
+ if (serviceIssues.length > 0) return {
48561
+ ok: false,
48562
+ message: "anvil.yaml is invalid for Anvil",
48563
+ issues: serviceIssues
48564
+ };
48565
+ const serviceConfigIssues = validateServiceConfigs(result.output.services, "services");
48566
+ if (serviceConfigIssues.length > 0) return {
48567
+ ok: false,
48568
+ message: "anvil.yaml is invalid for Anvil",
48569
+ issues: serviceConfigIssues
48570
+ };
48571
+ const scheduledTaskIssues = validateScheduledTasks(result.output.scheduled_tasks, "scheduled_tasks");
48572
+ if (scheduledTaskIssues.length > 0) return {
48573
+ ok: false,
48574
+ message: "anvil.yaml is invalid for Anvil",
48575
+ issues: scheduledTaskIssues
48576
+ };
48577
+ const dbIssues = validateDbSchema(result.output.db_schema, "db_schema");
48578
+ if (dbIssues.length > 0) return {
48579
+ ok: false,
48580
+ message: "anvil.yaml is invalid for Anvil",
48581
+ issues: dbIssues
48582
+ };
48583
+ return {
48584
+ ok: true
48585
+ };
48113
48586
  } catch (error) {
48114
- logger_logger.error(`anvil.yaml: YAML parse error: ${error}`);
48115
- return false;
48587
+ return {
48588
+ ok: false,
48589
+ message: "anvil.yaml is not valid YAML",
48590
+ issues: [
48591
+ {
48592
+ path: "",
48593
+ message: error instanceof Error ? error.message : String(error)
48594
+ }
48595
+ ]
48596
+ };
48597
+ }
48598
+ }
48599
+ function inferValidationTarget(filePath) {
48600
+ const normalizedPath = filePath.replace(/\\/g, "/");
48601
+ if ("anvil.yaml" === normalizedPath || normalizedPath.endsWith("/anvil.yaml")) return "anvil.yaml";
48602
+ const clientCodePrefix = "client_code/";
48603
+ if (normalizedPath.startsWith(clientCodePrefix) || normalizedPath.includes(`/${clientCodePrefix}`)) {
48604
+ const startIndex = normalizedPath.lastIndexOf(clientCodePrefix);
48605
+ const relativeToClientCode = normalizedPath.slice(startIndex + clientCodePrefix.length);
48606
+ if (relativeToClientCode.endsWith(".yaml")) return "form_template.yaml";
48607
+ }
48608
+ return null;
48609
+ }
48610
+ function resolveExistingPath(filePath) {
48611
+ const absolutePath = external_path_default().resolve(filePath);
48612
+ return external_fs_.existsSync(absolutePath) ? absolutePath : null;
48613
+ }
48614
+ function findAnvilAppRoot(filePath) {
48615
+ const existingPath = resolveExistingPath(filePath);
48616
+ if (!existingPath) return null;
48617
+ let currentDir = external_fs_.statSync(existingPath).isDirectory() ? existingPath : external_path_default().dirname(existingPath);
48618
+ let appRoot = null;
48619
+ while(true){
48620
+ if (external_fs_.existsSync(external_path_default().join(currentDir, "anvil.yaml"))) appRoot = currentDir;
48621
+ const parentDir = external_path_default().dirname(currentDir);
48622
+ if (parentDir === currentDir) return appRoot;
48623
+ currentDir = parentDir;
48624
+ }
48625
+ }
48626
+ function validatePath(filePath, yamlContent) {
48627
+ const target = inferValidationTarget(filePath);
48628
+ const existingPath = resolveExistingPath(filePath);
48629
+ const appRoot = findAnvilAppRoot(filePath);
48630
+ if (existingPath && !appRoot) return {
48631
+ ok: false,
48632
+ target: target ?? "unknown",
48633
+ message: "File is not inside an Anvil app",
48634
+ issues: [
48635
+ {
48636
+ path: "",
48637
+ message: "could not find an anvil.yaml app root for this file"
48638
+ }
48639
+ ]
48640
+ };
48641
+ if ("anvil.yaml" === target) {
48642
+ if (existingPath && appRoot && external_path_default().resolve(existingPath) !== external_path_default().join(appRoot, "anvil.yaml")) return {
48643
+ ok: false,
48644
+ target,
48645
+ message: "File is not the app root anvil.yaml",
48646
+ issues: [
48647
+ {
48648
+ path: "",
48649
+ message: `expected ${external_path_default().join(appRoot, "anvil.yaml")}`
48650
+ }
48651
+ ]
48652
+ };
48653
+ const result = validateAnvilYaml(yamlContent);
48654
+ return result.ok ? {
48655
+ ok: true,
48656
+ target
48657
+ } : {
48658
+ ...result,
48659
+ target
48660
+ };
48661
+ }
48662
+ if ("form_template.yaml" === target) {
48663
+ if (existingPath && appRoot) {
48664
+ const clientCodeRoot = external_path_default().join(appRoot, "client_code");
48665
+ const relativeToClientCode = external_path_default().relative(clientCodeRoot, existingPath);
48666
+ const isInsideClientCode = relativeToClientCode.length > 0 && !relativeToClientCode.startsWith("..") && !external_path_default().isAbsolute(relativeToClientCode);
48667
+ if (!isInsideClientCode || !existingPath.endsWith(".yaml")) return {
48668
+ ok: false,
48669
+ target,
48670
+ message: "File is not a client_code YAML form",
48671
+ issues: [
48672
+ {
48673
+ path: "",
48674
+ message: `expected a .yaml file under ${clientCodeRoot}`
48675
+ }
48676
+ ]
48677
+ };
48678
+ }
48679
+ const result = validateFormTemplate(yamlContent);
48680
+ return result.ok ? {
48681
+ ok: true,
48682
+ target
48683
+ } : {
48684
+ ...result,
48685
+ target
48686
+ };
48116
48687
  }
48688
+ return {
48689
+ ok: false,
48690
+ target: "unknown",
48691
+ message: "No validator for this path",
48692
+ issues: [
48693
+ {
48694
+ path: "",
48695
+ message: "supported paths are anvil.yaml, client_code/**/form_template.yaml, and client_code/**/*.yaml"
48696
+ }
48697
+ ]
48698
+ };
48117
48699
  }
48118
48700
  function normalizeClientCodePath(relativePath) {
48119
48701
  return relativePath.replace(/\\/g, "/");
@@ -48153,10 +48735,15 @@ var __webpack_exports__ = {};
48153
48735
  try {
48154
48736
  const content = await readFileContent(repoPath, relativePath, stagedOnly);
48155
48737
  if ("yaml" === format) {
48156
- if (!validateFormTemplate(content)) return {
48157
- ok: false,
48158
- reason: "package" === kind ? "Invalid form_template.yaml - validation failed" : "Invalid form yaml - validation failed"
48159
- };
48738
+ const validationResult = validateFormTemplate(content);
48739
+ if (!validationResult.ok) {
48740
+ const firstIssue = validationResult.issues[0];
48741
+ const issueSummary = firstIssue ? `${formatValidationPath(firstIssue.path)}: ${firstIssue.message}` : void 0;
48742
+ return {
48743
+ ok: false,
48744
+ reason: issueSummary ? `${validationResult.message}. ${issueSummary}` : validationResult.message
48745
+ };
48746
+ }
48160
48747
  const parsed = jsYaml.load(content);
48161
48748
  const template = {
48162
48749
  ...parsed,
@@ -48830,10 +49417,11 @@ var __webpack_exports__ = {};
48830
49417
  });
48831
49418
  }
48832
49419
  function buildFormDataFromTemplate(template, html, options) {
49420
+ const { class_name: _templateClassName, code: _templateCode, is_package: _templateIsPackage, ...templateRest } = template && "object" == typeof template ? template : {};
48833
49421
  const formData = {
49422
+ ...templateRest,
48834
49423
  class_name: options.className,
48835
49424
  code: options.code,
48836
- ...template,
48837
49425
  is_package: options.isPackage
48838
49426
  };
48839
49427
  if (void 0 !== html) {
@@ -49043,7 +49631,8 @@ var __webpack_exports__ = {};
49043
49631
  requirements: currentRequirements
49044
49632
  }
49045
49633
  };
49046
- if (!validateAnvilYaml(yamlContent)) throw new Error("anvil.yaml validation failed - see errors above");
49634
+ const validationResult = validateAnvilYaml(yamlContent);
49635
+ if (!validationResult.ok) throw new Error("anvil.yaml validation failed - see errors above");
49047
49636
  const changes = [];
49048
49637
  for (const [key, value] of Object.entries(parsedYaml))if (!previousYaml || !deepEqual(previousYaml[key], value)) changes.push({
49049
49638
  key,
@@ -49538,6 +50127,19 @@ var __webpack_exports__ = {};
49538
50127
  };
49539
50128
  }
49540
50129
  }
50130
+ function stripNonSemanticYamlFields(yamlFile, parsed) {
50131
+ const normalized = {
50132
+ ...parsed ?? {}
50133
+ };
50134
+ delete normalized.is_package;
50135
+ const normalizedPath = yamlFile.replace(/\\/g, "/");
50136
+ const isFormTemplate = normalizedPath.startsWith("client_code/") && (normalizedPath.endsWith("/form_template.yaml") || /^client_code\/[^/]+\.ya?ml$/i.test(normalizedPath) && !normalizedPath.endsWith("anvil.yaml"));
50137
+ if (isFormTemplate) {
50138
+ delete normalized.class_name;
50139
+ delete normalized.code;
50140
+ }
50141
+ return normalized;
50142
+ }
49541
50143
  class SyncManager extends Emitter_Emitter {
49542
50144
  config;
49543
50145
  syncInProgress = false;
@@ -49658,8 +50260,8 @@ var __webpack_exports__ = {};
49658
50260
  }
49659
50261
  const workingParsed = js_yaml_load(workingContent);
49660
50262
  const headParsed = js_yaml_load(headContent);
49661
- const { is_package: _w, ...workingForCompare } = workingParsed;
49662
- const { is_package: _h, ...headForCompare } = headParsed;
50263
+ const workingForCompare = stripNonSemanticYamlFields(yamlFile, workingParsed);
50264
+ const headForCompare = stripNonSemanticYamlFields(yamlFile, headParsed);
49663
50265
  if (deepEqual(workingForCompare, headForCompare)) {
49664
50266
  logger_logger.verbose(chalk_source.gray(` Discarding formatting-only change: ${yamlFile}`));
49665
50267
  await this.config.gitService.checkout([
@@ -51034,6 +51636,33 @@ var __webpack_exports__ = {};
51034
51636
  function getUsernameConfigKey(appId) {
51035
51637
  return `anvil.auth.${appId}.username`;
51036
51638
  }
51639
+ function shellQuote(value) {
51640
+ return `'${value.replace(/'/g, "'\\''")}'`;
51641
+ }
51642
+ function shellQuoteDouble(value) {
51643
+ return `"${value.replace(/(["$`])/g, "\\$1")}"`;
51644
+ }
51645
+ function getDefaultCliPath(options) {
51646
+ const servicesDir = options?.servicesDir ?? __dirname;
51647
+ const pathExists = options?.pathExists ?? external_fs_default().existsSync;
51648
+ const candidates = [
51649
+ external_path_default().resolve(servicesDir, "..", "cli.js"),
51650
+ external_path_default().resolve(servicesDir, "..", "cli.ts")
51651
+ ];
51652
+ return candidates.find((candidate)=>pathExists(candidate));
51653
+ }
51654
+ function getGitCredentialHelperCommand(options) {
51655
+ const platform = options?.platform ?? process.platform;
51656
+ const execPath = options?.execPath ?? process.execPath;
51657
+ const cliPath = options?.cliPath ?? getDefaultCliPath() ?? process.argv[1];
51658
+ if (!execPath || !cliPath) return "!anvil git-credential";
51659
+ if ("win32" === platform) {
51660
+ const resolvedExecPath = external_path_default().win32.resolve(execPath);
51661
+ const resolvedCliPath = external_path_default().win32.resolve(cliPath);
51662
+ return `!${shellQuoteDouble(resolvedExecPath)} ${shellQuoteDouble(resolvedCliPath)} git-credential`;
51663
+ }
51664
+ return `!${shellQuote(external_path_default().resolve(execPath))} ${shellQuote(external_path_default().resolve(cliPath))} git-credential`;
51665
+ }
51037
51666
  async function getLocalConfigValue(repoPath, key) {
51038
51667
  try {
51039
51668
  const value = (await esm_default(repoPath).raw([
@@ -51102,7 +51731,7 @@ var __webpack_exports__ = {};
51102
51731
  "--local",
51103
51732
  "--add",
51104
51733
  `credential.${scope}.helper`,
51105
- "!anvil git-credential"
51734
+ getGitCredentialHelperCommand()
51106
51735
  ]);
51107
51736
  await git.raw([
51108
51737
  "config",
@@ -53466,6 +54095,44 @@ var __webpack_exports__ = {};
53466
54095
  program.command("update").description("Update anvil to the latest version").alias("u").action(async ()=>{
53467
54096
  await handleUpdateCommand();
53468
54097
  });
54098
+ program.command("validate").description("Validate YAML based on path (anvil.yaml or client_code/**/*.yaml)").argument("<file>", "Path to YAML file").action((file)=>{
54099
+ let yamlContent;
54100
+ try {
54101
+ yamlContent = (0, external_fs_.readFileSync)(file, "utf8");
54102
+ } catch (error) {
54103
+ logger_logger.error(`Failed to read ${file}: ${error.message}`);
54104
+ process.exitCode = 1;
54105
+ return;
54106
+ }
54107
+ const result = validatePath(file, yamlContent);
54108
+ if (result.ok) {
54109
+ if (getGlobalOutputConfig().jsonMode) logJsonResult(true, {
54110
+ data: {
54111
+ file,
54112
+ type: result.target,
54113
+ valid: true
54114
+ }
54115
+ });
54116
+ else logger_logger.success(`${file} is valid (${result.target})`);
54117
+ return;
54118
+ }
54119
+ if (getGlobalOutputConfig().jsonMode) logJsonResult(false, {
54120
+ error: result.message,
54121
+ data: {
54122
+ file,
54123
+ type: result.target,
54124
+ issues: result.issues
54125
+ }
54126
+ });
54127
+ else {
54128
+ logger_logger.error(`${file} failed validation: ${result.message}`);
54129
+ for (const issue of result.issues){
54130
+ const issuePath = issue.path.length > 0 ? issue.path : "root";
54131
+ logger_logger.error(` - ${issuePath}: ${issue.message}`);
54132
+ }
54133
+ }
54134
+ process.exitCode = 1;
54135
+ });
53469
54136
  if (watchCommand) {
53470
54137
  const watchOptions = watchCommand.options.map((opt)=>{
53471
54138
  const flags = opt.flags;