diff --git a/.changeset/chilly-shirts-bathe.md b/.changeset/chilly-shirts-bathe.md
new file mode 100644
index 00000000000..cd17eb42e16
--- /dev/null
+++ b/.changeset/chilly-shirts-bathe.md
@@ -0,0 +1,5 @@
+---
+"create-fuels": patch
+---
+
+chore: add test to verify `create fuels` template integrity
diff --git a/.env.example b/.env.example
index fb61325cdd1..9db6730ab3f 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,3 @@
FUEL_NETWORK_URL=
TEST_WALLET_PVT_KEY=
+PUBLISHED_NPM_VERSION=
\ No newline at end of file
diff --git a/.github/actions/pr-release/action.yaml b/.github/actions/pr-release/action.yaml
new file mode 100644
index 00000000000..1e2bddb8e10
--- /dev/null
+++ b/.github/actions/pr-release/action.yaml
@@ -0,0 +1,41 @@
+name: "Publish PR to NPM"
+inputs:
+ npm-token:
+ description: "NPM token for authenticating to NPM registry"
+ required: true
+
+ github-token:
+ description: "GitHub token for authenticating to GitHub"
+ required: true
+
+ pr-number:
+ description: "PR number"
+ default: ${{ github.event.pull_request.number }}
+
+outputs:
+ published_version:
+ description: "Published version of the PR"
+ value: ${{ steps.release.outputs.published_version }}
+
+runs:
+ using: "composite"
+ steps:
+ - name: Ensure NPM access
+ shell: bash
+ run: npm whoami
+ env:
+ NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
+
+ - name: Release to @pr-${{ inputs.pr-number }} tag on npm
+ id: release
+ shell: bash
+ run: |
+ pnpm changeset:next
+ git add .changeset/fuel-labs-ci.md
+ pnpm changeset version --snapshot pr-${{ inputs.pr-number }}
+ changetsets=$(pnpm changeset publish --tag pr-${{ inputs.pr-number }})
+ published_version=$(echo "$changetsets" | grep -oP '@\K([0-9]+\.){2}[0-9]+-pr-${{ inputs.pr-number }}-\d+' | head -1)
+ echo "published_version=$published_version" >> $GITHUB_OUTPUT
+ env:
+ NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
+ GITHUB_TOKEN: ${{ inputs.github-token }}
diff --git a/.github/workflows/pr-release.yaml b/.github/workflows/pr-release.yaml
index ce014ebeca4..6cc274cba00 100644
--- a/.github/workflows/pr-release.yaml
+++ b/.github/workflows/pr-release.yaml
@@ -20,31 +20,20 @@ jobs:
- name: CI Setup
uses: ./.github/actions/ci-setup
- - name: Ensure NPM access
- run: npm whoami
- env:
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
-
- name: Build
run: pnpm build
- - name: Release to @pr-${{ github.event.pull_request.number }} tag on npm
+ - name: Publish to NPM
id: release
- run: |
- pnpm changeset:next
- git add .changeset/fuel-labs-ci.md
- pnpm changeset version --snapshot pr-${{ env.PR_NUMBER }}
- changetsets=$(pnpm changeset publish --tag pr-${{ env.PR_NUMBER }})
- published_version=$(echo "$changetsets" | grep -oP '@\K([0-9]+\.){2}[0-9]+-pr-${{ env.PR_NUMBER }}-\d+' | head -1)
- echo "published_version=$published_version" >> $GITHUB_OUTPUT
- env:
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- PR_NUMBER: ${{ github.event.pull_request.number }}
+ uses: ./.github/actions/pr-release
+ with:
+ npm-token: ${{ secrets.NPM_TOKEN }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ pr-number: ${{ github.event.pull_request.number }}
- uses: mshick/add-pr-comment@v2
with:
message: |
This PR is published in NPM with version **${{ steps.release.outputs.published_version }}**
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index db26ad596cc..018e15227d9 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -58,9 +58,19 @@ jobs:
with:
fetch-depth: 0
- - name: CI Setup
+ - name: Test Setup
uses: ./.github/actions/test-setup
+ - name: Publish PR to NPM
+ id: release
+ uses: ./.github/actions/pr-release
+ with:
+ npm-token: ${{ secrets.NPM_TOKEN }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Echo Published Version
+ run: echo ${{ steps.release.outputs.published_version }}
+
- name: Pretest
run: pnpm pretest
@@ -71,6 +81,7 @@ jobs:
TEST_WALLET_PVT_KEY: ${{ secrets.TEST_WALLET_PVT_KEY }}
TEST_WALLET_ADDRESS: ${{ secrets.TEST_WALLET_ADDRESS }}
FUEL_TESTNET_NETWORK_URL: ${{ secrets.FUEL_TESTNET_NETWORK_URL }}
+ PUBLISHED_NPM_VERSION: ${{ steps.release.outputs.published_version }}
test:
if: github.base_ref == 'master' || github.ref_name == 'master'
diff --git a/packages/create-fuels/package.json b/packages/create-fuels/package.json
index c995e8baac9..3a6df41e067 100644
--- a/packages/create-fuels/package.json
+++ b/packages/create-fuels/package.json
@@ -26,6 +26,7 @@
"prompts": "^2.4.2"
},
"devDependencies": {
+ "@fuel-ts/errors": "workspace:*",
"@fuel-ts/versions": "workspace:*",
"@types/prompts": "^2.4.8",
"glob": "^10.2.6"
diff --git a/packages/create-fuels/test/cli.test.ts b/packages/create-fuels/test/cli.test.ts
index 39ba46471d3..51d185dd276 100644
--- a/packages/create-fuels/test/cli.test.ts
+++ b/packages/create-fuels/test/cli.test.ts
@@ -1,20 +1,18 @@
import { mkdirSync } from 'fs';
-import { glob } from 'glob';
import type { ProgramsToInclude } from '../src/cli';
import { runScaffoldCli, setupProgram } from '../src/cli';
import type { ProjectPaths } from './utils/bootstrapProject';
-import { bootstrapProject, cleanupFilesystem, resetFilesystem } from './utils/bootstrapProject';
+import {
+ bootstrapProject,
+ cleanupFilesystem,
+ copyTemplate,
+ resetFilesystem,
+} from './utils/bootstrapProject';
+import { generateArgv } from './utils/generateArgs';
import { mockLogger } from './utils/mockLogger';
-
-const getAllFiles = async (pathToDir: string) => {
- const files = await glob(`${pathToDir}/**/*`, {
- ignore: ['**/node_modules/**', '**/.next/**', '**/sway-api/**'],
- });
- const filesWithoutPrefix = files.map((file) => file.replace(pathToDir, ''));
- return filesWithoutPrefix;
-};
+import { filterOriginalTemplateFiles, getAllFiles } from './utils/templateFiles';
const possibleProgramsToInclude: ProgramsToInclude[] = [
{ contract: true, predicate: false, script: false },
@@ -26,51 +24,6 @@ const possibleProgramsToInclude: ProgramsToInclude[] = [
{ contract: true, predicate: true, script: true },
];
-const defaultFlags = ['--pnpm'];
-
-const generateArgs = (programsToInclude: ProgramsToInclude, projectName?: string) => {
- const args = ['', ''];
- if (projectName) {
- args.push(projectName);
- }
- if (programsToInclude.contract) {
- args.push('-c');
- }
- if (programsToInclude.predicate) {
- args.push('-p');
- }
- if (programsToInclude.script) {
- args.push('-s');
- }
- args.push(...defaultFlags);
- return args;
-};
-
-const filterOriginalTemplateFiles = (files: string[], programsToInclude: ProgramsToInclude) => {
- let newFiles = [...files];
-
- newFiles = newFiles.filter((file) => {
- if (file.includes('CHANGELOG')) {
- return false;
- }
- if (!programsToInclude.contract && file.includes('contract')) {
- return false;
- }
- if (!programsToInclude.predicate && file.includes('predicate')) {
- return false;
- }
- if (!programsToInclude.script && file.includes('script')) {
- return false;
- }
- if (['/gitignore', '/env'].includes(file)) {
- return false;
- }
- return true;
- });
-
- return newFiles;
-};
-
/**
* @group node
*/
@@ -80,6 +33,7 @@ describe('CLI', () => {
beforeEach(() => {
paths = bootstrapProject(__filename);
+ copyTemplate(paths.sourceTemplate, paths.template);
});
afterEach(() => {
@@ -95,7 +49,7 @@ describe('CLI', () => {
test.each(possibleProgramsToInclude)(
'create-fuels extracts the template to the specified directory',
async (programsToInclude) => {
- const args = generateArgs(programsToInclude, paths.root);
+ const args = generateArgv(programsToInclude, paths.root);
await runScaffoldCli({
program: setupProgram(),
@@ -113,7 +67,7 @@ describe('CLI', () => {
);
test('create-fuels reports an error if the project directory already exists', async () => {
- const args = generateArgs(
+ const args = generateArgv(
{
contract: true,
predicate: true,
@@ -140,7 +94,7 @@ describe('CLI', () => {
});
test('create-fuels reports an error if no programs are chosen to be included', async () => {
- const args = generateArgs(
+ const args = generateArgv(
{
contract: false,
predicate: false,
diff --git a/packages/create-fuels/test/e2e.test.ts b/packages/create-fuels/test/e2e.test.ts
new file mode 100644
index 00000000000..1d62730c74c
--- /dev/null
+++ b/packages/create-fuels/test/e2e.test.ts
@@ -0,0 +1,65 @@
+import { safeExec } from '@fuel-ts/errors/test-utils';
+import { execSync } from 'child_process';
+
+import type { ProjectPaths } from './utils/bootstrapProject';
+import { bootstrapProject, resetFilesystem } from './utils/bootstrapProject';
+import { generateArgs } from './utils/generateArgs';
+import {
+ filterForcBuildFiles,
+ filterOriginalTemplateFiles,
+ getAllFiles,
+} from './utils/templateFiles';
+
+const { log } = console;
+
+const PUBLISHED_NPM_VERSION = process.env.PUBLISHED_NPM_VERSION;
+const programsToInclude = { contract: true, predicate: true, script: true };
+const availablePackages = ['pnpm'];
+
+/**
+ * @group e2e
+ */
+describe('`create fuels` package integrity', () => {
+ let paths: ProjectPaths;
+ let shouldSkip = false;
+
+ beforeAll(() => {
+ if (!PUBLISHED_NPM_VERSION) {
+ log('Skipping live `create fuels` test');
+ shouldSkip = true;
+ }
+ });
+
+ beforeEach(() => {
+ paths = bootstrapProject(__filename);
+ });
+
+ afterEach(() => {
+ resetFilesystem(paths.root);
+ });
+
+ it.each(availablePackages)(
+ 'should perform `%s create fuels`',
+ async (packageManager) => {
+ if (shouldSkip) {
+ return;
+ }
+
+ const args = generateArgs(programsToInclude, paths.root, packageManager).join(' ');
+ const expectedTemplateFiles = await getAllFiles(paths.sourceTemplate).then((files) =>
+ filterOriginalTemplateFiles(files, programsToInclude).filter(filterForcBuildFiles)
+ );
+
+ const { error: createFuelsError } = await safeExec(() =>
+ execSync(`${packageManager} create fuels@${PUBLISHED_NPM_VERSION} ${args}`, {
+ stdio: 'inherit',
+ })
+ );
+
+ const actualTemplateFiles = await getAllFiles(paths.root);
+ expect(createFuelsError).toBeUndefined();
+ expect(actualTemplateFiles.sort()).toEqual(expectedTemplateFiles.sort());
+ },
+ { timeout: 30000 }
+ );
+});
diff --git a/packages/create-fuels/test/utils/bootstrapProject.ts b/packages/create-fuels/test/utils/bootstrapProject.ts
index ba0828c6b3e..fb2983c98ef 100644
--- a/packages/create-fuels/test/utils/bootstrapProject.ts
+++ b/packages/create-fuels/test/utils/bootstrapProject.ts
@@ -4,6 +4,7 @@ import { basename, join } from 'path';
export type ProjectPaths = {
root: string;
template: string;
+ sourceTemplate: string;
};
/**
@@ -21,9 +22,6 @@ export const bootstrapProject = (
// Template paths
const templateDir = join(templatesDir, templateName);
const localTemplateDir = join(testTemplateDir, templateName);
- if (!existsSync(localTemplateDir)) {
- cpSync(templateDir, localTemplateDir, { recursive: true });
- }
// Unique name
const testFilename = basename(testFilepath.replace(/\./g, '-'));
@@ -35,9 +33,16 @@ export const bootstrapProject = (
return {
root,
template: localTemplateDir,
+ sourceTemplate: templateDir,
};
};
+export const copyTemplate = (srcDir: string, destDir: string) => {
+ if (!existsSync(destDir)) {
+ cpSync(srcDir, destDir, { recursive: true });
+ }
+};
+
export const resetFilesystem = (dirPath: string) => {
if (existsSync(dirPath)) {
rmSync(dirPath, { recursive: true });
diff --git a/packages/create-fuels/test/utils/generateArgs.ts b/packages/create-fuels/test/utils/generateArgs.ts
new file mode 100644
index 00000000000..80b93284f66
--- /dev/null
+++ b/packages/create-fuels/test/utils/generateArgs.ts
@@ -0,0 +1,28 @@
+import type { ProgramsToInclude } from '../../src/cli';
+
+export const generateArgs = (
+ programsToInclude: ProgramsToInclude,
+ projectName?: string,
+ packageManager: string = 'pnpm'
+): string[] => {
+ const args = [];
+ if (projectName) {
+ args.push(projectName);
+ }
+ if (programsToInclude.contract) {
+ args.push('-c');
+ }
+ if (programsToInclude.predicate) {
+ args.push('-p');
+ }
+ if (programsToInclude.script) {
+ args.push('-s');
+ }
+ args.push(`--${packageManager}`);
+ return args;
+};
+
+export const generateArgv = (
+ programsToInclude: ProgramsToInclude,
+ projectName?: string
+): string[] => ['', '', ...generateArgs(programsToInclude, projectName)];
diff --git a/packages/create-fuels/test/utils/templateFiles.ts b/packages/create-fuels/test/utils/templateFiles.ts
new file mode 100644
index 00000000000..3121ec7b0fe
--- /dev/null
+++ b/packages/create-fuels/test/utils/templateFiles.ts
@@ -0,0 +1,42 @@
+import { glob } from 'glob';
+
+import type { ProgramsToInclude } from '../../src/cli';
+
+export const getAllFiles = async (pathToDir: string) => {
+ const files = await glob(`${pathToDir}/**/*`, {
+ ignore: ['**/node_modules/**', '**/.next/**', '**/sway-api/**'],
+ });
+ const filesWithoutPrefix = files.map((file) => file.replace(pathToDir, ''));
+ return filesWithoutPrefix;
+};
+
+export const filterOriginalTemplateFiles = (
+ files: string[],
+ programsToInclude: ProgramsToInclude
+) => {
+ let newFiles = [...files];
+
+ newFiles = newFiles.filter((file) => {
+ if (file.includes('CHANGELOG')) {
+ return false;
+ }
+ if (!programsToInclude.contract && file.includes('contract')) {
+ return false;
+ }
+ if (!programsToInclude.predicate && file.includes('predicate')) {
+ return false;
+ }
+ if (!programsToInclude.script && file.includes('script')) {
+ return false;
+ }
+ if (['/gitignore', '/env'].includes(file)) {
+ return false;
+ }
+ return true;
+ });
+
+ return newFiles;
+};
+
+export const filterForcBuildFiles = (file: string) =>
+ !file.includes('contract/out') && !file.includes('script/out') && !file.includes('predicate/out');
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 05c8783c175..2f4c16d7f1e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -803,6 +803,9 @@ importers:
specifier: ^2.4.2
version: 2.4.2
devDependencies:
+ '@fuel-ts/errors':
+ specifier: workspace:*
+ version: link:../errors
'@fuel-ts/versions':
specifier: workspace:*
version: link:../versions
diff --git a/scripts/tests-find.sh b/scripts/tests-find.sh
index 264d459b3cf..74e258411bb 100755
--- a/scripts/tests-find.sh
+++ b/scripts/tests-find.sh
@@ -12,4 +12,4 @@ elif [[ $* == *--browser* ]]; then
grep -lE "@group\s+browser" $FILES
elif [[ $* == *--e2e* ]]; then
grep -lE "@group\s+e2e" $FILES
-fi
\ No newline at end of file
+fi
diff --git a/templates/nextjs/.gitignore b/templates/nextjs/.gitignore
index bc516c4936d..22a95c4132a 100644
--- a/templates/nextjs/.gitignore
+++ b/templates/nextjs/.gitignore
@@ -31,7 +31,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
-next-env.d.ts
.fuels
diff --git a/templates/nextjs/next-env.d.ts b/templates/nextjs/next-env.d.ts
new file mode 100644
index 00000000000..4f11a03dc6c
--- /dev/null
+++ b/templates/nextjs/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.