flowdular 0.5.1 → 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.
@@ -1427,7 +1427,7 @@ var project_schema_default = {
1427
1427
  },
1428
1428
  $defs: {
1429
1429
  deliveryTarget: {
1430
- enum: ["workspace", "git-pr", "official-modules"]
1430
+ enum: ["workspace", "git-pr"]
1431
1431
  },
1432
1432
  gitName: {
1433
1433
  type: "string",
@@ -1590,14 +1590,6 @@ var moduleCatalogSchema = module_catalog_schema_default;
1590
1590
  var moduleArtifactSchema = module_artifact_schema_default;
1591
1591
 
1592
1592
  // ../contracts/src/translations.ts
1593
- var PLURAL_CATEGORIES = [
1594
- "zero",
1595
- "one",
1596
- "two",
1597
- "few",
1598
- "many",
1599
- "other"
1600
- ];
1601
1593
  var WHOLE_NUMBER_SAMPLES = [
1602
1594
  ...Array.from({ length: 200 }, (_, index) => index),
1603
1595
  1e3,
@@ -1608,61 +1600,6 @@ var WHOLE_NUMBER_SAMPLES = [
1608
1600
  1e8,
1609
1601
  1e9
1610
1602
  ];
1611
- function requiredPluralCategories(locale) {
1612
- const rules = new Intl.PluralRules(locale);
1613
- const selected = /* @__PURE__ */ new Set(["other"]);
1614
- for (const sample of WHOLE_NUMBER_SAMPLES) selected.add(rules.select(sample));
1615
- return PLURAL_CATEGORIES.filter((category) => selected.has(category));
1616
- }
1617
- function pluralMember(bundle, key) {
1618
- const dot = key.lastIndexOf(".");
1619
- if (dot <= 0) return null;
1620
- const category = key.slice(dot + 1);
1621
- if (!PLURAL_CATEGORIES.includes(category)) return null;
1622
- const base = key.slice(0, dot);
1623
- if (!Object.hasOwn(bundle, base + ".other")) return null;
1624
- const inflected = PLURAL_CATEGORIES.some(
1625
- (other) => other !== "other" && Object.hasOwn(bundle, base + "." + other)
1626
- );
1627
- return inflected ? { base, category } : null;
1628
- }
1629
- function translationKeys(bundle) {
1630
- const keys = /* @__PURE__ */ new Set();
1631
- for (const key of Object.keys(bundle)) {
1632
- keys.add(pluralMember(bundle, key)?.base ?? key);
1633
- }
1634
- return [...keys].sort();
1635
- }
1636
- function pluralFamilyIssues(bundle, locale) {
1637
- const families = /* @__PURE__ */ new Map();
1638
- for (const key of Object.keys(bundle)) {
1639
- const member = pluralMember(bundle, key);
1640
- if (member === null) continue;
1641
- const categories = families.get(member.base) ?? /* @__PURE__ */ new Set();
1642
- categories.add(member.category);
1643
- families.set(member.base, categories);
1644
- }
1645
- const required = requiredPluralCategories(locale);
1646
- const known = new Intl.PluralRules(
1647
- locale
1648
- ).resolvedOptions().pluralCategories;
1649
- const issues = [];
1650
- for (const base of [...families.keys()].sort()) {
1651
- const categories = families.get(base);
1652
- const missing = required.filter((category) => !categories.has(category));
1653
- if (missing.length > 0) {
1654
- issues.push(
1655
- `${base} lacks ${missing.map((category) => base + "." + category).join(", ")} for ${locale}`
1656
- );
1657
- }
1658
- for (const category of categories) {
1659
- if (!known.includes(category)) {
1660
- issues.push(`${base}.${category} is never selected for ${locale}`);
1661
- }
1662
- }
1663
- }
1664
- return issues;
1665
- }
1666
1603
 
1667
1604
  // ../contracts/src/web.ts
1668
1605
  var RESERVED_WEB_SEGMENTS = Object.freeze([
@@ -1733,7 +1670,7 @@ var EXPRESSIONS = new Map(
1733
1670
  );
1734
1671
 
1735
1672
  // ../contracts/src/index.ts
1736
- var PLATFORM_API_VERSION = "0.1.29";
1673
+ var PLATFORM_API_VERSION = "0.2.3";
1737
1674
 
1738
1675
  // ../kernel/src/module-compatibility.ts
1739
1676
  function satisfiesModuleVersion(version, range) {
@@ -1745,18 +1682,42 @@ function compareModuleVersions(left, right) {
1745
1682
  if (!first || !second) return 0;
1746
1683
  return compareVersions(second, first);
1747
1684
  }
1685
+ function assertPlatformLine(moduleId, range, platformVersion) {
1686
+ const [major, minor] = parseVersion(platformVersion);
1687
+ const line = [major, minor, 0];
1688
+ const candidates = [[0, 0, 0]];
1689
+ for (const [named] of range.matchAll(/\d+\.\d+\.\d+/g)) {
1690
+ const version = parseVersion(named);
1691
+ candidates.push(version, [version[0], version[1], version[2] + 1]);
1692
+ }
1693
+ const older = candidates.some(
1694
+ (candidate) => compareVersions(candidate, line) < 0 && rangeSatisfies(candidate.join("."), range)
1695
+ );
1696
+ if (!older) return;
1697
+ throw new RegistryError(
1698
+ "MODULE_PLATFORM_INCOMPATIBLE",
1699
+ `${moduleId} declares platform API ${range}, which also admits versions before ${line.join(".")}; declare "^${line.join(".")}".`
1700
+ );
1701
+ }
1748
1702
  function assertModuleCompatibility(manifest, platformVersion = PLATFORM_API_VERSION) {
1749
1703
  if (parseVersion(manifest.version) === null)
1750
1704
  throw new RegistryError(
1751
1705
  "MODULE_VERSION_INVALID",
1752
1706
  `Invalid version for ${manifest.id}: ${manifest.version}`
1753
1707
  );
1754
- if (manifest.platformApi !== void 0 && (!isValidRange(manifest.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest.platformApi))) {
1708
+ if (manifest.platformApi === void 0)
1709
+ throw new RegistryError(
1710
+ "MODULE_PLATFORM_REQUIRED",
1711
+ `${manifest.id} declares no platformApi; add "platformApi": "^${PLATFORM_API_VERSION}".`
1712
+ );
1713
+ if (!isValidRange(manifest.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest.platformApi)) {
1755
1714
  throw new RegistryError(
1756
1715
  "MODULE_PLATFORM_INCOMPATIBLE",
1757
1716
  `${manifest.id} requires platform API ${manifest.platformApi}; available ${PLATFORM_API_VERSION}.`
1758
1717
  );
1759
1718
  }
1719
+ if (platformVersion !== null)
1720
+ assertPlatformLine(manifest.id, manifest.platformApi, platformVersion);
1760
1721
  const ids = /* @__PURE__ */ new Set();
1761
1722
  for (const dependency of manifest.dependencies) {
1762
1723
  if (!dependency.range.trim() || !isValidRange(dependency.range))
@@ -1926,7 +1887,7 @@ var WEEKDAY_NAMES = "sun mon tue wed thu fri sat".split(" ");
1926
1887
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
1927
1888
  ".git",
1928
1889
  ".flowdular",
1929
- ".coreloom",
1890
+ ".vercel",
1930
1891
  "node_modules",
1931
1892
  "dist"
1932
1893
  ]);
@@ -2040,11 +2001,6 @@ function validateModuleArtifact(value) {
2040
2001
  "Invalid module artifact."
2041
2002
  );
2042
2003
  assertModuleCompatibility(artifact.manifest);
2043
- distributionAssert(
2044
- artifact.manifest.platformApi,
2045
- "MODULE_PLATFORM_REQUIRED",
2046
- "Distributed modules must declare platformApi."
2047
- );
2048
2004
  const paths = /* @__PURE__ */ new Set();
2049
2005
  let size = 0;
2050
2006
  for (const file of artifact.files) {
@@ -2167,7 +2123,6 @@ async function packModule(root, review) {
2167
2123
 
2168
2124
  // src/module-catalog.ts
2169
2125
  import { readFile as readFile4, stat } from "node:fs/promises";
2170
- import { dirname as dirname3, resolve as resolve2 } from "node:path";
2171
2126
 
2172
2127
  // src/workspace.ts
2173
2128
  import { access, readFile as readFile3, realpath } from "node:fs/promises";
@@ -2191,8 +2146,14 @@ async function resolveExistingInside(root, candidate) {
2191
2146
  }
2192
2147
 
2193
2148
  // src/module-catalog.ts
2194
- var OFFICIAL_CATALOG = "https://raw.githubusercontent.com/Flowdular/official-modules/main/registry/index.json";
2195
- var OFFICIAL_PREFIX = "https://raw.githubusercontent.com/Flowdular/official-modules/";
2149
+ function platformAdmits(manifest) {
2150
+ try {
2151
+ assertModuleCompatibility(manifest);
2152
+ return true;
2153
+ } catch {
2154
+ return false;
2155
+ }
2156
+ }
2196
2157
  async function boundedRead(location, limit, fetcher = fetch) {
2197
2158
  if (!location.startsWith("https://")) {
2198
2159
  distributionAssert(
@@ -2208,10 +2169,11 @@ async function boundedRead(location, limit, fetcher = fetch) {
2208
2169
  );
2209
2170
  return result;
2210
2171
  }
2172
+ const url = new URL(location);
2211
2173
  distributionAssert(
2212
- location.startsWith(OFFICIAL_PREFIX),
2174
+ url.username === "" && url.password === "" && !url.search && !url.hash,
2213
2175
  "MODULE_SOURCE_UNTRUSTED",
2214
- "Only the official HTTPS publisher is supported. Use an explicit local catalog for offline releases."
2176
+ "HTTPS module sources must not contain credentials, a query or a fragment."
2215
2177
  );
2216
2178
  const response = await fetcher(location, {
2217
2179
  redirect: "error",
@@ -2220,7 +2182,7 @@ async function boundedRead(location, limit, fetcher = fetch) {
2220
2182
  distributionAssert(
2221
2183
  response.ok && response.body,
2222
2184
  "MODULE_DOWNLOAD_FAILED",
2223
- `Official module download failed (${response.status}).`
2185
+ `Module download failed (${response.status}).`
2224
2186
  );
2225
2187
  const reader = response.body.getReader();
2226
2188
  const chunks = [];
@@ -2242,11 +2204,11 @@ async function boundedRead(location, limit, fetcher = fetch) {
2242
2204
  }
2243
2205
  return Buffer.concat(chunks);
2244
2206
  }
2245
- async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
2207
+ async function loadModuleCatalog(location, fetcher = fetch) {
2246
2208
  distributionAssert(
2247
- location === OFFICIAL_CATALOG || !/^[a-z][a-z0-9+.-]*:/i.test(location),
2209
+ location.startsWith("https://") || !/^[a-z][a-z0-9+.-]*:/i.test(location),
2248
2210
  "MODULE_SOURCE_UNTRUSTED",
2249
- "Use the official catalog or an explicit local catalog path."
2211
+ "Use an explicit HTTPS or local catalog path."
2250
2212
  );
2251
2213
  const catalog = JSON.parse(
2252
2214
  (await boundedRead(location, 4 * 1024 * 1024, fetcher)).toString("utf8")
@@ -2264,11 +2226,6 @@ async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
2264
2226
  "Invalid module release."
2265
2227
  );
2266
2228
  assertModuleCompatibility(release.manifest, null);
2267
- distributionAssert(
2268
- release.manifest.platformApi,
2269
- "MODULE_PLATFORM_REQUIRED",
2270
- "Distributed modules must declare platformApi."
2271
- );
2272
2229
  const identity = release.manifest.id + "@" + release.manifest.version;
2273
2230
  distributionAssert(
2274
2231
  !identities.has(identity),
@@ -2279,26 +2236,6 @@ async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
2279
2236
  }
2280
2237
  return { catalog, location };
2281
2238
  }
2282
- async function releaseLocation(source, release) {
2283
- if (source.location.startsWith("https://")) {
2284
- const prefix = OFFICIAL_PREFIX + release.sourceCommit + "/";
2285
- distributionAssert(
2286
- release.artifact.startsWith(prefix) && new URL(release.artifact).href === release.artifact && !new URL(release.artifact).search && !new URL(release.artifact).hash,
2287
- "MODULE_SOURCE_UNTRUSTED",
2288
- "Release artifact must be pinned to its official source commit."
2289
- );
2290
- return release.artifact;
2291
- }
2292
- distributionAssert(
2293
- !/^[a-z][a-z0-9+.-]*:/i.test(release.artifact),
2294
- "MODULE_SOURCE_UNTRUSTED",
2295
- "An offline catalog must use local artifacts."
2296
- );
2297
- return resolveExistingInside(
2298
- dirname3(resolve2(source.location)),
2299
- release.artifact
2300
- );
2301
- }
2302
2239
  function resolveModuleReleases(catalog, target, installed) {
2303
2240
  const split = target.lastIndexOf("@");
2304
2241
  const id = split < 0 ? target : target.slice(0, split);
@@ -2336,10 +2273,7 @@ function resolveModuleReleases(catalog, target, installed) {
2336
2273
  ];
2337
2274
  }
2338
2275
  function platformCompatible(candidate) {
2339
- return satisfiesModuleVersion(
2340
- PLATFORM_API_VERSION,
2341
- candidate.manifest.platformApi
2342
- );
2276
+ return platformAdmits(candidate.manifest);
2343
2277
  }
2344
2278
  let attempts = 0;
2345
2279
  function search(selected2, pending) {
@@ -2397,31 +2331,24 @@ function resolveModuleReleases(catalog, target, installed) {
2397
2331
  return release ? [release] : [];
2398
2332
  });
2399
2333
  }
2400
- async function readRelease(source, release, fetcher = fetch) {
2401
- return boundedRead(
2402
- await releaseLocation(source, release),
2403
- MAX_ARTIFACT_BYTES,
2404
- fetcher
2405
- );
2406
- }
2407
2334
 
2408
2335
  // src/module-install.ts
2409
2336
  import {
2410
2337
  lstat as lstat3,
2411
2338
  mkdir,
2412
- readFile as readFile8,
2413
- readdir as readdir4,
2339
+ readFile as readFile7,
2340
+ readdir as readdir3,
2414
2341
  rename,
2415
2342
  rm,
2416
2343
  writeFile
2417
2344
  } from "node:fs/promises";
2418
- import { dirname as dirname6, join as join6, relative as relative5 } from "node:path";
2345
+ import { dirname as dirname4, join as join5, relative as relative4 } from "node:path";
2419
2346
 
2420
2347
  // src/sdk.ts
2421
2348
  import { createRequire } from "node:module";
2422
2349
  import { readFile as readFile5 } from "node:fs/promises";
2423
- import { dirname as dirname4, join as join3 } from "node:path";
2424
- var SDK_VERSION = "0.5.1";
2350
+ import { dirname as dirname3, join as join3 } from "node:path";
2351
+ var SDK_VERSION = "0.6.0";
2425
2352
  function npmPackage(specifier) {
2426
2353
  return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
2427
2354
  }
@@ -2445,7 +2372,7 @@ async function sdkModules(workspace) {
2445
2372
  if (!/^@flowdular\/sdk\/modules\/[a-z0-9-]+$/.test(entry.import))
2446
2373
  throw new Error("Invalid SDK module entrypoint.");
2447
2374
  const path = await resolveExistingInside(
2448
- dirname4(indexPath),
2375
+ dirname3(indexPath),
2449
2376
  entry.manifest
2450
2377
  );
2451
2378
  if (result.has(path)) throw new Error("Duplicate SDK module path.");
@@ -2522,10 +2449,184 @@ async function findModuleFiles(workspace) {
2522
2449
  }
2523
2450
 
2524
2451
  // src/module-validate.ts
2525
- import { access as access2, readFile as readFile7, readdir as readdir3 } from "node:fs/promises";
2526
- import { dirname as dirname5, join as join5, relative as relative4 } from "node:path";
2527
2452
  import { parse as parseYaml3 } from "yaml";
2528
2453
 
2454
+ // ../database/src/contracts.ts
2455
+ var DATABASE_ADAPTER_IDS = Object.freeze({
2456
+ postgresql: "flowdular.postgresql"
2457
+ });
2458
+ var DATABASE_DIALECT_IDS = Object.freeze({
2459
+ postgresql: "postgresql"
2460
+ });
2461
+ var DATABASE_CAPABILITY_IDS = Object.freeze({
2462
+ MIGRATION_LOCK: "flowdular.database.migration-lock",
2463
+ RETURNING: "flowdular.database.returning",
2464
+ ROW_LEVEL_SECURITY: "flowdular.database.row-level-security",
2465
+ SCHEMA_INTROSPECTION: "flowdular.database.schema-introspection",
2466
+ TENANT_CONTEXT: "flowdular.database.tenant-context",
2467
+ TRANSACTIONAL_DDL: "flowdular.database.transactional-ddl",
2468
+ TRANSACTIONS: "flowdular.database.transactions"
2469
+ });
2470
+ var DatabaseError = class extends Error {
2471
+ constructor(code, message, options) {
2472
+ super(message, options);
2473
+ this.code = code;
2474
+ this.name = "DatabaseError";
2475
+ }
2476
+ code;
2477
+ };
2478
+ function assertDatabaseId(id, kind) {
2479
+ if (!/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/.test(id)) {
2480
+ throw new DatabaseError(
2481
+ "INVALID_ARGUMENT",
2482
+ `"${id}" is not a valid database ${kind} id.`
2483
+ );
2484
+ }
2485
+ }
2486
+ function assertSchemaName(name) {
2487
+ if (!/^[a-z_][a-z0-9_]{0,62}$/.test(name)) {
2488
+ throw new DatabaseError(
2489
+ "INVALID_ARGUMENT",
2490
+ `"${name}" is not a valid database schema object name.`
2491
+ );
2492
+ }
2493
+ }
2494
+ function databasePlaceholder(dialectId, position) {
2495
+ assertDatabaseId(dialectId, "dialect");
2496
+ if (!Number.isSafeInteger(position) || position < 1) {
2497
+ throw new DatabaseError(
2498
+ "INVALID_ARGUMENT",
2499
+ "Database placeholder positions start at 1."
2500
+ );
2501
+ }
2502
+ if (dialectId === DATABASE_DIALECT_IDS.postgresql) return `$${position}`;
2503
+ throw new DatabaseError(
2504
+ "UNSUPPORTED_CAPABILITY",
2505
+ `The well-known placeholder helper does not support dialect "${dialectId}".`
2506
+ );
2507
+ }
2508
+ function databaseDialectSql(dialectId) {
2509
+ assertDatabaseId(dialectId, "dialect");
2510
+ const ledger = dialectId === DATABASE_DIALECT_IDS.postgresql ? { appliedAt: "BIGINT", suffix: "" } : void 0;
2511
+ if (!ledger) {
2512
+ throw new DatabaseError(
2513
+ "UNSUPPORTED_CAPABILITY",
2514
+ `The well-known dialect SQL helper does not support dialect "${dialectId}".`
2515
+ );
2516
+ }
2517
+ return Object.freeze({
2518
+ placeholder: (position) => databasePlaceholder(dialectId, position),
2519
+ migrationLedgerDdl: (table) => {
2520
+ assertSchemaName(table);
2521
+ return `CREATE TABLE IF NOT EXISTS ${table} (
2522
+ namespace TEXT NOT NULL,
2523
+ id TEXT NOT NULL,
2524
+ dialect TEXT NOT NULL,
2525
+ checksum TEXT NOT NULL,
2526
+ applied_at ${ledger.appliedAt} NOT NULL,
2527
+ PRIMARY KEY (namespace, id)
2528
+ )${ledger.suffix};
2529
+ -- Default application grants must never make the checksum ledger writable.
2530
+ -- Revoke existing grants as well, so databases created before this rule heal.
2531
+ DO $ledger_privileges$
2532
+ DECLARE
2533
+ granted_role RECORD;
2534
+ BEGIN
2535
+ FOR granted_role IN
2536
+ SELECT DISTINCT acl.grantee, roles.rolname
2537
+ FROM pg_class AS relation
2538
+ CROSS JOIN LATERAL aclexplode(relation.relacl) AS acl
2539
+ LEFT JOIN pg_roles AS roles ON roles.oid = acl.grantee
2540
+ WHERE relation.oid = '${table}'::regclass
2541
+ AND acl.grantee <> relation.relowner
2542
+ LOOP
2543
+ EXECUTE format('REVOKE ALL PRIVILEGES ON TABLE %s FROM %s',
2544
+ '${table}'::regclass,
2545
+ CASE WHEN granted_role.grantee = 0 THEN 'PUBLIC'
2546
+ ELSE quote_ident(granted_role.rolname) END);
2547
+ END LOOP;
2548
+ END
2549
+ $ledger_privileges$;`;
2550
+ }
2551
+ });
2552
+ }
2553
+
2554
+ // ../database/src/postgresql.ts
2555
+ var CAPABILITIES = Object.freeze({
2556
+ features: Object.freeze([
2557
+ DATABASE_CAPABILITY_IDS.MIGRATION_LOCK,
2558
+ DATABASE_CAPABILITY_IDS.RETURNING,
2559
+ DATABASE_CAPABILITY_IDS.ROW_LEVEL_SECURITY,
2560
+ DATABASE_CAPABILITY_IDS.SCHEMA_INTROSPECTION,
2561
+ DATABASE_CAPABILITY_IDS.TENANT_CONTEXT,
2562
+ DATABASE_CAPABILITY_IDS.TRANSACTIONAL_DDL,
2563
+ DATABASE_CAPABILITY_IDS.TRANSACTIONS
2564
+ ]),
2565
+ parameterStyle: "numbered",
2566
+ migrationLock: "advisory-transaction",
2567
+ transactionalDdl: true,
2568
+ returning: true,
2569
+ isolationLevels: [
2570
+ "read-committed",
2571
+ "repeatable-read",
2572
+ "serializable"
2573
+ ],
2574
+ cancellation: "before-start",
2575
+ schemaIntrospection: true,
2576
+ tenantIsolation: "transaction-local-rls",
2577
+ rootOperations: "allowed",
2578
+ sql: databaseDialectSql(DATABASE_DIALECT_IDS.postgresql)
2579
+ });
2580
+
2581
+ // ../database/src/provider.ts
2582
+ var PGLITE_RUNTIME_ROLE = "flowdular_runtime";
2583
+ var PGLITE_BACKGROUND_ROLE = "flowdular_background";
2584
+ var PGLITE_BOOTSTRAP = `DO $$
2585
+ BEGIN
2586
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PGLITE_RUNTIME_ROLE}') THEN
2587
+ CREATE ROLE ${PGLITE_RUNTIME_ROLE} NOSUPERUSER NOBYPASSRLS;
2588
+ END IF;
2589
+ END
2590
+ $$;
2591
+ DO $$
2592
+ BEGIN
2593
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PGLITE_BACKGROUND_ROLE}') THEN
2594
+ CREATE ROLE ${PGLITE_BACKGROUND_ROLE} NOSUPERUSER NOBYPASSRLS;
2595
+ END IF;
2596
+ END
2597
+ $$;
2598
+ GRANT USAGE ON SCHEMA public TO ${PGLITE_RUNTIME_ROLE}, ${PGLITE_BACKGROUND_ROLE};
2599
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
2600
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${PGLITE_RUNTIME_ROLE};
2601
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
2602
+ GRANT USAGE, SELECT ON SEQUENCES TO ${PGLITE_RUNTIME_ROLE};
2603
+ /* The background role gets no default table grant. A table it may poll across
2604
+ tenants says so itself, in its own migration, by granting the named columns
2605
+ under a FOR SELECT policy. Anything that forgets to is invisible to it. */
2606
+ `;
2607
+
2608
+ // ../database/src/backup.ts
2609
+ var BACKUP_KEY_VARIABLES = Object.freeze([
2610
+ "FD_AGENT_CREDENTIAL_KEY",
2611
+ "FD_AGENT_RUN_GRANT_KEY",
2612
+ "FD_APPROVAL_GRANT_KEY",
2613
+ "FD_WORKFLOWS_PAYLOAD_KEY",
2614
+ "FD_WORKFLOWS_CURSOR_KEY",
2615
+ "FD_AUTH_MFA_KEY",
2616
+ "FD_AUTOMATIONS_CREDENTIAL_KEY",
2617
+ "FD_NOTIFICATIONS_SECRET_KEY",
2618
+ "FD_STORAGE_ENCRYPTION_KEY",
2619
+ "FD_CONNECTORS_SECRET_KEY",
2620
+ "FD_AUDIT_ANCHOR_KEY"
2621
+ ]);
2622
+
2623
+ // ../database/src/reset.ts
2624
+ var TABLE_QUERY = Object.freeze({
2625
+ [DATABASE_DIALECT_IDS.postgresql]: `SELECT tablename AS name FROM pg_tables
2626
+ WHERE schemaname = current_schema()
2627
+ ORDER BY tablename`
2628
+ });
2629
+
2529
2630
  // src/dom-properties.ts
2530
2631
  var DOM_PROPERTY_SPELLINGS = [
2531
2632
  "acceptCharset",
@@ -2543,6 +2644,7 @@ var DOM_PROPERTY_SPELLINGS = [
2543
2644
  "disablePictureInPicture",
2544
2645
  "disableRemotePlayback",
2545
2646
  "encType",
2647
+ "enterKeyHint",
2546
2648
  "fetchPriority",
2547
2649
  "formAction",
2548
2650
  "formEncType",
@@ -2559,6 +2661,7 @@ var DOM_PROPERTY_SPELLINGS = [
2559
2661
  "itemScope",
2560
2662
  "itemType",
2561
2663
  "maxLength",
2664
+ "minLength",
2562
2665
  "noModule",
2563
2666
  "noValidate",
2564
2667
  "playsInline",
@@ -2574,426 +2677,10 @@ var DOM_PROPERTY_SPELLINGS = [
2574
2677
  var SPELLINGS = new Map(
2575
2678
  DOM_PROPERTY_SPELLINGS.map((name) => [name.toLowerCase(), name])
2576
2679
  );
2577
- function hostTagAttributes(source) {
2578
- const tags = [];
2579
- for (const match of source.matchAll(/<[a-z][a-z0-9-]*/g)) {
2580
- const start = match.index + match[0].length;
2581
- let depth = 0;
2582
- let quote = "";
2583
- let text = "";
2584
- let end = start;
2585
- for (; end < source.length; end += 1) {
2586
- const character = source[end];
2587
- if (quote !== "") {
2588
- if (character === quote) quote = "";
2589
- text += " ";
2590
- continue;
2591
- }
2592
- if (depth > 0) {
2593
- if (character === "{") depth += 1;
2594
- else if (character === "}") depth -= 1;
2595
- text += depth === 0 ? character : " ";
2596
- continue;
2597
- }
2598
- if (character === "{") {
2599
- depth = 1;
2600
- text += character;
2601
- continue;
2602
- }
2603
- if (character === '"' || character === "'") {
2604
- quote = character;
2605
- text += character;
2606
- continue;
2607
- }
2608
- if (character === ">") break;
2609
- text += character;
2610
- }
2611
- tags.push({ start, text });
2612
- }
2613
- return tags;
2614
- }
2615
- function domPropertyMisspellings(source) {
2616
- const found = [];
2617
- for (const tag of hostTagAttributes(source)) {
2618
- for (const attribute of tag.text.matchAll(
2619
- /(?<=\s)([A-Za-z][A-Za-z-]*)=/g
2620
- )) {
2621
- const name = attribute[1];
2622
- const expected = SPELLINGS.get(name.toLowerCase());
2623
- if (expected === void 0 || expected === name) continue;
2624
- const index = tag.start + attribute.index;
2625
- found.push({
2626
- name,
2627
- expected,
2628
- index,
2629
- line: source.slice(0, index).split("\n").length
2630
- });
2631
- }
2632
- }
2633
- return found.sort((left, right) => left.index - right.index);
2634
- }
2635
-
2636
- // src/module-validate.ts
2637
- function issue(code, message, path, severity = "error") {
2638
- return { code, message, path, severity };
2639
- }
2640
- async function exists(path) {
2641
- try {
2642
- await access2(path);
2643
- return true;
2644
- } catch {
2645
- return false;
2646
- }
2647
- }
2648
- async function readJson(path) {
2649
- return JSON.parse(await readFile7(path, "utf8"));
2650
- }
2651
- async function clientSourceFiles(moduleRoot) {
2652
- const clientRoot = join5(moduleRoot, "src/client");
2653
- if (!await exists(clientRoot)) return [];
2654
- return (await readdir3(clientRoot, {
2655
- recursive: true,
2656
- withFileTypes: true
2657
- })).filter((entry) => entry.isFile() && /\.(?:ts|tsx|tsrx)$/.test(entry.name)).map((entry) => join5(entry.parentPath, entry.name));
2658
- }
2659
- async function platformIssues(moduleRoot, manifest) {
2660
- const issues = [];
2661
- let exports = {};
2662
- let packageName;
2663
- let packageVersion;
2664
- try {
2665
- const pkg = await readJson(join5(moduleRoot, "package.json"));
2666
- exports = pkg.exports ?? {};
2667
- packageName = pkg.name;
2668
- packageVersion = pkg.version;
2669
- } catch (error) {
2670
- issues.push(
2671
- issue(
2672
- "PACKAGE_MANIFEST_INVALID",
2673
- `package.json could not be read: ${error instanceof Error ? error.message : String(error)}`,
2674
- "package.json"
2675
- )
2676
- );
2677
- return issues;
2678
- }
2679
- if (packageName !== manifest.package) {
2680
- issues.push(
2681
- issue(
2682
- "PACKAGE_NAME_MISMATCH",
2683
- `package.json name "${String(packageName)}" differs from module.json package "${manifest.package}".`,
2684
- "package.json"
2685
- )
2686
- );
2687
- }
2688
- if (manifest.platformApi === void 0) {
2689
- issues.push(
2690
- issue(
2691
- "PLATFORM_API_MISSING",
2692
- `module.json declares no platformApi; add "platformApi": "^${PLATFORM_API_VERSION}" so the platform contract the module was built against is checked.`,
2693
- "module.json"
2694
- )
2695
- );
2696
- }
2697
- if (packageVersion !== manifest.version) {
2698
- issues.push(
2699
- issue(
2700
- "PACKAGE_VERSION_MISMATCH",
2701
- `package.json version "${String(packageVersion)}" differs from module.json version "${manifest.version}".`,
2702
- "package.json"
2703
- )
2704
- );
2705
- }
2706
- if (manifest.platform?.server === true) {
2707
- if (!await exists(join5(moduleRoot, "src/platform.ts"))) {
2708
- issues.push(
2709
- issue(
2710
- "PLATFORM_SERVER_ENTRY_MISSING",
2711
- "platform.server requires src/platform.ts exporting createServerComposition.",
2712
- "src/platform.ts"
2713
- )
2714
- );
2715
- }
2716
- if (!("./platform" in exports)) {
2717
- issues.push(
2718
- issue(
2719
- "PLATFORM_EXPORT_MISSING",
2720
- 'platform.server requires a "./platform" entry in package.json exports.',
2721
- "package.json"
2722
- )
2723
- );
2724
- }
2725
- }
2726
- if (manifest.platform?.client === true) {
2727
- if (!await exists(join5(moduleRoot, "src/client/index.ts"))) {
2728
- issues.push(
2729
- issue(
2730
- "PLATFORM_CLIENT_ENTRY_MISSING",
2731
- "platform.client requires src/client/index.ts exporting createClientContribution.",
2732
- "src/client/index.ts"
2733
- )
2734
- );
2735
- }
2736
- if (!("./client" in exports)) {
2737
- issues.push(
2738
- issue(
2739
- "PLATFORM_CLIENT_EXPORT_MISSING",
2740
- 'platform.client requires a "./client" entry in package.json exports.',
2741
- "package.json"
2742
- )
2743
- );
2744
- }
2745
- }
2746
- return issues;
2747
- }
2748
- async function specIssues(moduleRoot, manifest, specDirectory) {
2749
- const specPath = join5(specDirectory, "module.yaml");
2750
- let spec;
2751
- try {
2752
- spec = parseYaml3(
2753
- await readFile7(join5(moduleRoot, specPath), "utf8")
2754
- );
2755
- } catch (error) {
2756
- return [
2757
- issue(
2758
- "SPEC_FILE_MISSING",
2759
- `Module specification could not be read: ${error instanceof Error ? error.message : String(error)}`,
2760
- specPath,
2761
- "warning"
2762
- )
2763
- ];
2764
- }
2765
- const issues = [];
2766
- if (spec.id !== manifest.id) {
2767
- issues.push(
2768
- issue(
2769
- "SPEC_ID_MISMATCH",
2770
- `Specification id "${String(spec.id)}" differs from module.json id "${manifest.id}".`,
2771
- specPath
2772
- )
2773
- );
2774
- }
2775
- if (String(spec.specVersion) !== manifest.version) {
2776
- issues.push(
2777
- issue(
2778
- "SPEC_VERSION_DRIFT",
2779
- `module.json version ${manifest.version} differs from specVersion ${String(spec.specVersion)}.`,
2780
- specPath,
2781
- "warning"
2782
- )
2783
- );
2784
- }
2785
- if (Array.isArray(spec.dependencies)) {
2786
- const declared = spec.dependencies.map((entry) => `${String(entry.id)}@${String(entry.range)}`);
2787
- const actual = manifest.dependencies.map(
2788
- (entry) => `${entry.id}@${entry.range}`
2789
- );
2790
- const drift = [
2791
- ...declared.filter((entry) => !actual.includes(entry)),
2792
- ...actual.filter((entry) => !declared.includes(entry))
2793
- ];
2794
- if (drift.length > 0) {
2795
- issues.push(
2796
- issue(
2797
- "SPEC_DEPENDENCY_DRIFT",
2798
- `Specification dependencies differ from module.json: ${drift.join(", ")}. Run module version bump or align the ranges by hand.`,
2799
- specPath,
2800
- "warning"
2801
- )
2802
- );
2803
- }
2804
- }
2805
- return issues;
2806
- }
2807
- async function translationIssues(moduleRoot, manifest, projectLocales) {
2808
- const issues = [];
2809
- if (projectLocales) {
2810
- for (const locale of manifest.locales) {
2811
- if (!projectLocales.includes(locale)) {
2812
- issues.push(
2813
- issue(
2814
- "LOCALE_NOT_IN_PROJECT",
2815
- `Locale "${locale}" is not listed in flowdular.json locales.`,
2816
- "module.json",
2817
- "warning"
2818
- )
2819
- );
2820
- }
2821
- }
2822
- }
2823
- if (!manifest.capabilities.includes("translations")) return issues;
2824
- const keySets = /* @__PURE__ */ new Map();
2825
- const referenceBundleKeys = /* @__PURE__ */ new Set();
2826
- for (const locale of manifest.locales) {
2827
- const path = `translations/${locale}.json`;
2828
- if (!await exists(join5(moduleRoot, path))) {
2829
- issues.push(
2830
- issue(
2831
- "TRANSLATION_FILE_MISSING",
2832
- `Locale "${locale}" has no ${path}.`,
2833
- path
2834
- )
2835
- );
2836
- continue;
2837
- }
2838
- try {
2839
- const bundle = await readJson(join5(moduleRoot, path));
2840
- if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
2841
- throw new Error("Expected a JSON object.");
2842
- }
2843
- const strings = bundle;
2844
- keySets.set(locale, translationKeys(strings));
2845
- if (keySets.size === 1) {
2846
- for (const key of Object.keys(strings)) referenceBundleKeys.add(key);
2847
- }
2848
- const plurals = pluralFamilyIssues(strings, locale);
2849
- if (plurals.length > 0) {
2850
- issues.push(
2851
- issue(
2852
- "TRANSLATION_PLURAL_INCOMPLETE",
2853
- `${path} plural families do not match the ${locale} plural rules: ${plurals.join("; ")}.`,
2854
- path
2855
- )
2856
- );
2857
- }
2858
- } catch (error) {
2859
- issues.push(
2860
- issue(
2861
- "TRANSLATION_PARSE_ERROR",
2862
- `${path} is not a JSON object: ${error instanceof Error ? error.message : String(error)}`,
2863
- path
2864
- )
2865
- );
2866
- }
2867
- }
2868
- const [reference, ...others] = [...keySets.entries()];
2869
- if (!reference) return issues;
2870
- for (const [locale, keys] of others) {
2871
- const missing = reference[1].filter((key) => !keys.includes(key));
2872
- const extra = keys.filter((key) => !reference[1].includes(key));
2873
- if (missing.length === 0 && extra.length === 0) continue;
2874
- const detail = [
2875
- ...missing.length > 0 ? [`missing ${missing.join(", ")}`] : [],
2876
- ...extra.length > 0 ? [`extra ${extra.join(", ")}`] : []
2877
- ].join("; ");
2878
- issues.push(
2879
- issue(
2880
- "TRANSLATION_KEYS_MISMATCH",
2881
- `translations/${locale}.json keys differ from translations/${reference[0]}.json: ${detail}.`,
2882
- `translations/${locale}.json`
2883
- )
2884
- );
2885
- }
2886
- const referenceKeys = /* @__PURE__ */ new Set([...reference[1], ...referenceBundleKeys]);
2887
- const files = await clientSourceFiles(moduleRoot);
2888
- if (files.length > 0) {
2889
- const namespace = manifest.id.split(".")[0] ?? manifest.id;
2890
- const prefix = namespace + ".";
2891
- const missing = /* @__PURE__ */ new Map();
2892
- const pattern = /\bt\s*\(\s*(['"])([a-zA-Z0-9._-]+)\1/g;
2893
- for (const file of files) {
2894
- const source = await readFile7(file, "utf8");
2895
- for (const match of source.matchAll(pattern)) {
2896
- const fullKey = match[2];
2897
- if (!fullKey.startsWith(prefix) || fullKey.endsWith(".")) continue;
2898
- const localKey = fullKey.slice(prefix.length);
2899
- if (!referenceKeys.has(localKey)) {
2900
- missing.set(fullKey, relative4(moduleRoot, file));
2901
- }
2902
- }
2903
- }
2904
- for (const [key, path] of [...missing].sort(
2905
- ([left], [right]) => left.localeCompare(right)
2906
- )) {
2907
- issues.push(
2908
- issue(
2909
- "TRANSLATION_KEY_MISSING",
2910
- `Translation key "${key}" is used by the client but absent from translations/${reference[0]}.json.`,
2911
- path
2912
- )
2913
- );
2914
- }
2915
- }
2916
- return issues;
2917
- }
2918
- function filtersTags(source) {
2919
- const tags = [];
2920
- for (const match of source.matchAll(/<Filters\b/g)) {
2921
- let depth = 0;
2922
- let end = match.index + match[0].length;
2923
- for (; end < source.length; end += 1) {
2924
- const character = source[end];
2925
- if (character === "{") depth += 1;
2926
- else if (character === "}") depth -= 1;
2927
- else if (character === ">" && depth === 0) break;
2928
- }
2929
- tags.push(source.slice(match.index, end));
2930
- }
2931
- return tags;
2932
- }
2933
- async function userInterfaceIssues(moduleRoot) {
2934
- const issues = [];
2935
- for (const file of await clientSourceFiles(moduleRoot)) {
2936
- const source = await readFile7(file, "utf8");
2937
- const path = relative4(moduleRoot, file);
2938
- if (/<table\b/.test(source)) {
2939
- issues.push(
2940
- issue(
2941
- "RAW_TABLE_FORBIDDEN",
2942
- "Module screens use Table or TableCard from @flowdular/ui instead of raw table markup.",
2943
- path
2944
- )
2945
- );
2946
- }
2947
- if (/from\s+['"]@octanejs\/tanstack-table['"]/.test(source)) {
2948
- issues.push(
2949
- issue(
2950
- "TANSTACK_TABLE_DIRECT_IMPORT",
2951
- "Modules use the shared Table contract from @flowdular/ui; TanStack configuration belongs to the UI package.",
2952
- path
2953
- )
2954
- );
2955
- }
2956
- const misspelled = domPropertyMisspellings(source);
2957
- if (misspelled.length > 0) {
2958
- issues.push(
2959
- issue(
2960
- "DOM_PROPERTY_CASING",
2961
- "Host elements use Octane's DOM property spelling: " + misspelled.map(
2962
- (found) => `line ${found.line} ${found.name} as ${found.expected}`
2963
- ).join(", ") + ".",
2964
- path
2965
- )
2966
- );
2967
- }
2968
- if (filtersTags(source).some((tag) => !/\slabel=/.test(tag))) {
2969
- issues.push(
2970
- issue(
2971
- "FILTERS_LABEL_MISSING",
2972
- 'Filters from @flowdular/ui needs a translated label; without one it shows the English word "Filters" in every locale.',
2973
- path
2974
- )
2975
- );
2976
- }
2977
- }
2978
- return issues;
2979
- }
2980
- async function moduleLayoutIssues(moduleRoot, manifest, options = {}) {
2981
- return [
2982
- ...await platformIssues(moduleRoot, manifest),
2983
- ...await specIssues(
2984
- moduleRoot,
2985
- manifest,
2986
- options.specDirectory ?? "spec"
2987
- ),
2988
- ...await translationIssues(moduleRoot, manifest, options.projectLocales),
2989
- ...await userInterfaceIssues(moduleRoot)
2990
- ];
2991
- }
2992
2680
 
2993
2681
  // src/module-install.ts
2994
2682
  var LOCK = "flowdular.modules.lock.json";
2995
- var TRANSACTION = ".flowdular-module-install";
2996
- async function exists2(path) {
2683
+ async function exists(path) {
2997
2684
  try {
2998
2685
  await lstat3(path);
2999
2686
  return true;
@@ -3003,14 +2690,14 @@ async function exists2(path) {
3003
2690
  }
3004
2691
  }
3005
2692
  async function safeParents(workspace, path) {
3006
- const parts = relative5(
2693
+ const parts = relative4(
3007
2694
  workspace.root,
3008
2695
  resolveInside(workspace.root, path)
3009
2696
  ).split("/");
3010
2697
  let current = workspace.root;
3011
2698
  for (const part of parts) {
3012
- current = join6(current, part);
3013
- if (!await exists2(current)) break;
2699
+ current = join5(current, part);
2700
+ if (!await exists(current)) break;
3014
2701
  distributionAssert(
3015
2702
  !(await lstat3(current)).isSymbolicLink(),
3016
2703
  "MODULE_DESTINATION_LINK",
@@ -3035,7 +2722,7 @@ function validateLock(workspace, value) {
3035
2722
  );
3036
2723
  const path = resolveInside(workspace.root, entry.directory);
3037
2724
  distributionAssert(
3038
- moduleRoots(workspace).some((root) => dirname6(path) === root) && !ids.has(entry.id) && !dirs.has(path.toLowerCase()),
2725
+ moduleRoots(workspace).some((root) => dirname4(path) === root) && !ids.has(entry.id) && !dirs.has(path.toLowerCase()),
3039
2726
  "MODULE_LOCK_INVALID",
3040
2727
  "Duplicate or invalid installed module path."
3041
2728
  );
@@ -3052,17 +2739,17 @@ function validateLock(workspace, value) {
3052
2739
  return lock;
3053
2740
  }
3054
2741
  async function readLock(workspace) {
3055
- const path = join6(workspace.root, LOCK);
2742
+ const path = join5(workspace.root, LOCK);
3056
2743
  await safeParents(workspace, path);
3057
- if (!await exists2(path))
2744
+ if (!await exists(path))
3058
2745
  return { lock: { schemaVersion: 1, modules: [] }, raw: null };
3059
- const raw = await readFile8(path, "utf8");
2746
+ const raw = await readFile7(path, "utf8");
3060
2747
  return { lock: validateLock(workspace, JSON.parse(raw)), raw };
3061
2748
  }
3062
2749
  async function directoryHashes(root) {
3063
2750
  const hashes = /* @__PURE__ */ Object.create(null);
3064
2751
  async function visit(directory, prefix) {
3065
- for (const entry of await readdir4(directory, { withFileTypes: true })) {
2752
+ for (const entry of await readdir3(directory, { withFileTypes: true })) {
3066
2753
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git")
3067
2754
  continue;
3068
2755
  distributionAssert(
@@ -3072,14 +2759,14 @@ async function directoryHashes(root) {
3072
2759
  );
3073
2760
  const path = prefix + entry.name;
3074
2761
  if (entry.isDirectory())
3075
- await visit(join6(directory, entry.name), path + "/");
2762
+ await visit(join5(directory, entry.name), path + "/");
3076
2763
  else {
3077
2764
  distributionAssert(
3078
2765
  entry.isFile(),
3079
2766
  "MODULE_LOCAL_CHANGES",
3080
2767
  "Installed module contains a special file."
3081
2768
  );
3082
- hashes[path] = hashBytes(await readFile8(join6(directory, entry.name)));
2769
+ hashes[path] = hashBytes(await readFile7(join5(directory, entry.name)));
3083
2770
  }
3084
2771
  }
3085
2772
  }
@@ -3093,7 +2780,7 @@ async function assertUnmodified(workspace, entry) {
3093
2780
  const path = resolveInside(workspace.root, entry.directory);
3094
2781
  await safeParents(workspace, path);
3095
2782
  distributionAssert(
3096
- await exists2(path) && equalHashes(await directoryHashes(path), entry.files),
2783
+ await exists(path) && equalHashes(await directoryHashes(path), entry.files),
3097
2784
  "MODULE_LOCAL_CHANGES",
3098
2785
  `Module ${entry.id} has local changes or is missing. Preserve/merge those changes before updating.`
3099
2786
  );
@@ -3108,8 +2795,8 @@ async function validateInstalledModules(workspace) {
3108
2795
  for (const entry of lock.modules) {
3109
2796
  await assertUnmodified(workspace, entry);
3110
2797
  const manifest = JSON.parse(
3111
- await readFile8(
3112
- join6(workspace.root, entry.directory, "module.json"),
2798
+ await readFile7(
2799
+ join5(workspace.root, entry.directory, "module.json"),
3113
2800
  "utf8"
3114
2801
  )
3115
2802
  );
@@ -3121,278 +2808,15 @@ async function validateInstalledModules(workspace) {
3121
2808
  }
3122
2809
  return lock;
3123
2810
  }
3124
- async function rollback(workspace, journal) {
3125
- const transaction = join6(workspace.root, TRANSACTION);
3126
- validateLock(workspace, {
3127
- schemaVersion: 1,
3128
- modules: journal.entries.map((entry) => entry.installed)
3129
- });
3130
- for (const [index, entry] of [...journal.entries.entries()].reverse()) {
3131
- const destination = resolveInside(
3132
- workspace.root,
3133
- entry.installed.directory
3134
- );
3135
- await safeParents(workspace, destination);
3136
- const backup = join6(transaction, `backup-${index}`);
3137
- const moved = !await exists2(join6(transaction, `stage-${index}`));
3138
- if (moved && await exists2(destination)) {
3139
- const hashes = await directoryHashes(destination);
3140
- if (equalHashes(hashes, entry.installed.files))
3141
- await rm(destination, { recursive: true });
3142
- else
3143
- distributionAssert(
3144
- entry.previous && equalHashes(hashes, entry.previous.files) && !await exists2(backup),
3145
- "MODULE_RECOVERY_CONFLICT",
3146
- `Recovery preserved modified files in ${entry.installed.directory}.`
3147
- );
3148
- }
3149
- if (await exists2(backup)) {
3150
- distributionAssert(
3151
- !await exists2(destination),
3152
- "MODULE_RECOVERY_CONFLICT",
3153
- "Recovery destination is occupied."
3154
- );
3155
- await safeParents(workspace, backup);
3156
- await rename(backup, destination);
3157
- }
3158
- }
3159
- const lockPath = join6(workspace.root, LOCK);
3160
- await safeParents(workspace, lockPath);
3161
- const current = await exists2(lockPath) ? await readFile8(lockPath, "utf8") : null;
3162
- distributionAssert(
3163
- current === journal.previous || current === journal.next,
3164
- "MODULE_RECOVERY_CONFLICT",
3165
- "Lockfile changed during installation; recovery preserved it."
3166
- );
3167
- if (journal.previous === null) await rm(lockPath, { force: true });
3168
- else await writeFile(lockPath, journal.previous);
3169
- }
3170
- async function installModule(workspace, options) {
3171
- const transaction = join6(workspace.root, TRANSACTION);
3172
- await safeParents(workspace, transaction);
3173
- distributionAssert(
3174
- !await exists2(transaction),
3175
- "MODULE_INSTALL_BUSY",
3176
- "A module installation is active or interrupted. Use module recover after its process exits."
3177
- );
3178
- const source = await loadModuleCatalog(options.registry, options.fetcher);
3179
- const snapshot = await readLock(workspace);
3180
- const manifests = [];
3181
- for (const root of moduleRoots(workspace)) await safeParents(workspace, root);
3182
- const files = await findModuleFiles(workspace);
3183
- for (const file of files)
3184
- manifests.push(JSON.parse(await readFile8(file, "utf8")));
3185
- const id = options.target.split("@")[0];
3186
- const previous = snapshot.lock.modules.find((entry) => entry.id === id);
3187
- distributionAssert(
3188
- previous || !manifests.some((manifest) => manifest.id === id),
3189
- "MODULE_NOT_MANAGED",
3190
- `Module ${id} already exists as unmanaged workspace/package source.`
3191
- );
3192
- if (options.update) {
3193
- distributionAssert(
3194
- previous,
3195
- "MODULE_NOT_MANAGED",
3196
- `Module ${id} is not managed by this installer.`
3197
- );
3198
- await assertUnmodified(workspace, previous);
3199
- }
3200
- const retained = options.update ? manifests.filter((manifest) => manifest.id !== id) : manifests;
3201
- const releases = resolveModuleReleases(
3202
- source.catalog,
3203
- options.target,
3204
- retained
3205
- );
3206
- if (options.update) {
3207
- const next = releases.find((release) => release.manifest.id === id);
3208
- distributionAssert(
3209
- next && compareModuleVersions(next.manifest.version, previous.version) <= 0,
3210
- "MODULE_DOWNGRADE_FORBIDDEN",
3211
- "Module downgrades are not supported."
3212
- );
3213
- }
3214
- const registry = createModuleRegistry(
3215
- [...retained, ...releases.map((release) => release.manifest)].map(
3216
- (manifest) => ({ manifest })
3217
- )
3218
- );
3219
- void registry;
3220
- if (!releases.length) {
3221
- if (previous) await assertUnmodified(workspace, previous);
3222
- return { applied: false, modules: [], activationRequired: false };
3223
- }
3224
- const entries = [];
3225
- let totalBytes = 0;
3226
- for (const release of releases) {
3227
- const bytes = await readRelease(source, release, options.fetcher);
3228
- totalBytes += bytes.length;
3229
- distributionAssert(
3230
- totalBytes <= 96 * 1024 * 1024,
3231
- "MODULE_INSTALL_LIMIT",
3232
- "The dependency closure exceeds the installation size limit."
3233
- );
3234
- distributionAssert(
3235
- hashBytes(bytes) === release.sha256,
3236
- "MODULE_ARTIFACT_DIGEST",
3237
- `Artifact digest mismatch for ${release.manifest.id}.`
3238
- );
3239
- const artifact = validateModuleArtifact(JSON.parse(bytes.toString("utf8")));
3240
- distributionAssert(
3241
- JSON.stringify(artifact.manifest) === JSON.stringify(release.manifest),
3242
- "MODULE_ARTIFACT_IDENTITY",
3243
- "Catalog and artifact manifest differ."
3244
- );
3245
- const old = snapshot.lock.modules.find((entry) => entry.id === release.manifest.id) ?? null;
3246
- const destination = old ? resolveInside(workspace.root, old.directory) : join6(
3247
- moduleRoots(workspace)[0],
3248
- release.manifest.package.replace("@flowdular/module-", "")
3249
- );
3250
- await safeParents(workspace, destination);
3251
- if (old) {
3252
- distributionAssert(
3253
- options.update && old.id === id,
3254
- "MODULE_ALREADY_INSTALLED",
3255
- `Module ${old.id} is already managed.`
3256
- );
3257
- await assertUnmodified(workspace, old);
3258
- } else
3259
- distributionAssert(
3260
- !await exists2(destination),
3261
- "MODULE_DESTINATION_EXISTS",
3262
- `Destination exists: ${relative5(workspace.root, destination)}`
3263
- );
3264
- const hashes = Object.fromEntries(
3265
- artifact.files.map((file) => [file.path, file.sha256])
3266
- );
3267
- if (old) {
3268
- for (const [path, hash] of Object.entries(old.files))
3269
- if (path.startsWith("migrations/"))
3270
- distributionAssert(
3271
- hashes[path] === hash,
3272
- "MODULE_MIGRATION_CHANGED",
3273
- `Update changes historical migration ${path}.`
3274
- );
3275
- }
3276
- entries.push({
3277
- release,
3278
- artifact,
3279
- previous: old,
3280
- installed: {
3281
- id: release.manifest.id,
3282
- version: release.manifest.version,
3283
- directory: relative5(workspace.root, destination),
3284
- artifact: release.artifact,
3285
- sha256: release.sha256,
3286
- sourceCommit: release.sourceCommit,
3287
- files: hashes
3288
- }
3289
- });
3290
- }
3291
- const nextLock = {
3292
- schemaVersion: 1,
3293
- modules: [
3294
- ...snapshot.lock.modules.filter(
3295
- (entry) => !entries.some((next) => next.installed.id === entry.id)
3296
- ),
3297
- ...entries.map((entry) => entry.installed)
3298
- ].sort((a, b) => a.id.localeCompare(b.id))
3299
- };
3300
- validateLock(workspace, nextLock);
3301
- if (!options.apply)
3302
- return {
3303
- applied: false,
3304
- modules: entries.map((entry) => entry.installed),
3305
- activationRequired: true
3306
- };
3307
- await mkdir(transaction);
3308
- const journal = {
3309
- schemaVersion: 1,
3310
- pid: process.pid,
3311
- previous: snapshot.raw,
3312
- next: JSON.stringify(nextLock, null, " ") + "\n",
3313
- entries: entries.map((entry) => ({
3314
- installed: entry.installed,
3315
- previous: entry.previous
3316
- }))
3317
- };
3318
- let journalWritten = false;
3319
- try {
3320
- await writeFile(
3321
- join6(transaction, "owner.json"),
3322
- JSON.stringify({ schemaVersion: 1, pid: process.pid })
3323
- );
3324
- for (const [index, entry] of entries.entries()) {
3325
- const stage = join6(transaction, `stage-${index}`);
3326
- await mkdir(stage);
3327
- for (const file of entry.artifact.files) {
3328
- const path = join6(stage, file.path);
3329
- await mkdir(dirname6(path), { recursive: true });
3330
- await writeFile(path, Buffer.from(file.content, "base64"), {
3331
- flag: "wx"
3332
- });
3333
- }
3334
- const issues = await moduleLayoutIssues(stage, entry.artifact.manifest);
3335
- distributionAssert(
3336
- !issues.some((issue2) => issue2.severity === "error"),
3337
- "MODULE_LAYOUT_INVALID",
3338
- `Invalid module layout: ${issues.map((issue2) => issue2.message).join("; ")}`
3339
- );
3340
- }
3341
- await writeFile(join6(transaction, "journal.tmp"), JSON.stringify(journal));
3342
- await rename(
3343
- join6(transaction, "journal.tmp"),
3344
- join6(transaction, "journal.json")
3345
- );
3346
- journalWritten = true;
3347
- distributionAssert(
3348
- (await readLock(workspace)).raw === snapshot.raw,
3349
- "MODULE_CONCURRENT_CHANGE",
3350
- "Module lock changed during planning."
3351
- );
3352
- for (const [index, entry] of entries.entries()) {
3353
- const destination = resolveInside(
3354
- workspace.root,
3355
- entry.installed.directory
3356
- );
3357
- await safeParents(workspace, destination);
3358
- if (entry.previous) {
3359
- await assertUnmodified(workspace, entry.previous);
3360
- await rename(destination, join6(transaction, `backup-${index}`));
3361
- } else
3362
- distributionAssert(
3363
- !await exists2(destination),
3364
- "MODULE_DESTINATION_EXISTS",
3365
- `Destination appeared during installation: ${entry.installed.directory}`
3366
- );
3367
- await mkdir(dirname6(destination), { recursive: true });
3368
- await rename(join6(transaction, `stage-${index}`), destination);
3369
- }
3370
- await writeFile(join6(transaction, "next-lock.json"), journal.next);
3371
- await rename(
3372
- join6(transaction, "next-lock.json"),
3373
- join6(workspace.root, LOCK)
3374
- );
3375
- } catch (error) {
3376
- if (journalWritten) await rollback(workspace, journal);
3377
- await rm(transaction, { recursive: true });
3378
- throw error;
3379
- }
3380
- await rm(transaction, { recursive: true });
3381
- return {
3382
- applied: true,
3383
- modules: entries.map((entry) => entry.installed),
3384
- activationRequired: true
3385
- };
3386
- }
3387
2811
 
3388
2812
  // src/module-sync.ts
3389
2813
  import { spawnSync } from "node:child_process";
3390
- import { access as access3, mkdir as mkdir2, readFile as readFile9, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
3391
- import { dirname as dirname7, join as join8 } from "node:path";
2814
+ import { access as access2, mkdir as mkdir2, readFile as readFile8, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
2815
+ import { dirname as dirname5, join as join7 } from "node:path";
3392
2816
 
3393
2817
  // src/format.ts
3394
2818
  import { createRequire as createRequire3 } from "node:module";
3395
- import { join as join7 } from "node:path";
2819
+ import { join as join6 } from "node:path";
3396
2820
  import { pathToFileURL } from "node:url";
3397
2821
  var DEFAULT_OPTIONS = {
3398
2822
  useTabs: true,
@@ -3401,7 +2825,7 @@ var DEFAULT_OPTIONS = {
3401
2825
  };
3402
2826
  var TSRX_PLUGIN = "@tsrx/prettier-plugin";
3403
2827
  function resolveFrom(workspaceRoot, specifier) {
3404
- for (const base of [join7(workspaceRoot, "package.json"), import.meta.url]) {
2828
+ for (const base of [join6(workspaceRoot, "package.json"), import.meta.url]) {
3405
2829
  try {
3406
2830
  return createRequire3(base).resolve(specifier);
3407
2831
  } catch {
@@ -3429,7 +2853,7 @@ async function loadWorkspaceFormatter(workspaceRoot) {
3429
2853
  return null;
3430
2854
  }
3431
2855
  const configured = await prettier.resolveConfig(
3432
- join7(workspaceRoot, "package.json"),
2856
+ join6(workspaceRoot, "package.json"),
3433
2857
  { editorconfig: false }
3434
2858
  );
3435
2859
  const { plugins: _ignored, ...options } = configured ?? DEFAULT_OPTIONS;
@@ -3518,7 +2942,7 @@ async function loadManifests(workspace) {
3518
2942
  const descriptors = /* @__PURE__ */ new Map();
3519
2943
  for (const file of files) {
3520
2944
  const manifest = JSON.parse(
3521
- await readFile9(file, "utf8")
2945
+ await readFile8(file, "utf8")
3522
2946
  );
3523
2947
  if (!validators.module(manifest))
3524
2948
  throw new Error(`Invalid module manifest: ${file}`);
@@ -3623,7 +3047,7 @@ async function takeSnapshots(paths) {
3623
3047
  return Promise.all(
3624
3048
  paths.map(async (path) => {
3625
3049
  try {
3626
- return { path, content: await readFile9(path, "utf8") };
3050
+ return { path, content: await readFile8(path, "utf8") };
3627
3051
  } catch (error) {
3628
3052
  if (error.code === "ENOENT") {
3629
3053
  return { path, content: void 0 };
@@ -3639,7 +3063,7 @@ async function restoreSnapshots(snapshots) {
3639
3063
  await rm2(snapshot.path, { force: true });
3640
3064
  continue;
3641
3065
  }
3642
- await mkdir2(dirname7(snapshot.path), { recursive: true });
3066
+ await mkdir2(dirname5(snapshot.path), { recursive: true });
3643
3067
  await writeFile2(snapshot.path, snapshot.content);
3644
3068
  }
3645
3069
  }
@@ -3669,7 +3093,7 @@ async function syncPlatformModules(workspace, apply, enabledOverride) {
3669
3093
  content: generateClientComposition(modules, sdk, applicationPath)
3670
3094
  }
3671
3095
  ].map(async (target) => {
3672
- const absolute = join8(workspace.root, target.path);
3096
+ const absolute = join7(workspace.root, target.path);
3673
3097
  return {
3674
3098
  ...target,
3675
3099
  content: formatter ? await formatter.format(absolute, target.content) : target.content
@@ -3678,15 +3102,15 @@ async function syncPlatformModules(workspace, apply, enabledOverride) {
3678
3102
  );
3679
3103
  const files = [];
3680
3104
  for (const target of targets) {
3681
- const absolute = join8(workspace.root, target.path);
3105
+ const absolute = join7(workspace.root, target.path);
3682
3106
  let current = "";
3683
3107
  try {
3684
- current = await readFile9(absolute, "utf8");
3108
+ current = await readFile8(absolute, "utf8");
3685
3109
  } catch {
3686
3110
  }
3687
3111
  const changed = current !== target.content;
3688
3112
  if (apply && changed) {
3689
- await mkdir2(dirname7(absolute), { recursive: true });
3113
+ await mkdir2(dirname5(absolute), { recursive: true });
3690
3114
  await writeFile2(absolute, target.content);
3691
3115
  }
3692
3116
  files.push({ path: target.path, changed });
@@ -3710,9 +3134,9 @@ async function enableModule(workspace, id, apply) {
3710
3134
  const currentSet = new Set(current);
3711
3135
  const newlyEnabled = enabled.filter((moduleId) => !currentSet.has(moduleId));
3712
3136
  const configChanged = !equalIds(current, enabled);
3713
- const platformPackagePath = join8(workspace.root, "platform/package.json");
3137
+ const platformPackagePath = join7(workspace.root, "platform/package.json");
3714
3138
  const platformPackage = JSON.parse(
3715
- await readFile9(platformPackagePath, "utf8")
3139
+ await readFile8(platformPackagePath, "utf8")
3716
3140
  );
3717
3141
  const dependencies = platformPackage.dependencies ?? {};
3718
3142
  const platformDependenciesAdded = modules.map((module) => npmPackage(module.package)).filter((packageName) => !(packageName in dependencies)).sort((left, right) => left.localeCompare(right));
@@ -3729,8 +3153,8 @@ async function enableModule(workspace, id, apply) {
3729
3153
  }
3730
3154
  const linked = async (module) => {
3731
3155
  try {
3732
- await access3(
3733
- join8(
3156
+ await access2(
3157
+ join7(
3734
3158
  workspace.root,
3735
3159
  "platform/node_modules",
3736
3160
  npmPackage(module.package)
@@ -3748,16 +3172,16 @@ async function enableModule(workspace, id, apply) {
3748
3172
  if (apply) {
3749
3173
  const snapshots = await takeSnapshots([
3750
3174
  workspace.configPath,
3751
- join8(workspace.root, "pnpm-lock.yaml"),
3175
+ join7(workspace.root, "pnpm-lock.yaml"),
3752
3176
  platformPackagePath,
3753
3177
  ...Object.values(GENERATED_FILES).map(
3754
- (path) => join8(workspace.root, path)
3178
+ (path) => join7(workspace.root, path)
3755
3179
  )
3756
3180
  ]);
3757
3181
  try {
3758
3182
  if (configChanged) {
3759
3183
  const config = JSON.parse(
3760
- await readFile9(workspace.configPath, "utf8")
3184
+ await readFile8(workspace.configPath, "utf8")
3761
3185
  );
3762
3186
  const configModules = config.modules ?? {};
3763
3187
  configModules.enabled = enabled;
@@ -3812,7 +3236,6 @@ ${output}` : ""}`
3812
3236
  export {
3813
3237
  enableModule,
3814
3238
  hashBytes,
3815
- installModule,
3816
3239
  loadModuleCatalog,
3817
3240
  packModule,
3818
3241
  readModuleSource,