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.
Files changed (2) hide show
  1. package/dist/index.mjs +114 -200
  2. 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
- async function walk(currentDir) {
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 walk(filesDir);
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 are officially supported for type acquisition.
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 SUPPORTED_PACKAGES = new Set([
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 ?? "none";
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
- * Fire-and-forget processing of a component file to fetch missing types.
1510
+ * Process component files to fetch missing types or add `package.json` dependencies.
1557
1511
  * JSON files are ignored.
1558
1512
  */
1559
- process(fileName, content) {
1560
- if (!content || JSON_EXTENSION_REGEX.test(fileName)) return;
1561
- Promise.resolve().then(async () => {
1562
- await this.processImports(fileName, content);
1563
- }).catch((err) => {
1564
- debug(`Type installer failed for ${fileName}`, err);
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
- const coreImports = await this.buildPinnedImports(CORE_LIBRARIES);
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 processImports(fileName, content) {
1592
- const allImports = extractImports(content).filter((i) => i.type === "npm");
1593
- if (allImports.length === 0) return;
1546
+ async processNpmPackages(packageNames, fileCount) {
1547
+ const allPackageNames = [...packageNames];
1594
1548
  if (this.npmStrategy === "package-manager") {
1595
- await this.enqueuePackageJsonRefresh(allImports.map((imp) => getBasePackageName(imp.name)));
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
- const imports = this.npmStrategy === "acquire-types" ? allImports : allImports.filter((i) => this.isSupportedPackage(i.name));
1599
- if (allImports.length - imports.length > 0 && this.npmStrategy !== "acquire-types") debug(`Skipping unsupported packages: ${allImports.filter((i) => !this.isSupportedPackage(i.name)).map((i) => i.name).join(", ")} (use --unsupported-npm to enable)`);
1600
- if (imports.length === 0) return;
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
- if (this.npmStrategy === "acquire-types") await this.resolvePackageJsonPins();
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 for ${fileName} (${imports.length} packages)`);
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(filteredContent);
1572
+ await this.ata((await this.buildPinnedImports(packagesForAta)).join("\n"));
1610
1573
  } catch (err) {
1611
- warn(`Type fetching failed for ${fileName}`);
1612
- debug(`ATA error for ${fileName}:`, err);
1574
+ warn("Type fetching failed");
1575
+ debug("ATA error:", err);
1613
1576
  }
1614
1577
  }
1615
- async collectPackageManagerPackageNames() {
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
- success("Updated dependencies. Run your package manager to install them.");
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 is in the supported list.
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
- isSupportedPackage(pkgName) {
1689
- if (SUPPORTED_PACKAGES.has(pkgName)) return true;
1611
+ isDefaultPackage(pkgName) {
1612
+ if (DEFAULT_PACKAGES.has(pkgName)) return true;
1690
1613
  const basePkg = getBasePackageName(pkgName);
1691
- return SUPPORTED_PACKAGES.has(basePkg);
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
- const detectedLockfile = await detectLockfile(projectDir);
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 === "none" || value === "acquire-types" || value === "package-manager";
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
- runtime.installer?.process(result.path, result.file.content);
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.installer?.process(fileName, content);
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
- status(syncCompleteStatusMessage(config));
3457
+ statusMessage = syncCompleteStatusMessage(config);
3560
3458
  }
3561
3459
  } else {
3562
3460
  const message = syncCompleteSuccessMessage(runtime, effect);
3563
3461
  if (message) success(message);
3564
- status(syncCompleteStatusMessage(config));
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").option("--unsupported-npm [mode]", "Handle unsupported npm packages (acquire-types or package-manager)", parseUnsupportedNpmMode).action(async (projectHash, options) => {
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.1",
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"