flowdular 0.5.1 → 0.6.1
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 +395 -952
- package/dist/index.js +7004 -4212
- package/package.json +2 -2
package/dist/distribution.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/module-artifact.ts
|
|
2
2
|
import { parse as parseYaml2 } from "yaml";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { lstat, readdir
|
|
5
|
-
import { join
|
|
4
|
+
import { lstat, readdir, readFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
6
|
|
|
7
7
|
// ../kernel/src/errors.ts
|
|
8
8
|
var RegistryError = class extends Error {
|
|
@@ -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.4";
|
|
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))
|
|
@@ -1902,8 +1863,6 @@ function createModuleRegistry(input) {
|
|
|
1902
1863
|
}
|
|
1903
1864
|
|
|
1904
1865
|
// src/validation.ts
|
|
1905
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
1906
|
-
import { basename, dirname, join, relative } from "node:path";
|
|
1907
1866
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
1908
1867
|
import { parse as parseYaml } from "yaml";
|
|
1909
1868
|
var ajv = new Ajv2020({ allErrors: true, strict: true });
|
|
@@ -1923,26 +1882,6 @@ var MONTH_NAMES = "jan feb mar apr may jun jul aug sep oct nov dec".split(
|
|
|
1923
1882
|
" "
|
|
1924
1883
|
);
|
|
1925
1884
|
var WEEKDAY_NAMES = "sun mon tue wed thu fri sat".split(" ");
|
|
1926
|
-
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1927
|
-
".git",
|
|
1928
|
-
".flowdular",
|
|
1929
|
-
".coreloom",
|
|
1930
|
-
"node_modules",
|
|
1931
|
-
"dist"
|
|
1932
|
-
]);
|
|
1933
|
-
async function findNamedFiles(root, name) {
|
|
1934
|
-
const matches = [];
|
|
1935
|
-
async function visit(directory) {
|
|
1936
|
-
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
1937
|
-
if (SKIPPED_DIRECTORIES.has(entry.name)) continue;
|
|
1938
|
-
const path = join(directory, entry.name);
|
|
1939
|
-
if (entry.isDirectory()) await visit(path);
|
|
1940
|
-
else if (entry.name === name) matches.push(path);
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
await visit(root);
|
|
1944
|
-
return matches.sort();
|
|
1945
|
-
}
|
|
1946
1885
|
|
|
1947
1886
|
// src/module-artifact.ts
|
|
1948
1887
|
var MAX_ARTIFACT_BYTES = 48 * 1024 * 1024;
|
|
@@ -1987,7 +1926,7 @@ async function readModuleSource(root) {
|
|
|
1987
1926
|
const files = [];
|
|
1988
1927
|
let size = 0;
|
|
1989
1928
|
async function visit(directory, prefix) {
|
|
1990
|
-
for (const entry of await
|
|
1929
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
1991
1930
|
const path = prefix + entry.name;
|
|
1992
1931
|
if (!prefix && !ROOT_FILES.has(path) && !ROOTS.has(path)) continue;
|
|
1993
1932
|
distributionAssert(
|
|
@@ -2001,20 +1940,20 @@ async function readModuleSource(root) {
|
|
|
2001
1940
|
"MODULE_SOURCE_PATH",
|
|
2002
1941
|
`Unexpected module directory: ${path}`
|
|
2003
1942
|
);
|
|
2004
|
-
await visit(
|
|
1943
|
+
await visit(join(directory, entry.name), path + "/");
|
|
2005
1944
|
} else {
|
|
2006
1945
|
distributionAssert(
|
|
2007
1946
|
entry.isFile() && safeSourcePath(path),
|
|
2008
1947
|
"MODULE_SOURCE_PATH",
|
|
2009
1948
|
`Unexpected module source: ${path}`
|
|
2010
1949
|
);
|
|
2011
|
-
const stat2 = await lstat(
|
|
1950
|
+
const stat2 = await lstat(join(directory, entry.name));
|
|
2012
1951
|
distributionAssert(
|
|
2013
1952
|
size + stat2.size <= MAX_SOURCE_BYTES && files.length < MAX_FILES,
|
|
2014
1953
|
"MODULE_SOURCE_LIMIT",
|
|
2015
1954
|
"Module source is too large."
|
|
2016
1955
|
);
|
|
2017
|
-
const bytes = await
|
|
1956
|
+
const bytes = await readFile(join(directory, entry.name));
|
|
2018
1957
|
size += bytes.length;
|
|
2019
1958
|
files.push({
|
|
2020
1959
|
path,
|
|
@@ -2040,11 +1979,6 @@ function validateModuleArtifact(value) {
|
|
|
2040
1979
|
"Invalid module artifact."
|
|
2041
1980
|
);
|
|
2042
1981
|
assertModuleCompatibility(artifact.manifest);
|
|
2043
|
-
distributionAssert(
|
|
2044
|
-
artifact.manifest.platformApi,
|
|
2045
|
-
"MODULE_PLATFORM_REQUIRED",
|
|
2046
|
-
"Distributed modules must declare platformApi."
|
|
2047
|
-
);
|
|
2048
1982
|
const paths = /* @__PURE__ */ new Set();
|
|
2049
1983
|
let size = 0;
|
|
2050
1984
|
for (const file of artifact.files) {
|
|
@@ -2166,33 +2100,28 @@ async function packModule(root, review) {
|
|
|
2166
2100
|
}
|
|
2167
2101
|
|
|
2168
2102
|
// src/module-catalog.ts
|
|
2169
|
-
import { readFile as
|
|
2170
|
-
import { dirname as dirname3, resolve as resolve2 } from "node:path";
|
|
2103
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
2171
2104
|
|
|
2172
2105
|
// src/workspace.ts
|
|
2173
|
-
import {
|
|
2174
|
-
import { dirname as dirname2, isAbsolute, relative as relative2, resolve } from "node:path";
|
|
2106
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
2175
2107
|
function resolveInside(root, candidate) {
|
|
2176
2108
|
const absolute = isAbsolute(candidate) ? resolve(candidate) : resolve(root, candidate);
|
|
2177
|
-
const pathFromRoot =
|
|
2109
|
+
const pathFromRoot = relative(root, absolute);
|
|
2178
2110
|
if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
|
|
2179
2111
|
throw new Error(`Path escapes the workspace: ${candidate}`);
|
|
2180
2112
|
}
|
|
2181
2113
|
return absolute;
|
|
2182
2114
|
}
|
|
2183
|
-
async function resolveExistingInside(root, candidate) {
|
|
2184
|
-
const canonicalRoot = await realpath(root);
|
|
2185
|
-
const canonicalCandidate = await realpath(resolveInside(root, candidate));
|
|
2186
|
-
const pathFromRoot = relative2(canonicalRoot, canonicalCandidate);
|
|
2187
|
-
if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) {
|
|
2188
|
-
throw new Error(`Path escapes the workspace through a link: ${candidate}`);
|
|
2189
|
-
}
|
|
2190
|
-
return canonicalCandidate;
|
|
2191
|
-
}
|
|
2192
2115
|
|
|
2193
2116
|
// src/module-catalog.ts
|
|
2194
|
-
|
|
2195
|
-
|
|
2117
|
+
function platformAdmits(manifest) {
|
|
2118
|
+
try {
|
|
2119
|
+
assertModuleCompatibility(manifest);
|
|
2120
|
+
return true;
|
|
2121
|
+
} catch {
|
|
2122
|
+
return false;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2196
2125
|
async function boundedRead(location, limit, fetcher = fetch) {
|
|
2197
2126
|
if (!location.startsWith("https://")) {
|
|
2198
2127
|
distributionAssert(
|
|
@@ -2200,7 +2129,7 @@ async function boundedRead(location, limit, fetcher = fetch) {
|
|
|
2200
2129
|
"MODULE_DOWNLOAD_LIMIT",
|
|
2201
2130
|
"Module metadata/artifact exceeds the size limit."
|
|
2202
2131
|
);
|
|
2203
|
-
const result = await
|
|
2132
|
+
const result = await readFile2(location);
|
|
2204
2133
|
distributionAssert(
|
|
2205
2134
|
result.length <= limit,
|
|
2206
2135
|
"MODULE_DOWNLOAD_LIMIT",
|
|
@@ -2208,10 +2137,11 @@ async function boundedRead(location, limit, fetcher = fetch) {
|
|
|
2208
2137
|
);
|
|
2209
2138
|
return result;
|
|
2210
2139
|
}
|
|
2140
|
+
const url = new URL(location);
|
|
2211
2141
|
distributionAssert(
|
|
2212
|
-
|
|
2142
|
+
url.username === "" && url.password === "" && !url.search && !url.hash,
|
|
2213
2143
|
"MODULE_SOURCE_UNTRUSTED",
|
|
2214
|
-
"
|
|
2144
|
+
"HTTPS module sources must not contain credentials, a query or a fragment."
|
|
2215
2145
|
);
|
|
2216
2146
|
const response = await fetcher(location, {
|
|
2217
2147
|
redirect: "error",
|
|
@@ -2220,7 +2150,7 @@ async function boundedRead(location, limit, fetcher = fetch) {
|
|
|
2220
2150
|
distributionAssert(
|
|
2221
2151
|
response.ok && response.body,
|
|
2222
2152
|
"MODULE_DOWNLOAD_FAILED",
|
|
2223
|
-
`
|
|
2153
|
+
`Module download failed (${response.status}).`
|
|
2224
2154
|
);
|
|
2225
2155
|
const reader = response.body.getReader();
|
|
2226
2156
|
const chunks = [];
|
|
@@ -2242,11 +2172,11 @@ async function boundedRead(location, limit, fetcher = fetch) {
|
|
|
2242
2172
|
}
|
|
2243
2173
|
return Buffer.concat(chunks);
|
|
2244
2174
|
}
|
|
2245
|
-
async function loadModuleCatalog(location
|
|
2175
|
+
async function loadModuleCatalog(location, fetcher = fetch) {
|
|
2246
2176
|
distributionAssert(
|
|
2247
|
-
location
|
|
2177
|
+
location.startsWith("https://") || !/^[a-z][a-z0-9+.-]*:/i.test(location),
|
|
2248
2178
|
"MODULE_SOURCE_UNTRUSTED",
|
|
2249
|
-
"Use
|
|
2179
|
+
"Use an explicit HTTPS or local catalog path."
|
|
2250
2180
|
);
|
|
2251
2181
|
const catalog = JSON.parse(
|
|
2252
2182
|
(await boundedRead(location, 4 * 1024 * 1024, fetcher)).toString("utf8")
|
|
@@ -2264,11 +2194,6 @@ async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
|
|
|
2264
2194
|
"Invalid module release."
|
|
2265
2195
|
);
|
|
2266
2196
|
assertModuleCompatibility(release.manifest, null);
|
|
2267
|
-
distributionAssert(
|
|
2268
|
-
release.manifest.platformApi,
|
|
2269
|
-
"MODULE_PLATFORM_REQUIRED",
|
|
2270
|
-
"Distributed modules must declare platformApi."
|
|
2271
|
-
);
|
|
2272
2197
|
const identity = release.manifest.id + "@" + release.manifest.version;
|
|
2273
2198
|
distributionAssert(
|
|
2274
2199
|
!identities.has(identity),
|
|
@@ -2279,26 +2204,6 @@ async function loadModuleCatalog(location = OFFICIAL_CATALOG, fetcher = fetch) {
|
|
|
2279
2204
|
}
|
|
2280
2205
|
return { catalog, location };
|
|
2281
2206
|
}
|
|
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
2207
|
function resolveModuleReleases(catalog, target, installed) {
|
|
2303
2208
|
const split = target.lastIndexOf("@");
|
|
2304
2209
|
const id = split < 0 ? target : target.slice(0, split);
|
|
@@ -2336,10 +2241,7 @@ function resolveModuleReleases(catalog, target, installed) {
|
|
|
2336
2241
|
];
|
|
2337
2242
|
}
|
|
2338
2243
|
function platformCompatible(candidate) {
|
|
2339
|
-
return
|
|
2340
|
-
PLATFORM_API_VERSION,
|
|
2341
|
-
candidate.manifest.platformApi
|
|
2342
|
-
);
|
|
2244
|
+
return platformAdmits(candidate.manifest);
|
|
2343
2245
|
}
|
|
2344
2246
|
let attempts = 0;
|
|
2345
2247
|
function search(selected2, pending) {
|
|
@@ -2397,135 +2299,345 @@ function resolveModuleReleases(catalog, target, installed) {
|
|
|
2397
2299
|
return release ? [release] : [];
|
|
2398
2300
|
});
|
|
2399
2301
|
}
|
|
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
2302
|
|
|
2408
2303
|
// src/module-install.ts
|
|
2409
2304
|
import {
|
|
2410
|
-
lstat as
|
|
2305
|
+
lstat as lstat2,
|
|
2411
2306
|
mkdir,
|
|
2412
|
-
readFile as
|
|
2413
|
-
readdir as
|
|
2307
|
+
readFile as readFile3,
|
|
2308
|
+
readdir as readdir2,
|
|
2414
2309
|
rename,
|
|
2415
2310
|
rm,
|
|
2416
2311
|
writeFile
|
|
2417
2312
|
} from "node:fs/promises";
|
|
2418
|
-
import { dirname as
|
|
2313
|
+
import { dirname as dirname3, join as join3, relative as relative3 } from "node:path";
|
|
2419
2314
|
|
|
2420
|
-
// src/
|
|
2315
|
+
// ../kernel/src/module-manifests.ts
|
|
2316
|
+
import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
2421
2317
|
import { createRequire } from "node:module";
|
|
2422
|
-
import {
|
|
2423
|
-
|
|
2424
|
-
var
|
|
2425
|
-
|
|
2426
|
-
|
|
2318
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
2319
|
+
var SDK_INDEX = "@flowdular/sdk/modules.json";
|
|
2320
|
+
var SDK_IMPORT = /^@flowdular\/sdk\/modules\/[a-z0-9-]+$/;
|
|
2321
|
+
var SDK_INDEX_LIMIT = 256;
|
|
2322
|
+
var UNRESOLVED = /* @__PURE__ */ new Set([
|
|
2323
|
+
"MODULE_NOT_FOUND",
|
|
2324
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED"
|
|
2325
|
+
]);
|
|
2326
|
+
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
2327
|
+
".git",
|
|
2328
|
+
".flowdular",
|
|
2329
|
+
".vercel",
|
|
2330
|
+
"node_modules",
|
|
2331
|
+
"dist"
|
|
2332
|
+
]);
|
|
2333
|
+
function errorCode(error) {
|
|
2334
|
+
return error.code ?? "";
|
|
2427
2335
|
}
|
|
2428
|
-
|
|
2429
|
-
const
|
|
2336
|
+
function insideRealPath(root, path, shown = path) {
|
|
2337
|
+
const canonicalRoot = realpathSync(root);
|
|
2338
|
+
const canonical = realpathSync(path);
|
|
2339
|
+
const fromRoot = relative2(canonicalRoot, canonical);
|
|
2340
|
+
if (fromRoot.startsWith("..") || isAbsolute2(fromRoot)) {
|
|
2341
|
+
throw new Error(`Path escapes the workspace through a link: ${shown}`);
|
|
2342
|
+
}
|
|
2343
|
+
return canonical;
|
|
2344
|
+
}
|
|
2345
|
+
function manifestPackage(path) {
|
|
2346
|
+
try {
|
|
2347
|
+
const manifest = JSON.parse(readFileSync(path, "utf8"));
|
|
2348
|
+
return typeof manifest.package === "string" ? manifest.package : void 0;
|
|
2349
|
+
} catch {
|
|
2350
|
+
return void 0;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function moduleRootDirectories(workspaceRoot, roots) {
|
|
2354
|
+
const configured = roots ?? ["modules"];
|
|
2355
|
+
if (!Array.isArray(configured) || configured.length === 0 || configured.some(
|
|
2356
|
+
(root) => typeof root !== "string" || !root || root.startsWith(".") || root.includes("\\") || root.split("/").some((part) => part === ".." || !part)
|
|
2357
|
+
)) {
|
|
2358
|
+
throw new Error(
|
|
2359
|
+
"Invalid modules.roots. Use relative workspace directories."
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
return configured.map((root) => {
|
|
2363
|
+
const path = resolve2(workspaceRoot, root);
|
|
2364
|
+
if (!relative2(workspaceRoot, path)) {
|
|
2365
|
+
throw new Error("The workspace root cannot be a module root.");
|
|
2366
|
+
}
|
|
2367
|
+
return path;
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
function sdkModuleManifests(workspaceRoot) {
|
|
2371
|
+
const require2 = createRequire(join2(workspaceRoot, "platform/package.json"));
|
|
2430
2372
|
let indexPath;
|
|
2431
2373
|
try {
|
|
2432
|
-
indexPath = require2.resolve(
|
|
2374
|
+
indexPath = require2.resolve(SDK_INDEX);
|
|
2433
2375
|
} catch (error) {
|
|
2434
|
-
if (
|
|
2435
|
-
error.code ?? ""
|
|
2436
|
-
))
|
|
2437
|
-
return /* @__PURE__ */ new Map();
|
|
2376
|
+
if (UNRESOLVED.has(errorCode(error))) return /* @__PURE__ */ new Map();
|
|
2438
2377
|
throw error;
|
|
2439
2378
|
}
|
|
2440
|
-
const index = JSON.parse(
|
|
2441
|
-
if (index.schemaVersion !== 1 || !Array.isArray(index.modules) || index.modules.length >
|
|
2379
|
+
const index = JSON.parse(readFileSync(indexPath, "utf8"));
|
|
2380
|
+
if (index.schemaVersion !== 1 || !Array.isArray(index.modules) || index.modules.length > SDK_INDEX_LIMIT) {
|
|
2442
2381
|
throw new Error("Invalid SDK module index.");
|
|
2382
|
+
}
|
|
2383
|
+
const sdkRoot = dirname2(indexPath);
|
|
2443
2384
|
const result = /* @__PURE__ */ new Map();
|
|
2444
2385
|
for (const entry of index.modules) {
|
|
2445
|
-
if (
|
|
2386
|
+
if (typeof entry?.import !== "string" || !SDK_IMPORT.test(entry.import)) {
|
|
2446
2387
|
throw new Error("Invalid SDK module entrypoint.");
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2388
|
+
}
|
|
2389
|
+
if (typeof entry.manifest !== "string") {
|
|
2390
|
+
throw new Error("Invalid SDK module manifest path.");
|
|
2391
|
+
}
|
|
2392
|
+
const lexical = resolve2(sdkRoot, entry.manifest);
|
|
2393
|
+
const fromRoot = relative2(sdkRoot, lexical);
|
|
2394
|
+
if (fromRoot.startsWith("..") || isAbsolute2(fromRoot)) {
|
|
2395
|
+
throw new Error(`Path escapes the workspace: ${entry.manifest}`);
|
|
2396
|
+
}
|
|
2397
|
+
const path = insideRealPath(sdkRoot, lexical, entry.manifest);
|
|
2451
2398
|
if (result.has(path)) throw new Error("Duplicate SDK module path.");
|
|
2452
2399
|
result.set(path, entry.import);
|
|
2453
2400
|
}
|
|
2454
2401
|
return result;
|
|
2455
2402
|
}
|
|
2456
|
-
|
|
2457
|
-
// src/module-files.ts
|
|
2458
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
2459
|
-
import { lstat as lstat2, readFile as readFile6 } from "node:fs/promises";
|
|
2460
|
-
import { join as join4, relative as relative3 } from "node:path";
|
|
2461
|
-
function moduleRoots(workspace) {
|
|
2462
|
-
const roots = workspace.config.modules?.roots ?? ["modules"];
|
|
2463
|
-
if (!Array.isArray(roots) || !roots.length || roots.some(
|
|
2464
|
-
(root) => typeof root !== "string" || !root || root.startsWith(".") || root.includes("\\") || root.split("/").some((part) => part === ".." || !part)
|
|
2465
|
-
))
|
|
2466
|
-
throw new Error(
|
|
2467
|
-
"Invalid modules.roots. Use relative workspace directories."
|
|
2468
|
-
);
|
|
2469
|
-
return roots.map((root) => {
|
|
2470
|
-
const path = resolveInside(workspace.root, root);
|
|
2471
|
-
if (!relative3(workspace.root, path))
|
|
2472
|
-
throw new Error("The workspace root cannot be a module root.");
|
|
2473
|
-
return path;
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
async function findModuleFiles(workspace) {
|
|
2403
|
+
function findModuleManifests(workspaceRoot, roots) {
|
|
2477
2404
|
const files = /* @__PURE__ */ new Set();
|
|
2478
|
-
for (const root of
|
|
2405
|
+
for (const root of moduleRootDirectories(workspaceRoot, roots)) {
|
|
2479
2406
|
try {
|
|
2480
|
-
|
|
2407
|
+
lstatSync(root);
|
|
2481
2408
|
} catch (error) {
|
|
2482
|
-
if (error
|
|
2409
|
+
if (errorCode(error) === "ENOENT") continue;
|
|
2483
2410
|
throw error;
|
|
2484
2411
|
}
|
|
2485
|
-
|
|
2486
|
-
for (const
|
|
2487
|
-
if (
|
|
2412
|
+
insideRealPath(workspaceRoot, root);
|
|
2413
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
2414
|
+
if (!entry.isDirectory() || SKIPPED_DIRECTORIES.has(entry.name)) continue;
|
|
2415
|
+
const path = join2(root, entry.name, "module.json");
|
|
2416
|
+
try {
|
|
2417
|
+
if (!lstatSync(path).isDirectory()) files.add(path);
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
2420
|
+
}
|
|
2488
2421
|
}
|
|
2489
2422
|
}
|
|
2490
2423
|
const packages = /* @__PURE__ */ new Set();
|
|
2491
|
-
for (const file of files)
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
);
|
|
2495
|
-
for (const file of (await sdkModules(workspace)).keys()) {
|
|
2496
|
-
const manifest = JSON.parse(await readFile6(file, "utf8"));
|
|
2497
|
-
if (!packages.has(manifest.package)) files.add(file);
|
|
2424
|
+
for (const file of files) {
|
|
2425
|
+
const name = manifestPackage(file);
|
|
2426
|
+
if (name) packages.add(name);
|
|
2498
2427
|
}
|
|
2499
|
-
const
|
|
2428
|
+
for (const file of sdkModuleManifests(workspaceRoot).keys()) {
|
|
2429
|
+
const name = manifestPackage(file);
|
|
2430
|
+
if (!name || !packages.has(name)) files.add(file);
|
|
2431
|
+
}
|
|
2432
|
+
const platformPackage = join2(workspaceRoot, "platform/package.json");
|
|
2433
|
+
let dependencies = {};
|
|
2500
2434
|
try {
|
|
2501
|
-
|
|
2502
|
-
const require2 = createRequire2(platformPackage);
|
|
2503
|
-
for (const name of Object.keys(pkg.dependencies ?? {}).sort()) {
|
|
2504
|
-
if (!name.startsWith("@flowdular/module-") || packages.has(name))
|
|
2505
|
-
continue;
|
|
2506
|
-
let file;
|
|
2507
|
-
try {
|
|
2508
|
-
file = require2.resolve(name + "/module.json");
|
|
2509
|
-
} catch (error) {
|
|
2510
|
-
if (["MODULE_NOT_FOUND", "ERR_PACKAGE_PATH_NOT_EXPORTED"].includes(
|
|
2511
|
-
error.code ?? ""
|
|
2512
|
-
))
|
|
2513
|
-
continue;
|
|
2514
|
-
throw error;
|
|
2515
|
-
}
|
|
2516
|
-
files.add(file);
|
|
2517
|
-
}
|
|
2435
|
+
dependencies = JSON.parse(readFileSync(platformPackage, "utf8")).dependencies ?? {};
|
|
2518
2436
|
} catch (error) {
|
|
2519
|
-
if (error
|
|
2437
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
2438
|
+
}
|
|
2439
|
+
const require2 = createRequire(platformPackage);
|
|
2440
|
+
for (const name of Object.keys(dependencies).sort()) {
|
|
2441
|
+
if (!name.startsWith("@flowdular/module-") || packages.has(name)) continue;
|
|
2442
|
+
try {
|
|
2443
|
+
files.add(require2.resolve(`${name}/module.json`));
|
|
2444
|
+
} catch (error) {
|
|
2445
|
+
if (!UNRESOLVED.has(errorCode(error))) throw error;
|
|
2446
|
+
}
|
|
2520
2447
|
}
|
|
2521
2448
|
return [...files].sort();
|
|
2522
2449
|
}
|
|
2523
2450
|
|
|
2451
|
+
// src/module-files.ts
|
|
2452
|
+
function configuredRoots(workspace) {
|
|
2453
|
+
return workspace.config.modules?.roots;
|
|
2454
|
+
}
|
|
2455
|
+
function moduleRoots(workspace) {
|
|
2456
|
+
return [...moduleRootDirectories(workspace.root, configuredRoots(workspace))];
|
|
2457
|
+
}
|
|
2458
|
+
async function findModuleFiles(workspace) {
|
|
2459
|
+
return [...findModuleManifests(workspace.root, configuredRoots(workspace))];
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2524
2462
|
// 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
2463
|
import { parse as parseYaml3 } from "yaml";
|
|
2528
2464
|
|
|
2465
|
+
// ../database/src/contracts.ts
|
|
2466
|
+
var DATABASE_ADAPTER_IDS = Object.freeze({
|
|
2467
|
+
postgresql: "flowdular.postgresql"
|
|
2468
|
+
});
|
|
2469
|
+
var DATABASE_DIALECT_IDS = Object.freeze({
|
|
2470
|
+
postgresql: "postgresql"
|
|
2471
|
+
});
|
|
2472
|
+
var DATABASE_CAPABILITY_IDS = Object.freeze({
|
|
2473
|
+
MIGRATION_LOCK: "flowdular.database.migration-lock",
|
|
2474
|
+
RETURNING: "flowdular.database.returning",
|
|
2475
|
+
ROW_LEVEL_SECURITY: "flowdular.database.row-level-security",
|
|
2476
|
+
SCHEMA_INTROSPECTION: "flowdular.database.schema-introspection",
|
|
2477
|
+
TENANT_CONTEXT: "flowdular.database.tenant-context",
|
|
2478
|
+
TRANSACTIONAL_DDL: "flowdular.database.transactional-ddl",
|
|
2479
|
+
TRANSACTIONS: "flowdular.database.transactions"
|
|
2480
|
+
});
|
|
2481
|
+
var DatabaseError = class extends Error {
|
|
2482
|
+
constructor(code, message, options) {
|
|
2483
|
+
super(message, options);
|
|
2484
|
+
this.code = code;
|
|
2485
|
+
this.name = "DatabaseError";
|
|
2486
|
+
}
|
|
2487
|
+
code;
|
|
2488
|
+
};
|
|
2489
|
+
function assertDatabaseId(id, kind) {
|
|
2490
|
+
if (!/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/.test(id)) {
|
|
2491
|
+
throw new DatabaseError(
|
|
2492
|
+
"INVALID_ARGUMENT",
|
|
2493
|
+
`"${id}" is not a valid database ${kind} id.`
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
function assertSchemaName(name) {
|
|
2498
|
+
if (!/^[a-z_][a-z0-9_]{0,62}$/.test(name)) {
|
|
2499
|
+
throw new DatabaseError(
|
|
2500
|
+
"INVALID_ARGUMENT",
|
|
2501
|
+
`"${name}" is not a valid database schema object name.`
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
function databasePlaceholder(dialectId, position) {
|
|
2506
|
+
assertDatabaseId(dialectId, "dialect");
|
|
2507
|
+
if (!Number.isSafeInteger(position) || position < 1) {
|
|
2508
|
+
throw new DatabaseError(
|
|
2509
|
+
"INVALID_ARGUMENT",
|
|
2510
|
+
"Database placeholder positions start at 1."
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
if (dialectId === DATABASE_DIALECT_IDS.postgresql) return `$${position}`;
|
|
2514
|
+
throw new DatabaseError(
|
|
2515
|
+
"UNSUPPORTED_CAPABILITY",
|
|
2516
|
+
`The well-known placeholder helper does not support dialect "${dialectId}".`
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
function databaseDialectSql(dialectId) {
|
|
2520
|
+
assertDatabaseId(dialectId, "dialect");
|
|
2521
|
+
const ledger = dialectId === DATABASE_DIALECT_IDS.postgresql ? { appliedAt: "BIGINT", suffix: "" } : void 0;
|
|
2522
|
+
if (!ledger) {
|
|
2523
|
+
throw new DatabaseError(
|
|
2524
|
+
"UNSUPPORTED_CAPABILITY",
|
|
2525
|
+
`The well-known dialect SQL helper does not support dialect "${dialectId}".`
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
return Object.freeze({
|
|
2529
|
+
placeholder: (position) => databasePlaceholder(dialectId, position),
|
|
2530
|
+
migrationLedgerDdl: (table) => {
|
|
2531
|
+
assertSchemaName(table);
|
|
2532
|
+
return `CREATE TABLE IF NOT EXISTS ${table} (
|
|
2533
|
+
namespace TEXT NOT NULL,
|
|
2534
|
+
id TEXT NOT NULL,
|
|
2535
|
+
dialect TEXT NOT NULL,
|
|
2536
|
+
checksum TEXT NOT NULL,
|
|
2537
|
+
applied_at ${ledger.appliedAt} NOT NULL,
|
|
2538
|
+
PRIMARY KEY (namespace, id)
|
|
2539
|
+
)${ledger.suffix};
|
|
2540
|
+
-- Default application grants must never make the checksum ledger writable.
|
|
2541
|
+
-- Revoke existing grants as well, so databases created before this rule heal.
|
|
2542
|
+
DO $ledger_privileges$
|
|
2543
|
+
DECLARE
|
|
2544
|
+
granted_role RECORD;
|
|
2545
|
+
BEGIN
|
|
2546
|
+
FOR granted_role IN
|
|
2547
|
+
SELECT DISTINCT acl.grantee, roles.rolname
|
|
2548
|
+
FROM pg_class AS relation
|
|
2549
|
+
CROSS JOIN LATERAL aclexplode(relation.relacl) AS acl
|
|
2550
|
+
LEFT JOIN pg_roles AS roles ON roles.oid = acl.grantee
|
|
2551
|
+
WHERE relation.oid = '${table}'::regclass
|
|
2552
|
+
AND acl.grantee <> relation.relowner
|
|
2553
|
+
LOOP
|
|
2554
|
+
EXECUTE format('REVOKE ALL PRIVILEGES ON TABLE %s FROM %s',
|
|
2555
|
+
'${table}'::regclass,
|
|
2556
|
+
CASE WHEN granted_role.grantee = 0 THEN 'PUBLIC'
|
|
2557
|
+
ELSE quote_ident(granted_role.rolname) END);
|
|
2558
|
+
END LOOP;
|
|
2559
|
+
END
|
|
2560
|
+
$ledger_privileges$;`;
|
|
2561
|
+
}
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// ../database/src/postgresql.ts
|
|
2566
|
+
var CAPABILITIES = Object.freeze({
|
|
2567
|
+
features: Object.freeze([
|
|
2568
|
+
DATABASE_CAPABILITY_IDS.MIGRATION_LOCK,
|
|
2569
|
+
DATABASE_CAPABILITY_IDS.RETURNING,
|
|
2570
|
+
DATABASE_CAPABILITY_IDS.ROW_LEVEL_SECURITY,
|
|
2571
|
+
DATABASE_CAPABILITY_IDS.SCHEMA_INTROSPECTION,
|
|
2572
|
+
DATABASE_CAPABILITY_IDS.TENANT_CONTEXT,
|
|
2573
|
+
DATABASE_CAPABILITY_IDS.TRANSACTIONAL_DDL,
|
|
2574
|
+
DATABASE_CAPABILITY_IDS.TRANSACTIONS
|
|
2575
|
+
]),
|
|
2576
|
+
parameterStyle: "numbered",
|
|
2577
|
+
migrationLock: "advisory-transaction",
|
|
2578
|
+
transactionalDdl: true,
|
|
2579
|
+
returning: true,
|
|
2580
|
+
isolationLevels: [
|
|
2581
|
+
"read-committed",
|
|
2582
|
+
"repeatable-read",
|
|
2583
|
+
"serializable"
|
|
2584
|
+
],
|
|
2585
|
+
cancellation: "before-start",
|
|
2586
|
+
schemaIntrospection: true,
|
|
2587
|
+
tenantIsolation: "transaction-local-rls",
|
|
2588
|
+
rootOperations: "allowed",
|
|
2589
|
+
sql: databaseDialectSql(DATABASE_DIALECT_IDS.postgresql)
|
|
2590
|
+
});
|
|
2591
|
+
|
|
2592
|
+
// ../database/src/provider.ts
|
|
2593
|
+
var PGLITE_RUNTIME_ROLE = "flowdular_runtime";
|
|
2594
|
+
var PGLITE_BACKGROUND_ROLE = "flowdular_background";
|
|
2595
|
+
var PGLITE_BOOTSTRAP = `DO $$
|
|
2596
|
+
BEGIN
|
|
2597
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PGLITE_RUNTIME_ROLE}') THEN
|
|
2598
|
+
CREATE ROLE ${PGLITE_RUNTIME_ROLE} NOSUPERUSER NOBYPASSRLS;
|
|
2599
|
+
END IF;
|
|
2600
|
+
END
|
|
2601
|
+
$$;
|
|
2602
|
+
DO $$
|
|
2603
|
+
BEGIN
|
|
2604
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PGLITE_BACKGROUND_ROLE}') THEN
|
|
2605
|
+
CREATE ROLE ${PGLITE_BACKGROUND_ROLE} NOSUPERUSER NOBYPASSRLS;
|
|
2606
|
+
END IF;
|
|
2607
|
+
END
|
|
2608
|
+
$$;
|
|
2609
|
+
GRANT USAGE ON SCHEMA public TO ${PGLITE_RUNTIME_ROLE}, ${PGLITE_BACKGROUND_ROLE};
|
|
2610
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
2611
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${PGLITE_RUNTIME_ROLE};
|
|
2612
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
2613
|
+
GRANT USAGE, SELECT ON SEQUENCES TO ${PGLITE_RUNTIME_ROLE};
|
|
2614
|
+
/* The background role gets no default table grant. A table it may poll across
|
|
2615
|
+
tenants says so itself, in its own migration, by granting the named columns
|
|
2616
|
+
under a FOR SELECT policy. Anything that forgets to is invisible to it. */
|
|
2617
|
+
`;
|
|
2618
|
+
|
|
2619
|
+
// ../database/src/backup.ts
|
|
2620
|
+
var BACKUP_KEY_VARIABLES = Object.freeze([
|
|
2621
|
+
"FD_AGENT_CREDENTIAL_KEY",
|
|
2622
|
+
"FD_AGENT_RUN_GRANT_KEY",
|
|
2623
|
+
"FD_APPROVAL_GRANT_KEY",
|
|
2624
|
+
"FD_WORKFLOWS_PAYLOAD_KEY",
|
|
2625
|
+
"FD_WORKFLOWS_CURSOR_KEY",
|
|
2626
|
+
"FD_AUTH_MFA_KEY",
|
|
2627
|
+
"FD_AUTOMATIONS_CREDENTIAL_KEY",
|
|
2628
|
+
"FD_NOTIFICATIONS_SECRET_KEY",
|
|
2629
|
+
"FD_STORAGE_ENCRYPTION_KEY",
|
|
2630
|
+
"FD_CONNECTORS_SECRET_KEY",
|
|
2631
|
+
"FD_AUDIT_ANCHOR_KEY"
|
|
2632
|
+
]);
|
|
2633
|
+
|
|
2634
|
+
// ../database/src/reset.ts
|
|
2635
|
+
var TABLE_QUERY = Object.freeze({
|
|
2636
|
+
[DATABASE_DIALECT_IDS.postgresql]: `SELECT tablename AS name FROM pg_tables
|
|
2637
|
+
WHERE schemaname = current_schema()
|
|
2638
|
+
ORDER BY tablename`
|
|
2639
|
+
});
|
|
2640
|
+
|
|
2529
2641
|
// src/dom-properties.ts
|
|
2530
2642
|
var DOM_PROPERTY_SPELLINGS = [
|
|
2531
2643
|
"acceptCharset",
|
|
@@ -2543,6 +2655,7 @@ var DOM_PROPERTY_SPELLINGS = [
|
|
|
2543
2655
|
"disablePictureInPicture",
|
|
2544
2656
|
"disableRemotePlayback",
|
|
2545
2657
|
"encType",
|
|
2658
|
+
"enterKeyHint",
|
|
2546
2659
|
"fetchPriority",
|
|
2547
2660
|
"formAction",
|
|
2548
2661
|
"formEncType",
|
|
@@ -2559,6 +2672,7 @@ var DOM_PROPERTY_SPELLINGS = [
|
|
|
2559
2672
|
"itemScope",
|
|
2560
2673
|
"itemType",
|
|
2561
2674
|
"maxLength",
|
|
2675
|
+
"minLength",
|
|
2562
2676
|
"noModule",
|
|
2563
2677
|
"noValidate",
|
|
2564
2678
|
"playsInline",
|
|
@@ -2574,428 +2688,12 @@ var DOM_PROPERTY_SPELLINGS = [
|
|
|
2574
2688
|
var SPELLINGS = new Map(
|
|
2575
2689
|
DOM_PROPERTY_SPELLINGS.map((name) => [name.toLowerCase(), name])
|
|
2576
2690
|
);
|
|
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
2691
|
|
|
2993
2692
|
// src/module-install.ts
|
|
2994
2693
|
var LOCK = "flowdular.modules.lock.json";
|
|
2995
|
-
|
|
2996
|
-
async function exists2(path) {
|
|
2694
|
+
async function exists(path) {
|
|
2997
2695
|
try {
|
|
2998
|
-
await
|
|
2696
|
+
await lstat2(path);
|
|
2999
2697
|
return true;
|
|
3000
2698
|
} catch (error) {
|
|
3001
2699
|
if (error.code === "ENOENT") return false;
|
|
@@ -3003,16 +2701,16 @@ async function exists2(path) {
|
|
|
3003
2701
|
}
|
|
3004
2702
|
}
|
|
3005
2703
|
async function safeParents(workspace, path) {
|
|
3006
|
-
const parts =
|
|
2704
|
+
const parts = relative3(
|
|
3007
2705
|
workspace.root,
|
|
3008
2706
|
resolveInside(workspace.root, path)
|
|
3009
2707
|
).split("/");
|
|
3010
2708
|
let current = workspace.root;
|
|
3011
2709
|
for (const part of parts) {
|
|
3012
|
-
current =
|
|
3013
|
-
if (!await
|
|
2710
|
+
current = join3(current, part);
|
|
2711
|
+
if (!await exists(current)) break;
|
|
3014
2712
|
distributionAssert(
|
|
3015
|
-
!(await
|
|
2713
|
+
!(await lstat2(current)).isSymbolicLink(),
|
|
3016
2714
|
"MODULE_DESTINATION_LINK",
|
|
3017
2715
|
"Module installation paths cannot contain links."
|
|
3018
2716
|
);
|
|
@@ -3035,7 +2733,7 @@ function validateLock(workspace, value) {
|
|
|
3035
2733
|
);
|
|
3036
2734
|
const path = resolveInside(workspace.root, entry.directory);
|
|
3037
2735
|
distributionAssert(
|
|
3038
|
-
moduleRoots(workspace).some((root) =>
|
|
2736
|
+
moduleRoots(workspace).some((root) => dirname3(path) === root) && !ids.has(entry.id) && !dirs.has(path.toLowerCase()),
|
|
3039
2737
|
"MODULE_LOCK_INVALID",
|
|
3040
2738
|
"Duplicate or invalid installed module path."
|
|
3041
2739
|
);
|
|
@@ -3052,17 +2750,17 @@ function validateLock(workspace, value) {
|
|
|
3052
2750
|
return lock;
|
|
3053
2751
|
}
|
|
3054
2752
|
async function readLock(workspace) {
|
|
3055
|
-
const path =
|
|
2753
|
+
const path = join3(workspace.root, LOCK);
|
|
3056
2754
|
await safeParents(workspace, path);
|
|
3057
|
-
if (!await
|
|
2755
|
+
if (!await exists(path))
|
|
3058
2756
|
return { lock: { schemaVersion: 1, modules: [] }, raw: null };
|
|
3059
|
-
const raw = await
|
|
2757
|
+
const raw = await readFile3(path, "utf8");
|
|
3060
2758
|
return { lock: validateLock(workspace, JSON.parse(raw)), raw };
|
|
3061
2759
|
}
|
|
3062
2760
|
async function directoryHashes(root) {
|
|
3063
2761
|
const hashes = /* @__PURE__ */ Object.create(null);
|
|
3064
2762
|
async function visit(directory, prefix) {
|
|
3065
|
-
for (const entry of await
|
|
2763
|
+
for (const entry of await readdir2(directory, { withFileTypes: true })) {
|
|
3066
2764
|
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git")
|
|
3067
2765
|
continue;
|
|
3068
2766
|
distributionAssert(
|
|
@@ -3072,14 +2770,14 @@ async function directoryHashes(root) {
|
|
|
3072
2770
|
);
|
|
3073
2771
|
const path = prefix + entry.name;
|
|
3074
2772
|
if (entry.isDirectory())
|
|
3075
|
-
await visit(
|
|
2773
|
+
await visit(join3(directory, entry.name), path + "/");
|
|
3076
2774
|
else {
|
|
3077
2775
|
distributionAssert(
|
|
3078
2776
|
entry.isFile(),
|
|
3079
2777
|
"MODULE_LOCAL_CHANGES",
|
|
3080
2778
|
"Installed module contains a special file."
|
|
3081
2779
|
);
|
|
3082
|
-
hashes[path] = hashBytes(await
|
|
2780
|
+
hashes[path] = hashBytes(await readFile3(join3(directory, entry.name)));
|
|
3083
2781
|
}
|
|
3084
2782
|
}
|
|
3085
2783
|
}
|
|
@@ -3093,7 +2791,7 @@ async function assertUnmodified(workspace, entry) {
|
|
|
3093
2791
|
const path = resolveInside(workspace.root, entry.directory);
|
|
3094
2792
|
await safeParents(workspace, path);
|
|
3095
2793
|
distributionAssert(
|
|
3096
|
-
await
|
|
2794
|
+
await exists(path) && equalHashes(await directoryHashes(path), entry.files),
|
|
3097
2795
|
"MODULE_LOCAL_CHANGES",
|
|
3098
2796
|
`Module ${entry.id} has local changes or is missing. Preserve/merge those changes before updating.`
|
|
3099
2797
|
);
|
|
@@ -3108,8 +2806,8 @@ async function validateInstalledModules(workspace) {
|
|
|
3108
2806
|
for (const entry of lock.modules) {
|
|
3109
2807
|
await assertUnmodified(workspace, entry);
|
|
3110
2808
|
const manifest = JSON.parse(
|
|
3111
|
-
await
|
|
3112
|
-
|
|
2809
|
+
await readFile3(
|
|
2810
|
+
join3(workspace.root, entry.directory, "module.json"),
|
|
3113
2811
|
"utf8"
|
|
3114
2812
|
)
|
|
3115
2813
|
);
|
|
@@ -3121,278 +2819,24 @@ async function validateInstalledModules(workspace) {
|
|
|
3121
2819
|
}
|
|
3122
2820
|
return lock;
|
|
3123
2821
|
}
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
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);
|
|
2822
|
+
|
|
2823
|
+
// src/sdk.ts
|
|
2824
|
+
var SDK_VERSION = "0.6.1";
|
|
2825
|
+
function npmPackage(specifier) {
|
|
2826
|
+
return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
|
|
3169
2827
|
}
|
|
3170
|
-
async function
|
|
3171
|
-
|
|
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
|
-
};
|
|
2828
|
+
async function sdkModules(workspace) {
|
|
2829
|
+
return sdkModuleManifests(workspace.root);
|
|
3386
2830
|
}
|
|
3387
2831
|
|
|
3388
2832
|
// src/module-sync.ts
|
|
3389
2833
|
import { spawnSync } from "node:child_process";
|
|
3390
|
-
import { access
|
|
3391
|
-
import { dirname as
|
|
2834
|
+
import { access, mkdir as mkdir2, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2835
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
3392
2836
|
|
|
3393
2837
|
// src/format.ts
|
|
3394
|
-
import { createRequire as
|
|
3395
|
-
import { join as
|
|
2838
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
2839
|
+
import { join as join4 } from "node:path";
|
|
3396
2840
|
import { pathToFileURL } from "node:url";
|
|
3397
2841
|
var DEFAULT_OPTIONS = {
|
|
3398
2842
|
useTabs: true,
|
|
@@ -3401,9 +2845,9 @@ var DEFAULT_OPTIONS = {
|
|
|
3401
2845
|
};
|
|
3402
2846
|
var TSRX_PLUGIN = "@tsrx/prettier-plugin";
|
|
3403
2847
|
function resolveFrom(workspaceRoot, specifier) {
|
|
3404
|
-
for (const base of [
|
|
2848
|
+
for (const base of [join4(workspaceRoot, "package.json"), import.meta.url]) {
|
|
3405
2849
|
try {
|
|
3406
|
-
return
|
|
2850
|
+
return createRequire2(base).resolve(specifier);
|
|
3407
2851
|
} catch {
|
|
3408
2852
|
}
|
|
3409
2853
|
}
|
|
@@ -3429,7 +2873,7 @@ async function loadWorkspaceFormatter(workspaceRoot) {
|
|
|
3429
2873
|
return null;
|
|
3430
2874
|
}
|
|
3431
2875
|
const configured = await prettier.resolveConfig(
|
|
3432
|
-
|
|
2876
|
+
join4(workspaceRoot, "package.json"),
|
|
3433
2877
|
{ editorconfig: false }
|
|
3434
2878
|
);
|
|
3435
2879
|
const { plugins: _ignored, ...options } = configured ?? DEFAULT_OPTIONS;
|
|
@@ -3518,7 +2962,7 @@ async function loadManifests(workspace) {
|
|
|
3518
2962
|
const descriptors = /* @__PURE__ */ new Map();
|
|
3519
2963
|
for (const file of files) {
|
|
3520
2964
|
const manifest = JSON.parse(
|
|
3521
|
-
await
|
|
2965
|
+
await readFile4(file, "utf8")
|
|
3522
2966
|
);
|
|
3523
2967
|
if (!validators.module(manifest))
|
|
3524
2968
|
throw new Error(`Invalid module manifest: ${file}`);
|
|
@@ -3623,7 +3067,7 @@ async function takeSnapshots(paths) {
|
|
|
3623
3067
|
return Promise.all(
|
|
3624
3068
|
paths.map(async (path) => {
|
|
3625
3069
|
try {
|
|
3626
|
-
return { path, content: await
|
|
3070
|
+
return { path, content: await readFile4(path, "utf8") };
|
|
3627
3071
|
} catch (error) {
|
|
3628
3072
|
if (error.code === "ENOENT") {
|
|
3629
3073
|
return { path, content: void 0 };
|
|
@@ -3639,7 +3083,7 @@ async function restoreSnapshots(snapshots) {
|
|
|
3639
3083
|
await rm2(snapshot.path, { force: true });
|
|
3640
3084
|
continue;
|
|
3641
3085
|
}
|
|
3642
|
-
await mkdir2(
|
|
3086
|
+
await mkdir2(dirname4(snapshot.path), { recursive: true });
|
|
3643
3087
|
await writeFile2(snapshot.path, snapshot.content);
|
|
3644
3088
|
}
|
|
3645
3089
|
}
|
|
@@ -3669,7 +3113,7 @@ async function syncPlatformModules(workspace, apply, enabledOverride) {
|
|
|
3669
3113
|
content: generateClientComposition(modules, sdk, applicationPath)
|
|
3670
3114
|
}
|
|
3671
3115
|
].map(async (target) => {
|
|
3672
|
-
const absolute =
|
|
3116
|
+
const absolute = join5(workspace.root, target.path);
|
|
3673
3117
|
return {
|
|
3674
3118
|
...target,
|
|
3675
3119
|
content: formatter ? await formatter.format(absolute, target.content) : target.content
|
|
@@ -3678,15 +3122,15 @@ async function syncPlatformModules(workspace, apply, enabledOverride) {
|
|
|
3678
3122
|
);
|
|
3679
3123
|
const files = [];
|
|
3680
3124
|
for (const target of targets) {
|
|
3681
|
-
const absolute =
|
|
3125
|
+
const absolute = join5(workspace.root, target.path);
|
|
3682
3126
|
let current = "";
|
|
3683
3127
|
try {
|
|
3684
|
-
current = await
|
|
3128
|
+
current = await readFile4(absolute, "utf8");
|
|
3685
3129
|
} catch {
|
|
3686
3130
|
}
|
|
3687
3131
|
const changed = current !== target.content;
|
|
3688
3132
|
if (apply && changed) {
|
|
3689
|
-
await mkdir2(
|
|
3133
|
+
await mkdir2(dirname4(absolute), { recursive: true });
|
|
3690
3134
|
await writeFile2(absolute, target.content);
|
|
3691
3135
|
}
|
|
3692
3136
|
files.push({ path: target.path, changed });
|
|
@@ -3710,9 +3154,9 @@ async function enableModule(workspace, id, apply) {
|
|
|
3710
3154
|
const currentSet = new Set(current);
|
|
3711
3155
|
const newlyEnabled = enabled.filter((moduleId) => !currentSet.has(moduleId));
|
|
3712
3156
|
const configChanged = !equalIds(current, enabled);
|
|
3713
|
-
const platformPackagePath =
|
|
3157
|
+
const platformPackagePath = join5(workspace.root, "platform/package.json");
|
|
3714
3158
|
const platformPackage = JSON.parse(
|
|
3715
|
-
await
|
|
3159
|
+
await readFile4(platformPackagePath, "utf8")
|
|
3716
3160
|
);
|
|
3717
3161
|
const dependencies = platformPackage.dependencies ?? {};
|
|
3718
3162
|
const platformDependenciesAdded = modules.map((module) => npmPackage(module.package)).filter((packageName) => !(packageName in dependencies)).sort((left, right) => left.localeCompare(right));
|
|
@@ -3729,8 +3173,8 @@ async function enableModule(workspace, id, apply) {
|
|
|
3729
3173
|
}
|
|
3730
3174
|
const linked = async (module) => {
|
|
3731
3175
|
try {
|
|
3732
|
-
await
|
|
3733
|
-
|
|
3176
|
+
await access(
|
|
3177
|
+
join5(
|
|
3734
3178
|
workspace.root,
|
|
3735
3179
|
"platform/node_modules",
|
|
3736
3180
|
npmPackage(module.package)
|
|
@@ -3748,16 +3192,16 @@ async function enableModule(workspace, id, apply) {
|
|
|
3748
3192
|
if (apply) {
|
|
3749
3193
|
const snapshots = await takeSnapshots([
|
|
3750
3194
|
workspace.configPath,
|
|
3751
|
-
|
|
3195
|
+
join5(workspace.root, "pnpm-lock.yaml"),
|
|
3752
3196
|
platformPackagePath,
|
|
3753
3197
|
...Object.values(GENERATED_FILES).map(
|
|
3754
|
-
(path) =>
|
|
3198
|
+
(path) => join5(workspace.root, path)
|
|
3755
3199
|
)
|
|
3756
3200
|
]);
|
|
3757
3201
|
try {
|
|
3758
3202
|
if (configChanged) {
|
|
3759
3203
|
const config = JSON.parse(
|
|
3760
|
-
await
|
|
3204
|
+
await readFile4(workspace.configPath, "utf8")
|
|
3761
3205
|
);
|
|
3762
3206
|
const configModules = config.modules ?? {};
|
|
3763
3207
|
configModules.enabled = enabled;
|
|
@@ -3812,7 +3256,6 @@ ${output}` : ""}`
|
|
|
3812
3256
|
export {
|
|
3813
3257
|
enableModule,
|
|
3814
3258
|
hashBytes,
|
|
3815
|
-
installModule,
|
|
3816
3259
|
loadModuleCatalog,
|
|
3817
3260
|
packModule,
|
|
3818
3261
|
readModuleSource,
|