diff --git a/.changeset/config.json b/.changeset/config.json index c494a6d4edb..874399bff1b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,5 +7,15 @@ "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", - "ignore": ["fuel-gauge"] + "ignore": [ + "fuel-gauge", + "docs", + "demo-fuels", + "demo-react-cra", + "demo-react-vite", + "demo-nextjs", + "demo-node-esm", + "demo-typegen", + "@fuel-ts/docs-snippets" + ] } diff --git a/.changeset/light-cameras-tease.md b/.changeset/light-cameras-tease.md new file mode 100644 index 00000000000..7a6f1b9c6f9 --- /dev/null +++ b/.changeset/light-cameras-tease.md @@ -0,0 +1,5 @@ +--- +"fuels": patch +--- + +Adding new flag to Fuels CLI build command diff --git a/apps/docs/src/guide/cli/commands.md b/apps/docs/src/guide/cli/commands.md index 1c7181a5903..dcd6f040733 100644 --- a/apps/docs/src/guide/cli/commands.md +++ b/apps/docs/src/guide/cli/commands.md @@ -50,6 +50,19 @@ In a nutshell: ## `fuels build` +```console +npx fuels help build +``` + +``` +Options: + -p, --path Path to project root (default: "/Users/anderson/Code/fuel/fuels-ts/apps/docs") + -d, --deploy Deploy contracts after build (auto-starts a `fuel-core` node if needed) + -h, --help Display help +``` + +Examples: + ```console npx fuels build ``` @@ -57,6 +70,17 @@ npx fuels build 1. Build all Sway programs under your `workspace` using `forc` [1](#commands-for-wrapped-utiltities) 1. Generate types for them using `fuels-typegen` [2](#typegen) +```console +npx fuels build --deploy +``` + +Using the `--deploy` flag will aditionally: + +1. Auto-start a short-lived `fuel-core` node if _needed_ ([docs](./config-file.md#autostartfuelcore)) +2. Run `deploy` on that node + +> _This is useful when working with contracts because a contract's ID is generated only on deployment._ + ## `fuels deploy` ```console diff --git a/packages/fuels/src/cli.ts b/packages/fuels/src/cli.ts index 1842ef0f11a..c96d3cc43cf 100644 --- a/packages/fuels/src/cli.ts +++ b/packages/fuels/src/cli.ts @@ -70,6 +70,10 @@ export const configureCli = () => { (command = program.command(Commands.build)) .description('Build Sway programs and generate Typescript for them') .addOption(pathOption) + .option( + '-d, --deploy', + 'Deploy contracts after build (auto-starts a `fuel-core` node if needed)' + ) .action(withConfig(command, Commands.build, build)); (command = program.command(Commands.deploy)) diff --git a/packages/fuels/src/cli/commands/build/index.ts b/packages/fuels/src/cli/commands/build/index.ts index 55223453915..0a35fe65afb 100644 --- a/packages/fuels/src/cli/commands/build/index.ts +++ b/packages/fuels/src/cli/commands/build/index.ts @@ -1,11 +1,24 @@ +import { type Command } from 'commander'; + import type { FuelsConfig } from '../../types'; import { log } from '../../utils/logger'; +import { deploy } from '../deploy'; +import { autoStartFuelCore } from '../dev/startFuelCore'; import { buildSwayPrograms } from './buildSwayPrograms'; import { generateTypes } from './generateTypes'; -export async function build(config: FuelsConfig) { +export async function build(config: FuelsConfig, program?: Command) { log('Building..'); + await buildSwayPrograms(config); await generateTypes(config); + + const options = program?.opts(); + + if (options?.deploy) { + const fuelCore = await autoStartFuelCore(config); + await deploy(config); + fuelCore?.killChildProcess(); + } } diff --git a/packages/fuels/src/cli/commands/dev/index.ts b/packages/fuels/src/cli/commands/dev/index.ts index ae05d5210c3..b062b93d064 100644 --- a/packages/fuels/src/cli/commands/dev/index.ts +++ b/packages/fuels/src/cli/commands/dev/index.ts @@ -9,9 +9,8 @@ import { build } from '../build'; import { deploy } from '../deploy'; import { withConfigErrorHandler } from '../withConfig'; -import { defaultConsensusKey } from './defaultChainConfig'; import type { FuelCoreNode } from './startFuelCore'; -import { startFuelCore } from './startFuelCore'; +import { autoStartFuelCore } from './startFuelCore'; export const closeAllFileHandlers = (handlers: FSWatcher[]) => { handlers.forEach((h) => h.close()); @@ -56,15 +55,7 @@ export const configFileChanged = (state: DevState) => async (_event: string, pat }; export const dev = async (config: FuelsConfig) => { - let fuelCore: FuelCoreNode | undefined; - - if (config.autoStartFuelCore) { - fuelCore = await startFuelCore(config); - // eslint-disable-next-line no-param-reassign - config.providerUrl = fuelCore.providerUrl; - // eslint-disable-next-line no-param-reassign - config.privateKey = defaultConsensusKey; - } + const fuelCore = await autoStartFuelCore(config); const configFilePaths = getConfigFilepathsToWatch(config); diff --git a/packages/fuels/src/cli/commands/dev/startFuelCore.ts b/packages/fuels/src/cli/commands/dev/startFuelCore.ts index 53d7bbc21f3..4f27eec9532 100644 --- a/packages/fuels/src/cli/commands/dev/startFuelCore.ts +++ b/packages/fuels/src/cli/commands/dev/startFuelCore.ts @@ -105,3 +105,17 @@ export const startFuelCore = async (config: FuelsConfig): Promise core.on('error', reject); }); }; + +export const autoStartFuelCore = async (config: FuelsConfig) => { + let fuelCore: FuelCoreNode | undefined; + + if (config.autoStartFuelCore) { + fuelCore = await startFuelCore(config); + // eslint-disable-next-line no-param-reassign + config.providerUrl = fuelCore.providerUrl; + // eslint-disable-next-line no-param-reassign + config.privateKey = defaultConsensusKey; + } + + return fuelCore; +}; diff --git a/packages/fuels/src/cli/commands/withConfig.ts b/packages/fuels/src/cli/commands/withConfig.ts index d152b8bcbed..bc1870e1c4e 100644 --- a/packages/fuels/src/cli/commands/withConfig.ts +++ b/packages/fuels/src/cli/commands/withConfig.ts @@ -15,7 +15,10 @@ export const withConfigErrorHandler = async (err: Error, config?: FuelsConfig) = export function withConfig( program: Command, command: CType, - fn: (config: FuelsConfig) => Promise['data']> + fn: ( + config: FuelsConfig, + options?: Command + ) => Promise['data']> ) { return async () => { const options = program.opts(); @@ -30,7 +33,7 @@ export function withConfig( } try { - const eventData = await fn(config); + const eventData = await fn(config, program); config.onSuccess?.( { type: command, diff --git a/packages/fuels/test/features/build.test.ts b/packages/fuels/test/features/build.test.ts index 4dafb031ba6..5ba49d4ed24 100644 --- a/packages/fuels/test/features/build.test.ts +++ b/packages/fuels/test/features/build.test.ts @@ -1,8 +1,11 @@ import { existsSync } from 'fs'; import { join } from 'path'; +import * as deployMod from '../../src/cli/commands/deploy/index'; +import { mockStartFuelCore } from '../utils/mockStartFuelCore'; import { resetDiskAndMocks } from '../utils/resetDiskAndMocks'; import { + buildFlagsDeploy, contractsFooDir, generatedDir, initFlagsUseBuiltinBinaries, @@ -14,7 +17,16 @@ describe('build', () => { beforeEach(resetDiskAndMocks); afterEach(resetDiskAndMocks); + function mockAll() { + const { startFuelCore, killChildProcess } = mockStartFuelCore(); + const deploy = jest.spyOn(deployMod, 'deploy').mockImplementation(); + + return { startFuelCore, killChildProcess, deploy }; + } + it('should run `build` command', async () => { + const { startFuelCore, killChildProcess, deploy } = mockAll(); + await runInit(); await runBuild(); @@ -34,9 +46,15 @@ describe('build', () => { ].map((f) => join(__dirname, '..', 'fixtures', 'generated', f)); files.forEach((file) => expect(existsSync(file)).toBeTruthy()); + + expect(startFuelCore).toHaveBeenCalledTimes(0); + expect(deploy).toHaveBeenCalledTimes(0); + expect(killChildProcess).toHaveBeenCalledTimes(0); }); it('should run `build` command with contracts-only', async () => { + const { startFuelCore, killChildProcess, deploy } = mockAll(); + await runInit([initFlagsUseBuiltinBinaries, '-c', contractsFooDir, '-o', generatedDir].flat()); await runBuild(); @@ -49,5 +67,20 @@ describe('build', () => { ].map((f) => join(__dirname, '..', 'fixtures', 'generated', f)); files.forEach((file) => expect(existsSync(file)).toBeTruthy()); + + expect(startFuelCore).toHaveBeenCalledTimes(0); + expect(deploy).toHaveBeenCalledTimes(0); + expect(killChildProcess).toHaveBeenCalledTimes(0); + }); + + it('should run `build` command with `--deploy` flag', async () => { + const { startFuelCore, killChildProcess, deploy } = mockAll(); + + await runInit(); + await runBuild([buildFlagsDeploy]); + + expect(startFuelCore).toHaveBeenCalledTimes(1); + expect(deploy).toHaveBeenCalledTimes(1); + expect(killChildProcess).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/fuels/test/features/dev.test.ts b/packages/fuels/test/features/dev.test.ts index 98e4980d8d2..f2a14007fd3 100644 --- a/packages/fuels/test/features/dev.test.ts +++ b/packages/fuels/test/features/dev.test.ts @@ -1,10 +1,9 @@ import * as chokidar from 'chokidar'; -import type { FuelsConfig } from '../../src'; import * as buildMod from '../../src/cli/commands/build/index'; import * as deployMod from '../../src/cli/commands/deploy/index'; -import * as startCoreMod from '../../src/cli/commands/dev/startFuelCore'; import { mockLogger } from '../utils/mockLogger'; +import { mockStartFuelCore } from '../utils/mockStartFuelCore'; import { resetDiskAndMocks } from '../utils/resetDiskAndMocks'; import { runInit, runDev } from '../utils/runCommands'; @@ -20,18 +19,7 @@ describe('dev', () => { function mockAll() { mockLogger(); - const startFuelCore = jest - .spyOn(startCoreMod, 'startFuelCore') - .mockImplementation((_config: FuelsConfig) => - Promise.resolve({ - bindIp: '0.0.0.0', - accessIp: '127.0.0.1', - port: 4000, - providerUrl: `http://127.0.0.1:4000/graphql`, - killChildProcess: jest.fn(), - chainConfig: '/some/path/chainConfig.json', - }) - ); + const { startFuelCore, killChildProcess } = mockStartFuelCore(); const build = jest.spyOn(buildMod, 'build').mockImplementation(); const deploy = jest.spyOn(deployMod, 'deploy').mockImplementation(); @@ -41,16 +29,17 @@ describe('dev', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const watch = jest.spyOn(chokidar, 'watch').mockReturnValue({ on } as any); - return { startFuelCore, build, deploy, on, watch }; + return { startFuelCore, killChildProcess, build, deploy, on, watch }; } it('should run `dev` command', async () => { - const { startFuelCore, build, deploy, on, watch } = mockAll(); + const { startFuelCore, killChildProcess, build, deploy, on, watch } = mockAll(); await runInit(); await runDev(); expect(startFuelCore).toHaveBeenCalledTimes(1); + expect(killChildProcess).toHaveBeenCalledTimes(0); expect(build).toHaveBeenCalledTimes(1); expect(deploy).toHaveBeenCalledTimes(1); diff --git a/packages/fuels/test/utils/mockStartFuelCore.ts b/packages/fuels/test/utils/mockStartFuelCore.ts new file mode 100644 index 00000000000..b713d6c0187 --- /dev/null +++ b/packages/fuels/test/utils/mockStartFuelCore.ts @@ -0,0 +1,21 @@ +import type { FuelsConfig } from '../../src'; +import * as startFuelCoreMod from '../../src/cli/commands/dev/startFuelCore'; + +export const mockStartFuelCore = () => { + const killChildProcess = jest.fn(); + + const startFuelCore = jest + .spyOn(startFuelCoreMod, 'startFuelCore') + .mockImplementation((_config: FuelsConfig) => + Promise.resolve({ + bindIp: '0.0.0.0', + accessIp: '127.0.0.1', + port: 4000, + providerUrl: `http://127.0.0.1:4000/graphql`, + killChildProcess, + chainConfig: '/some/path/chainConfig.json', + }) + ); + + return { startFuelCore, killChildProcess }; +}; diff --git a/packages/fuels/test/utils/runCommands.ts b/packages/fuels/test/utils/runCommands.ts index 4a4dae96cdd..cddc499981c 100644 --- a/packages/fuels/test/utils/runCommands.ts +++ b/packages/fuels/test/utils/runCommands.ts @@ -47,6 +47,7 @@ export const initFlagsDefault = [ initFlagsUseBuiltinBinaries, initFlagsAutoStartFuelCore, ]; +export const buildFlagsDeploy = '--deploy'; /** * Command callers @@ -55,8 +56,8 @@ export async function runInit(flags: string[] = initFlagsDefault.flat()) { return runCommand(Commands.init, flags); } -export async function runBuild() { - return runCommand(Commands.build); +export async function runBuild(flags: string[] = []) { + return runCommand(Commands.build, flags); } export async function runDeploy() {