flowdular 0.3.1 → 0.4.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Flowdular: Build your business platform](https://cdn.jsdelivr.net/npm/flowdular@latest/assets/flowdular-banner.png)](https://flowdular.com)
1
+ [![Flowdular: Build your business platform](https://cdn.jsdelivr.net/npm/flowdular@latest/assets/flowdular-banner.webp)](https://flowdular.com)
2
2
 
3
3
  # Flowdular CLI
4
4
 
Binary file
@@ -2478,6 +2478,17 @@ var module_spec_schema_default = {
2478
2478
  type: "array",
2479
2479
  items: { $ref: "#/$defs/decision" },
2480
2480
  uniqueItems: true
2481
+ },
2482
+ research: { $ref: "#/$defs/research" },
2483
+ adapters: {
2484
+ type: "array",
2485
+ items: { $ref: "#/$defs/adapter" },
2486
+ uniqueItems: true
2487
+ },
2488
+ templates: {
2489
+ type: "array",
2490
+ items: { $ref: "#/$defs/template" },
2491
+ uniqueItems: true
2481
2492
  }
2482
2493
  },
2483
2494
  allOf: [
@@ -2495,7 +2506,10 @@ var module_spec_schema_default = {
2495
2506
  settings: false,
2496
2507
  agentTools: false,
2497
2508
  outOfScope: false,
2498
- decisions: false
2509
+ decisions: false,
2510
+ research: false,
2511
+ adapters: false,
2512
+ templates: false
2499
2513
  }
2500
2514
  }
2501
2515
  }
@@ -2701,6 +2715,113 @@ var module_spec_schema_default = {
2701
2715
  answer: { type: "string", minLength: 1 },
2702
2716
  decidedBy: { enum: ["user", "default"] }
2703
2717
  }
2718
+ },
2719
+ domain: {
2720
+ type: "string",
2721
+ maxLength: 253,
2722
+ pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"
2723
+ },
2724
+ research: {
2725
+ type: "object",
2726
+ additionalProperties: false,
2727
+ required: ["adapter", "evidenceOwner"],
2728
+ properties: {
2729
+ adapter: {
2730
+ enum: [
2731
+ "model-native",
2732
+ "searxng",
2733
+ "firecrawl",
2734
+ "connector",
2735
+ "recorded"
2736
+ ]
2737
+ },
2738
+ allowDomains: {
2739
+ type: "array",
2740
+ items: { $ref: "#/$defs/domain" },
2741
+ uniqueItems: true
2742
+ },
2743
+ denyDomains: {
2744
+ type: "array",
2745
+ items: { $ref: "#/$defs/domain" },
2746
+ uniqueItems: true
2747
+ },
2748
+ monthlyQueryBudget: { type: "integer", minimum: 1 },
2749
+ evidenceOwner: { $ref: "#/$defs/entityId" }
2750
+ }
2751
+ },
2752
+ adapter: {
2753
+ type: "object",
2754
+ additionalProperties: false,
2755
+ required: [
2756
+ "id",
2757
+ "direction",
2758
+ "connector",
2759
+ "operation",
2760
+ "port",
2761
+ "mapping"
2762
+ ],
2763
+ properties: {
2764
+ id: {
2765
+ type: "string",
2766
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2767
+ },
2768
+ direction: { enum: ["source", "sink"] },
2769
+ connector: { type: "string", pattern: "^[a-z][a-z0-9-]{0,95}$" },
2770
+ operation: { type: "string", pattern: "^[a-z][a-z0-9-]{0,63}$" },
2771
+ port: {
2772
+ type: "string",
2773
+ maxLength: 96,
2774
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2775
+ },
2776
+ schedule: {
2777
+ anyOf: [
2778
+ { type: "null" },
2779
+ {
2780
+ type: "string",
2781
+ maxLength: 100,
2782
+ pattern: "^[0-9a-z*,/-]+( [0-9a-z*,/-]+){4}$"
2783
+ }
2784
+ ]
2785
+ },
2786
+ mapping: {
2787
+ type: "array",
2788
+ minItems: 1,
2789
+ items: { $ref: "#/$defs/adapterMapping" },
2790
+ uniqueItems: true
2791
+ },
2792
+ recorded: {
2793
+ type: "string",
2794
+ pattern: "^adapters/[a-z0-9][a-z0-9-]*\\.recorded\\.json$"
2795
+ }
2796
+ }
2797
+ },
2798
+ adapterMapping: {
2799
+ type: "object",
2800
+ additionalProperties: false,
2801
+ required: ["to", "transform"],
2802
+ properties: {
2803
+ from: { type: "string", minLength: 1 },
2804
+ to: { type: "string", minLength: 1 },
2805
+ transform: { enum: ["rename", "constant", "format", "lookup"] },
2806
+ value: {
2807
+ anyOf: [{ type: "null" }, { type: "string", minLength: 1 }]
2808
+ }
2809
+ }
2810
+ },
2811
+ template: {
2812
+ type: "object",
2813
+ additionalProperties: false,
2814
+ required: ["id", "title", "inputEntity", "format", "body"],
2815
+ properties: {
2816
+ id: { $ref: "#/$defs/entityId" },
2817
+ title: { type: "string", minLength: 1 },
2818
+ inputEntity: { $ref: "#/$defs/entityId" },
2819
+ format: { enum: ["pdf", "docx"] },
2820
+ body: {
2821
+ type: "string",
2822
+ pattern: "^templates/[a-z][a-z0-9-]*\\.md$"
2823
+ }
2824
+ }
2704
2825
  }
