framer-code-link 0.21.0-alpha.2 → 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 +110 -199
  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);
1613
- }
1614
- }
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;
1574
+ warn("Type fetching failed");
1575
+ debug("ATA error:", err);
1626
1576
  }
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
1577
  }
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");
@@ -1661,7 +1585,7 @@ var Installer = class {
1661
1585
  const devDependencies = typeof pkg.devDependencies === "object" && pkg.devDependencies !== null && !Array.isArray(pkg.devDependencies) ? { ...pkg.devDependencies } : {};
1662
1586
  let changed = false;
1663
1587
  for (const packageName of uniquePackageNames) {
1664
- const version = (versions[packageName] ?? this.pinnedTypeVersions[packageName])?.replace(/^\^/, "");
1588
+ const version = versions[packageName] ?? this.pinnedTypeVersions[packageName];
1665
1589
  if (!version) continue;
1666
1590
  const targetDependencies = PACKAGE_MANAGER_DEV_DEPENDENCIES.includes(packageName) ? devDependencies : dependencies;
1667
1591
  const oppositeDependencies = PACKAGE_MANAGER_DEV_DEPENDENCIES.includes(packageName) ? dependencies : devDependencies;
@@ -1678,23 +1602,16 @@ var Installer = class {
1678
1602
  pkg.dependencies = sortDependencyMap(dependencies);
1679
1603
  pkg.devDependencies = sortDependencyMap(devDependencies);
1680
1604
  await fs.writeFile(packagePath, JSON.stringify(pkg, null, 4));
1681
- status("Synced project dependencies to package.json. 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,16 @@ 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
+ }
1902
1810
  function sortDependencyMap(dependencies) {
1903
1811
  return Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)));
1904
1812
  }
