@lark-apaas/fullstack-cli 1.1.6-alpha.10 → 1.1.6-alpha.11

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.js +385 -299
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import fs15 from "fs";
2
+ import fs14 from "fs";
3
3
  import path14 from "path";
4
4
  import { fileURLToPath as fileURLToPath3 } from "url";
5
5
  import { config as dotenvConfig } from "dotenv";
@@ -1385,39 +1385,55 @@ import path9 from "path";
1385
1385
  var DEFAULT_PLUGIN_VERSION = "1.0.0";
1386
1386
  var PLUGIN_MAPPING = {
1387
1387
  // 飞书相关
1388
- "official_feishu/send_message": {
1389
- pluginKey: "@official-plugins/send-feishu-message",
1390
- pluginVersion: "1.0.0"
1388
+ "@official-plugins/send-feishu-message": {
1389
+ sourceActionID: "official_feishu/send_message",
1390
+ pluginVersion: "1.0.0",
1391
+ actionName: "send_feishu_message"
1391
1392
  },
1392
- "official_feishu/create_group": {
1393
- pluginKey: "@official-plugins/feishu-group-create",
1394
- pluginVersion: "1.0.0"
1393
+ "@official-plugins/feishu-group-create": {
1394
+ sourceActionID: "official_feishu/create_group",
1395
+ pluginVersion: "1.0.0",
1396
+ actionName: "createGroup"
1395
1397
  },
1396
1398
  // AI 相关
1397
- "official_ai/text_generate": {
1398
- pluginKey: "@official-plugins/ai-text-generate",
1399
- pluginVersion: "1.0.0"
1399
+ "@official-plugins/ai-text-generate": {
1400
+ sourceActionID: "official_ai/text_generate",
1401
+ pluginVersion: "1.0.0",
1402
+ actionName: "text_generate"
1400
1403
  },
1401
- "official_ai/image_understanding": {
1402
- pluginKey: "@official-plugins/ai-image-understanding",
1403
- pluginVersion: "1.0.0"
1404
+ "@official-plugins/ai-image-understanding": {
1405
+ sourceActionID: "official_ai/image_understanding",
1406
+ pluginVersion: "1.0.0",
1407
+ actionName: "imageUnderstanding"
1404
1408
  },
1405
- "official_ai/image_generate": {
1406
- pluginKey: "@official-plugins/ai-text-to-image",
1407
- pluginVersion: "1.0.0"
1409
+ "@official-plugins/ai-text-to-image": {
1410
+ sourceActionID: "official_ai/image_generate",
1411
+ pluginVersion: "1.0.0",
1412
+ actionName: "textToImage"
1408
1413
  }
1409
1414
  };
1410
- function getPluginInfo(sourceActionID) {
1411
- const item = PLUGIN_MAPPING[sourceActionID];
1415
+ function getPluginInfoBySourceActionID(sourceActionID) {
1416
+ for (const [pluginKey, item] of Object.entries(PLUGIN_MAPPING)) {
1417
+ if (item.sourceActionID === sourceActionID) {
1418
+ return {
1419
+ pluginKey,
1420
+ pluginVersion: item.pluginVersion ?? DEFAULT_PLUGIN_VERSION,
1421
+ actionName: item.actionName
1422
+ };
1423
+ }
1424
+ }
1425
+ throw new Error(
1426
+ `Unknown sourceActionID: "${sourceActionID}". This sourceActionID is not in the built-in mapping. Please contact the developer to add it.`
1427
+ );
1428
+ }
1429
+ function getActionNameByPluginKey(pluginKey) {
1430
+ const item = PLUGIN_MAPPING[pluginKey];
1412
1431
  if (!item) {
1413
1432
  throw new Error(
1414
- `Unknown sourceActionID: "${sourceActionID}". This sourceActionID is not in the built-in mapping. Please contact the developer to add it.`
1433
+ `Unknown pluginKey: "${pluginKey}". This pluginKey is not in the built-in mapping. Please contact the developer to add it.`
1415
1434
  );
1416
1435
  }
1417
- return {
1418
- pluginKey: item.pluginKey,
1419
- pluginVersion: item.pluginVersion ?? DEFAULT_PLUGIN_VERSION
1420
- };
1436
+ return item.actionName;
1421
1437
  }
1422
1438
 
1423
1439
  // src/commands/migration/versions/v001_capability/json-migrator/form-value-transformers/capabilities/send-feishu-message.ts
@@ -1536,7 +1552,7 @@ function transformFormValue(sourceActionID, actionInput) {
1536
1552
 
1537
1553
  // src/commands/migration/versions/v001_capability/json-migrator/transformer.ts
1538
1554
  function transformCapability(old) {
1539
- const { pluginKey, pluginVersion } = getPluginInfo(old.sourceActionID);
1555
+ const { pluginKey, pluginVersion } = getPluginInfoBySourceActionID(old.sourceActionID);
1540
1556
  const formValue = transformFormValue(old.sourceActionID, old.actionInput);
1541
1557
  return {
1542
1558
  id: old.id,
@@ -1555,15 +1571,39 @@ function transformCapabilities(oldCapabilities) {
1555
1571
  }
1556
1572
 
1557
1573
  // src/commands/migration/versions/v001_capability/json-migrator/index.ts
1574
+ function loadExistingCapabilities() {
1575
+ const capabilitiesDir = getCapabilitiesDir2();
1576
+ if (!fs9.existsSync(capabilitiesDir)) {
1577
+ return [];
1578
+ }
1579
+ const files = fs9.readdirSync(capabilitiesDir);
1580
+ const capabilities = [];
1581
+ for (const file of files) {
1582
+ if (file === "capabilities.json" || !file.endsWith(".json")) {
1583
+ continue;
1584
+ }
1585
+ try {
1586
+ const filePath = path9.join(capabilitiesDir, file);
1587
+ const content = fs9.readFileSync(filePath, "utf-8");
1588
+ const capability = JSON.parse(content);
1589
+ if (capability.id && capability.pluginKey) {
1590
+ capabilities.push(capability);
1591
+ }
1592
+ } catch {
1593
+ }
1594
+ }
1595
+ return capabilities;
1596
+ }
1558
1597
  async function migrateJsonFiles(options) {
1559
1598
  const detection = detectJsonMigration();
1560
1599
  if (!detection.needsMigration) {
1600
+ const existingCapabilities = loadExistingCapabilities();
1561
1601
  return {
1562
1602
  success: true,
1563
1603
  skipped: true,
1564
1604
  reason: detection.reason,
1565
- count: 0,
1566
- capabilities: []
1605
+ count: existingCapabilities.length,
1606
+ capabilities: existingCapabilities
1567
1607
  };
1568
1608
  }
1569
1609
  const { oldCapabilities, oldFilePath } = detection;
@@ -1695,9 +1735,8 @@ async function installPlugins(capabilities, options) {
1695
1735
  }
1696
1736
 
1697
1737
  // src/commands/migration/versions/v001_capability/code-migrator/index.ts
1698
- import fs12 from "fs";
1699
1738
  import path11 from "path";
1700
- import * as ts4 from "typescript";
1739
+ import { Project } from "ts-morph";
1701
1740
 
1702
1741
  // src/commands/migration/versions/v001_capability/code-migrator/scanner.ts
1703
1742
  import fs11 from "fs";
@@ -1750,177 +1789,153 @@ function scanFilesToMigrate() {
1750
1789
  }
1751
1790
 
1752
1791
  // src/commands/migration/versions/v001_capability/code-migrator/analyzers/import-analyzer.ts
1753
- import * as ts from "typescript";
1754
1792
  function extractCapabilityId(importPath) {
1755
1793
  const match = importPath.match(/capabilities\/([^/]+)$/);
1756
- if (match) {
1757
- return match[1];
1758
- }
1759
- return null;
1794
+ return match ? match[1] : null;
1760
1795
  }
1761
1796
  function isCapabilityImport(importPath) {
1762
1797
  return importPath.includes("capabilities/") || importPath.includes("capabilities\\");
1763
1798
  }
1764
1799
  function analyzeImports(sourceFile) {
1765
1800
  const imports = [];
1766
- function visit(node) {
1767
- if (ts.isImportDeclaration(node)) {
1768
- const moduleSpecifier = node.moduleSpecifier;
1769
- if (ts.isStringLiteral(moduleSpecifier)) {
1770
- const importPath = moduleSpecifier.text;
1771
- if (isCapabilityImport(importPath)) {
1772
- const capabilityId = extractCapabilityId(importPath);
1773
- if (capabilityId && node.importClause) {
1774
- const importClause = node.importClause;
1775
- let importName = null;
1776
- if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) {
1777
- for (const element of importClause.namedBindings.elements) {
1778
- importName = element.name.text;
1779
- imports.push({
1780
- importName,
1781
- capabilityId,
1782
- start: node.getStart(),
1783
- end: node.getEnd(),
1784
- text: node.getText()
1785
- });
1786
- }
1787
- } else if (importClause.namedBindings && ts.isNamespaceImport(importClause.namedBindings)) {
1788
- importName = importClause.namedBindings.name.text;
1789
- imports.push({
1790
- importName,
1791
- capabilityId,
1792
- start: node.getStart(),
1793
- end: node.getEnd(),
1794
- text: node.getText()
1795
- });
1796
- } else if (importClause.name) {
1797
- importName = importClause.name.text;
1798
- imports.push({
1799
- importName,
1800
- capabilityId,
1801
- start: node.getStart(),
1802
- end: node.getEnd(),
1803
- text: node.getText()
1804
- });
1805
- }
1806
- }
1807
- }
1808
- }
1801
+ const importDeclarations = sourceFile.getImportDeclarations();
1802
+ for (const importDecl of importDeclarations) {
1803
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
1804
+ if (!isCapabilityImport(moduleSpecifier)) {
1805
+ continue;
1806
+ }
1807
+ const capabilityId = extractCapabilityId(moduleSpecifier);
1808
+ if (!capabilityId) {
1809
+ continue;
1810
+ }
1811
+ const namedImports = importDecl.getNamedImports();
1812
+ for (const namedImport of namedImports) {
1813
+ const importName = namedImport.getAliasNode()?.getText() || namedImport.getName();
1814
+ imports.push({
1815
+ importName,
1816
+ capabilityId,
1817
+ start: importDecl.getStart(),
1818
+ end: importDecl.getEnd(),
1819
+ text: importDecl.getText()
1820
+ });
1821
+ }
1822
+ const namespaceImport = importDecl.getNamespaceImport();
1823
+ if (namespaceImport) {
1824
+ imports.push({
1825
+ importName: namespaceImport.getText(),
1826
+ capabilityId,
1827
+ start: importDecl.getStart(),
1828
+ end: importDecl.getEnd(),
1829
+ text: importDecl.getText()
1830
+ });
1831
+ }
1832
+ const defaultImport = importDecl.getDefaultImport();
1833
+ if (defaultImport) {
1834
+ imports.push({
1835
+ importName: defaultImport.getText(),
1836
+ capabilityId,
1837
+ start: importDecl.getStart(),
1838
+ end: importDecl.getEnd(),
1839
+ text: importDecl.getText()
1840
+ });
1809
1841
  }
1810
- ts.forEachChild(node, visit);
1811
1842
  }
1812
- visit(sourceFile);
1813
1843
  return imports;
1814
1844
  }
1815
1845
 
1816
1846
  // src/commands/migration/versions/v001_capability/code-migrator/analyzers/call-site-analyzer.ts
1817
- import * as ts2 from "typescript";
1847
+ import { SyntaxKind } from "ts-morph";
1818
1848
  function analyzeCallSites(sourceFile, imports) {
1819
1849
  const callSites = [];
1820
1850
  const importMap = /* @__PURE__ */ new Map();
1821
1851
  for (const imp of imports) {
1822
1852
  importMap.set(imp.importName, imp.capabilityId);
1823
1853
  }
1824
- function visit(node) {
1825
- if (ts2.isCallExpression(node)) {
1826
- const expression = node.expression;
1827
- if (ts2.isIdentifier(expression)) {
1828
- const functionName = expression.text;
1829
- const capabilityId = importMap.get(functionName);
1830
- if (capabilityId) {
1831
- const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1832
- callSites.push({
1833
- functionName,
1834
- capabilityId,
1835
- start: node.getStart(),
1836
- end: node.getEnd(),
1837
- line: line + 1,
1838
- // 转为 1-based
1839
- text: node.getText()
1840
- });
1841
- }
1842
- } else if (ts2.isPropertyAccessExpression(expression)) {
1843
- const objectName = expression.expression;
1844
- if (ts2.isIdentifier(objectName)) {
1845
- const capabilityId = importMap.get(objectName.text);
1854
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
1855
+ for (const callExpr of callExpressions) {
1856
+ const expression = callExpr.getExpression();
1857
+ if (expression.getKind() === SyntaxKind.Identifier) {
1858
+ const functionName = expression.getText();
1859
+ const capabilityId = importMap.get(functionName);
1860
+ if (capabilityId) {
1861
+ callSites.push({
1862
+ functionName,
1863
+ capabilityId,
1864
+ start: callExpr.getStart(),
1865
+ end: callExpr.getEnd(),
1866
+ line: callExpr.getStartLineNumber(),
1867
+ text: callExpr.getText()
1868
+ });
1869
+ }
1870
+ } else if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
1871
+ const propAccess = expression.asKind(SyntaxKind.PropertyAccessExpression);
1872
+ if (propAccess) {
1873
+ const objectExpr = propAccess.getExpression();
1874
+ if (objectExpr.getKind() === SyntaxKind.Identifier) {
1875
+ const objectName = objectExpr.getText();
1876
+ const capabilityId = importMap.get(objectName);
1846
1877
  if (capabilityId) {
1847
- const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1848
1878
  callSites.push({
1849
- functionName: `${objectName.text}.${expression.name.text}`,
1879
+ functionName: `${objectName}.${propAccess.getName()}`,
1850
1880
  capabilityId,
1851
- start: node.getStart(),
1852
- end: node.getEnd(),
1853
- line: line + 1,
1854
- text: node.getText()
1881
+ start: callExpr.getStart(),
1882
+ end: callExpr.getEnd(),
1883
+ line: callExpr.getStartLineNumber(),
1884
+ text: callExpr.getText()
1855
1885
  });
1856
1886
  }
1857
1887
  }
1858
1888
  }
1859
1889
  }
1860
- ts2.forEachChild(node, visit);
1861
1890
  }
1862
- visit(sourceFile);
1863
1891
  return callSites;
1864
1892
  }
1865
1893
 
1866
1894
  // src/commands/migration/versions/v001_capability/code-migrator/analyzers/class-analyzer.ts
1867
- import * as ts3 from "typescript";
1868
- function hasDecorator(node, decoratorName) {
1869
- const decorators = ts3.getDecorators(node);
1870
- if (!decorators) {
1871
- return false;
1872
- }
1895
+ function hasDecorator(classDecl, decoratorName) {
1896
+ const decorators = classDecl.getDecorators();
1873
1897
  return decorators.some((decorator) => {
1874
- const expression = decorator.expression;
1875
- if (ts3.isIdentifier(expression)) {
1876
- return expression.text === decoratorName;
1877
- }
1878
- if (ts3.isCallExpression(expression) && ts3.isIdentifier(expression.expression)) {
1879
- return expression.expression.text === decoratorName;
1880
- }
1881
- return false;
1898
+ const name = decorator.getName();
1899
+ return name === decoratorName;
1882
1900
  });
1883
1901
  }
1884
1902
  function analyzeClass(sourceFile) {
1903
+ const classes = sourceFile.getClasses();
1885
1904
  let classInfo;
1886
- function visit(node) {
1887
- if (ts3.isClassDeclaration(node) && node.name) {
1888
- const isInjectable = hasDecorator(node, "Injectable");
1889
- const isController = hasDecorator(node, "Controller");
1890
- if (classInfo && classInfo.isInjectable && !isInjectable && !isController) {
1891
- return;
1892
- }
1893
- const info = {
1894
- name: node.name.text,
1895
- isInjectable,
1896
- isController,
1897
- constructorParamCount: 0
1898
- };
1899
- for (const member of node.members) {
1900
- if (ts3.isConstructorDeclaration(member)) {
1901
- info.constructorParamCount = member.parameters.length;
1902
- info.constructorStart = member.getStart();
1903
- info.constructorEnd = member.getEnd();
1904
- if (member.parameters.length > 0) {
1905
- const lastParam = member.parameters[member.parameters.length - 1];
1906
- info.constructorParamsEnd = lastParam.getEnd();
1907
- } else {
1908
- const constructorText = member.getText();
1909
- const parenIndex = constructorText.indexOf("(");
1910
- if (parenIndex !== -1) {
1911
- info.constructorParamsEnd = member.getStart() + parenIndex + 1;
1912
- }
1913
- }
1914
- break;
1915
- }
1916
- }
1917
- if (isInjectable || isController || !classInfo) {
1918
- classInfo = info;
1905
+ for (const classDecl of classes) {
1906
+ const name = classDecl.getName();
1907
+ if (!name) continue;
1908
+ const isInjectable = hasDecorator(classDecl, "Injectable");
1909
+ const isController = hasDecorator(classDecl, "Controller");
1910
+ if (classInfo && classInfo.isInjectable && !isInjectable && !isController) {
1911
+ continue;
1912
+ }
1913
+ const info = {
1914
+ name,
1915
+ isInjectable,
1916
+ isController,
1917
+ constructorParamCount: 0
1918
+ };
1919
+ const classBody = classDecl.getChildSyntaxListOrThrow();
1920
+ info.classBodyStart = classBody.getStart();
1921
+ const constructors = classDecl.getConstructors();
1922
+ if (constructors.length > 0) {
1923
+ const ctor = constructors[0];
1924
+ info.constructorParamCount = ctor.getParameters().length;
1925
+ info.constructorStart = ctor.getStart();
1926
+ info.constructorEnd = ctor.getEnd();
1927
+ const params = ctor.getParameters();
1928
+ if (params.length > 0) {
1929
+ const lastParam = params[params.length - 1];
1930
+ info.constructorParamsEnd = lastParam.getEnd();
1931
+ } else {
1932
+ info.constructorParamsEnd = ctor.getStart();
1919
1933
  }
1920
1934
  }
1921
- ts3.forEachChild(node, visit);
1935
+ if (isInjectable || isController || !classInfo) {
1936
+ classInfo = info;
1937
+ }
1922
1938
  }
1923
- visit(sourceFile);
1924
1939
  return classInfo;
1925
1940
  }
1926
1941
  function canAutoMigrate(classInfo) {
@@ -1940,128 +1955,203 @@ function canAutoMigrate(classInfo) {
1940
1955
  }
1941
1956
 
1942
1957
  // src/commands/migration/versions/v001_capability/code-migrator/transformers/import-transformer.ts
1943
- var CAPABILITY_SERVICE_IMPORT = `import { CapabilityService, migrationAdaptor } from '@lark-apaas/fullstack-nestjs-core';`;
1944
- var INJECT_IMPORT = `import { Inject } from '@nestjs/common';`;
1945
- function hasCapabilityServiceImport(content) {
1946
- return /import\s+.*CapabilityService.*from\s+['"]@lark-apaas\/fullstack-nestjs-core['"]/.test(content);
1947
- }
1948
- function hasInjectImport(content) {
1949
- return /import\s+.*\bInject\b.*from\s+['"]@nestjs\/common['"]/.test(content);
1950
- }
1951
- function findImportInsertPosition(content) {
1952
- const importRegex = /^import\s+.*?from\s+['"][^'"]+['"];?\s*$/gm;
1953
- let lastMatch = null;
1954
- let match;
1955
- while ((match = importRegex.exec(content)) !== null) {
1956
- lastMatch = match;
1957
- }
1958
- if (lastMatch) {
1959
- return lastMatch.index + lastMatch[0].length;
1960
- }
1961
- return 0;
1962
- }
1963
- function transformImports(content, imports) {
1964
- const sortedImports = [...imports].sort((a, b) => b.start - a.start);
1965
- let result = content;
1966
- for (const imp of sortedImports) {
1967
- const before = result.substring(0, imp.start);
1968
- const after = result.substring(imp.end);
1969
- const trimmedAfter = after.replace(/^\r?\n/, "");
1970
- result = before + trimmedAfter;
1958
+ var CORE_MODULE = "@lark-apaas/fullstack-nestjs-core";
1959
+ var NESTJS_COMMON_MODULE = "@nestjs/common";
1960
+ function hasCapabilityServiceImport(sourceFile) {
1961
+ const imports = sourceFile.getImportDeclarations();
1962
+ for (const importDecl of imports) {
1963
+ if (importDecl.getModuleSpecifierValue() === CORE_MODULE) {
1964
+ const namedImports = importDecl.getNamedImports();
1965
+ if (namedImports.some((ni) => ni.getName() === "CapabilityService")) {
1966
+ return true;
1967
+ }
1968
+ }
1969
+ }
1970
+ return false;
1971
+ }
1972
+ function hasInjectImport(sourceFile) {
1973
+ const imports = sourceFile.getImportDeclarations();
1974
+ for (const importDecl of imports) {
1975
+ if (importDecl.getModuleSpecifierValue() === NESTJS_COMMON_MODULE) {
1976
+ const namedImports = importDecl.getNamedImports();
1977
+ if (namedImports.some((ni) => ni.getName() === "Inject")) {
1978
+ return true;
1979
+ }
1980
+ }
1971
1981
  }
1982
+ return false;
1983
+ }
1984
+ function transformImports(sourceFile, capabilityImports) {
1972
1985
  let importAdded = false;
1973
- if (!hasCapabilityServiceImport(result)) {
1974
- const insertPos = findImportInsertPosition(result);
1975
- if (insertPos > 0) {
1976
- result = result.substring(0, insertPos) + "\n" + CAPABILITY_SERVICE_IMPORT + result.substring(insertPos);
1986
+ let importsRemoved = 0;
1987
+ const importPaths = new Set(capabilityImports.map((imp) => {
1988
+ const match = imp.text.match(/from\s+['"]([^'"]+)['"]/);
1989
+ return match ? match[1] : "";
1990
+ }));
1991
+ const importDeclarations = sourceFile.getImportDeclarations();
1992
+ for (const importDecl of importDeclarations) {
1993
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
1994
+ if (importPaths.has(moduleSpecifier)) {
1995
+ importDecl.remove();
1996
+ importsRemoved++;
1997
+ }
1998
+ }
1999
+ if (!hasCapabilityServiceImport(sourceFile)) {
2000
+ const existingCoreImport = sourceFile.getImportDeclaration(CORE_MODULE);
2001
+ if (existingCoreImport) {
2002
+ const namedImports = existingCoreImport.getNamedImports();
2003
+ const existingNames = namedImports.map((ni) => ni.getName());
2004
+ if (!existingNames.includes("CapabilityService")) {
2005
+ existingCoreImport.addNamedImport("CapabilityService");
2006
+ }
2007
+ if (!existingNames.includes("migrationAdaptor")) {
2008
+ existingCoreImport.addNamedImport("migrationAdaptor");
2009
+ }
1977
2010
  } else {
1978
- result = CAPABILITY_SERVICE_IMPORT + "\n" + result;
2011
+ sourceFile.addImportDeclaration({
2012
+ moduleSpecifier: CORE_MODULE,
2013
+ namedImports: ["CapabilityService", "migrationAdaptor"]
2014
+ });
1979
2015
  }
1980
2016
  importAdded = true;
1981
2017
  }
1982
- if (!hasInjectImport(result)) {
1983
- const insertPos = findImportInsertPosition(result);
1984
- if (insertPos > 0) {
1985
- result = result.substring(0, insertPos) + "\n" + INJECT_IMPORT + result.substring(insertPos);
2018
+ if (!hasInjectImport(sourceFile)) {
2019
+ const existingNestImport = sourceFile.getImportDeclaration(NESTJS_COMMON_MODULE);
2020
+ if (existingNestImport) {
2021
+ const namedImports = existingNestImport.getNamedImports();
2022
+ const existingNames = namedImports.map((ni) => ni.getName());
2023
+ if (!existingNames.includes("Inject")) {
2024
+ existingNestImport.addNamedImport("Inject");
2025
+ }
1986
2026
  } else {
1987
- result = INJECT_IMPORT + "\n" + result;
2027
+ sourceFile.addImportDeclaration({
2028
+ moduleSpecifier: NESTJS_COMMON_MODULE,
2029
+ namedImports: ["Inject"]
2030
+ });
1988
2031
  }
1989
2032
  importAdded = true;
1990
2033
  }
1991
- return { content: result, importAdded };
2034
+ return { importAdded, importsRemoved };
1992
2035
  }
1993
2036
 
1994
2037
  // src/commands/migration/versions/v001_capability/code-migrator/transformers/injection-transformer.ts
1995
- var INJECTION_PARAM = "@Inject() private readonly capabilityService: CapabilityService";
1996
- function hasCapabilityServiceInjection(content) {
1997
- return /capabilityService\s*:\s*CapabilityService/.test(content);
1998
- }
1999
- function addInjection(content, classInfo) {
2000
- if (hasCapabilityServiceInjection(content)) {
2001
- return { content, injectionAdded: false };
2002
- }
2003
- if (classInfo.constructorParamsEnd === void 0) {
2004
- return { content, injectionAdded: false };
2005
- }
2006
- let result = content;
2007
- const insertPos = classInfo.constructorParamsEnd;
2008
- if (classInfo.constructorParamCount > 0) {
2009
- const afterParams = content.substring(insertPos);
2010
- const beforeParams = content.substring(0, insertPos);
2011
- result = beforeParams + ",\n " + INJECTION_PARAM + afterParams;
2012
- } else {
2013
- const constructorMatch = content.match(/constructor\s*\(\s*/);
2014
- if (constructorMatch && constructorMatch.index !== void 0) {
2015
- const paramStart = constructorMatch.index + constructorMatch[0].length;
2016
- const before = content.substring(0, paramStart);
2017
- const after = content.substring(paramStart);
2018
- result = before + INJECTION_PARAM + after;
2038
+ import { Scope } from "ts-morph";
2039
+ var PARAM_NAME = "capabilityService";
2040
+ var PARAM_TYPE = "CapabilityService";
2041
+ function hasCapabilityServiceInjection(sourceFile) {
2042
+ const classes = sourceFile.getClasses();
2043
+ for (const classDecl of classes) {
2044
+ const constructors = classDecl.getConstructors();
2045
+ for (const ctor of constructors) {
2046
+ const params = ctor.getParameters();
2047
+ for (const param of params) {
2048
+ if (param.getName() === PARAM_NAME) {
2049
+ return true;
2050
+ }
2051
+ }
2019
2052
  }
2020
2053
  }
2021
- return { content: result, injectionAdded: result !== content };
2054
+ return false;
2022
2055
  }
2023
-
2024
- // src/commands/migration/versions/v001_capability/code-migrator/transformers/call-site-transformer.ts
2025
- function generateNewCall(capabilityId, originalCall) {
2026
- const parenIndex = originalCall.indexOf("(");
2027
- if (parenIndex === -1) {
2028
- return originalCall;
2029
- }
2030
- const argsStart = parenIndex + 1;
2031
- const argsEnd = originalCall.lastIndexOf(")");
2032
- let args = "{}";
2033
- if (argsEnd !== -1 && argsEnd > argsStart) {
2034
- const extractedArgs = originalCall.substring(argsStart, argsEnd).trim();
2035
- if (extractedArgs) {
2036
- args = extractedArgs;
2056
+ function findInjectableClass(sourceFile) {
2057
+ const classes = sourceFile.getClasses();
2058
+ for (const classDecl of classes) {
2059
+ const decorators = classDecl.getDecorators();
2060
+ const isInjectable = decorators.some((d) => d.getName() === "Injectable");
2061
+ const isController = decorators.some((d) => d.getName() === "Controller");
2062
+ if (isInjectable || isController) {
2063
+ return classDecl;
2037
2064
  }
2038
2065
  }
2039
- return `migrationAdaptor(this.capabilityService.load('${capabilityId}').call('run', ${args}))`;
2066
+ return void 0;
2040
2067
  }
2041
- function transformCallSites(content, callSites) {
2042
- const sortedCallSites = [...callSites].sort((a, b) => b.start - a.start);
2043
- let result = content;
2068
+ function addInjection(sourceFile) {
2069
+ if (hasCapabilityServiceInjection(sourceFile)) {
2070
+ return { injectionAdded: false };
2071
+ }
2072
+ const classDecl = findInjectableClass(sourceFile);
2073
+ if (!classDecl) {
2074
+ return { injectionAdded: false };
2075
+ }
2076
+ const constructors = classDecl.getConstructors();
2077
+ if (constructors.length > 0) {
2078
+ const ctor = constructors[0];
2079
+ ctor.addParameter({
2080
+ name: PARAM_NAME,
2081
+ type: PARAM_TYPE,
2082
+ scope: Scope.Private,
2083
+ isReadonly: true,
2084
+ decorators: [{ name: "Inject", arguments: [] }]
2085
+ });
2086
+ } else {
2087
+ classDecl.addConstructor({
2088
+ parameters: [{
2089
+ name: PARAM_NAME,
2090
+ type: PARAM_TYPE,
2091
+ scope: Scope.Private,
2092
+ isReadonly: true,
2093
+ decorators: [{ name: "Inject", arguments: [] }]
2094
+ }]
2095
+ });
2096
+ }
2097
+ return { injectionAdded: true };
2098
+ }
2099
+
2100
+ // src/commands/migration/versions/v001_capability/code-migrator/transformers/call-site-transformer.ts
2101
+ import { SyntaxKind as SyntaxKind2 } from "ts-morph";
2102
+ var DEFAULT_ACTION_NAME = "run";
2103
+ function generateNewCallText(capabilityId, actionName, args) {
2104
+ const argsText = args.trim() || "{}";
2105
+ return `migrationAdaptor(this.capabilityService.load('${capabilityId}').call('${actionName}', ${argsText}))`;
2106
+ }
2107
+ function transformCallSites(sourceFile, imports) {
2108
+ const importMap = /* @__PURE__ */ new Map();
2109
+ for (const imp of imports) {
2110
+ importMap.set(imp.importName, {
2111
+ capabilityId: imp.capabilityId,
2112
+ actionName: imp.actionName ?? DEFAULT_ACTION_NAME
2113
+ });
2114
+ }
2044
2115
  let replacedCount = 0;
2045
- for (const callSite of sortedCallSites) {
2046
- const newCall = generateNewCall(callSite.capabilityId, callSite.text);
2047
- const before = result.substring(0, callSite.start);
2048
- const after = result.substring(callSite.end);
2049
- result = before + newCall + after;
2050
- replacedCount++;
2116
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind2.CallExpression);
2117
+ const sortedCalls = [...callExpressions].sort((a, b) => b.getStart() - a.getStart());
2118
+ for (const callExpr of sortedCalls) {
2119
+ const expression = callExpr.getExpression();
2120
+ let importInfo;
2121
+ if (expression.getKind() === SyntaxKind2.Identifier) {
2122
+ const functionName = expression.getText();
2123
+ importInfo = importMap.get(functionName);
2124
+ } else if (expression.getKind() === SyntaxKind2.PropertyAccessExpression) {
2125
+ const propAccess = expression.asKind(SyntaxKind2.PropertyAccessExpression);
2126
+ if (propAccess) {
2127
+ const objectExpr = propAccess.getExpression();
2128
+ if (objectExpr.getKind() === SyntaxKind2.Identifier) {
2129
+ const objectName = objectExpr.getText();
2130
+ importInfo = importMap.get(objectName);
2131
+ }
2132
+ }
2133
+ }
2134
+ if (importInfo) {
2135
+ const args = callExpr.getArguments();
2136
+ const argsText = args.map((arg) => arg.getText()).join(", ");
2137
+ const newCallText = generateNewCallText(importInfo.capabilityId, importInfo.actionName, argsText);
2138
+ callExpr.replaceWithText(newCallText);
2139
+ replacedCount++;
2140
+ }
2051
2141
  }
2052
- return { content: result, replacedCount };
2142
+ return { replacedCount };
2053
2143
  }
2054
2144
 
2055
2145
  // src/commands/migration/versions/v001_capability/code-migrator/index.ts
2056
- function analyzeFile(filePath) {
2057
- const content = fs12.readFileSync(filePath, "utf-8");
2058
- const sourceFile = ts4.createSourceFile(
2059
- filePath,
2060
- content,
2061
- ts4.ScriptTarget.Latest,
2062
- true
2063
- );
2146
+ function analyzeFile(project, filePath, actionNameMap) {
2147
+ const sourceFile = project.addSourceFileAtPath(filePath);
2064
2148
  const imports = analyzeImports(sourceFile);
2149
+ for (const imp of imports) {
2150
+ const actionName = actionNameMap.get(imp.capabilityId);
2151
+ if (actionName) {
2152
+ imp.actionName = actionName;
2153
+ }
2154
+ }
2065
2155
  const callSites = analyzeCallSites(sourceFile, imports);
2066
2156
  const classInfo = analyzeClass(sourceFile);
2067
2157
  const { canMigrate, reason } = canAutoMigrate(classInfo);
@@ -2075,7 +2165,7 @@ function analyzeFile(filePath) {
2075
2165
  manualMigrationReason: reason
2076
2166
  };
2077
2167
  }
2078
- function migrateFile(analysis, dryRun) {
2168
+ function migrateFile(project, analysis, dryRun) {
2079
2169
  const absolutePath = path11.join(getProjectRoot3(), analysis.filePath);
2080
2170
  if (!analysis.canAutoMigrate) {
2081
2171
  return {
@@ -2088,42 +2178,19 @@ function migrateFile(analysis, dryRun) {
2088
2178
  };
2089
2179
  }
2090
2180
  try {
2091
- let content = fs12.readFileSync(absolutePath, "utf-8");
2092
- const callResult = transformCallSites(content, analysis.callSites);
2093
- content = callResult.content;
2094
- const sourceFile = ts4.createSourceFile(
2095
- analysis.filePath,
2096
- content,
2097
- ts4.ScriptTarget.Latest,
2098
- true
2099
- );
2100
- const newImports = analyzeImports(sourceFile);
2101
- const importResult = transformImports(content, newImports);
2102
- content = importResult.content;
2103
- let injectionAdded = false;
2104
- if (analysis.classInfo) {
2105
- const newSourceFile = ts4.createSourceFile(
2106
- analysis.filePath,
2107
- content,
2108
- ts4.ScriptTarget.Latest,
2109
- true
2110
- );
2111
- const newClassInfo = analyzeClass(newSourceFile);
2112
- if (newClassInfo) {
2113
- const injectionResult = addInjection(content, newClassInfo);
2114
- content = injectionResult.content;
2115
- injectionAdded = injectionResult.injectionAdded;
2116
- }
2117
- }
2181
+ const sourceFile = project.getSourceFileOrThrow(absolutePath);
2182
+ const callResult = transformCallSites(sourceFile, analysis.imports);
2183
+ const importResult = transformImports(sourceFile, analysis.imports);
2184
+ const injectionResult = addInjection(sourceFile);
2118
2185
  if (!dryRun) {
2119
- fs12.writeFileSync(absolutePath, content, "utf-8");
2186
+ sourceFile.saveSync();
2120
2187
  }
2121
2188
  return {
2122
2189
  filePath: analysis.filePath,
2123
2190
  success: true,
2124
- importsRemoved: analysis.imports.length,
2191
+ importsRemoved: importResult.importsRemoved,
2125
2192
  callSitesReplaced: callResult.replacedCount,
2126
- injectionAdded
2193
+ injectionAdded: injectionResult.injectionAdded
2127
2194
  };
2128
2195
  } catch (error) {
2129
2196
  return {
@@ -2136,20 +2203,39 @@ function migrateFile(analysis, dryRun) {
2136
2203
  };
2137
2204
  }
2138
2205
  }
2139
- async function migrateCode(options) {
2206
+ function buildActionNameMap(capabilities) {
2207
+ const map = /* @__PURE__ */ new Map();
2208
+ for (const cap of capabilities) {
2209
+ try {
2210
+ const actionName = getActionNameByPluginKey(cap.pluginKey);
2211
+ map.set(cap.id, actionName);
2212
+ } catch {
2213
+ map.set(cap.id, "run");
2214
+ }
2215
+ }
2216
+ return map;
2217
+ }
2218
+ async function migrateCode(options, capabilities) {
2140
2219
  const result = {
2141
2220
  autoMigrated: [],
2142
2221
  manualRequired: []
2143
2222
  };
2223
+ const actionNameMap = buildActionNameMap(capabilities);
2144
2224
  const filesToMigrate = scanFilesToMigrate();
2145
2225
  if (filesToMigrate.length === 0) {
2146
2226
  console.log(" No files need code migration.\n");
2147
2227
  return result;
2148
2228
  }
2229
+ const project = new Project({
2230
+ skipAddingFilesFromTsConfig: true,
2231
+ compilerOptions: {
2232
+ allowJs: true
2233
+ }
2234
+ });
2149
2235
  const analyses = [];
2150
2236
  for (const filePath of filesToMigrate) {
2151
2237
  try {
2152
- const analysis = analyzeFile(filePath);
2238
+ const analysis = analyzeFile(project, filePath, actionNameMap);
2153
2239
  analyses.push(analysis);
2154
2240
  } catch (error) {
2155
2241
  console.error(` \u2717 Failed to analyze ${filePath}: ${error}`);
@@ -2167,7 +2253,7 @@ async function migrateCode(options) {
2167
2253
  });
2168
2254
  }
2169
2255
  for (const analysis of autoMigratable) {
2170
- const migrationResult = migrateFile(analysis, options.dryRun);
2256
+ const migrationResult = migrateFile(project, analysis, options.dryRun);
2171
2257
  result.autoMigrated.push(migrationResult);
2172
2258
  }
2173
2259
  return result;
@@ -2183,17 +2269,17 @@ function getSuggestion(analysis) {
2183
2269
  }
2184
2270
 
2185
2271
  // src/commands/migration/versions/v001_capability/cleanup.ts
2186
- import fs13 from "fs";
2272
+ import fs12 from "fs";
2187
2273
  import path12 from "path";
2188
2274
  function cleanupOldFiles(capabilities, dryRun) {
2189
2275
  const deletedFiles = [];
2190
2276
  const errors = [];
2191
2277
  const capabilitiesDir = getCapabilitiesDir2();
2192
2278
  const oldJsonPath = path12.join(capabilitiesDir, "capabilities.json");
2193
- if (fs13.existsSync(oldJsonPath)) {
2279
+ if (fs12.existsSync(oldJsonPath)) {
2194
2280
  try {
2195
2281
  if (!dryRun) {
2196
- fs13.unlinkSync(oldJsonPath);
2282
+ fs12.unlinkSync(oldJsonPath);
2197
2283
  }
2198
2284
  deletedFiles.push("capabilities.json");
2199
2285
  } catch (error) {
@@ -2202,10 +2288,10 @@ function cleanupOldFiles(capabilities, dryRun) {
2202
2288
  }
2203
2289
  for (const cap of capabilities) {
2204
2290
  const tsFilePath = path12.join(capabilitiesDir, `${cap.id}.ts`);
2205
- if (fs13.existsSync(tsFilePath)) {
2291
+ if (fs12.existsSync(tsFilePath)) {
2206
2292
  try {
2207
2293
  if (!dryRun) {
2208
- fs13.unlinkSync(tsFilePath);
2294
+ fs12.unlinkSync(tsFilePath);
2209
2295
  }
2210
2296
  deletedFiles.push(`${cap.id}.ts`);
2211
2297
  } catch (error) {
@@ -2221,7 +2307,7 @@ function cleanupOldFiles(capabilities, dryRun) {
2221
2307
  }
2222
2308
 
2223
2309
  // src/commands/migration/versions/v001_capability/report-generator.ts
2224
- import fs14 from "fs";
2310
+ import fs13 from "fs";
2225
2311
  import path13 from "path";
2226
2312
  var REPORT_FILE = "capability-migration-report.md";
2227
2313
  function printSummary(result) {
@@ -2385,7 +2471,7 @@ async function generateReport(result) {
2385
2471
  }
2386
2472
  lines.push("");
2387
2473
  const reportPath = path13.join(getProjectRoot3(), REPORT_FILE);
2388
- fs14.writeFileSync(reportPath, lines.join("\n"), "utf-8");
2474
+ fs13.writeFileSync(reportPath, lines.join("\n"), "utf-8");
2389
2475
  console.log(`\u{1F4C4} Report generated: ${REPORT_FILE}`);
2390
2476
  }
2391
2477
 
@@ -2478,7 +2564,7 @@ async function runCapabilityMigration(options = {}) {
2478
2564
  console.log("\u{1F527} Step 3: Code Migration");
2479
2565
  console.log(" Scanning server/ directory...\n");
2480
2566
  try {
2481
- codeResult = await migrateCode(fullOptions);
2567
+ codeResult = await migrateCode(fullOptions, jsonResult.capabilities);
2482
2568
  let hasCodeError = false;
2483
2569
  for (const file of codeResult.autoMigrated) {
2484
2570
  if (file.success) {
@@ -2904,11 +2990,11 @@ var commands = [
2904
2990
 
2905
2991
  // src/index.ts
2906
2992
  var envPath = path14.join(process.cwd(), ".env");
2907
- if (fs15.existsSync(envPath)) {
2993
+ if (fs14.existsSync(envPath)) {
2908
2994
  dotenvConfig({ path: envPath });
2909
2995
  }
2910
2996
  var __dirname = path14.dirname(fileURLToPath3(import.meta.url));
2911
- var pkg = JSON.parse(fs15.readFileSync(path14.join(__dirname, "../package.json"), "utf-8"));
2997
+ var pkg = JSON.parse(fs14.readFileSync(path14.join(__dirname, "../package.json"), "utf-8"));
2912
2998
  var cli = new FullstackCLI(pkg.version);
2913
2999
  cli.useAll(commands);
2914
3000
  cli.run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.6-alpha.10",
3
+ "version": "1.1.6-alpha.11",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,6 +37,7 @@
37
37
  "commander": "^13.0.0",
38
38
  "dotenv": "^16.0.0",
39
39
  "drizzle-kit": "0.31.5",
40
+ "ts-morph": "^27.0.0",
40
41
  "zod-to-json-schema": "^3.24.1"
41
42
  },
42
43
  "devDependencies": {