2705
2826
  }
2706
2827
  };
@@ -2867,6 +2988,15 @@ var project_schema_default = {
2867
2988
  $ref: "#/$defs/githubAccount"
2868
2989
  },
2869
2990
  uniqueItems: true
2991
+ },
2992
+ labels: {
2993
+ type: "array",
2994
+ maxItems: 10,
2995
+ items: {
2996
+ type: "string",
2997
+ pattern: "^[A-Za-z0-9][A-Za-z0-9 ._:-]{0,49}$"
2998
+ },
2999
+ uniqueItems: true
2870
3000
  }
2871
3001
  }
2872
3002
  },
@@ -3114,8 +3244,79 @@ var projectSchema = project_schema_default;
3114
3244
  var moduleCatalogSchema = module_catalog_schema_default;
3115
3245
  var moduleArtifactSchema = module_artifact_schema_default;
3116
3246
 
3247
+ // ../contracts/src/translations.ts
3248
+ var PLURAL_CATEGORIES = [
3249
+ "zero",
3250
+ "one",
3251
+ "two",
3252
+ "few",
3253
+ "many",
3254
+ "other"
3255
+ ];
3256
+ var WHOLE_NUMBER_SAMPLES = [
3257
+ ...Array.from({ length: 200 }, (_, index) => index),
3258
+ 1e3,
3259
+ 1e4,
3260
+ 1e5,
3261
+ 1e6,
3262
+ 1e7,
3263
+ 1e8,
3264
+ 1e9
3265
+ ];
3266
+ function requiredPluralCategories(locale) {
3267
+ const rules = new Intl.PluralRules(locale);
3268
+ const selected = /* @__PURE__ */ new Set(["other"]);
3269
+ for (const sample of WHOLE_NUMBER_SAMPLES) selected.add(rules.select(sample));
3270
+ return PLURAL_CATEGORIES.filter((category) => selected.has(category));
3271
+ }
3272
+ function pluralMember(bundle, key) {
3273
+ const dot = key.lastIndexOf(".");
3274
+ if (dot <= 0) return null;
3275
+ const category = key.slice(dot + 1);
3276
+ if (!PLURAL_CATEGORIES.includes(category)) return null;
3277
+ const base = key.slice(0, dot);
3278
+ return Object.hasOwn(bundle, base + ".other") ? { base, category } : null;
3279
+ }
3280
+ function translationKeys(bundle) {
3281
+ const keys = /* @__PURE__ */ new Set();
3282
+ for (const key of Object.keys(bundle)) {
3283
+ keys.add(pluralMember(bundle, key)?.base ?? key);
3284
+ }
3285
+ return [...keys].sort();
3286
+ }
3287
+ function pluralFamilyIssues(bundle, locale) {
3288
+ const families = /* @__PURE__ */ new Map();
3289
+ for (const key of Object.keys(bundle)) {
3290
+ const member = pluralMember(bundle, key);
3291
+ if (member === null) continue;
3292
+ const categories = families.get(member.base) ?? /* @__PURE__ */ new Set();
3293
+ categories.add(member.category);
3294
+ families.set(member.base, categories);
3295
+ }
3296
+ const required = requiredPluralCategories(locale);
3297
+ const known = new Intl.PluralRules(
3298
+ locale
3299
+ ).resolvedOptions().pluralCategories;
3300
+ const issues = [];
3301
+ for (const base of [...families.keys()].sort()) {
3302
+ const categories = families.get(base);
3303
+ const missing = required.filter((category) => !categories.has(category));
3304
+ if (missing.length > 0) {
3305
+ issues.push(
3306
+ `${base} lacks ${missing.map((category) => base + "." + category).join(", ")} for ${locale}`
3307
+ );
3308
+ }
3309
+ for (const category of categories) {
3310
+ if (!known.includes(category)) {
3311
+ issues.push(`${base}.${category} is never selected for ${locale}`);
3312
+ }
3313
+ }
3314
+ }
3315
+ return issues;
3316
+ }
3317
+
3117
3318
  // ../contracts/src/index.ts
