forked from thirdweb-dev/js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add detect-project command to CLI (thirdweb-dev#926)
Co-authored-by: Jonas Daniels <[email protected]>
- Loading branch information
Showing
55 changed files
with
1,693 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"thirdweb": patch | ||
--- | ||
|
||
Added detect-project command, reorganized folder structure for detecting, added many more detections. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"thirdweb": patch | ||
--- | ||
|
||
Added log to see detected app type to the "detect-project" command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,4 +16,7 @@ public/dist | |
yalc.lock | ||
build/ | ||
playwright-report/ | ||
.vscode/ | ||
.vscode/ | ||
|
||
# Artifacts | ||
packages/cli/artifacts/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import detectPackageManager from "../core/detection/detectPackageManager"; | ||
import detectFramework from "../core/detection/detectFramework"; | ||
import detectLibrary from "../core/detection/detectLibrary"; | ||
import detectLanguage from "../core/detection/detectLanguage"; | ||
import { logger } from "../core/helpers/logger"; | ||
import path from "path"; | ||
import { ContractLibrariesType, contractLibraries } from "../core/types/ProjectType"; | ||
|
||
export async function detectProjectV2(options: any) { | ||
logger.setSettings({ | ||
minLevel: options.debug ? "debug" : "info", | ||
}); | ||
|
||
let projectPath = process.cwd(); | ||
if (options.path) { | ||
logger.debug("Overriding project path to " + options.path); | ||
|
||
const resolvedPath = (options.path as string).startsWith("/") | ||
? options.path | ||
: path.resolve(`${projectPath}/${options.path}`); | ||
projectPath = resolvedPath; | ||
} | ||
|
||
logger.debug("Processing project at path " + projectPath); | ||
|
||
const detectedPackageManager = await detectPackageManager( | ||
projectPath, | ||
options, | ||
); | ||
const detectedLanguage = await detectLanguage(projectPath, options); | ||
const detectedLibrary = await detectLibrary( | ||
projectPath, | ||
options, | ||
detectedPackageManager, | ||
); | ||
const detectedFramework = await detectFramework( | ||
projectPath, | ||
options, | ||
detectedPackageManager, | ||
); | ||
const detectedAppType = contractLibraries.includes(detectedFramework as ContractLibrariesType) ? "contract" : "app"; | ||
|
||
logger.debug("Detected package manager: " + detectedPackageManager); | ||
logger.debug("Detected library: " + detectedLibrary); | ||
logger.debug("Detected language: " + detectedLanguage); | ||
logger.debug("Detected framework: " + detectedFramework); | ||
logger.debug("Detected app type: " + detectedAppType); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import { ALWAYS_SUGGESTED } from "../constants/features"; | ||
import build from "../core/builder/build"; | ||
import detect from "../core/detection/detect"; | ||
import detectPackageManager from "../core/detection/detectPackageManager"; | ||
import { logger, spinner } from "../core/helpers/logger"; | ||
import { createContractsPrompt } from "../core/helpers/selector"; | ||
import { ContractFeatures, Feature } from "../core/interfaces/ContractFeatures"; | ||
import { ContractPayload } from "../core/interfaces/ContractPayload"; | ||
import { getPkgManager } from "../create/helpers/get-pkg-manager"; | ||
import { detectFeatures, FeatureWithEnabled } from "@thirdweb-dev/sdk"; | ||
import chalk from "chalk"; | ||
import { existsSync, readFileSync } from "fs"; | ||
import ora from "ora"; | ||
import path from "path"; | ||
|
||
export async function detectProject(options: any) { | ||
logger.setSettings({ | ||
minLevel: options.debug ? "debug" : "info", | ||
}); | ||
|
||
let projectPath = process.cwd(); | ||
if (options.path) { | ||
logger.debug("Overriding project path to " + options.path); | ||
|
||
const resolvedPath = (options.path as string).startsWith("/") | ||
? options.path | ||
: path.resolve(`${projectPath}/${options.path}`); | ||
projectPath = resolvedPath; | ||
} | ||
|
||
logger.debug("Processing project at path " + projectPath); | ||
|
||
const packageManagerType = await detectPackageManager(projectPath, options); | ||
console.log(packageManagerType); | ||
const projectType = await detect(projectPath, options); | ||
|
||
let compiledResult; | ||
const compileLoader = spinner("Compiling project..."); | ||
try { | ||
compiledResult = await build(projectPath, projectType, options); | ||
} catch (e) { | ||
compileLoader.fail("Compilation failed"); | ||
logger.error(e); | ||
process.exit(1); | ||
} | ||
compileLoader.succeed("Compilation successful"); | ||
|
||
let selectedContracts: ContractPayload[] = []; | ||
if (compiledResult.contracts.length === 1) { | ||
selectedContracts = [compiledResult.contracts[0]]; | ||
} else { | ||
if (options.all) { | ||
selectedContracts = compiledResult.contracts; | ||
} else { | ||
const choices = compiledResult.contracts.map((c) => ({ | ||
name: c.name, | ||
value: c, | ||
})); | ||
const prompt = createContractsPrompt( | ||
choices, | ||
"Choose which contracts to run detection on", | ||
); | ||
const selection: Record<string, ContractPayload> = await prompt.run(); | ||
selectedContracts = Object.keys(selection).map((key) => selection[key]); | ||
} | ||
} | ||
|
||
let contractsWithFeatures: ContractFeatures[] = selectedContracts.map( | ||
(contract) => { | ||
const abi: Parameters<typeof detectFeatures>[0] = JSON.parse( | ||
contract.metadata, | ||
)["output"]["abi"]; | ||
const features = extractFeatures(detectFeatures(abi)); | ||
|
||
const enabledFeatures: Feature[] = features.enabledFeatures.map( | ||
(feature) => ({ | ||
name: feature.name, | ||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`, | ||
}), | ||
); | ||
const suggestedFeatures: Feature[] = features.suggestedFeatures.map( | ||
(feature) => ({ | ||
name: feature.name, | ||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`, | ||
}), | ||
); | ||
|
||
return { | ||
name: contract.name, | ||
enabledFeatures, | ||
suggestedFeatures, | ||
}; | ||
}, | ||
); | ||
|
||
contractsWithFeatures.map((contractWithFeatures) => { | ||
logger.info(`\n`); | ||
if (contractWithFeatures.enabledFeatures.length === 0) { | ||
ora( | ||
`No extensions detected on ${chalk.blueBright( | ||
contractWithFeatures.name, | ||
)}`, | ||
).stopAndPersist({ symbol: "🔎" }); | ||
} else { | ||
ora( | ||
`Detected extension on ${chalk.blueBright(contractWithFeatures.name)}`, | ||
).stopAndPersist({ symbol: "🔎" }); | ||
contractWithFeatures.enabledFeatures.map((feature) => { | ||
logger.info(`✔️ ${chalk.green(feature.name)}`); | ||
}); | ||
} | ||
logger.info(``); | ||
ora(`Suggested extensions`).info(); | ||
contractWithFeatures.suggestedFeatures.map((feature) => { | ||
logger.info( | ||
`${chalk.dim(chalk.gray(`-`))} ${chalk.gray( | ||
feature.name, | ||
)} - ${chalk.dim(chalk.gray(feature.reference))}`, | ||
); | ||
}); | ||
|
||
let deployCmd = `npx thirdweb@latest deploy`; | ||
if (existsSync(projectPath + "/package.json")) { | ||
const packageManager = getPkgManager(); | ||
const useYarn = packageManager === "yarn"; | ||
const pkgJson = JSON.parse( | ||
readFileSync(projectPath + "/package.json", "utf-8"), | ||
); | ||
if (pkgJson?.scripts?.deploy === deployCmd) { | ||
deployCmd = `${packageManager}${useYarn ? "" : " run"} deploy`; | ||
} | ||
} | ||
|
||
logger.info(``); | ||
ora( | ||
`Once you're done writing your contracts, you can run the following command to deploy them:`, | ||
).info(); | ||
logger.info(``); | ||
logger.info(` ${chalk.cyan(deployCmd)}`); | ||
logger.info(``); | ||
}); | ||
} | ||
|
||
function extractFeatures( | ||
input: ReturnType<typeof detectFeatures>, | ||
enabledFeatures: FeatureWithEnabled[] = [], | ||
suggestedFeatures: FeatureWithEnabled[] = [], | ||
parent = "__ROOT__", | ||
) { | ||
if (!input) { | ||
return { | ||
enabledFeatures, | ||
suggestedFeatures, | ||
}; | ||
} | ||
for (const featureKey in input) { | ||
const feature = input[featureKey]; | ||
// if feature is enabled, then add it to enabledFeatures | ||
if (feature.enabled) { | ||
enabledFeatures.push(feature); | ||
} | ||
// otherwise if it is disabled, but it's parent is enabled or suggested, then add it to suggestedFeatures | ||
else if ( | ||
enabledFeatures.findIndex((f) => f.name === parent) > -1 || | ||
ALWAYS_SUGGESTED.includes(feature.name) | ||
) { | ||
suggestedFeatures.push(feature); | ||
} | ||
// recurse | ||
extractFeatures( | ||
feature.features, | ||
enabledFeatures, | ||
suggestedFeatures, | ||
feature.name, | ||
); | ||
} | ||
|
||
return { | ||
enabledFeatures, | ||
suggestedFeatures, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { info } from "../helpers/logger"; | ||
import { FrameworkType, PackageManagerType } from "../types/ProjectType"; | ||
import inquirer from "inquirer"; | ||
import NextDetector from "./frameworks/next"; | ||
import CRADetector from "./frameworks/cra"; | ||
import RemixDetector from "./frameworks/remix"; | ||
import GatsbyDetector from "./frameworks/gatsby"; | ||
import VueDetector from "./frameworks/vue"; | ||
import ReactNativeCLIDetector from "./frameworks/reactNativeCli"; | ||
import DjangoDetector from "./frameworks/django"; | ||
import ExpoDetector from "./frameworks/expo"; | ||
import FoundryDetector from "./frameworks/foundry"; | ||
import HardhatDetector from "./frameworks/hardhat"; | ||
import { FrameworkDetector } from "./detector"; | ||
import TruffleDetector from "./frameworks/truffle"; | ||
import BrownieDetector from "./frameworks/brownie"; | ||
import FastAPIDetector from "./frameworks/fastAPI"; | ||
import FlaskDetector from "./frameworks/flask"; | ||
import PopulusDetector from "./frameworks/populus"; | ||
import FastifyDetector from "./frameworks/fastify"; | ||
import EchoDetector from "./frameworks/echo"; | ||
import FiberDetector from "./frameworks/fiber"; | ||
import GinDetector from "./frameworks/gin"; | ||
import RevelDetector from "./frameworks/revel"; | ||
import ZenjectDetector from "./frameworks/zenject"; | ||
|
||
export default async function detect( | ||
path: string, | ||
options: any, | ||
detectedPackageManager: PackageManagerType, | ||
): Promise<FrameworkType> { | ||
// We could optimize further if we want, by only running the detectors that match the package manager. | ||
const frameworkDetectors: FrameworkDetector[] = [ | ||
new BrownieDetector(), | ||
new CRADetector(), | ||
new DjangoDetector(), | ||
new EchoDetector(), | ||
new ExpoDetector(), | ||
new FastAPIDetector(), | ||
new FastifyDetector(), | ||
new FiberDetector(), | ||
new FlaskDetector(), | ||
new FoundryDetector(), | ||
new GatsbyDetector(), | ||
new GinDetector(), | ||
new HardhatDetector(), | ||
new NextDetector(), | ||
new PopulusDetector(), | ||
new ReactNativeCLIDetector(), | ||
new RemixDetector(), | ||
new RevelDetector(), | ||
new TruffleDetector(), | ||
new VueDetector(), | ||
new ZenjectDetector(), | ||
]; | ||
|
||
const possibleFrameworks = frameworkDetectors | ||
.filter((detector) => detector.matches(path, detectedPackageManager)) | ||
.map((detector) => detector.frameworkType); | ||
|
||
if (!possibleFrameworks.length) { | ||
return "none"; | ||
} | ||
|
||
if (possibleFrameworks.length === 1) { | ||
return possibleFrameworks[0]; | ||
} | ||
|
||
info( | ||
`Detected multiple possible frameworks: ${possibleFrameworks | ||
.map((s) => `"${s}"`) | ||
.join(", ")}`, | ||
); | ||
|
||
const question = | ||
"We detected multiple possible frameworks which one do you want to use?"; | ||
|
||
if (options.ci) { | ||
return possibleFrameworks[0]; | ||
} else { | ||
const answer = await inquirer.prompt({ | ||
type: "list", | ||
choices: possibleFrameworks, | ||
name: question, | ||
}); | ||
return answer[question]; | ||
} | ||
} |
Oops, something went wrong.