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.
- package/README.md +14 -8
- package/dist/distribution.js +261 -838
- package/dist/index.js +6682 -4063
- package/package.json +2 -2
package/dist/distribution.js
CHANGED
|
@@ -1427,7 +1427,7 @@ var project_schema_default = {
|
|
|
1427
1427
|
},
|
|
1428
1428
|
$defs: {
|
|
1429
1429
|
deliveryTarget: {
|
|
1430
|
-
enum: ["workspace", "git-pr"
|
|
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.
|
|
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
|
|
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
|
-
".
|
|
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
|
-
|
|
2195
|
-
|
|
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
|
-
|
|
2174
|
+
url.username === "" && url.password === "" && !url.search && !url.hash,
|
|
2213
2175
|
"MODULE_SOURCE_UNTRUSTED",
|
|
2214
|
-
"
|
|
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
|
-
`
|
|
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
|
|
2207
|
+
async function loadModuleCatalog(location, fetcher = fetch) {
|
|
2246
2208
|
distributionAssert(
|
|
2247
|
-
location
|
|
2209
|
+
location.startsWith("https://") || !/^[a-z][a-z0-9+.-]*:/i.test(location),
|
|
2248
2210
|
"MODULE_SOURCE_UNTRUSTED",
|
|
2249
|
-
"Use
|
|
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
|
|
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
|
|
2413
|
-
readdir as
|
|
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
|
|
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
|
|
2424
|
-
var SDK_VERSION = "0.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
3013
|
-
if (!await
|
|
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) =>
|
|
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 =
|
|
2742
|
+
const path = join5(workspace.root, LOCK);
|
|
3056
2743
|
await safeParents(workspace, path);
|
|
3057
|
-
if (!await
|
|
2744
|
+
if (!await exists(path))
|
|
3058
2745
|
return { lock: { schemaVersion: 1, modules: [] }, raw: null };
|
|
3059
|
-
const raw = await
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
3112
|
-
|
|
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
|
|
3391
|
-
import { dirname as
|
|
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
|
|
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 [
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
3105
|
+
const absolute = join7(workspace.root, target.path);
|
|
3682
3106
|
let current = "";
|
|
3683
3107
|
try {
|
|
3684
|
-
current = await
|
|
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(
|
|
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 =
|
|
3137
|
+
const platformPackagePath = join7(workspace.root, "platform/package.json");
|
|
3714
3138
|
const platformPackage = JSON.parse(
|
|
3715
|
-
await
|
|
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
|
|
3733
|
-
|
|
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
|
-
|
|
3175
|
+
join7(workspace.root, "pnpm-lock.yaml"),
|
|
3752
3176
|
platformPackagePath,
|
|
3753
3177
|
...Object.values(GENERATED_FILES).map(
|
|
3754
|
-
(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
|
|
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,
|