From beb9bd12e14832a5f1d192dc7dcae1b8dd3e2e40 Mon Sep 17 00:00:00 2001 From: Lars Jacobsson Date: Thu, 14 Sep 2023 23:32:06 +0200 Subject: [PATCH] Python cdk (#26) * python cdk wip * wip * [samp local] python cdk support --------- Co-authored-by: ljacobsson --- package.json | 2 +- src/commands/local/cdk-construct-finder.js | 47 -------- src/commands/local/lib/connect.js | 1 + src/commands/local/lib/handler-finder.js | 5 +- src/commands/local/local.js | 23 ++-- src/commands/local/runtime-env-finder.js | 31 ++--- .../nodejs/cdk/cdk-construct-finder.js | 25 ++++ .../nodejs/cdk}/cdk-wrapper.js | 11 +- .../nodejs/ide-support/vscode/setup.js | 1 + .../python/cdk/cdk-construct-finder.js | 25 ++++ .../runtime-support/python/cdk/cdk-wrapper.js | 84 +++++++++++++ .../runtime-support/python/iac-support/cdk.js | 114 ++++++++++++++++++ src/commands/stepfunctions/index.js | 6 +- src/shared/fileFinder.js | 29 +++++ 14 files changed, 320 insertions(+), 84 deletions(-) delete mode 100644 src/commands/local/cdk-construct-finder.js create mode 100644 src/commands/local/runtime-support/nodejs/cdk/cdk-construct-finder.js rename src/commands/local/{ => runtime-support/nodejs/cdk}/cdk-wrapper.js (98%) create mode 100644 src/commands/local/runtime-support/python/cdk/cdk-construct-finder.js create mode 100644 src/commands/local/runtime-support/python/cdk/cdk-wrapper.js create mode 100644 src/commands/local/runtime-support/python/iac-support/cdk.js create mode 100644 src/shared/fileFinder.js diff --git a/package.json b/package.json index 2ced79a..e1cd8bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "samp-cli", - "version": "1.0.55", + "version": "1.0.56", "description": "CLI tool for extended productivity with AWS Serverless Application Model (SAM)", "main": "index.js", "scripts": { diff --git a/src/commands/local/cdk-construct-finder.js b/src/commands/local/cdk-construct-finder.js deleted file mode 100644 index 3ebad12..0000000 --- a/src/commands/local/cdk-construct-finder.js +++ /dev/null @@ -1,47 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -function findFilesWithExtension(rootDir, fileExtension) { - let files = []; - - function traverseDir(currentDir) { - const items = fs.readdirSync(currentDir); - - items.forEach((item) => { - const itemPath = path.join(currentDir, item); - const itemStats = fs.statSync(itemPath); - - if (itemStats.isDirectory()) { - traverseDir(itemPath); - } else if (path.extname(itemPath) === fileExtension && !itemPath.includes('node_modules') && !itemPath.includes('.samp-out')) { - files.push(itemPath); - } - }); - } - - traverseDir(rootDir); - return files; -} - -function findConstructs() { - const rootDir = '.'; - const fileExtension = '.ts'; - const searchString = /extends.*Stack/g; - const files = findFilesWithExtension(rootDir, fileExtension); - const matchedFiles = []; - - files.forEach((file) => { - const fileContent = fs.readFileSync(file, 'utf8'); - - if (fileContent.match(searchString)) { - matchedFiles.push(file); - } - }); - - return matchedFiles; -} - - -module.exports = { - findConstructs -}; \ No newline at end of file diff --git a/src/commands/local/lib/connect.js b/src/commands/local/lib/connect.js index 0c945b1..94d8376 100644 --- a/src/commands/local/lib/connect.js +++ b/src/commands/local/lib/connect.js @@ -152,6 +152,7 @@ if (!fs.existsSync(".lambda-debug")) { const stackName = targetConfig.stack_name; template = parse("template", fs.readFileSync(findSAMTemplateFile('.')).toString()); + stack = await cfnClient.send(new ListStackResourcesCommand({ StackName: stackName })); let token = stack.NextToken; if (token) { diff --git a/src/commands/local/lib/handler-finder.js b/src/commands/local/lib/handler-finder.js index f5a3a46..73ff0be 100644 --- a/src/commands/local/lib/handler-finder.js +++ b/src/commands/local/lib/handler-finder.js @@ -34,6 +34,7 @@ export function locatePythonHandler(template, obj, baseDir) { const globals = template.Globals?.Function || {}; const handlerFolders = (obj.handler || globals.Handler).split('/'); const functionHandler = handlerFolders.pop(); + baseDir += "/" + handlerFolders.join('/'); // remove folders if they don't exist on disk handlerFolders.forEach((folder, index) => { if (!fs.existsSync(`${process.cwd()}/${baseDir}${handlerFolders.slice(0, index + 1).join('/')}`)) { @@ -44,11 +45,13 @@ export function locatePythonHandler(template, obj, baseDir) { const handler = (obj.handler + '/' + functionHandler.split('.')[0]).replace(/\/\//g, '/'); const handlerMethod = functionHandler.split('.')[1]; let pyExt = ".py"; - const module = `file://${`${process.cwd()}/${baseDir}${handler}${pyExt}`.replace(/\/\//g, '/')}`.replace('.samp-out/./', '.samp-out/'); + const module = `file://${`${process.cwd()}/${baseDir}${handler}${pyExt}`.replace(/\/\//g, '/')}`.replace('.samp-out/./', '.samp-out/').replace('.samp-out/', ''); + return { module, handlerMethod, runtime: obj.runtime || globals.Runtime || "python" }; } export function locateHandler(template, obj, baseDir) { + console.log("LOCATE HANDLER", obj); if (!obj.runtime || obj.runtime.startsWith("nodejs")) return locateJsHandler(template, obj, baseDir); if (obj.runtime.startsWith("dotnet")) return locateDotnetHandler(template, obj, baseDir); if (obj.runtime.startsWith("python")) return locatePythonHandler(template, obj, baseDir); diff --git a/src/commands/local/local.js b/src/commands/local/local.js index fede3f5..81a53b5 100644 --- a/src/commands/local/local.js +++ b/src/commands/local/local.js @@ -5,7 +5,6 @@ const path = require('path'); const inputUtil = require('../../shared/inputUtil'); const settingsUtil = require('../../shared/settingsUtil'); const glob = require('glob'); -const { findConstructs } = require('./cdk-construct-finder'); const { fromSSO } = require('@aws-sdk/credential-provider-sso'); const { CloudFormationClient, ListStackResourcesCommand } = require('@aws-sdk/client-cloudformation'); const samConfigParser = require('../../shared/samConfigParser'); @@ -16,6 +15,11 @@ function setEnvVars(cmd) { process.env.SAMP_REGION = cmd.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || ''; process.env.SAMP_STACKNAME = process.env.SAMP_STACKNAME || cmd.stackName || ''; process.env.SAMP_CDK_STACK_PATH = cmd.construct || process.env.SAMP_CDK_STACK_PATH || ''; + + if (!process.env.SAMP_REGION) { + console.log("Please specify a region using --region or by setting the AWS_REGION or AWS_DEFAULT_REGION environment variable"); + process.exit(1); + } process.env.SAMP_TEMPLATE_PATH = cmd.template || process.env.SAMP_TEMPLATE_PATH || undefined; } @@ -120,7 +124,7 @@ function setupCDK_TS(initialised, cmd) { print(data); if (data.toString().includes("Watching for file changes") && !initialised) { - const cdkWrapper = exec(`node ${__dirname}/cdk-wrapper.js .samp-out/${cmd.construct.replace(".ts", "")}.js`, {}); + const cdkWrapper = exec(`node ${__dirname}/runtime-support/${env.runtime}/cdk/cdk-wrapper.js .samp-out/${cmd.construct.replace(".ts", "")}.js`, {}); cdkWrapper.stdout.on('data', (data) => { print(data); }); @@ -160,11 +164,14 @@ async function setupDebug(cmd) { let selectedFunctionsCsv = cmd.functions || targetConfig.selected_functions; let construct; if (env.iac === "cdk") { - const constructs = findConstructs(); - constructs.push("Enter manually"); - construct = await inputUtil.autocomplete("Which stack construct do you want to debug?", constructs); - if (construct === "Enter manually") { - construct = await inputUtil.text("Enter which stack construct do you want to debug?"); + if (env.isNodeJS) { + const constructs = require(`./runtime-support/${env.runtime}/cdk/cdk-construct-finder`).findConstructs(); + constructs.push("Enter manually"); + construct = await inputUtil.autocomplete("Which stack construct do you want to debug?", constructs); + if (construct === "Enter manually") { + construct = await inputUtil.text("Enter which stack construct do you want to debug"); + } + cmd.construct = construct; } const cdkTree = JSON.parse(fs.readFileSync("cdk.out/tree.json", "utf8")); const stacks = Object.keys(cdkTree.tree.children).filter(c => c !== "Tree"); @@ -173,6 +180,8 @@ async function setupDebug(cmd) { if (stack === "Enter manually") { stack = await inputUtil.text("What's the name of the deployed stack?"); } + cmd.stackName = stack; + process.env.SAMP_STACKNAME = stack; const regions = [ "ap-south-1", "eu-north-1", diff --git a/src/commands/local/runtime-env-finder.js b/src/commands/local/runtime-env-finder.js index a2aae0d..1f35f99 100644 --- a/src/commands/local/runtime-env-finder.js +++ b/src/commands/local/runtime-env-finder.js @@ -12,30 +12,23 @@ function determineRuntime() { const runtime = (template.Resources[firstFunction].Properties.Runtime || template.Globals?.Function?.Runtime).substring(0, 3); switch (runtime) { case "dot": - return { iac: "sam", functionLanguage: "dotnet" }; + return { iac: "sam", functionLanguage: "dotnet", runtime: "dotnet" }; case "pyt": - return { iac: "sam", functionLanguage: "python" }; + return { iac: "sam", functionLanguage: "python", runtime: "python" }; case "jav": - return { iac: "sam", functionLanguage: "java" }; + return { iac: "sam", functionLanguage: "java", runtime: "java" }; + case "nod": { + const isTs = fs.existsSync(path.join(process.cwd(), 'tsconfig.json')); + return { iac: "sam", functionLanguage: isTs ? "ts" : "js", runtime: "nodejs", isNodeJS: true }; + } } } - if (fs.existsSync('cdk.json')) return { iac: "cdk", functionLanguage: "ts", isNodeJS: true }; - if (fs.existsSync('tsconfig.json')) return { iac: "sam", functionLanguage: "ts", isNodeJS: true };; - - // does a file ending -csproj exist in the current folder or subfolders? - const csprojFiles = []; - const walkSync = (dir, filelist = []) => { - fs.readdirSync(dir).forEach(file => { - const dirFile = path.join(dir, file); - try { - filelist = walkSync(dirFile, filelist); - } catch (err) { - if (err.code === 'ENOTDIR' || err.code === 'EBUSY') filelist = [...filelist, dirFile]; - else throw err; - } - }); - return filelist; + if (fs.existsSync('cdk.json')) { + const cdkJson = JSON.parse(fs.readFileSync('cdk.json', 'utf8')); + if (cdkJson.app.includes('python')) return { iac: "cdk", functionLanguage: "python", runtime: "python" }; + return { iac: "cdk", functionLanguage: "ts", runtime: "nodejs", isNodeJS: true }; } + if (fs.existsSync('tsconfig.json')) return { iac: "sam", functionLanguage: "ts", runtime: "nodejs", isNodeJS: true }; walkSync(process.cwd()).forEach(file => { if (file.endsWith('.csproj')) csprojFiles.push(file); }); diff --git a/src/commands/local/runtime-support/nodejs/cdk/cdk-construct-finder.js b/src/commands/local/runtime-support/nodejs/cdk/cdk-construct-finder.js new file mode 100644 index 0000000..57a0f32 --- /dev/null +++ b/src/commands/local/runtime-support/nodejs/cdk/cdk-construct-finder.js @@ -0,0 +1,25 @@ +const { findFilesWithExtension } = require('../../../../../shared/fileFinder'); +const fs = require('fs'); + +function findConstructs() { + const rootDir = '.'; + const fileExtension = '.ts'; + const searchString = /extends.*Stack/g; + const files = findFilesWithExtension(rootDir, fileExtension); + const matchedFiles = []; + + files.forEach((file) => { + const fileContent = fs.readFileSync(file, 'utf8'); + + if (fileContent.match(searchString)) { + matchedFiles.push(file); + } + }); + + return matchedFiles; +} + + +module.exports = { + findConstructs +}; \ No newline at end of file diff --git a/src/commands/local/cdk-wrapper.js b/src/commands/local/runtime-support/nodejs/cdk/cdk-wrapper.js similarity index 98% rename from src/commands/local/cdk-wrapper.js rename to src/commands/local/runtime-support/nodejs/cdk/cdk-wrapper.js index 5926f41..c726013 100644 --- a/src/commands/local/cdk-wrapper.js +++ b/src/commands/local/runtime-support/nodejs/cdk/cdk-wrapper.js @@ -15,7 +15,6 @@ for (const file of getAllJsFiles(baseDir)) { } } - const TargetStack = require(`${process.cwd()}/${process.argv[2]}`); const className = Object.keys(TargetStack)[0]; @@ -32,21 +31,21 @@ for (const key of Object.keys(resources)) { const resource = JSON.parse(JSON.stringify(resources[key], getCircularReplacer())); delete resource.node?._children?._children?._children?.stack; const fingerprintOptions = findShallowestOccurrence(resource, 'fingerprintOptions').occurrence; - + const entry = fingerprintOptions?.bundling?.relativeEntryPath || (fingerprintOptions?.path ? `${fingerprintOptions?.path}/` : null); - + let logicalId = null; let handler if (entry) { for (const fn of Object.keys(synthedTemplate.Resources)) { const resource = synthedTemplate.Resources[fn]; if (resource.Type === "AWS::Lambda::Function") { - if (!resource.Properties?.Code?.S3Bucket) continue; + if (!resource.Properties?.Code?.S3Bucket) continue; if (!resource.Properties?.Runtime.startsWith("node")) continue; if (resource.Metadata?.["aws:asset:is-bundled"] === false) continue; - if (resource.Metadata?.['aws:cdk:path'].includes(`/${key}/`) && resource.Metadata?.['aws:cdk:path'].includes('/Resource')) { + if (resource.Metadata?.['aws:cdk:path'].includes(`/${key}/`) && resource.Metadata?.['aws:cdk:path'].includes('/Resource')) { //get filename from entry - + const entryFile = entry.substring(entry.lastIndexOf("/") + 1).split(".")[0]; logicalId = fn; handler = `${entryFile}.${resource.Properties.Handler.split(".")[1]}`; diff --git a/src/commands/local/runtime-support/nodejs/ide-support/vscode/setup.js b/src/commands/local/runtime-support/nodejs/ide-support/vscode/setup.js index ad54840..d8465f9 100644 --- a/src/commands/local/runtime-support/nodejs/ide-support/vscode/setup.js +++ b/src/commands/local/runtime-support/nodejs/ide-support/vscode/setup.js @@ -42,6 +42,7 @@ function copyConfig(name, args) { if (process.env.SAMP_TEMPLATE_PATH) { task.command += ` --template ${process.env.SAMP_TEMPLATE_PATH}`; } + task.command += ` --region ${process.env.SAMP_REGION}`; let tasksJson; if (fs.existsSync(`${pwd}/.vscode/tasks.json`)) { tasksJson = commentJson.parse(fs.readFileSync(`${pwd}/.vscode/tasks.json`, "utf8")); diff --git a/src/commands/local/runtime-support/python/cdk/cdk-construct-finder.js b/src/commands/local/runtime-support/python/cdk/cdk-construct-finder.js new file mode 100644 index 0000000..b2b18b3 --- /dev/null +++ b/src/commands/local/runtime-support/python/cdk/cdk-construct-finder.js @@ -0,0 +1,25 @@ +const { findFilesWithExtension } = require('../../../../../shared/fileFinder'); +const fs = require('fs'); + +function findConstructs() { + const rootDir = '.'; + const fileExtension = '.py'; + const searchString = /from aws_cdk import/g; + const files = findFilesWithExtension(rootDir, fileExtension); + const matchedFiles = []; + + files.forEach((file) => { + const fileContent = fs.readFileSync(file, 'utf8'); + + if (fileContent.match(searchString)) { + matchedFiles.push(file); + } + }); + + return matchedFiles; +} + + +module.exports = { + findConstructs +}; \ No newline at end of file diff --git a/src/commands/local/runtime-support/python/cdk/cdk-wrapper.js b/src/commands/local/runtime-support/python/cdk/cdk-wrapper.js new file mode 100644 index 0000000..f882fe4 --- /dev/null +++ b/src/commands/local/runtime-support/python/cdk/cdk-wrapper.js @@ -0,0 +1,84 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const yamlDump = require('js-yaml').dump; +function calculateHash(filePath) { + const fileContent = fs.readFileSync(filePath); + return crypto.createHash('md5').update(fileContent).digest('hex'); +} + +function findMatchingSource(assetHash, sourceDirectories) { + for (const sourceDir of sourceDirectories) { + const sourceFiles = fs.readdirSync(sourceDir); + for (const sourceFile of sourceFiles) { + if (fs.statSync(path.join(sourceDir, sourceFile)).isDirectory()) { + const matchingSourceDir = findMatchingSource(assetHash, [path.join(sourceDir, sourceFile)]); + if (matchingSourceDir !== null) { + return matchingSourceDir; + } + } else { + const sourceFilePath = path.join(sourceDir, sourceFile); + const sourceHash = calculateHash(sourceFilePath); + if (sourceHash === assetHash) { + return sourceDir; + } + } + } + } + return null; +} + +function createAssetSourceMap(assetDirectories, sourceDirectories) { + + const assetSourceMap = {}; + + for (const assetDir of assetDirectories) { + const assetFiles = fs.readdirSync(assetDir); + for (const assetFile of assetFiles) { + const assetFilePath = path.join(assetDir, assetFile); + const assetHash = calculateHash(assetFilePath); + const matchingSource = findMatchingSource(assetHash, sourceDirectories); + if (matchingSource) { + const assetHashKey = path.basename(assetDir); + assetSourceMap[assetHashKey] = matchingSource; + } + } + } + + return assetSourceMap; +} + + + +const assetDirectories = fs.readdirSync('cdk.out') + .map(folder => path.join('cdk.out', folder)) + .filter(folderPath => fs.statSync(folderPath).isDirectory()); + +const sourceDirectories = fs.readdirSync('.') + .map(handler => path.join('.', handler)) + .filter(handlerPath => fs.statSync(handlerPath).isDirectory() && !handlerPath.startsWith('.') && !handlerPath.startsWith('__') && !handlerPath.startsWith('cdk.out')); +const assetSourceMap = createAssetSourceMap(assetDirectories, sourceDirectories); + +const template = JSON.parse(fs.readFileSync('cdk.out/' + process.argv[2] + '.template.json', 'utf-8')); +// Get all AWS::Lambda::Function resources +const functions = Object.keys(template.Resources) + .filter(key => template.Resources[key].Type === "AWS::Lambda::Function") +const mockTemplate = { + Resources: {} +}; + +for (const asset of Object.keys(assetSourceMap)) { + for (const fn of functions) { + const resource = template.Resources[fn]; + if (resource.Metadata['aws:asset:path'] === asset) { + const handlerSplit = resource.Properties.Handler.split('/'); + mockTemplate.Resources[fn] = { + Type: "AWS::Serverless::Function", + Properties: { CodeUri: '.', Handler: `${assetSourceMap[asset]}/${handlerSplit[handlerSplit.length - 1]}`, Runtime: 'python' } + }; + break; + } + } +} + +fs.writeFileSync(".samp-out/mock-template.yaml", yamlDump(mockTemplate)); diff --git a/src/commands/local/runtime-support/python/iac-support/cdk.js b/src/commands/local/runtime-support/python/iac-support/cdk.js new file mode 100644 index 0000000..09dfe21 --- /dev/null +++ b/src/commands/local/runtime-support/python/iac-support/cdk.js @@ -0,0 +1,114 @@ +const fs = require('fs'); +const path = require('path'); +const { exec } = require('child_process'); +async function setup(initialised, cmd) { + process.env.outDir = ".samp-out"; + process.env.SAMP_TEMPLATE_PATH = ".samp-out/mock-template.yaml"; + await run(initialised, cmd); +} + +async function run(initialised, cmd) { + try { + if (!fs.existsSync('.samp-out')) { + fs.mkdirSync('.samp-out'); + } + if (!fs.existsSync('.samp-out/samp-requests')) { + fs.mkdirSync('.samp-out/samp-requests'); + } + + const cdkWrapper = exec(`node ${__dirname}/../cdk/cdk-wrapper.js ${cmd.stackName}`, {}); + cdkWrapper.stdout.on('data', (data) => { + print(data); + }); + cdkWrapper.stderr.on('data', (data) => { + print(data); + }); + + mergeRequirementFiles(process.cwd()); + removeDuplicatesFromFile('.samp-out/requirements.tmp'); + fs.cpSync(`${__dirname}/../../../runtime-support/python`, `.samp-out/`, { recursive: true }); + + const pythonProcess = exec(`pip install -r ${process.cwd()}/.samp-out/requirements.txt -t ${process.cwd()}/.samp-out`, {}); + console.log("Installing dependencies..."); + pythonProcess.stderr.on('data', (data) => print(data)); + //pythonProcess.stdout.on('data', (data) => print(data)); + pythonProcess.on('exit', (code) => { + initialised = true; + const childProcess = exec(`node ${__dirname}../../../../runner.js run`, {}); + childProcess.stdout.on('data', (data) => print(data)); + childProcess.stderr.on('data', (data) => print(data)); + if (!cmd.debug) { + const pythonProcess = exec(`${process.env.SAMP_PYTHON_EXECUTABLE || 'python'} entrypoint.py`, { cwd: `${process.cwd()}/.samp-out` }); + pythonProcess.stderr.on('data', (data) => print(data)); + pythonProcess.stdout.on('data', (data) => print(data)); + } else { + console.log("You can now select '[SAMP] Debug Lambda functions' and start debugging"); + } + }); + return initialised; + } catch (error) { + console.log(error); + } +} + +function print(data) { + if (!process.env.muteParentOutput) { + console.error("[SAMP] " + data.toString().replace(/\n$/, '')); + } +} + +function mergeRequirementFiles(dirPath) { + const outputFile = '.samp-out/requirements.tmp'; + + const mergeContents = (filePaths, outputFilePath) => { + const mergedContent = filePaths + .map(filePath => fs.readFileSync(filePath, 'utf-8')) + .join('\n'); + + fs.writeFileSync(outputFilePath, mergedContent); + }; + + const gatherFilePaths = (dir) => { + let filePaths = []; + + const files = fs.readdirSync(dir); + + files.forEach(file => { + const filePath = path.join(dir, file); + const fileStat = fs.statSync(filePath); + + if (fileStat.isDirectory()) { + filePaths = filePaths.concat(gatherFilePaths(filePath)); + } else if (file === 'requirements.txt') { + filePaths.push(filePath); + } + }); + + return filePaths; + }; + + const requirementFilePaths = gatherFilePaths(dirPath); + + if (requirementFilePaths.length > 0) { + mergeContents(requirementFilePaths, outputFile); + } else { + console.log('No requirements.txt files found.'); + } +} + +function removeDuplicatesFromFile(filePath) { + const outputFile = '.samp-out/requirements.txt'; + + const lines = fs.readFileSync(filePath, 'utf-8').split('\n'); + let uniqueLines = [...new Set(lines)]; // Use a Set to remove duplicates + uniqueLines = uniqueLines.filter(line => !line.startsWith('pytest')); + uniqueLines.push('watchdog'); + const uniqueContent = uniqueLines.join('\n'); + fs.writeFileSync(outputFile, uniqueContent); + fs.unlinkSync(filePath); +} + + +module.exports = { + setup +}; \ No newline at end of file diff --git a/src/commands/stepfunctions/index.js b/src/commands/stepfunctions/index.js index c9cfa2b..a4edcf3 100644 --- a/src/commands/stepfunctions/index.js +++ b/src/commands/stepfunctions/index.js @@ -15,9 +15,9 @@ program .action(async (cmd, opts) => { if (cmd === "init") { - cmd.logicalId = await inputUtil.text("Name of state machine resource", "StateMachine"); - cmd.aslFile = await inputUtil.text("Path to output ASL definition file", "statemachine.yaml"); - cmd.eventSource = await inputUtil.list("Event source for state machine", ["none", "eventbridge", "api", "schedule"]); + opts.logicalId = await inputUtil.text("Name of state machine resource", "StateMachine"); + opts.aslFile = await inputUtil.text("Path to output ASL definition file", "statemachine.yaml"); + opts.eventSource = await inputUtil.list("Event source for state machine", ["none", "eventbridge", "api", "schedule"]); await init.run(opts); } else if (cmd === "sync") { await sync.run(opts); diff --git a/src/shared/fileFinder.js b/src/shared/fileFinder.js new file mode 100644 index 0000000..539235c --- /dev/null +++ b/src/shared/fileFinder.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const path = require('path'); + +function findFilesWithExtension(rootDir, fileExtension, packagesDir = "node_modules") { + let files = []; + + function traverseDir(currentDir) { + const items = fs.readdirSync(currentDir); + + items.forEach((item) => { + const itemPath = path.join(currentDir, item); + const itemStats = fs.statSync(itemPath); + + if (itemStats.isDirectory()) { + traverseDir(itemPath); + } else if (path.extname(itemPath) === fileExtension && !itemPath.includes(packagesDir) && !itemPath.startsWith(".") && !itemPath.includes('.samp-out')) { + files.push(itemPath); + } + }); + } + + traverseDir(rootDir); + return files; +} + + +module.exports = { + findFilesWithExtension +}; \ No newline at end of file