From accff3ff438303835202accc310816271e452e8d Mon Sep 17 00:00:00 2001 From: Alexander Belopashentsev <61732514+belopash@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:29:49 +0500 Subject: [PATCH] feat: introduce .squidignore (#75) * feat: introduce .squidingore * refactor: extract build and upload from deploy command * fix: build -> pack * fix: simplify package.json check --- package.json | 2 + src/commands/deploy.ts | 216 +++++++++++++++++++++++++++-------------- src/deploy-command.ts | 8 +- yarn.lock | 2 +- 4 files changed, 149 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index 54adec1..1dc1cb4 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,8 @@ "fast-levenshtein": "^3.0.0", "figlet": "^1.7.0", "form-data": "^4.0.0", + "glob": "^10.3.10", + "ignore": "^5.3.1", "inquirer": "^8.2.5", "js-yaml": "^4.1.0", "lodash": "^4.17.21", diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index 2c26d4e..0e1355b 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -5,6 +5,8 @@ import { promisify } from 'util'; import { Args, Flags, ux as CliUx } from '@oclif/core'; import { ManifestValue } from '@subsquid/manifest'; import chalk from 'chalk'; +import { globSync } from 'glob'; +import ignore from 'ignore'; import inquirer from 'inquirer'; import targz from 'targz'; @@ -21,6 +23,8 @@ const SQUID_PATH_DESC = [ ` - a github URL to a git repo with a branch or commit tag`, ]; +const PACKAGE_JSON = 'package.json'; + const lockFiles = { npm: 'package-lock.json', yarn: 'yarn.lock', @@ -54,6 +58,7 @@ export default class Deploy extends DeployCommand { source: Args.string({ description: SQUID_PATH_DESC.join('\n'), required: true, + default: '.', }), }; @@ -99,14 +104,13 @@ export default class Deploy extends DeployCommand { const orgCode = await this.promptOrganization(org, 'using "-o" flag'); if (!isUrl) { + this.log(`🦑 Releasing the squid from local folder`); + const res = resolveManifest(source, manifestPath); - if ('error' in res) return this.error(res.error); + if ('error' in res) return this.showError(res.error, 'MANIFEST_VALIDATION_FAILED'); const { buildDir, squidDir, manifest } = res; - const archiveName = `${manifest.name}-v${manifest.version}.tar.gz`; - const squidArtifact = path.join(buildDir, archiveName); - this.log(chalk.dim(`Squid directory: ${squidDir}`)); this.log(chalk.dim(`Build directory: ${buildDir}`)); this.log(chalk.dim(`Manifest: ${manifestPath}`)); @@ -135,75 +139,10 @@ export default class Deploy extends DeployCommand { } } - if (!hasPackageJson(squidDir)) { - return this.error( - [ - `The package.json file was not found in the squid directory`, - ``, - `Squid directory ${squidDir}`, - ``, - `Please provide a path to the root of a squid directory`, - ``, - ].join('\n'), - ); - } - - // const lockFile = get(lockFiles, manifest.build.package_manager); - // if (!hasLockFile(squidDir, lockFile)) { - // return this.error( - // [ - // `${lockFile || 'Lockfile'} is not found in the squid directory`, - // ``, - // `Squid directory ${squidDir}`, - // ``, - // `Please provide a path to the root of a squid directory`, - // ``, - // ].join('\n'), - // ); - // } - - CliUx.ux.action.start(`◷ Compressing the squid to ${archiveName} `); - - let filesCount = 0; - await compressAsync({ - src: squidDir, - dest: squidArtifact, - tar: { - ignore: (name) => { - const relativePath = path.relative(path.resolve(squidDir), path.resolve(name)); - - switch (relativePath) { - case 'node_modules': - case 'builds': - case 'lib': - case 'Dockerfile': - // FIXME: .env ? - case '.git': - case '.github': - case '.idea': - this.log(chalk.dim(`-- ignoring ${relativePath}`)); - return true; - default: - this.log(chalk.dim(`adding ${relativePath}`)); - - filesCount++; - return false; - } - }, - }, - }); - CliUx.ux.action.stop(`${filesCount} file(s) ✔️`); - if (filesCount === 0) { - return this.error(`0 files were found in ${squidDir}. Please check the squid source, looks like it is empty`); - } - - CliUx.ux.action.start(`◷ Uploading ${path.basename(squidArtifact)}`); + const archiveName = `${manifest.name}-v${manifest.version}.tar.gz`; - const { error, fileUrl: artifactUrl } = await uploadFile(orgCode, squidArtifact); - if (error) return this.error(error); - else if (!artifactUrl) return this.error('The artifact URL is missing'); - - this.log(`🦑 Releasing the squid from local folder`); + const actifactPath = await this.pack({ buildDir, squidDir, archiveName }); + const artifactUrl = await this.upload({ orgCode, actifactPath }); deploy = await deploySquid({ orgCode, @@ -231,10 +170,115 @@ export default class Deploy extends DeployCommand { this.log('✔️ Done!'); } + + private async pack({ buildDir, squidDir, archiveName }: { buildDir: string; squidDir: string; archiveName: string }) { + CliUx.ux.action.start(`◷ Compressing the squid to ${archiveName} `); + + const squidignore = createSquidIgnore(squidDir); + + if (!hasPackageJson(squidDir) || squidignore?.ignores(PACKAGE_JSON)) { + return this.showError( + [ + `The ${PACKAGE_JSON} file was not found in the squid directory`, + ``, + `Squid directory: ${squidDir}`, + ``, + `Please provide a path to the root of a squid directory`, + ``, + ].join('\n'), + 'PACKING_FAILED', + ); + } + + // const lockFile = get(lockFiles, manifest.build.package_manager); + // if (!hasLockFile(squidDir, lockFile)) { + // return this.error( + // [ + // `${lockFile || 'Lockfile'} is not found in the squid directory`, + // ``, + // `Squid directory ${squidDir}`, + // ``, + // `Please provide a path to the root of a squid directory`, + // ``, + // ].join('\n'), + // ); + // } + + const squidArtifact = path.join(buildDir, archiveName); + + let filesCount = 0; + await compressAsync({ + src: squidDir, + dest: squidArtifact, + tar: { + // if squidignore does not exist, we fallback to the old ignore approach + ignore: squidignore + ? (name) => { + const relativePath = path.relative(path.resolve(squidDir), path.resolve(name)); + + if (squidignore.ignores(relativePath)) { + this.log(chalk.dim(`-- ignoring ${relativePath}`)); + return true; + } else { + this.log(chalk.dim(`adding ${relativePath}`)); + filesCount++; + return false; + } + } + : (name) => { + const relativePath = path.relative(path.resolve(squidDir), path.resolve(name)); + + switch (relativePath) { + case 'node_modules': + case 'builds': + case 'lib': + case 'Dockerfile': + // FIXME: .env ? + case '.git': + case '.github': + case '.idea': + this.log(chalk.dim(`-- ignoring ${relativePath}`)); + return true; + default: + this.log(chalk.dim(`adding ${relativePath}`)); + + filesCount++; + return false; + } + }, + }, + }); + + if (filesCount === 0) { + return this.showError( + `0 files were found in ${squidDir}. Please check the squid source, looks like it is empty`, + 'PACKING_FAILED', + ); + } + + CliUx.ux.action.stop(`${filesCount} file(s) ✔️`); + + return squidArtifact; + } + + private async upload({ orgCode, actifactPath }: { orgCode: string; actifactPath: string }) { + CliUx.ux.action.start(`◷ Uploading ${path.basename(actifactPath)}`); + + const { error, fileUrl: artifactUrl } = await uploadFile(orgCode, actifactPath); + if (error) { + return this.showError(error); + } else if (!artifactUrl) { + return this.showError('The artifact URL is missing', 'UPLOAD_FAILED'); + } + + CliUx.ux.action.stop('✔️'); + + return artifactUrl; + } } function hasPackageJson(squidDir: string) { - return fs.existsSync(path.join(squidDir, 'package.json')); + return fs.existsSync(path.join(squidDir, PACKAGE_JSON)); } function hasLockFile(squidDir: string, lockFile?: string) { @@ -244,3 +288,29 @@ function hasLockFile(squidDir: string, lockFile?: string) { return Object.values(lockFiles).some((lf) => fs.existsSync(path.join(squidDir, lf))); } } + +function createSquidIgnore(squidDir: string) { + const ig = ignore(); + + const ignoreFilePaths = globSync(['.squidignore', '**/.squidignore'], { + cwd: squidDir, + nodir: true, + posix: true, + }); + + if (ignoreFilePaths.length === 0) { + return undefined; + } + + for (const ignoreFilePath of ignoreFilePaths) { + const ignoreCwd = path.dirname(ignoreFilePath); + + const patterns = fs.readFileSync(ignoreFilePath).toString().split('\n'); + for (const pattern of patterns) { + if (pattern.length === 0) continue; + ig.add(path.posix.join(ignoreCwd, pattern)); + } + } + + return ig; +} diff --git a/src/deploy-command.ts b/src/deploy-command.ts index ec85bd3..c3eb966 100644 --- a/src/deploy-command.ts +++ b/src/deploy-command.ts @@ -173,10 +173,10 @@ Do you want to attach to the running deploy process?`, }); }; - showError(text: string): boolean { - CliUx.ux.action.stop(''); + showError(text: string, reason?: string): never { + CliUx.ux.action.stop('❌'); - const reason = this.deploy?.failed || 'UNEXPECTED'; + reason = reason || this.deploy?.failed || 'UNEXPECTED'; const errors: (string | null)[] = [text]; if (reason === 'UNEXPECTED') { errors.push( @@ -190,8 +190,6 @@ Do you want to attach to the running deploy process?`, // FIXME: maybe we should send an error report ourselves here with more details? this.error(errors.filter(Boolean).join('\n')); - - return true; } isFailed() { diff --git a/yarn.lock b/yarn.lock index c5035b8..298f06f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3228,7 +3228,7 @@ ignore@^5.1.1, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -ignore@^5.2.4: +ignore@^5.2.4, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==