3118
- var PLATFORM_API_VERSION = "0.1.8";
3319
+ var PLATFORM_API_VERSION = "0.1.21";
3119
3320
 
3120
3321
  // ../kernel/src/module-compatibility.ts
3121
3322
  function satisfiesModuleVersion(version, range) {
@@ -3298,6 +3499,10 @@ var validators = {
3298
3499
  cliExtension: ajv.compile(cliExtensionSchema),
3299
3500
  platformSpec: ajv.compile(platformSpecSchema)
3300
3501
  };
3502
+ var MONTH_NAMES = "jan feb mar apr may jun jul aug sep oct nov dec".split(
3503
+ " "
3504
+ );
3505
+ var WEEKDAY_NAMES = "sun mon tue wed thu fri sat".split(" ");
3301
3506
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
3302
3507
  ".git",
3303
3508
  ".flowdular",
@@ -3796,7 +4001,7 @@ import { dirname as dirname6, join as join6, relative as relative5 } from "node:
3796
4001
  import { createRequire } from "node:module";
3797
4002
  import { readFile as readFile5 } from "node:fs/promises";
3798
4003
  import { dirname as dirname4, join as join3 } from "node:path";
3799
- var SDK_VERSION = "0.3.1";
4004
+ var SDK_VERSION = "0.4.0";
3800
4005
  function npmPackage(specifier) {
3801
4006
  return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
3802
4007
  }
@@ -3900,6 +4105,115 @@ async function findModuleFiles(workspace) {
3900
4105
  import { access as access2, readFile as readFile7, readdir as readdir3 } from "node:fs/promises";
3901
4106
  import { dirname as dirname5, join as join5, relative as relative4 } from "node:path";
3902
4107
  import { parse as parseYaml3 } from "yaml";
4108
+
4109
+ // src/dom-properties.ts
4110
+ var DOM_PROPERTY_SPELLINGS = [
4111
+ "acceptCharset",
4112
+ "allowFullScreen",
4113
+ "autoCapitalize",
4114
+ "autoComplete",
4115
+ "autoCorrect",
4116
+ "autoFocus",
4117
+ "autoPlay",
4118
+ "charSet",
4119
+ "contentEditable",
4120
+ "crossOrigin",
4121
+ "defaultChecked",
4122
+ "defaultValue",
4123
+ "disablePictureInPicture",
4124
+ "disableRemotePlayback",
4125
+ "encType",
4126
+ "fetchPriority",
4127
+ "formAction",
4128
+ "formEncType",
4129
+ "formMethod",
4130
+ "formNoValidate",
4131
+ "formTarget",
4132
+ "httpEquiv",
4133
+ "imageSizes",
4134
+ "imageSrcSet",
4135
+ "inputMode",
4136
+ "itemID",
4137
+ "itemProp",
4138
+ "itemRef",
4139
+ "itemScope",
4140
+ "itemType",
4141
+ "maxLength",
4142
+ "noModule",
4143
+ "noValidate",
4144
+ "playsInline",
4145
+ "readOnly",
4146
+ "referrerPolicy",
4147
+ "spellCheck",
4148
+ "srcDoc",
4149
+ "srcLang",
4150
+ "srcSet",
4151
+ "tabIndex",
4152
+ "viewBox"
4153
+ ];
4154
+ var SPELLINGS = new Map(
4155
+ DOM_PROPERTY_SPELLINGS.map((name) => [name.toLowerCase(), name])
4156
+ );
4157
+ function hostTagAttributes(source) {
4158
+ const tags = [];
4159
+ for (const match of source.matchAll(/<[a-z][a-z0-9-]*/g)) {
4160
+ const start = match.index + match[0].length;
4161
+ let depth = 0;
4162
+ let quote = "";
4163
+ let text = "";
4164
+ let end = start;
4165
+ for (; end < source.length; end += 1) {
4166
+ const character = source[end];
4167
+ if (quote !== "") {
4168
+ if (character === quote) quote = "";
4169
+ text += " ";
4170
+ continue;
4171
+ }
4172
+ if (depth > 0) {
4173
+ if (character === "{") depth += 1;
4174
+ else if (character === "}") depth -= 1;
4175
+ text += depth === 0 ? character : " ";
4176
+ continue;
4177
+ }
4178
+ if (character === "{") {
4179
+ depth = 1;
4180
+ text += character;
4181
+ continue;
4182
+ }
4183
+ if (character === '"' || character === "'") {
4184
+ quote = character;
4185
+ text += character;
4186
+ continue;
4187
+ }
4188
+ if (character === ">") break;
4189
+ text += character;
4190
+ }
4191
+ tags.push({ start, text });
4192
+ }
4193
+ return tags;
4194
+ }
4195
+ function domPropertyMisspellings(source) {
4196
+ const found = [];
4197
+ for (const tag of hostTagAttributes(source)) {
4198
+ for (const attribute of tag.text.matchAll(
4199
+ /(?<=\s)([A-Za-z][A-Za-z-]*)=/g
4200
+ )) {
4201
+ const name = attribute[1];
4202
+ const expected = SPELLINGS.get(name.toLowerCase());
4203
+ if (expected === void 0 || expected === name) continue;
4204
+ const index = tag.start + attribute.index;
4205
+ found.push({
4206
+ name,
4207
+ expected,
4208
+ index,
4209
+ line: source.slice(0, index).split("\n").length
4210
+ });
4211
+ }
4212
+ }
4213
+ return found.sort((left, right) => left.index - right.index);
4214
+ }
4215
+
4216
+ // src/module-validate.ts
3903
4217
  function issue(code, message, path, severity = "error") {
3904
4218
  return { code, message, path, severity };
3905
4219
  }
@@ -4088,6 +4402,7 @@ async function translationIssues(moduleRoot, manifest, projectLocales) {
4088
4402
  }
4089
4403
  if (!manifest.capabilities.includes("translations")) return issues;
4090
4404
  const keySets = /* @__PURE__ */ new Map();
4405
+ const referenceBundleKeys = /* @__PURE__ */ new Set();
4091
4406
  for (const locale of manifest.locales) {
4092
4407
  const path = `translations/${locale}.json`;
4093
4408
  if (!await exists(join5(moduleRoot, path))) {
@@ -4105,7 +4420,21 @@ async function translationIssues(moduleRoot, manifest, projectLocales) {
4105
4420
  if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
4106
4421
  throw new Error("Expected a JSON object.");
4107
4422
  }
4108
- keySets.set(locale, Object.keys(bundle).sort());
4423
+ const strings = bundle;
4424
+ keySets.set(locale, translationKeys(strings));
4425
+ if (keySets.size === 1) {
4426
+ for (const key of Object.keys(strings)) referenceBundleKeys.add(key);
4427
+ }
4428
+ const plurals = pluralFamilyIssues(strings, locale);
4429
+ if (plurals.length > 0) {
4430
+ issues.push(
4431
+ issue(
4432
+ "TRANSLATION_PLURAL_INCOMPLETE",
4433
+ `${path} plural families do not match the ${locale} plural rules: ${plurals.join("; ")}.`,
4434
+ path
4435
+ )
4436
+ );
4437
+ }
4109
4438
  } catch (error) {
4110
4439
  issues.push(
4111
4440
  issue(
@@ -4134,7 +4463,7 @@ async function translationIssues(moduleRoot, manifest, projectLocales) {
4134
4463
  )
4135
4464
  );
4136
4465
  }
4137
- const referenceKeys = new Set(reference[1]);
4466
+ const referenceKeys = /* @__PURE__ */ new Set([...reference[1], ...referenceBundleKeys]);
4138
4467
  const files = await clientSourceFiles(moduleRoot);
4139
4468
  if (files.length > 0) {
4140
4469
  const namespace = manifest.id.split(".")[0] ?? manifest.id;
@@ -4166,6 +4495,21 @@ async function translationIssues(moduleRoot, manifest, projectLocales) {
4166
4495
  }
4167
4496
  return issues;
4168
4497
  }
4498
+ function filtersTags(source) {
4499
+ const tags = [];
4500
+ for (const match of source.matchAll(/<Filters\b/g)) {
4501
+ let depth = 0;
4502
+ let end = match.index + match[0].length;
4503
+ for (; end < source.length; end += 1) {
4504
+ const character = source[end];
4505
+ if (character === "{") depth += 1;
4506
+ else if (character === "}") depth -= 1;
4507
+ else if (character === ">" && depth === 0) break;
4508
+ }
4509
+ tags.push(source.slice(match.index, end));
4510
+ }
4511
+ return tags;
4512
+ }
4169
4513
  async function userInterfaceIssues(moduleRoot) {
4170
4514
  const issues = [];
4171
4515
  for (const file of await clientSourceFiles(moduleRoot)) {
@@ -4189,6 +4533,27 @@ async function userInterfaceIssues(moduleRoot) {
4189
4533
  )
4190
4534
  );
4191
4535
  }
4536
+ const misspelled = domPropertyMisspellings(source);
4537
+ if (misspelled.length > 0) {
4538
+ issues.push(
4539
+ issue(
4540
+ "DOM_PROPERTY_CASING",
4541
+ "Host elements use Octane's DOM property spelling: " + misspelled.map(
4542
+ (found) => `line ${found.line} ${found.name} as ${found.expected}`
4543
+ ).join(", ") + ".",
4544
+ path
4545
+ )
4546
+ );
4547
+ }
4548
+ if (filtersTags(source).some((tag) => !/\slabel=/.test(tag))) {
4549
+ issues.push(
4550
+ issue(
4551
+ "FILTERS_LABEL_MISSING",
4552
+ 'Filters from @flowdular/ui needs a translated label; without one it shows the English word "Filters" in every locale.',
4553
+ path
4554
+ )
4555
+ );
4556
+ }
4192
4557
  }
4193
4558
  return issues;
4194
4559
  }
@@ -4710,8 +5075,8 @@ import type { WebMount } from '${serverPackage}';
4710
5075
  ` + capabilityScope(module) + ` metrics: createModuleMetrics('${module.id}'),
4711
5076
  }), moduleId: '${module.id}' },`
4712
5077
  ).join("\n");
4713
- return GENERATED_HEADER + typeImport + `import { createModuleMetrics } from '${serverPackage}';
4714
- ` + imports + "\n\nexport function composeModuleServer(\n context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [\n" + calls + "\n ];\n}\n\nexport const moduleWebMounts: readonly WebMount[] = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
5078
+ return GENERATED_HEADER + typeImport + `import { bindModuleCompositions, createModuleMetrics } from '${serverPackage}';
5079
+ ` + imports + "\n\nexport function composeModuleServer(\n context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return bindModuleCompositions([\n" + calls + "\n ]);\n}\n\nexport const moduleWebMounts: readonly WebMount[] = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
4715
5080
  }
4716
5081
  function generateClientComposition(modules, sdk = false, applicationPath = "/app") {
4717
5082
  const client = modules.filter((module) => module.client);