framer-code-link 0.21.0-alpha.2 → 0.21.0-alpha.4
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 +108 -206
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { Command, InvalidArgumentError } from "commander";
|
|
3
|
+
import { Command, InvalidArgumentError, Option } from "commander";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
@@ -916,32 +916,31 @@ const DEFAULT_REMOTE_DRIFT_MS = 2e3;
|
|
|
916
916
|
*/
|
|
917
917
|
async function listFiles(filesDir) {
|
|
918
918
|
const files = [];
|
|
919
|
-
|
|
920
|
-
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
921
|
-
for (const entry of entries) {
|
|
922
|
-
const entryPath = path.join(currentDir, entry.name);
|
|
923
|
-
if (entry.isDirectory()) {
|
|
924
|
-
await walk(entryPath);
|
|
925
|
-
continue;
|
|
926
|
-
}
|
|
927
|
-
if (!isSupportedExtension(entry.name)) continue;
|
|
928
|
-
const sanitizedPath = sanitizeFilePath(normalizePath(path.relative(filesDir, entryPath)), false).path;
|
|
929
|
-
try {
|
|
930
|
-
const [content, stats] = await Promise.all([fs.readFile(entryPath, "utf-8"), fs.stat(entryPath)]);
|
|
931
|
-
files.push({
|
|
932
|
-
name: sanitizedPath,
|
|
933
|
-
content,
|
|
934
|
-
modifiedAt: stats.mtimeMs
|
|
935
|
-
});
|
|
936
|
-
} catch (err) {
|
|
937
|
-
warn(`Failed to read ${entryPath}:`, err);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
}
|
|
919
|
+
let entries;
|
|
941
920
|
try {
|
|
942
|
-
await
|
|
921
|
+
entries = await fs.readdir(filesDir, {
|
|
922
|
+
withFileTypes: true,
|
|
923
|
+
recursive: true
|
|
924
|
+
});
|
|
943
925
|
} catch (err) {
|
|
944
926
|
warn("Failed to list files:", err);
|
|
927
|
+
return files;
|
|
928
|
+
}
|
|
929
|
+
for (const entry of entries) {
|
|
930
|
+
if (!entry.isFile()) continue;
|
|
931
|
+
if (!isSupportedExtension(entry.name)) continue;
|
|
932
|
+
const entryPath = path.join(entry.parentPath, entry.name);
|
|
933
|
+
const sanitizedPath = sanitizeFilePath(normalizePath(path.relative(filesDir, entryPath)), false).path;
|
|
934
|
+
try {
|
|
935
|
+
const [content, stats] = await Promise.all([fs.readFile(entryPath, "utf-8"), fs.stat(entryPath)]);
|
|
936
|
+
files.push({
|
|
937
|
+
name: sanitizedPath,
|
|
938
|
+
content,
|
|
939
|
+
modifiedAt: stats.mtimeMs
|
|
940
|
+
});
|
|
941
|
+
} catch (err) {
|
|
942
|
+
warn(`Failed to read ${entryPath}:`, err);
|
|
943
|
+
}
|
|
945
944
|
}
|
|
946
945
|
return files;
|
|
947
946
|
}
|
|
@@ -1288,49 +1287,6 @@ function tryGitInit(projectDir) {
|
|
|
1288
1287
|
}
|
|
1289
1288
|
}
|
|
1290
1289
|
|
|
1291
|
-
//#endregion
|
|
1292
|
-
//#region src/utils/imports.ts
|
|
1293
|
-
/**
|
|
1294
|
-
* Extract npm and URL-based imports from source code.
|
|
1295
|
-
*/
|
|
1296
|
-
function extractImports(code) {
|
|
1297
|
-
const imports = [];
|
|
1298
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1299
|
-
const npmRegex = /import\s+(?:(?:\*\s+as\s+\w+)|(?:\w+)|(?:\{[^}]*\}))\s+from\s+['"]([^./][^'"]+)['"]/g;
|
|
1300
|
-
const urlRegex = /import\s+(?:(?:\*\s+as\s+\w+)|(?:\w+)|(?:\{[^}]*\}))\s+from\s+['"]https?:\/\/[^'"]+['"]/g;
|
|
1301
|
-
let match;
|
|
1302
|
-
while ((match = npmRegex.exec(code)) !== null) {
|
|
1303
|
-
const pkgName = match[1];
|
|
1304
|
-
const normalized = pkgName.startsWith("@") ? pkgName.split("/").slice(0, 2).join("/") : pkgName.split("/")[0];
|
|
1305
|
-
if (!seen.has(normalized)) {
|
|
1306
|
-
seen.add(normalized);
|
|
1307
|
-
imports.push({
|
|
1308
|
-
type: "npm",
|
|
1309
|
-
name: normalized,
|
|
1310
|
-
raw: match[0]
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
while ((match = urlRegex.exec(code)) !== null) {
|
|
1315
|
-
const pkgName = extractPackageFromUrl(match[0]);
|
|
1316
|
-
if (pkgName && !seen.has(pkgName)) {
|
|
1317
|
-
seen.add(pkgName);
|
|
1318
|
-
imports.push({
|
|
1319
|
-
type: "url",
|
|
1320
|
-
name: pkgName,
|
|
1321
|
-
raw: match[0]
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
return imports;
|
|
1326
|
-
}
|
|
1327
|
-
/**
|
|
1328
|
-
* Attempt to derive an npm-style package specifier from a URL import.
|
|
1329
|
-
*/
|
|
1330
|
-
function extractPackageFromUrl(url) {
|
|
1331
|
-
return /\/(@?[^@/]+(?:\/[^@/]+)?)/.exec(url)?.[1] ?? null;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
1290
|
//#endregion
|
|
1335
1291
|
//#region src/helpers/skills.ts
|
|
1336
1292
|
/**
|
|
@@ -1464,10 +1420,9 @@ const DEFAULT_PINNED_TYPE_VERSIONS = {
|
|
|
1464
1420
|
};
|
|
1465
1421
|
const JSON_EXTENSION_REGEX = /\.json$/i;
|
|
1466
1422
|
/**
|
|
1467
|
-
* Packages that
|
|
1468
|
-
* Use --unsupported-npm flag to allow other packages.
|
|
1423
|
+
* Packages that receive ATA types even when unsupported npm is not enabled.
|
|
1469
1424
|
*/
|
|
1470
|
-
const
|
|
1425
|
+
const DEFAULT_PACKAGES = new Set([
|
|
1471
1426
|
"framer",
|
|
1472
1427
|
"framer-motion",
|
|
1473
1428
|
"react",
|
|
@@ -1487,14 +1442,12 @@ var Installer = class {
|
|
|
1487
1442
|
requestDependencyVersions;
|
|
1488
1443
|
ata;
|
|
1489
1444
|
processedImports = /* @__PURE__ */ new Set();
|
|
1490
|
-
packageManagerPackages = /* @__PURE__ */ new Set();
|
|
1491
|
-
packageJsonRefreshPromise = Promise.resolve();
|
|
1492
1445
|
initializationPromise = null;
|
|
1493
1446
|
pinnedTypeVersions = { ...DEFAULT_PINNED_TYPE_VERSIONS };
|
|
1494
1447
|
pinnedTypeVersionsPromise = null;
|
|
1495
1448
|
constructor(config) {
|
|
1496
1449
|
this.projectDir = config.projectDir;
|
|
1497
|
-
this.npmStrategy = config.npmStrategy
|
|
1450
|
+
this.npmStrategy = config.npmStrategy;
|
|
1498
1451
|
this.requestDependencyVersions = config.requestDependencyVersions ?? (async (packages) => Object.fromEntries(packages.map((packageName) => [packageName, null])));
|
|
1499
1452
|
const seenPackages = /* @__PURE__ */ new Set();
|
|
1500
1453
|
this.ata = setupTypeAcquisition({
|
|
@@ -1553,16 +1506,21 @@ var Installer = class {
|
|
|
1553
1506
|
return this.initializationPromise;
|
|
1554
1507
|
}
|
|
1555
1508
|
/**
|
|
1556
|
-
*
|
|
1509
|
+
* Process component files to fetch missing types or add `package.json` dependencies.
|
|
1557
1510
|
* JSON files are ignored.
|
|
1558
1511
|
*/
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1512
|
+
async processFiles(files) {
|
|
1513
|
+
const packageNames = /* @__PURE__ */ new Set();
|
|
1514
|
+
for (const file of files) {
|
|
1515
|
+
if (!file.content || JSON_EXTENSION_REGEX.test(file.name)) continue;
|
|
1516
|
+
try {
|
|
1517
|
+
const imports = extractNpmPackageNames(file.content);
|
|
1518
|
+
for (const packageName of imports) packageNames.add(packageName);
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
debug(`Type installer failed to parse imports for ${file.name}`, err);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
await this.processNpmPackages(packageNames, files.length);
|
|
1566
1524
|
}
|
|
1567
1525
|
async initializeProject() {
|
|
1568
1526
|
await Promise.all([
|
|
@@ -1575,83 +1533,48 @@ var Installer = class {
|
|
|
1575
1533
|
]);
|
|
1576
1534
|
if (this.npmStrategy === "package-manager") {
|
|
1577
1535
|
await this.resolvePinnedTypeVersions();
|
|
1578
|
-
await this.enqueuePackageJsonRefresh(await this.collectPackageManagerPackageNames());
|
|
1579
1536
|
return;
|
|
1580
1537
|
}
|
|
1581
1538
|
this.pinnedTypeVersionsPromise = this.resolvePinnedTypeVersions();
|
|
1582
1539
|
Promise.resolve().then(async () => {
|
|
1583
|
-
|
|
1584
|
-
const packageJsonDeps = this.npmStrategy === "acquire-types" ? Object.keys(this.pinnedTypeVersions).filter((name) => !SUPPORTED_PACKAGES.has(name)) : [];
|
|
1585
|
-
const imports = [...coreImports, ...await this.buildPinnedImports(packageJsonDeps)].join("\n");
|
|
1586
|
-
await this.ata(imports);
|
|
1540
|
+
await this.ata((await this.buildPinnedImports(CORE_LIBRARIES)).join("\n"));
|
|
1587
1541
|
}).catch((err) => {
|
|
1588
1542
|
debug("Type installation failed", err);
|
|
1589
1543
|
});
|
|
1590
1544
|
}
|
|
1591
|
-
async
|
|
1592
|
-
const
|
|
1593
|
-
if (allImports.length === 0) return;
|
|
1545
|
+
async processNpmPackages(packageNames, fileCount) {
|
|
1546
|
+
const allPackageNames = [...packageNames];
|
|
1594
1547
|
if (this.npmStrategy === "package-manager") {
|
|
1595
|
-
|
|
1548
|
+
for (const packageName of [...CORE_LIBRARIES, ...PACKAGE_MANAGER_DEV_DEPENDENCIES]) allPackageNames.push(packageName);
|
|
1549
|
+
try {
|
|
1550
|
+
await this.updatePackageJsonDependencies(allPackageNames);
|
|
1551
|
+
} catch (err) {
|
|
1552
|
+
warn("Could not update package.json dependency versions", err);
|
|
1553
|
+
}
|
|
1596
1554
|
return;
|
|
1597
1555
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
if (
|
|
1556
|
+
if (allPackageNames.length === 0) return;
|
|
1557
|
+
let packagesForAta;
|
|
1558
|
+
if (this.npmStrategy === "acquire-types") packagesForAta = allPackageNames;
|
|
1559
|
+
else {
|
|
1560
|
+
packagesForAta = allPackageNames.filter((packageName) => this.isDefaultPackage(packageName));
|
|
1561
|
+
const unsupportedPackages = allPackageNames.filter((packageName) => !this.isDefaultPackage(packageName));
|
|
1562
|
+
if (unsupportedPackages.length > 0) debug(`Skipping unsupported packages: ${unsupportedPackages.join(", ")} (use --unsupported-npm to enable)`);
|
|
1563
|
+
}
|
|
1564
|
+
if (packagesForAta.length === 0) return;
|
|
1601
1565
|
await this.pinnedTypeVersionsPromise;
|
|
1602
|
-
|
|
1603
|
-
const hash = imports.map((imp) => this.pinImport(imp.name)).sort().join(",");
|
|
1566
|
+
const hash = packagesForAta.map((packageName) => this.pinImport(packageName)).sort().join(",");
|
|
1604
1567
|
if (this.processedImports.has(hash)) return;
|
|
1605
1568
|
this.processedImports.add(hash);
|
|
1606
|
-
debug(`Processing imports
|
|
1607
|
-
const filteredContent = this.npmStrategy === "acquire-types" ? content : await this.buildFilteredImports(imports);
|
|
1569
|
+
debug(`Processing imports from ${fileCount} ${fileCount === 1 ? "file" : "files"} (${packagesForAta.length} packages)`);
|
|
1608
1570
|
try {
|
|
1609
|
-
await this.ata(
|
|
1571
|
+
await this.ata((await this.buildPinnedImports(packagesForAta)).join("\n"));
|
|
1610
1572
|
} catch (err) {
|
|
1611
|
-
warn(
|
|
1612
|
-
debug(
|
|
1573
|
+
warn("Type fetching failed");
|
|
1574
|
+
debug("ATA error:", err);
|
|
1613
1575
|
}
|
|
1614
1576
|
}
|
|
1615
|
-
async
|
|
1616
|
-
const packageNames = new Set([...CORE_LIBRARIES, ...PACKAGE_MANAGER_DEV_DEPENDENCIES]);
|
|
1617
|
-
await this.addPackageNamesFromDirectory(path.join(this.projectDir, "files"), packageNames);
|
|
1618
|
-
return [...packageNames];
|
|
1619
|
-
}
|
|
1620
|
-
async addPackageNamesFromDirectory(directory, packageNames) {
|
|
1621
|
-
let entries;
|
|
1622
|
-
try {
|
|
1623
|
-
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
1624
|
-
} catch {
|
|
1625
|
-
return;
|
|
1626
|
-
}
|
|
1627
|
-
await Promise.all(entries.map(async (entry) => {
|
|
1628
|
-
const entryPath = path.join(directory, entry.name);
|
|
1629
|
-
if (entry.isDirectory()) {
|
|
1630
|
-
await this.addPackageNamesFromDirectory(entryPath, packageNames);
|
|
1631
|
-
return;
|
|
1632
|
-
}
|
|
1633
|
-
if (!entry.isFile() || JSON_EXTENSION_REGEX.test(entry.name)) return;
|
|
1634
|
-
try {
|
|
1635
|
-
const content = await fs.readFile(entryPath, "utf-8");
|
|
1636
|
-
for (const imported of extractImports(content).filter((i) => i.type === "npm")) packageNames.add(getBasePackageName(imported.name));
|
|
1637
|
-
} catch {}
|
|
1638
|
-
}));
|
|
1639
|
-
}
|
|
1640
|
-
async enqueuePackageJsonRefresh(packageNames) {
|
|
1641
|
-
const missingPackageNames = packageNames.map((name) => getBasePackageName(name)).filter((name) => {
|
|
1642
|
-
if (this.packageManagerPackages.has(name)) return false;
|
|
1643
|
-
this.packageManagerPackages.add(name);
|
|
1644
|
-
return true;
|
|
1645
|
-
});
|
|
1646
|
-
if (missingPackageNames.length === 0) return this.packageJsonRefreshPromise;
|
|
1647
|
-
this.packageJsonRefreshPromise = this.packageJsonRefreshPromise.then(async () => {
|
|
1648
|
-
await this.refreshPackageJsonFromPlugin(missingPackageNames);
|
|
1649
|
-
}).catch((err) => {
|
|
1650
|
-
warn("Could not refresh package.json dependency versions", err);
|
|
1651
|
-
});
|
|
1652
|
-
return this.packageJsonRefreshPromise;
|
|
1653
|
-
}
|
|
1654
|
-
async refreshPackageJsonFromPlugin(packageNames) {
|
|
1577
|
+
async updatePackageJsonDependencies(packageNames) {
|
|
1655
1578
|
const uniquePackageNames = [...new Set(packageNames)].sort();
|
|
1656
1579
|
const versions = await this.requestDependencyVersions(uniquePackageNames);
|
|
1657
1580
|
const packagePath = path.join(this.projectDir, "package.json");
|
|
@@ -1661,7 +1584,7 @@ var Installer = class {
|
|
|
1661
1584
|
const devDependencies = typeof pkg.devDependencies === "object" && pkg.devDependencies !== null && !Array.isArray(pkg.devDependencies) ? { ...pkg.devDependencies } : {};
|
|
1662
1585
|
let changed = false;
|
|
1663
1586
|
for (const packageName of uniquePackageNames) {
|
|
1664
|
-
const version =
|
|
1587
|
+
const version = versions[packageName] ?? this.pinnedTypeVersions[packageName];
|
|
1665
1588
|
if (!version) continue;
|
|
1666
1589
|
const targetDependencies = PACKAGE_MANAGER_DEV_DEPENDENCIES.includes(packageName) ? devDependencies : dependencies;
|
|
1667
1590
|
const oppositeDependencies = PACKAGE_MANAGER_DEV_DEPENDENCIES.includes(packageName) ? dependencies : devDependencies;
|
|
@@ -1678,23 +1601,16 @@ var Installer = class {
|
|
|
1678
1601
|
pkg.dependencies = sortDependencyMap(dependencies);
|
|
1679
1602
|
pkg.devDependencies = sortDependencyMap(devDependencies);
|
|
1680
1603
|
await fs.writeFile(packagePath, JSON.stringify(pkg, null, 4));
|
|
1681
|
-
status("
|
|
1604
|
+
status("Updated dependencies. Run your package manager to install them.");
|
|
1682
1605
|
debug(`Updated package.json dependency versions for ${uniquePackageNames.join(", ")}`);
|
|
1683
1606
|
}
|
|
1684
1607
|
/**
|
|
1685
|
-
* Check if a package
|
|
1686
|
-
* Also checks for subpath imports (e.g., "framer/build" -> "framer")
|
|
1608
|
+
* Check if a package receives ATA types without unsupported npm enabled.
|
|
1687
1609
|
*/
|
|
1688
|
-
|
|
1689
|
-
if (
|
|
1610
|
+
isDefaultPackage(pkgName) {
|
|
1611
|
+
if (DEFAULT_PACKAGES.has(pkgName)) return true;
|
|
1690
1612
|
const basePkg = getBasePackageName(pkgName);
|
|
1691
|
-
return
|
|
1692
|
-
}
|
|
1693
|
-
/**
|
|
1694
|
-
* Build synthetic import statements for ATA from filtered imports
|
|
1695
|
-
*/
|
|
1696
|
-
async buildFilteredImports(imports) {
|
|
1697
|
-
return (await this.buildPinnedImports(imports.map((imp) => imp.name))).join("\n");
|
|
1613
|
+
return DEFAULT_PACKAGES.has(basePkg);
|
|
1698
1614
|
}
|
|
1699
1615
|
async buildPinnedImports(imports) {
|
|
1700
1616
|
await this.pinnedTypeVersionsPromise;
|
|
@@ -1713,25 +1629,6 @@ var Installer = class {
|
|
|
1713
1629
|
} catch (err) {
|
|
1714
1630
|
debug(`Falling back to default ATA pins for ${FRAMER_PACKAGE_NAME}`, err);
|
|
1715
1631
|
}
|
|
1716
|
-
if (this.npmStrategy === "acquire-types") await this.resolvePackageJsonPins();
|
|
1717
|
-
}
|
|
1718
|
-
async resolvePackageJsonPins() {
|
|
1719
|
-
try {
|
|
1720
|
-
const pkgPath = path.join(this.projectDir, "package.json");
|
|
1721
|
-
const raw = await fs.readFile(pkgPath, "utf-8");
|
|
1722
|
-
const pkg = JSON.parse(raw);
|
|
1723
|
-
const allDeps = {
|
|
1724
|
-
...pkg.dependencies ?? {},
|
|
1725
|
-
...pkg.devDependencies ?? {}
|
|
1726
|
-
};
|
|
1727
|
-
for (const [name, range] of Object.entries(allDeps)) {
|
|
1728
|
-
const version = normalizePinnedVersion(range);
|
|
1729
|
-
if (version) this.pinnedTypeVersions[name] = version;
|
|
1730
|
-
}
|
|
1731
|
-
debug(`Resolved ${Object.keys(allDeps).length} package.json version pins`);
|
|
1732
|
-
} catch {
|
|
1733
|
-
warn("Could not read package.json for version pinning");
|
|
1734
|
-
}
|
|
1735
1632
|
}
|
|
1736
1633
|
/**
|
|
1737
1634
|
* Build an import statement with an optional `// types:` version pin for ATA.
|
|
@@ -1899,6 +1796,15 @@ function getBasePackageName(packageName) {
|
|
|
1899
1796
|
if (packageName.startsWith("@")) return parts.length >= 2 ? parts.slice(0, 2).join("/") : packageName;
|
|
1900
1797
|
return parts[0] ?? packageName;
|
|
1901
1798
|
}
|
|
1799
|
+
function extractNpmPackageNames(code) {
|
|
1800
|
+
const { importedFiles } = ts.preProcessFile(code, true, true);
|
|
1801
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1802
|
+
for (const { fileName } of importedFiles) if (isPackageSpecifier(fileName)) seen.add(getBasePackageName(fileName));
|
|
1803
|
+
return [...seen];
|
|
1804
|
+
}
|
|
1805
|
+
function isPackageSpecifier(specifier) {
|
|
1806
|
+
return !specifier.startsWith(".") && !specifier.startsWith("/") && !/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(specifier);
|
|
1807
|
+
}
|
|
1902
1808
|
function sortDependencyMap(dependencies) {
|
|
1903
1809
|
return Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
1904
1810
|
}
|
|
@@ -2097,12 +2003,6 @@ async function matchesProject(packageJsonPath, projectHash) {
|
|
|
2097
2003
|
//#endregion
|
|
2098
2004
|
//#region src/helpers/npm-strategy.ts
|
|
2099
2005
|
const CONFIG_FIELD = "codeLink.npmStrategy";
|
|
2100
|
-
const LOCKFILES = [
|
|
2101
|
-
"yarn.lock",
|
|
2102
|
-
"pnpm-lock.yaml",
|
|
2103
|
-
"package-lock.json",
|
|
2104
|
-
"bun.lockb"
|
|
2105
|
-
];
|
|
2106
2006
|
async function resolveNpmStrategy(config, projectDir) {
|
|
2107
2007
|
if (config.npmStrategy) {
|
|
2108
2008
|
debug(`Using npm strategy from CLI flag: ${config.npmStrategy}`);
|
|
@@ -2113,13 +2013,7 @@ async function resolveNpmStrategy(config, projectDir) {
|
|
|
2113
2013
|
debug(`Using npm strategy from package.json ${CONFIG_FIELD}: ${packageJsonStrategy}`);
|
|
2114
2014
|
return packageJsonStrategy;
|
|
2115
2015
|
}
|
|
2116
|
-
|
|
2117
|
-
if (detectedLockfile) {
|
|
2118
|
-
debug(`Using npm strategy package-manager from ${detectedLockfile}`);
|
|
2119
|
-
return "package-manager";
|
|
2120
|
-
}
|
|
2121
|
-
debug("Using default npm strategy: none");
|
|
2122
|
-
return "none";
|
|
2016
|
+
debug("No npm strategy from CLI or package.json");
|
|
2123
2017
|
}
|
|
2124
2018
|
async function readPackageJsonStrategy(projectDir) {
|
|
2125
2019
|
try {
|
|
@@ -2134,15 +2028,8 @@ async function readPackageJsonStrategy(projectDir) {
|
|
|
2134
2028
|
return null;
|
|
2135
2029
|
}
|
|
2136
2030
|
}
|
|
2137
|
-
async function detectLockfile(projectDir) {
|
|
2138
|
-
for (const fileName of LOCKFILES) try {
|
|
2139
|
-
await fs.access(path.join(projectDir, fileName));
|
|
2140
|
-
return fileName;
|
|
2141
|
-
} catch {}
|
|
2142
|
-
return null;
|
|
2143
|
-
}
|
|
2144
2031
|
function isNpmStrategy(value) {
|
|
2145
|
-
return value === "
|
|
2032
|
+
return value === "acquire-types" || value === "package-manager";
|
|
2146
2033
|
}
|
|
2147
2034
|
|
|
2148
2035
|
//#endregion
|
|
@@ -3426,12 +3313,14 @@ async function writeFiles(files, ctx, options) {
|
|
|
3426
3313
|
const filesToWrite = options.echoPolicy === "skip-expected-echoes" ? filterEchoedFiles(files, runtime.memory) : files;
|
|
3427
3314
|
if (options.echoPolicy === "skip-expected-echoes" && filesToWrite.length !== files.length) debug(`Skipped ${pluralize(files.length - filesToWrite.length, "echoed change")}`);
|
|
3428
3315
|
const results = await writeRemoteFiles(filesToWrite, runtime.workspace.filesDir, runtime.memory);
|
|
3316
|
+
const installerFiles = [];
|
|
3429
3317
|
for (const result of results) {
|
|
3430
3318
|
if (!result.ok) continue;
|
|
3431
3319
|
if (!options.silent) fileDown(result.path);
|
|
3432
3320
|
runtime.memory.recordSyncedContent(result.path, result.file.content, result.file.modifiedAt ?? Date.now());
|
|
3433
|
-
|
|
3321
|
+
if (!options.silent) installerFiles.push(result.file);
|
|
3434
3322
|
}
|
|
3323
|
+
await processInstallerFiles(runtime, installerFiles);
|
|
3435
3324
|
}
|
|
3436
3325
|
async function deleteFiles(fileNames, ctx) {
|
|
3437
3326
|
const { runtime } = ctx;
|
|
@@ -3458,7 +3347,10 @@ async function sendLocalChange(fileName, content, ctx) {
|
|
|
3458
3347
|
})) return;
|
|
3459
3348
|
runtime.memory.armContentEcho(fileName, content);
|
|
3460
3349
|
fileUp(fileName);
|
|
3461
|
-
runtime
|
|
3350
|
+
await processInstallerFiles(runtime, [{
|
|
3351
|
+
name: fileName,
|
|
3352
|
+
content
|
|
3353
|
+
}]);
|
|
3462
3354
|
}
|
|
3463
3355
|
async function sendFileDelete(fileNames, ctx) {
|
|
3464
3356
|
if (fileNames.length === 0) return;
|
|
@@ -3554,19 +3446,22 @@ async function applySyncComplete(effect, ctx) {
|
|
|
3554
3446
|
const wasDisconnected = runtime.disconnectUi.wasRecentlyDisconnected();
|
|
3555
3447
|
let shouldShutdown = !!config.once;
|
|
3556
3448
|
let shouldTryGitInit = false;
|
|
3449
|
+
let statusMessage = null;
|
|
3557
3450
|
if (wasDisconnected) {
|
|
3558
3451
|
const didShow = runtime.disconnectUi.didShowNotice();
|
|
3559
3452
|
shouldShutdown = didShow && !!config.once;
|
|
3560
3453
|
if (didShow) {
|
|
3561
3454
|
success(`Reconnected, synced ${pluralize(effect.totalCount, "file")} (${effect.updatedCount} updated, ${effect.unchangedCount} unchanged)`);
|
|
3562
|
-
|
|
3455
|
+
statusMessage = syncCompleteStatusMessage(config);
|
|
3563
3456
|
}
|
|
3564
3457
|
} else {
|
|
3565
3458
|
const message = syncCompleteSuccessMessage(runtime, effect);
|
|
3566
3459
|
if (message) success(message);
|
|
3567
|
-
|
|
3460
|
+
statusMessage = syncCompleteStatusMessage(config);
|
|
3568
3461
|
shouldTryGitInit = !!(runtime.workspace.projectDirCreated && runtime.workspace.projectDir);
|
|
3569
3462
|
}
|
|
3463
|
+
await processAllInstallerFiles(ctx);
|
|
3464
|
+
if (statusMessage) status(statusMessage);
|
|
3570
3465
|
await sendToPlugin(syncState.socket, {
|
|
3571
3466
|
type: "sync-status",
|
|
3572
3467
|
status: "ready"
|
|
@@ -3576,6 +3471,21 @@ async function applySyncComplete(effect, ctx) {
|
|
|
3576
3471
|
if (shouldTryGitInit && runtime.workspace.projectDir) tryGitInit(runtime.workspace.projectDir);
|
|
3577
3472
|
if (shouldShutdown) await shutdown();
|
|
3578
3473
|
}
|
|
3474
|
+
async function processInstallerFiles(runtime, files) {
|
|
3475
|
+
if (!runtime.installer || runtime.lastEmittedSyncStatus !== "ready") return;
|
|
3476
|
+
if (files.length === 0) return;
|
|
3477
|
+
try {
|
|
3478
|
+
await runtime.installer.processFiles(files);
|
|
3479
|
+
} catch (err) {
|
|
3480
|
+
debug("Type installer failed", err);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
async function processAllInstallerFiles(ctx) {
|
|
3484
|
+
const { runtime } = ctx;
|
|
3485
|
+
if (!runtime.installer || !runtime.workspace.filesDir) return;
|
|
3486
|
+
const files = await listFiles(runtime.workspace.filesDir);
|
|
3487
|
+
await runtime.installer.processFiles(files);
|
|
3488
|
+
}
|
|
3579
3489
|
async function flushPendingSyncComplete(ctx) {
|
|
3580
3490
|
const result = ctx.runtime.claimPendingSyncComplete();
|
|
3581
3491
|
if (result.status === "ready") await applySyncComplete({
|
|
@@ -4076,18 +3986,10 @@ async function start(config) {
|
|
|
4076
3986
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
4077
3987
|
const program = new Command();
|
|
4078
3988
|
function parseUnsupportedNpmMode(mode) {
|
|
4079
|
-
if (mode === void 0) return "acquire-types";
|
|
4080
3989
|
if (mode === "acquire-types" || mode === "package-manager") return mode;
|
|
4081
3990
|
throw new InvalidArgumentError("unsupported npm mode must be 'acquire-types' or 'package-manager'");
|
|
4082
3991
|
}
|
|
4083
|
-
program.
|
|
4084
|
-
if (err.code === "commander.missingArgument") {
|
|
4085
|
-
console.error("Missing Project ID. Copy command via Code Link Plugin.");
|
|
4086
|
-
process.exit(err.exitCode);
|
|
4087
|
-
}
|
|
4088
|
-
throw err;
|
|
4089
|
-
});
|
|
4090
|
-
program.name("framer-code-link").description("Sync Framer code components to your local filesystem").version(version).argument("[projectHash]", "Framer Project ID Hash (auto-detected from package.json if omitted)").option("-n, --name <name>", "Project name (optional)").option("-d, --dir <directory>", "Explicit project directory").option("--once", "Exit after the initial sync completes").option("-v, --verbose", "Enable verbose logging").option("--log-level <level>", "Set log level (debug, info, warn, error)").option("--dangerously-auto-delete", "Automatically delete remote files without confirmation").option("--unsupported-npm [mode]", "Handle unsupported npm packages (acquire-types or package-manager)", parseUnsupportedNpmMode).action(async (projectHash, options) => {
|
|
3992
|
+
program.name("framer-code-link").description("Sync Framer code components to your local filesystem").version(version).argument("[projectHash]", "Framer Project ID Hash (auto-detected from package.json if omitted)").option("-n, --name <name>", "Project name (optional)").option("-d, --dir <directory>", "Explicit project directory").option("--once", "Exit after the initial sync completes").option("-v, --verbose", "Enable verbose logging").option("--log-level <level>", "Set log level (debug, info, warn, error)").option("--dangerously-auto-delete", "Automatically delete remote files without confirmation").addOption(new Option("--unsupported-npm [mode]", "Handle unsupported npm packages (default without mode: acquire-types; modes: acquire-types, package-manager)").argParser(parseUnsupportedNpmMode).preset("acquire-types")).action(async (projectHash, options) => {
|
|
4091
3993
|
if (!projectHash) {
|
|
4092
3994
|
const detected = await getProjectHashFromCwd();
|
|
4093
3995
|
if (detected) projectHash = detected;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framer-code-link",
|
|
3
|
-
"version": "0.21.0-alpha.
|
|
3
|
+
"version": "0.21.0-alpha.4",
|
|
4
4
|
"description": "CLI tool for syncing Framer code components - controller-centric architecture",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@typescript/ata": "^0.9.8",
|
|
27
27
|
"chokidar": "^5.0.0",
|
|
28
28
|
"commander": "^14.0.3",
|
|
29
|
+
"parse-imports": "^3.0.0",
|
|
29
30
|
"prettier": "^3.7.4",
|
|
30
31
|
"typescript": "^5.9.3",
|
|
31
32
|
"ws": "^8.18.3"
|