framer-code-link 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +103 -66
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -562,6 +562,7 @@ async function getOrCreateCerts() {
|
|
|
562
562
|
if (existingKey || existingCert) await invalidateIncompleteServerBundle();
|
|
563
563
|
status("Generating local certificates to connect securely. You may be asked for your password.");
|
|
564
564
|
await generateCerts(mkcertPath);
|
|
565
|
+
status("Successfully generated certificates.");
|
|
565
566
|
return {
|
|
566
567
|
key: await fs.readFile(SERVER_KEY_PATH, "utf-8"),
|
|
567
568
|
cert: await fs.readFile(SERVER_CERT_PATH, "utf-8")
|
|
@@ -1452,9 +1453,21 @@ async function findSkillsSourceDir() {
|
|
|
1452
1453
|
const FETCH_TIMEOUT_MS = 6e4;
|
|
1453
1454
|
const MAX_FETCH_RETRIES = 3;
|
|
1454
1455
|
const MAX_CONSECUTIVE_FAILURES = 10;
|
|
1455
|
-
const
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1456
|
+
const FRAMER_PACKAGE_NAME = "framer";
|
|
1457
|
+
const CORE_LIBRARIES = [
|
|
1458
|
+
"framer-motion",
|
|
1459
|
+
"framer",
|
|
1460
|
+
"react",
|
|
1461
|
+
"react-dom"
|
|
1462
|
+
];
|
|
1463
|
+
/** Packages with pinned type versions — used by ATA's `// types:` comment syntax */
|
|
1464
|
+
const DEFAULT_PINNED_TYPE_VERSIONS = {
|
|
1465
|
+
"framer-motion": "12.34.3",
|
|
1466
|
+
"react": "18.2.0",
|
|
1467
|
+
"react-dom": "18.2.0",
|
|
1468
|
+
"@types/react": "18.2.0",
|
|
1469
|
+
"@types/react-dom": "18.2.0"
|
|
1470
|
+
};
|
|
1458
1471
|
const JSON_EXTENSION_REGEX = /\.json$/i;
|
|
1459
1472
|
/**
|
|
1460
1473
|
* Packages that are officially supported for type acquisition.
|
|
@@ -1464,6 +1477,7 @@ const SUPPORTED_PACKAGES = new Set([
|
|
|
1464
1477
|
"framer",
|
|
1465
1478
|
"framer-motion",
|
|
1466
1479
|
"react",
|
|
1480
|
+
"react-dom",
|
|
1467
1481
|
"@types/react",
|
|
1468
1482
|
"eventemitter3",
|
|
1469
1483
|
"csstype",
|
|
@@ -1479,6 +1493,8 @@ var Installer = class {
|
|
|
1479
1493
|
ata;
|
|
1480
1494
|
processedImports = /* @__PURE__ */ new Set();
|
|
1481
1495
|
initializationPromise = null;
|
|
1496
|
+
pinnedTypeVersions = { ...DEFAULT_PINNED_TYPE_VERSIONS };
|
|
1497
|
+
pinnedTypeVersionsPromise = null;
|
|
1482
1498
|
constructor(config) {
|
|
1483
1499
|
this.projectDir = config.projectDir;
|
|
1484
1500
|
this.allowUnsupportedNpm = config.allowUnsupportedNpm ?? false;
|
|
@@ -1559,10 +1575,12 @@ var Installer = class {
|
|
|
1559
1575
|
this.ensureSkills(),
|
|
1560
1576
|
this.ensureGitignore()
|
|
1561
1577
|
]);
|
|
1578
|
+
this.pinnedTypeVersionsPromise = this.resolvePinnedTypeVersions();
|
|
1562
1579
|
Promise.resolve().then(async () => {
|
|
1563
|
-
await this.
|
|
1564
|
-
const
|
|
1565
|
-
await this.
|
|
1580
|
+
const coreImports = await this.buildPinnedImports(CORE_LIBRARIES);
|
|
1581
|
+
const packageJsonDeps = this.allowUnsupportedNpm ? Object.keys(this.pinnedTypeVersions).filter((name) => !SUPPORTED_PACKAGES.has(name)) : [];
|
|
1582
|
+
const imports = [...coreImports, ...await this.buildPinnedImports(packageJsonDeps)].join("\n");
|
|
1583
|
+
await this.ata(imports);
|
|
1566
1584
|
}).catch((err) => {
|
|
1567
1585
|
debug("Type installation failed", err);
|
|
1568
1586
|
});
|
|
@@ -1573,11 +1591,13 @@ var Installer = class {
|
|
|
1573
1591
|
const imports = this.allowUnsupportedNpm ? allImports : allImports.filter((i) => this.isSupportedPackage(i.name));
|
|
1574
1592
|
if (allImports.length - imports.length > 0 && !this.allowUnsupportedNpm) debug(`Skipping unsupported packages: ${allImports.filter((i) => !this.isSupportedPackage(i.name)).map((i) => i.name).join(", ")} (use --unsupported-npm to enable)`);
|
|
1575
1593
|
if (imports.length === 0) return;
|
|
1576
|
-
|
|
1594
|
+
await this.pinnedTypeVersionsPromise;
|
|
1595
|
+
if (this.allowUnsupportedNpm) await this.resolvePackageJsonPins();
|
|
1596
|
+
const hash = imports.map((imp) => this.pinImport(imp.name)).sort().join(",");
|
|
1577
1597
|
if (this.processedImports.has(hash)) return;
|
|
1578
1598
|
this.processedImports.add(hash);
|
|
1579
1599
|
debug(`Processing imports for ${fileName} (${imports.length} packages)`);
|
|
1580
|
-
const filteredContent = this.allowUnsupportedNpm ? content : this.buildFilteredImports(imports);
|
|
1600
|
+
const filteredContent = this.allowUnsupportedNpm ? content : await this.buildFilteredImports(imports);
|
|
1581
1601
|
try {
|
|
1582
1602
|
await this.ata(filteredContent);
|
|
1583
1603
|
} catch (err) {
|
|
@@ -1597,8 +1617,55 @@ var Installer = class {
|
|
|
1597
1617
|
/**
|
|
1598
1618
|
* Build synthetic import statements for ATA from filtered imports
|
|
1599
1619
|
*/
|
|
1600
|
-
buildFilteredImports(imports) {
|
|
1601
|
-
return imports.map((imp) =>
|
|
1620
|
+
async buildFilteredImports(imports) {
|
|
1621
|
+
return (await this.buildPinnedImports(imports.map((imp) => imp.name))).join("\n");
|
|
1622
|
+
}
|
|
1623
|
+
async buildPinnedImports(imports) {
|
|
1624
|
+
await this.pinnedTypeVersionsPromise;
|
|
1625
|
+
return imports.map((name) => this.pinImport(name));
|
|
1626
|
+
}
|
|
1627
|
+
async resolvePinnedTypeVersions() {
|
|
1628
|
+
try {
|
|
1629
|
+
const framerManifest = await fetchNpmPackageManifest(FRAMER_PACKAGE_NAME);
|
|
1630
|
+
const framerVersion = normalizePinnedVersion(framerManifest.version);
|
|
1631
|
+
if (framerVersion) this.pinnedTypeVersions.framer = framerVersion;
|
|
1632
|
+
for (const [pkg, defaultVersion] of Object.entries(DEFAULT_PINNED_TYPE_VERSIONS)) {
|
|
1633
|
+
const manifestDep = pkg.replace(/^@types\//, "");
|
|
1634
|
+
this.pinnedTypeVersions[pkg] = normalizePinnedVersion(getManifestDependencyVersion(framerManifest, manifestDep)) ?? defaultVersion;
|
|
1635
|
+
}
|
|
1636
|
+
debug(`Resolved ATA pins from ${FRAMER_PACKAGE_NAME}@${framerVersion ?? "latest"} (framer-motion ${this.pinnedTypeVersions["framer-motion"]}, react ${this.pinnedTypeVersions.react})`);
|
|
1637
|
+
} catch (err) {
|
|
1638
|
+
debug(`Falling back to default ATA pins for ${FRAMER_PACKAGE_NAME}`, err);
|
|
1639
|
+
}
|
|
1640
|
+
if (this.allowUnsupportedNpm) await this.resolvePackageJsonPins();
|
|
1641
|
+
}
|
|
1642
|
+
async resolvePackageJsonPins() {
|
|
1643
|
+
try {
|
|
1644
|
+
const pkgPath = path.join(this.projectDir, "package.json");
|
|
1645
|
+
const raw = await fs.readFile(pkgPath, "utf-8");
|
|
1646
|
+
const pkg = JSON.parse(raw);
|
|
1647
|
+
const allDeps = {
|
|
1648
|
+
...pkg.dependencies ?? {},
|
|
1649
|
+
...pkg.devDependencies ?? {}
|
|
1650
|
+
};
|
|
1651
|
+
for (const [name, range] of Object.entries(allDeps)) {
|
|
1652
|
+
const version = normalizePinnedVersion(range);
|
|
1653
|
+
if (version) this.pinnedTypeVersions[name] = version;
|
|
1654
|
+
}
|
|
1655
|
+
debug(`Resolved ${Object.keys(allDeps).length} package.json version pins`);
|
|
1656
|
+
} catch {
|
|
1657
|
+
warn("Could not read package.json for version pinning");
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Build an import statement with an optional `// types:` version pin for ATA.
|
|
1662
|
+
* Resolves the base package name for subpath imports (e.g., "framer-motion/dist" -> "framer-motion").
|
|
1663
|
+
*/
|
|
1664
|
+
pinImport(name) {
|
|
1665
|
+
const base = name.startsWith("@") ? name.split("/").slice(0, 2).join("/") : name.split("/")[0];
|
|
1666
|
+
const version = this.pinnedTypeVersions[base];
|
|
1667
|
+
if (version) return `import "${name}"; // types: ${version}`;
|
|
1668
|
+
return `import "${name}";`;
|
|
1602
1669
|
}
|
|
1603
1670
|
async writeTypeFile(receivedPath, code) {
|
|
1604
1671
|
const normalized = receivedPath.replace(/^\//, "");
|
|
@@ -1622,7 +1689,8 @@ var Installer = class {
|
|
|
1622
1689
|
const response = await fetch(`https://registry.npmjs.org/${pkgName}`);
|
|
1623
1690
|
if (!response.ok) return;
|
|
1624
1691
|
const npmData = await response.json();
|
|
1625
|
-
const
|
|
1692
|
+
const pinnedVersion = this.pinnedTypeVersions[pkgName];
|
|
1693
|
+
const version = pinnedVersion ? this.findMatchingVersion(Object.keys(npmData.versions ?? {}), pinnedVersion) : npmData["dist-tags"]?.latest;
|
|
1626
1694
|
if (!version || !npmData.versions?.[version]) return;
|
|
1627
1695
|
const pkg = npmData.versions[version];
|
|
1628
1696
|
if (pkg.exports) for (const key of Object.keys(pkg.exports)) pkg.exports[key] = fixExportTypes(pkg.exports[key]);
|
|
@@ -1630,6 +1698,17 @@ var Installer = class {
|
|
|
1630
1698
|
await fs.writeFile(pkgJsonPath, JSON.stringify(pkg, null, 2));
|
|
1631
1699
|
} catch {}
|
|
1632
1700
|
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Find the best matching version from a list of available versions.
|
|
1703
|
+
* Supports exact versions ("18.2.0") — returns exact match if available.
|
|
1704
|
+
*/
|
|
1705
|
+
findMatchingVersion(versions, pinned) {
|
|
1706
|
+
if (versions.includes(pinned)) return pinned;
|
|
1707
|
+
const [major, minor] = pinned.split(".");
|
|
1708
|
+
const prefix = `${major}.${minor}.`;
|
|
1709
|
+
const matching = versions.filter((v) => v.startsWith(prefix));
|
|
1710
|
+
return matching.length > 0 ? matching[matching.length - 1] : void 0;
|
|
1711
|
+
}
|
|
1633
1712
|
async ensureTsConfig() {
|
|
1634
1713
|
const tsconfigPath = path.join(this.projectDir, "tsconfig.json");
|
|
1635
1714
|
try {
|
|
@@ -1735,61 +1814,19 @@ declare module "*.json"
|
|
|
1735
1814
|
await fs.writeFile(gitignorePath, content);
|
|
1736
1815
|
debug("Created .gitignore");
|
|
1737
1816
|
}
|
|
1738
|
-
async ensureReact18Types() {
|
|
1739
|
-
const reactTypesDir = path.join(this.projectDir, "node_modules/@types/react");
|
|
1740
|
-
const reactFiles = [
|
|
1741
|
-
"package.json",
|
|
1742
|
-
"index.d.ts",
|
|
1743
|
-
"global.d.ts",
|
|
1744
|
-
"jsx-runtime.d.ts",
|
|
1745
|
-
"jsx-dev-runtime.d.ts"
|
|
1746
|
-
];
|
|
1747
|
-
if (await this.hasTypePackage(reactTypesDir, REACT_TYPES_VERSION, reactFiles)) debug("📦 React types (from cache)");
|
|
1748
|
-
else {
|
|
1749
|
-
debug("Downloading React 18 types...");
|
|
1750
|
-
await this.downloadTypePackage("@types/react", REACT_TYPES_VERSION, reactTypesDir, reactFiles);
|
|
1751
|
-
}
|
|
1752
|
-
const reactDomDir = path.join(this.projectDir, "node_modules/@types/react-dom");
|
|
1753
|
-
const reactDomFiles = [
|
|
1754
|
-
"package.json",
|
|
1755
|
-
"index.d.ts",
|
|
1756
|
-
"client.d.ts"
|
|
1757
|
-
];
|
|
1758
|
-
if (await this.hasTypePackage(reactDomDir, REACT_DOM_TYPES_VERSION, reactDomFiles)) debug("📦 React DOM types (from cache)");
|
|
1759
|
-
else await this.downloadTypePackage("@types/react-dom", REACT_DOM_TYPES_VERSION, reactDomDir, reactDomFiles);
|
|
1760
|
-
}
|
|
1761
|
-
async hasTypePackage(destinationDir, version, files) {
|
|
1762
|
-
try {
|
|
1763
|
-
const pkgJsonPath = path.join(destinationDir, "package.json");
|
|
1764
|
-
const pkgJson = await fs.readFile(pkgJsonPath, "utf-8");
|
|
1765
|
-
if (JSON.parse(pkgJson).version !== version) return false;
|
|
1766
|
-
for (const file of files) {
|
|
1767
|
-
if (file === "package.json") continue;
|
|
1768
|
-
await fs.access(path.join(destinationDir, file));
|
|
1769
|
-
}
|
|
1770
|
-
return true;
|
|
1771
|
-
} catch {
|
|
1772
|
-
return false;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
async downloadTypePackage(pkgName, version, destinationDir, files) {
|
|
1776
|
-
const baseUrl = `https://unpkg.com/${pkgName}@${version}`;
|
|
1777
|
-
await fs.mkdir(destinationDir, { recursive: true });
|
|
1778
|
-
await Promise.all(files.map(async (file) => {
|
|
1779
|
-
const destination = path.join(destinationDir, file);
|
|
1780
|
-
try {
|
|
1781
|
-
await fs.access(destination);
|
|
1782
|
-
return;
|
|
1783
|
-
} catch {}
|
|
1784
|
-
try {
|
|
1785
|
-
const response = await fetch(`${baseUrl}/${file}`);
|
|
1786
|
-
if (!response.ok) return;
|
|
1787
|
-
const content = await response.text();
|
|
1788
|
-
await fs.writeFile(destination, content);
|
|
1789
|
-
} catch {}
|
|
1790
|
-
}));
|
|
1791
|
-
}
|
|
1792
1817
|
};
|
|
1818
|
+
function getManifestDependencyVersion(manifest, packageName) {
|
|
1819
|
+
return manifest.peerDependencies?.[packageName] ?? manifest.dependencies?.[packageName];
|
|
1820
|
+
}
|
|
1821
|
+
function normalizePinnedVersion(version) {
|
|
1822
|
+
if (!version) return void 0;
|
|
1823
|
+
return /\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/.exec(version)?.[0];
|
|
1824
|
+
}
|
|
1825
|
+
async function fetchNpmPackageManifest(packageName) {
|
|
1826
|
+
const response = await fetchWithRetry(`https://registry.npmjs.org/${packageName}/latest`);
|
|
1827
|
+
if (!response.ok) throw new Error(`Failed to fetch ${packageName} manifest: ${response.status}`);
|
|
1828
|
+
return await response.json();
|
|
1829
|
+
}
|
|
1793
1830
|
/**
|
|
1794
1831
|
* Transform package.json exports to include .d.ts type paths
|
|
1795
1832
|
*/
|
|
@@ -3149,7 +3186,6 @@ async function executeEffect(effect, context) {
|
|
|
3149
3186
|
* Starts the sync controller with the given configuration
|
|
3150
3187
|
*/
|
|
3151
3188
|
async function start(config) {
|
|
3152
|
-
status("Waiting for Plugin connection...");
|
|
3153
3189
|
const hashTracker = createHashTracker();
|
|
3154
3190
|
const fileMetadataCache = new FileMetadataCache();
|
|
3155
3191
|
const pendingRenameConfirmations = /* @__PURE__ */ new Map();
|
|
@@ -3191,6 +3227,7 @@ async function start(config) {
|
|
|
3191
3227
|
info("");
|
|
3192
3228
|
throw new Error("TLS certificate generation failed");
|
|
3193
3229
|
}
|
|
3230
|
+
status("Waiting for Plugin connection...");
|
|
3194
3231
|
const connection = await initConnection(config.port, certs);
|
|
3195
3232
|
connection.on("handshake", (client, message) => {
|
|
3196
3233
|
debug(`Received handshake: ${message.projectName} (${message.projectId})`);
|