framer-code-link 0.21.0-alpha.1 → 0.21.0-alpha.3
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 +114 -200
- 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";
|
|
@@ -16,6 +16,7 @@ import { createHash as createHash$1 } from "crypto";
|
|
|
16
16
|
import { execSync } from "child_process";
|
|
17
17
|
import fs$2 from "fs";
|
|
18
18
|
import { setupTypeAcquisition } from "@typescript/ata";
|
|
19
|
+
import { parseImports } from "parse-imports";
|
|
19
20
|
import ts from "typescript";
|
|
20
21
|
import { fileURLToPath } from "node:url";
|
|
21
22
|
import chokidar from "chokidar";
|
|
@@ -916,32 +917,31 @@ const DEFAULT_REMOTE_DRIFT_MS = 2e3;
|
|
|
916
917
|
*/
|
|
917
918
|
async function listFiles(filesDir) {
|
|
918
919
|
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
|
-
}
|
|
920
|
+
let entries;
|
|
941
921
|
try {
|
|
942
|
-
await
|
|
922
|
+
entries = await fs.readdir(filesDir, {
|
|
923
|
+
withFileTypes: true,
|
|
924
|
+
recursive: true
|
|
925
|
+
});
|
|
943
926
|
} catch (err) {
|
|
944
927
|
warn("Failed to list files:", err);
|
|
928
|
+
return files;
|
|
929
|
+
}
|
|
930
|
+
for (const entry of entries) {
|
|
931
|
+
if (!entry.isFile()) continue;
|
|
932
|
+
if (!isSupportedExtension(entry.name)) continue;
|
|
933
|
+
const entryPath = path.join(entry.parentPath, entry.name);
|
|
934
|
+
const sanitizedPath = sanitizeFilePath(normalizePath(path.relative(filesDir, entryPath)), false).path;
|
|
935
|
+
try {
|
|
936
|
+
const [content, stats] = await Promise.all([fs.readFile(entryPath, "utf-8"), fs.stat(entryPath)]);
|
|
937
|
+
files.push({
|
|
938
|
+
name: sanitizedPath,
|
|
939
|
+
content,
|
|
940
|
+
modifiedAt: stats.mtimeMs
|
|
941
|
+
});
|
|
942
|
+
} catch (err) {
|
|
943
|
+
warn(`Failed to read ${entryPath}:`, err);
|
|
944
|
+
}
|
|
945
945
|
}
|
|
946
946
|
return files;
|
|
947
947
|
}
|
|
@@ -1288,49 +1288,6 @@ function tryGitInit(projectDir) {
|
|
|
1288
1288
|
}
|
|
1289
1289
|
}
|
|
1290
1290
|
|
|
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
1291
|
//#endregion
|
|
1335
1292
|
//#region src/helpers/skills.ts
|
|
1336
1293
|
/**
|
|
@@ -1464,10 +1421,9 @@ const DEFAULT_PINNED_TYPE_VERSIONS = {
|
|
|
1464
1421
|
};
|
|
1465
1422
|
const JSON_EXTENSION_REGEX = /\.json$/i;
|
|
1466
1423
|
/**
|
|
1467
|
-
* Packages that
|
|
1468
|
-
* Use --unsupported-npm flag to allow other packages.
|
|
1424
|
+
* Packages that receive ATA types even when unsupported npm is not enabled.
|
|
1469
1425
|
*/
|
|
1470
|
-
const
|
|
1426
|
+
const DEFAULT_PACKAGES = new Set([
|
|
1471
1427
|
"framer",
|
|
1472
1428
|
"framer-motion",
|
|
1473
1429
|
"react",
|
|
@@ -1487,14 +1443,12 @@ var Installer = class {
|
|
|
1487
1443
|
requestDependencyVersions;
|
|
1488
1444
|
ata;
|
|
1489
1445
|
processedImports = /* @__PURE__ */ new Set();
|
|
1490
|
-
packageManagerPackages = /* @__PURE__ */ new Set();
|
|
1491
|
-
packageJsonRefreshPromise = Promise.resolve();
|
|
1492
1446
|
initializationPromise = null;
|
|
1493
1447
|
pinnedTypeVersions = { ...DEFAULT_PINNED_TYPE_VERSIONS };
|
|
1494
1448
|
pinnedTypeVersionsPromise = null;
|
|
1495
1449
|
constructor(config) {
|
|
1496
1450
|
this.projectDir = config.projectDir;
|
|
1497
|
-
this.npmStrategy = config.npmStrategy
|
|
1451
|
+
this.npmStrategy = config.npmStrategy;
|
|
1498
1452
|
this.requestDependencyVersions = config.requestDependencyVersions ?? (async (packages) => Object.fromEntries(packages.map((packageName) => [packageName, null])));
|
|
1499
1453
|
const seenPackages = /* @__PURE__ */ new Set();
|
|
1500
1454
|
this.ata = setupTypeAcquisition({
|
|
@@ -1553,16 +1507,21 @@ var Installer = class {
|
|
|
1553
1507
|
return this.initializationPromise;
|
|
1554
1508
|
}
|
|
1555
1509
|
/**
|
|
1556
|
-
*
|
|
1510
|
+
* Process component files to fetch missing types or add `package.json` dependencies.
|
|
1557
1511
|
* JSON files are ignored.
|
|
1558
1512
|
*/
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1513
|
+
async processFiles(files) {
|
|
1514
|
+
const packageNames = /* @__PURE__ */ new Set();
|
|
1515
|
+
for (const file of files) {
|
|
1516
|
+
if (!file.content || JSON_EXTENSION_REGEX.test(file.name)) continue;
|
|
1517
|
+
try {
|
|
1518
|
+
const imports = await extractNpmPackageNames(file.content);
|
|
1519
|
+
for (const packageName of imports) packageNames.add(packageName);
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
debug(`Type installer failed to parse imports for ${file.name}`, err);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
await this.processNpmPackages(packageNames, files.length);
|
|
1566
1525
|
}
|
|
1567
1526
|
async initializeProject() {
|
|
1568
1527
|
await Promise.all([
|
|
@@ -1575,83 +1534,48 @@ var Installer = class {
|
|
|
1575
1534
|
]);
|
|
1576
1535
|
if (this.npmStrategy === "package-manager") {
|
|
1577
1536
|
await this.resolvePinnedTypeVersions();
|
|
1578
|
-
await this.enqueuePackageJsonRefresh(await this.collectPackageManagerPackageNames());
|
|
1579
1537
|
return;
|
|
1580
1538
|
}
|
|
1581
1539
|
this.pinnedTypeVersionsPromise = this.resolvePinnedTypeVersions();
|
|
1582
1540
|
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);
|
|
1541
|
+
await this.ata((await this.buildPinnedImports(CORE_LIBRARIES)).join("\n"));
|
|
1587
1542
|
}).catch((err) => {
|
|
1588
1543
|
debug("Type installation failed", err);
|
|
1589
1544
|
});
|
|
1590
1545
|
}
|
|
1591
|
-
async
|
|
1592
|
-
const
|
|
1593
|
-
if (allImports.length === 0) return;
|
|
1546
|
+
async processNpmPackages(packageNames, fileCount) {
|
|
1547
|
+
const allPackageNames = [...packageNames];
|
|
1594
1548
|
if (this.npmStrategy === "package-manager") {
|
|
1595
|
-
|
|
1549
|
+
for (const packageName of [...CORE_LIBRARIES, ...PACKAGE_MANAGER_DEV_DEPENDENCIES]) allPackageNames.push(packageName);
|
|
1550
|
+
try {
|
|
1551
|
+
await this.updatePackageJsonDependencies(allPackageNames);
|
|
1552
|
+
} catch (err) {
|
|
1553
|
+
warn("Could not update package.json dependency versions", err);
|
|
1554
|
+
}
|
|
1596
1555
|
return;
|
|
1597
1556
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
if (
|
|
1557
|
+
if (allPackageNames.length === 0) return;
|
|
1558
|
+
let packagesForAta;
|
|
1559
|
+
if (this.npmStrategy === "acquire-types") packagesForAta = allPackageNames;
|
|
1560
|
+
else {
|
|
1561
|
+
packagesForAta = allPackageNames.filter((packageName) => this.isDefaultPackage(packageName));
|
|
1562
|
+
const unsupportedPackages = allPackageNames.filter((packageName) => !this.isDefaultPackage(packageName));
|
|
1563
|
+
if (unsupportedPackages.length > 0) debug(`Skipping unsupported packages: ${unsupportedPackages.join(", ")} (use --unsupported-npm to enable)`);
|
|
1564
|
+
}
|
|
1565
|
+
if (packagesForAta.length === 0) return;
|
|
1601
1566
|
await this.pinnedTypeVersionsPromise;
|
|
1602
|
-
|
|
1603
|
-
const hash = imports.map((imp) => this.pinImport(imp.name)).sort().join(",");
|
|
1567
|
+
const hash = packagesForAta.map((packageName) => this.pinImport(packageName)).sort().join(",");
|
|
1604
1568
|
if (this.processedImports.has(hash)) return;
|
|
1605
1569
|
this.processedImports.add(hash);
|
|
1606
|
-
debug(`Processing imports
|
|
1607
|
-
const filteredContent = this.npmStrategy === "acquire-types" ? content : await this.buildFilteredImports(imports);
|
|
1570
|
+
debug(`Processing imports from ${fileCount} ${fileCount === 1 ? "file" : "files"} (${packagesForAta.length} packages)`);
|
|
1608
1571
|
try {
|
|
1609
|
-
await this.ata(
|
|
1572
|
+
await this.ata((await this.buildPinnedImports(packagesForAta)).join("\n"));
|
|
1610
1573
|
} catch (err) {
|
|
1611
|
-
warn(
|
|
1612
|
-
debug(
|
|
1574
|
+
warn("Type fetching failed");
|
|
1575
|
+
debug("ATA error:", err);
|
|
1613
1576
|
}
|
|
1614
1577
|
}
|
|
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) {
|
|
1578
|
+
async updatePackageJsonDependencies(packageNames) {
|
|
1655
1579
|
const uniquePackageNames = [...new Set(packageNames)].sort();
|
|
1656
1580
|
const versions = await this.requestDependencyVersions(uniquePackageNames);
|
|
1657
1581
|
const packagePath = path.join(this.projectDir, "package.json");
|
|
@@ -1675,26 +1599,19 @@ var Installer = class {
|
|
|
1675
1599
|
}
|
|
1676
1600
|
}
|
|
1677
1601
|
if (!changed) return;
|
|
1678
|
-
pkg.dependencies = dependencies;
|
|
1679
|
-
pkg.devDependencies = devDependencies;
|
|
1602
|
+
pkg.dependencies = sortDependencyMap(dependencies);
|
|
1603
|
+
pkg.devDependencies = sortDependencyMap(devDependencies);
|
|
1680
1604
|
await fs.writeFile(packagePath, JSON.stringify(pkg, null, 4));
|
|
1681
|
-
|
|
1605
|
+
status("Updated dependencies. Run your package manager to install them.");
|
|
1682
1606
|
debug(`Updated package.json dependency versions for ${uniquePackageNames.join(", ")}`);
|
|
1683
1607
|
}
|
|
1684
1608
|
/**
|
|
1685
|
-
* Check if a package
|
|
1686
|
-
* Also checks for subpath imports (e.g., "framer/build" -> "framer")
|
|
1609
|
+
* Check if a package receives ATA types without unsupported npm enabled.
|
|
1687
1610
|
*/
|
|
1688
|
-
|
|
1689
|
-
if (
|
|
1611
|
+
isDefaultPackage(pkgName) {
|
|
1612
|
+
if (DEFAULT_PACKAGES.has(pkgName)) return true;
|
|
1690
1613
|
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");
|
|
1614
|
+
return DEFAULT_PACKAGES.has(basePkg);
|
|
1698
1615
|
}
|
|
1699
1616
|
async buildPinnedImports(imports) {
|
|
1700
1617
|
await this.pinnedTypeVersionsPromise;
|
|
@@ -1713,25 +1630,6 @@ var Installer = class {
|
|
|
1713
1630
|
} catch (err) {
|
|
1714
1631
|
debug(`Falling back to default ATA pins for ${FRAMER_PACKAGE_NAME}`, err);
|
|
1715
1632
|
}
|
|
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
1633
|
}
|
|
1736
1634
|
/**
|
|
1737
1635
|
* Build an import statement with an optional `// types:` version pin for ATA.
|
|
@@ -1899,6 +1797,19 @@ function getBasePackageName(packageName) {
|
|
|
1899
1797
|
if (packageName.startsWith("@")) return parts.length >= 2 ? parts.slice(0, 2).join("/") : packageName;
|
|
1900
1798
|
return parts[0] ?? packageName;
|
|
1901
1799
|
}
|
|
1800
|
+
async function extractNpmPackageNames(code) {
|
|
1801
|
+
const imports = await parseImports(code);
|
|
1802
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1803
|
+
for (const imported of imports) {
|
|
1804
|
+
const specifier = imported.moduleSpecifier;
|
|
1805
|
+
if (specifier.type !== "package" || !specifier.isConstant || !specifier.value) continue;
|
|
1806
|
+
seen.add(getBasePackageName(specifier.value));
|
|
1807
|
+
}
|
|
1808
|
+
return [...seen];
|
|
1809
|
+
}
|
|
1810
|
+
function sortDependencyMap(dependencies) {
|
|
1811
|
+
return Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
1812
|
+
}
|
|
1902
1813
|
function normalizePinnedVersion(version) {
|
|
1903
1814
|
if (!version) return void 0;
|
|
1904
1815
|
return /\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/.exec(version)?.[0];
|
|
@@ -2094,12 +2005,6 @@ async function matchesProject(packageJsonPath, projectHash) {
|
|
|
2094
2005
|
//#endregion
|
|
2095
2006
|
//#region src/helpers/npm-strategy.ts
|
|
2096
2007
|
const CONFIG_FIELD = "codeLink.npmStrategy";
|
|
2097
|
-
const LOCKFILES = [
|
|
2098
|
-
"yarn.lock",
|
|
2099
|
-
"pnpm-lock.yaml",
|
|
2100
|
-
"package-lock.json",
|
|
2101
|
-
"bun.lockb"
|
|
2102
|
-
];
|
|
2103
2008
|
async function resolveNpmStrategy(config, projectDir) {
|
|
2104
2009
|
if (config.npmStrategy) {
|
|
2105
2010
|
debug(`Using npm strategy from CLI flag: ${config.npmStrategy}`);
|
|
@@ -2110,13 +2015,7 @@ async function resolveNpmStrategy(config, projectDir) {
|
|
|
2110
2015
|
debug(`Using npm strategy from package.json ${CONFIG_FIELD}: ${packageJsonStrategy}`);
|
|
2111
2016
|
return packageJsonStrategy;
|
|
2112
2017
|
}
|
|
2113
|
-
|
|
2114
|
-
if (detectedLockfile) {
|
|
2115
|
-
debug(`Using npm strategy package-manager from ${detectedLockfile}`);
|
|
2116
|
-
return "package-manager";
|
|
2117
|
-
}
|
|
2118
|
-
debug("Using default npm strategy: none");
|
|
2119
|
-
return "none";
|
|
2018
|
+
debug("No npm strategy from CLI or package.json");
|
|
2120
2019
|
}
|
|
2121
2020
|
async function readPackageJsonStrategy(projectDir) {
|
|
2122
2021
|
try {
|
|
@@ -2131,15 +2030,8 @@ async function readPackageJsonStrategy(projectDir) {
|
|
|
2131
2030
|
return null;
|
|
2132
2031
|
}
|
|
2133
2032
|
}
|
|
2134
|
-
async function detectLockfile(projectDir) {
|
|
2135
|
-
for (const fileName of LOCKFILES) try {
|
|
2136
|
-
await fs.access(path.join(projectDir, fileName));
|
|
2137
|
-
return fileName;
|
|
2138
|
-
} catch {}
|
|
2139
|
-
return null;
|
|
2140
|
-
}
|
|
2141
2033
|
function isNpmStrategy(value) {
|
|
2142
|
-
return value === "
|
|
2034
|
+
return value === "acquire-types" || value === "package-manager";
|
|
2143
2035
|
}
|
|
2144
2036
|
|
|
2145
2037
|
//#endregion
|
|
@@ -3423,12 +3315,14 @@ async function writeFiles(files, ctx, options) {
|
|
|
3423
3315
|
const filesToWrite = options.echoPolicy === "skip-expected-echoes" ? filterEchoedFiles(files, runtime.memory) : files;
|
|
3424
3316
|
if (options.echoPolicy === "skip-expected-echoes" && filesToWrite.length !== files.length) debug(`Skipped ${pluralize(files.length - filesToWrite.length, "echoed change")}`);
|
|
3425
3317
|
const results = await writeRemoteFiles(filesToWrite, runtime.workspace.filesDir, runtime.memory);
|
|
3318
|
+
const installerFiles = [];
|
|
3426
3319
|
for (const result of results) {
|
|
3427
3320
|
if (!result.ok) continue;
|
|
3428
3321
|
if (!options.silent) fileDown(result.path);
|
|
3429
3322
|
runtime.memory.recordSyncedContent(result.path, result.file.content, result.file.modifiedAt ?? Date.now());
|
|
3430
|
-
|
|
3323
|
+
if (!options.silent) installerFiles.push(result.file);
|
|
3431
3324
|
}
|
|
3325
|
+
await processInstallerFiles(runtime, installerFiles);
|
|
3432
3326
|
}
|
|
3433
3327
|
async function deleteFiles(fileNames, ctx) {
|
|
3434
3328
|
const { runtime } = ctx;
|
|
@@ -3455,7 +3349,10 @@ async function sendLocalChange(fileName, content, ctx) {
|
|
|
3455
3349
|
})) return;
|
|
3456
3350
|
runtime.memory.armContentEcho(fileName, content);
|
|
3457
3351
|
fileUp(fileName);
|
|
3458
|
-
runtime
|
|
3352
|
+
await processInstallerFiles(runtime, [{
|
|
3353
|
+
name: fileName,
|
|
3354
|
+
content
|
|
3355
|
+
}]);
|
|
3459
3356
|
}
|
|
3460
3357
|
async function sendFileDelete(fileNames, ctx) {
|
|
3461
3358
|
if (fileNames.length === 0) return;
|
|
@@ -3551,19 +3448,22 @@ async function applySyncComplete(effect, ctx) {
|
|
|
3551
3448
|
const wasDisconnected = runtime.disconnectUi.wasRecentlyDisconnected();
|
|
3552
3449
|
let shouldShutdown = !!config.once;
|
|
3553
3450
|
let shouldTryGitInit = false;
|
|
3451
|
+
let statusMessage = null;
|
|
3554
3452
|
if (wasDisconnected) {
|
|
3555
3453
|
const didShow = runtime.disconnectUi.didShowNotice();
|
|
3556
3454
|
shouldShutdown = didShow && !!config.once;
|
|
3557
3455
|
if (didShow) {
|
|
3558
3456
|
success(`Reconnected, synced ${pluralize(effect.totalCount, "file")} (${effect.updatedCount} updated, ${effect.unchangedCount} unchanged)`);
|
|
3559
|
-
|
|
3457
|
+
statusMessage = syncCompleteStatusMessage(config);
|
|
3560
3458
|
}
|
|
3561
3459
|
} else {
|
|
3562
3460
|
const message = syncCompleteSuccessMessage(runtime, effect);
|
|
3563
3461
|
if (message) success(message);
|
|
3564
|
-
|
|
3462
|
+
statusMessage = syncCompleteStatusMessage(config);
|
|
3565
3463
|
shouldTryGitInit = !!(runtime.workspace.projectDirCreated && runtime.workspace.projectDir);
|
|
3566
3464
|
}
|
|
3465
|
+
await processAllInstallerFiles(ctx);
|
|
3466
|
+
if (statusMessage) status(statusMessage);
|
|
3567
3467
|
await sendToPlugin(syncState.socket, {
|
|
3568
3468
|
type: "sync-status",
|
|
3569
3469
|
status: "ready"
|
|
@@ -3573,6 +3473,21 @@ async function applySyncComplete(effect, ctx) {
|
|
|
3573
3473
|
if (shouldTryGitInit && runtime.workspace.projectDir) tryGitInit(runtime.workspace.projectDir);
|
|
3574
3474
|
if (shouldShutdown) await shutdown();
|
|
3575
3475
|
}
|
|
3476
|
+
async function processInstallerFiles(runtime, files) {
|
|
3477
|
+
if (!runtime.installer || runtime.lastEmittedSyncStatus !== "ready") return;
|
|
3478
|
+
if (files.length === 0) return;
|
|
3479
|
+
try {
|
|
3480
|
+
await runtime.installer.processFiles(files);
|
|
3481
|
+
} catch (err) {
|
|
3482
|
+
debug("Type installer failed", err);
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
async function processAllInstallerFiles(ctx) {
|
|
3486
|
+
const { runtime } = ctx;
|
|
3487
|
+
if (!runtime.installer || !runtime.workspace.filesDir) return;
|
|
3488
|
+
const files = await listFiles(runtime.workspace.filesDir);
|
|
3489
|
+
await runtime.installer.processFiles(files);
|
|
3490
|
+
}
|
|
3576
3491
|
async function flushPendingSyncComplete(ctx) {
|
|
3577
3492
|
const result = ctx.runtime.claimPendingSyncComplete();
|
|
3578
3493
|
if (result.status === "ready") await applySyncComplete({
|
|
@@ -4073,7 +3988,6 @@ async function start(config) {
|
|
|
4073
3988
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
4074
3989
|
const program = new Command();
|
|
4075
3990
|
function parseUnsupportedNpmMode(mode) {
|
|
4076
|
-
if (mode === void 0) return "acquire-types";
|
|
4077
3991
|
if (mode === "acquire-types" || mode === "package-manager") return mode;
|
|
4078
3992
|
throw new InvalidArgumentError("unsupported npm mode must be 'acquire-types' or 'package-manager'");
|
|
4079
3993
|
}
|
|
@@ -4084,7 +3998,7 @@ program.exitOverride((err) => {
|
|
|
4084
3998
|
}
|
|
4085
3999
|
throw err;
|
|
4086
4000
|
});
|
|
4087
|
-
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").
|
|
4001
|
+
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) => {
|
|
4088
4002
|
if (!projectHash) {
|
|
4089
4003
|
const detected = await getProjectHashFromCwd();
|
|
4090
4004
|
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.3",
|
|
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"
|