@@ -2097,12 +2005,6 @@ async function matchesProject(packageJsonPath, projectHash) {
2097
2005
  //#endregion
2098
2006
  //#region src/helpers/npm-strategy.ts
2099
2007
  const CONFIG_FIELD = "codeLink.npmStrategy";
2100
- const LOCKFILES = [
2101
- "yarn.lock",
2102
- "pnpm-lock.yaml",
2103
- "package-lock.json",
2104
- "bun.lockb"
2105
- ];
2106
2008
  async function resolveNpmStrategy(config, projectDir) {
2107
2009
  if (config.npmStrategy) {
2108
2010
  debug(`Using npm strategy from CLI flag: ${config.npmStrategy}`);
@@ -2113,13 +2015,7 @@ async function resolveNpmStrategy(config, projectDir) {
2113
2015
  debug(`Using npm strategy from package.json ${CONFIG_FIELD}: ${packageJsonStrategy}`);
2114
2016
  return packageJsonStrategy;
2115
2017
  }
2116
- const detectedLockfile = await detectLockfile(projectDir);
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";
2018
+ debug("No npm strategy from CLI or package.json");
2123
2019
  }
2124
2020
  async function readPackageJsonStrategy(projectDir) {
2125
2021
  try {
@@ -2134,15 +2030,8 @@ async function readPackageJsonStrategy(projectDir) {
2134
2030
  return null;
2135
2031
  }
2136
2032
  }
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
2033
  function isNpmStrategy(value) {
2145
- return value === "none" || value === "acquire-types" || value === "package-manager";
2034
+ return value === "acquire-types" || value === "package-manager";
2146
2035
  }
2147
2036
 
2148
2037
  //#endregion
@@ -3426,12 +3315,14 @@ async function writeFiles(files, ctx, options) {
3426
3315
  const filesToWrite = options.echoPolicy === "skip-expected-echoes" ? filterEchoedFiles(files, runtime.memory) : files;
3427
3316
  if (options.echoPolicy === "skip-expected-echoes" && filesToWrite.length !== files.length) debug(`Skipped ${pluralize(files.length - filesToWrite.length, "echoed change")}`);
3428
3317
  const results = await writeRemoteFiles(filesToWrite, runtime.workspace.filesDir, runtime.memory);
3318
+ const installerFiles = [];
3429
3319
  for (const result of results) {
3430
3320
  if (!result.ok) continue;
3431
3321
  if (!options.silent) fileDown(result.path);
3432
3322
  runtime.memory.recordSyncedContent(result.path, result.file.content, result.file.modifiedAt ?? Date.now());
3433
- runtime.installer?.process(result.path, result.file.content);
3323
+ if (!options.silent) installerFiles.push(result.file);
3434
3324
  }
3325
+ await processInstallerFiles(runtime, installerFiles);
3435
3326
  }
3436
3327
  async function deleteFiles(fileNames, ctx) {
3437
3328
  const { runtime } = ctx;
@@ -3458,7 +3349,10 @@ async function sendLocalChange(fileName, content, ctx) {
3458
3349
  })) return;
3459
3350
  runtime.memory.armContentEcho(fileName, content);
3460
3351
  fileUp(fileName);
3461
- runtime.installer?.process(fileName, content);
3352
+ await processInstallerFiles(runtime, [{
3353
+ name: fileName,
3354
+ content
3355
+ }]);
3462
3356
  }
3463
3357
  async function sendFileDelete(fileNames, ctx) {
3464
3358
  if (fileNames.length === 0) return;
@@ -3554,19 +3448,22 @@ async function applySyncComplete(effect, ctx) {
3554
3448
  const wasDisconnected = runtime.disconnectUi.wasRecentlyDisconnected();
3555
3449
  let shouldShutdown = !!config.once;
3556
3450
  let shouldTryGitInit = false;
3451
+ let statusMessage = null;
3557
3452
  if (wasDisconnected) {
3558
3453
  const didShow = runtime.disconnectUi.didShowNotice();
3559
3454
  shouldShutdown = didShow && !!config.once;
3560
3455
  if (didShow) {
3561
3456
  success(`Reconnected, synced ${pluralize(effect.totalCount, "file")} (${effect.updatedCount} updated, ${effect.unchangedCount} unchanged)`);
3562
- status(syncCompleteStatusMessage(config));
3457
+ statusMessage = syncCompleteStatusMessage(config);
3563
3458
  }
3564
3459
  } else {
3565
3460
  const message = syncCompleteSuccessMessage(runtime, effect);
3566
3461
  if (message) success(message);
3567
- status(syncCompleteStatusMessage(config));
3462
+ statusMessage = syncCompleteStatusMessage(config);
3568
3463
  shouldTryGitInit = !!(runtime.workspace.projectDirCreated && runtime.workspace.projectDir);
3569
3464
  }
3465
+ await processAllInstallerFiles(ctx);
3466
+ if (statusMessage) status(statusMessage);
3570
3467
  await sendToPlugin(syncState.socket, {
3571
3468
  type: "sync-status",
3572
3469
  status: "ready"
@@ -3576,6 +3473,21 @@ async function applySyncComplete(effect, ctx) {
3576
3473
  if (shouldTryGitInit && runtime.workspace.projectDir) tryGitInit(runtime.workspace.projectDir);
3577
3474
  if (shouldShutdown) await shutdown();
3578
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
+ }
3579
3491
  async function flushPendingSyncComplete(ctx) {
3580
3492
  const result = ctx.runtime.claimPendingSyncComplete();
3581
3493
  if (result.status === "ready") await applySyncComplete({
@@ -4076,7 +3988,6 @@ async function start(config) {
4076
3988
  const { version } = createRequire(import.meta.url)("../package.json");
4077
3989
  const program = new Command();
4078
3990
  function parseUnsupportedNpmMode(mode) {
4079
- if (mode === void 0) return "acquire-types";
4080
3991
  if (mode === "acquire-types" || mode === "package-manager") return mode;
4081
3992
  throw new InvalidArgumentError("unsupported npm mode must be 'acquire-types' or 'package-manager'");
4082
3993
  }
@@ -4087,7 +3998,7 @@ program.exitOverride((err) => {
4087
3998
  }
4088
3999
  throw err;
4089
4000
  });
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) => {
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) => {
4091
4002
  if (!projectHash) {
4092
4003
  const detected = await getProjectHashFromCwd();
4093
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.2",
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"