From 1106ad0184d85b1b3b2bc157151aec9b9335cabb Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Fri, 14 Feb 2025 12:30:07 +0100 Subject: [PATCH 1/5] Add gradle, refactor other manifest commands --- src/cli.ts | 4 +- src/commands/manifest/auto.ts | 78 ------- src/commands/manifest/cmd-auto.ts | 109 ++++++++++ src/commands/manifest/cmd-gradle.ts | 161 +++++++++++++++ src/commands/manifest/cmd-kotlin.ts | 165 +++++++++++++++ src/commands/manifest/cmd-manifest.ts | 76 +++++++ src/commands/manifest/cmd-scala.ts | 157 ++++++++++++++ src/commands/manifest/gradle-to-maven.ts | 104 ++++++++++ src/commands/manifest/index.ts | 70 ------- src/commands/manifest/init.gradle | 250 +++++++++++++++++++++++ src/commands/manifest/sbt-to-maven.ts | 102 +++++++++ src/commands/manifest/scala.ts | 249 ---------------------- 12 files changed, 1126 insertions(+), 399 deletions(-) delete mode 100644 src/commands/manifest/auto.ts create mode 100644 src/commands/manifest/cmd-auto.ts create mode 100644 src/commands/manifest/cmd-gradle.ts create mode 100644 src/commands/manifest/cmd-kotlin.ts create mode 100644 src/commands/manifest/cmd-manifest.ts create mode 100644 src/commands/manifest/cmd-scala.ts create mode 100644 src/commands/manifest/gradle-to-maven.ts delete mode 100644 src/commands/manifest/index.ts create mode 100644 src/commands/manifest/init.gradle create mode 100644 src/commands/manifest/sbt-to-maven.ts delete mode 100644 src/commands/manifest/scala.ts diff --git a/src/cli.ts b/src/cli.ts index 8058a2777..18b857f0e 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -17,7 +17,7 @@ import { fixCommand } from './commands/fix' import { infoCommand } from './commands/info' import { loginCommand } from './commands/login' import { logoutCommand } from './commands/logout' -import { manifestCommand } from './commands/manifest' +import { cmdManifest } from './commands/manifest/cmd-manifest.ts' import { npmCommand } from './commands/npm' import { npxCommand } from './commands/npx' import { optimizeCommand } from './commands/optimize' @@ -68,7 +68,7 @@ void (async () => { analytics: analyticsCommand, 'diff-scan': diffScanCommand, 'threat-feed': threatFeedCommand, - manifest: manifestCommand + manifest: cmdManifest }, { aliases: { diff --git a/src/commands/manifest/auto.ts b/src/commands/manifest/auto.ts deleted file mode 100644 index 7793adf51..000000000 --- a/src/commands/manifest/auto.ts +++ /dev/null @@ -1,78 +0,0 @@ -import fs from 'node:fs' - -import meow from 'meow' - -import { scala } from './scala.ts' - -import type { CliSubcommand } from '../../utils/meow-with-subcommands' - -const description = 'Auto-detect build and attempt to generate manifest file' - -const help = (name: string) => ` - Usage - $ ${name} - - Tries to figure out what language your current repo uses. If it finds a - supported case then it will try to generate the manifest file for that - language with the default or detected settings. - - This command takes no arguments except --verbose. -` - -export const auto: CliSubcommand = { - description, - async run(argv, importMeta, { parentName }) { - // Allow `--verbose` to pass through - let verbose = false - const args = argv.filter(arg => { - if (arg === '--verbose') { - verbose = true - return false - } - return true - }) - - const name = `${parentName} auto` - if (args.length) { - // note: meow will exit if it prints the --help screen - meow(help(name), { - argv: ['--help'], - description, - importMeta - }) - } - - const subArgs = [] - if (verbose) subArgs.push('--verbose', '1') - const scalaDir = '.' - if (fs.existsSync(scalaDir)) { - console.log( - 'Detected a Scala sbt build, running default Scala generator...' - ) - subArgs.push(scalaDir) - await scala.run(subArgs, importMeta, { parentName }) - return - } - - // Show new help screen and exit - meow( - ` - $ ${name} - - Unfortunately this script did not discover a supported language in the - current folder. - - - Make sure this script would work with your target build - - Make sure to run it from the correct folder - - Make sure the necessary build tools are available (\`PATH\`) - - If that doesn't work, see \`${name} --help\` for config details - `, - { - argv: ['--help'], - description, - importMeta - } - ) - } -} diff --git a/src/commands/manifest/cmd-auto.ts b/src/commands/manifest/cmd-auto.ts new file mode 100644 index 000000000..547bad319 --- /dev/null +++ b/src/commands/manifest/cmd-auto.ts @@ -0,0 +1,109 @@ +import fs from 'node:fs' +import path from 'node:path' + +import meow from 'meow' + +import { cmdManifestGradle } from './cmd-gradle.ts' +import { cmdManifestScala } from './cmd-scala.ts' +import { commonFlags } from '../../flags.ts' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +const config: CliCommandConfig = { + commandName: 'auto', + description: 'Auto-detect build and attempt to generate manifest file', + hidden: false, + flags: { + ...commonFlags, + verbose: { + type: 'boolean', + default: false, + description: 'Enable debug output, may help when running into errors' + } + // TODO: support output flags + }, + help: (parentName, config) => ` + Usage + $ ${parentName} ${config.commandName} + + Tries to figure out what language your current repo uses. If it finds a + supported case then it will try to generate the manifest file for that + language with the default or detected settings. + + This command takes no arguments except --verbose. + ` +} + +export const cmdManifestAuto = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + // Allow `--verbose` to pass through + let verbose = false + const args = argv.filter(arg => { + if (arg === '--verbose') { + verbose = true + return false + } + return true + }) + + if (args.length) { + meow(config.help(parentName, config), { + argv: ['--help'], + description: config.description, + importMeta, + flags: config.flags + }) + return + } + + const subArgs = [] + if (verbose) subArgs.push('--verbose', '1') + + const dir = '.' + + if (fs.existsSync(path.join(dir, 'build.sbt'))) { + console.log( + 'Detected a Scala sbt build, running default Scala generator...' + ) + subArgs.push(dir) + await cmdManifestScala.run(subArgs, importMeta, { parentName }) + return + } + + if (fs.existsSync(path.join(dir, 'gradlew'))) { + console.log('Detected a gradle build, running default gradle generator...') + await cmdManifestGradle.run(subArgs, importMeta, { parentName }) + return + } + + // Show new help screen and exit + meow( + ` + $ ${parentName} ${config.commandName} + + Unfortunately this script did not discover a supported language in the + current folder. + + - Make sure this script would work with your target build + - Make sure to run it from the correct folder + - Make sure the necessary build tools are available (\`PATH\`) + + If that doesn't work, see \`${parentName} --help\` for config details for + your target language. + `, + { + argv: [], + description: config.description, + importMeta + } + ).showHelp() +} diff --git a/src/commands/manifest/cmd-gradle.ts b/src/commands/manifest/cmd-gradle.ts new file mode 100644 index 000000000..6141f6548 --- /dev/null +++ b/src/commands/manifest/cmd-gradle.ts @@ -0,0 +1,161 @@ +import meow from 'meow' + +import { Spinner } from '@socketsecurity/registry/lib/spinner' + +import { gradleToMaven } from './gradle-to-maven.ts' +import { commonFlags } from '../../flags.ts' +import { getFlagListOutput } from '../../utils/output-formatting.ts' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +const config: CliCommandConfig = { + commandName: 'gradle', + description: + 'Use Gradle to generate a manifest file (`pom.xml`) for a Gradle/Java/Kotlin/etc project', + hidden: false, + flags: { + ...commonFlags, + bin: { + type: 'string', + default: 'DIR/gradlew', + description: 'Location of gradlew binary to use' + }, + gradleOpts: { + type: 'string', + default: '', + description: + 'Additional options to pass on to ./gradlew, see `./gradlew --help`' + }, + out: { + type: 'string', + default: './socket.pom.xml', + description: + 'Path of output file; where to store the resulting manifest, see also --stdout' + }, + stdout: { + type: 'boolean', + description: 'Print resulting pom.xml to stdout (supersedes --out)' + }, + task: { + type: 'string', + default: 'all', + description: 'Task to target. By default targets all.' + }, + verbose: { + type: 'boolean', + description: 'Print debug messages' + } + }, + help: (parentName, config) => ` + Usage + $ ${parentName} ${config.commandName} [--gradle=path/to/gradle/binary] [--out=path/to/result] DIR + + Options + ${getFlagListOutput(config.flags, 6)} + + Uses gradle, preferably through your local project \`gradlew\`, to generate a + \`pom.xml\` file for each task. If you have no \`gradlew\` you can try the + global \`gradle\` binary but that may not work (hard to predict). + + The \`pom.xml\` is a manifest file similar to \`package.json\` for npm or + or requirements.txt for PyPi), but specifically for Maven, which is Java's + dependency repository. Languages like Kotlin and Scala piggy back on it too. + + There are some caveats with the gradle to \`pom.xml\` conversion: + + - each task will generate its own xml file and by default it generates one xml + for every task. + + - it's possible certain features don't translate well into the xml. If you + think something is missing that could be supported please reach out. + + - it works with your \`gradlew\` from your repo and local settings and config + + Examples + + $ ${parentName} ${config.commandName} . + $ ${parentName} ${config.commandName} --gradlew=../gradlew . + ` +} + +export const cmdManifestGradle = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + const name = `${parentName} gradle` + // note: meow will exit if it prints the --help screen + const cli = meow(config.help(parentName, config), { + flags: config.flags, + argv: argv.length === 0 ? ['--help'] : argv, + description: config.description, + allowUnknownFlags: false, + importMeta + }) + + if (cli.flags['verbose']) { + console.log('[VERBOSE] cli.flags:', cli.flags, ', cli.input:', cli.input) + } + + const target = cli.input[0] + if (!target) { + // will exit. + new Spinner() + .start('Parsing...') + .error( + `Failure: Missing DIR argument. See \`${name} --help\` for details.` + ) + process.exit(1) + } + + if (cli.input.length > 1) { + // will exit. + new Spinner() + .start('Parsing...') + .error( + `Failure: Can only accept one FILE or DIR, received ${cli.input.length} (make sure to escape spaces!). See \`${name} --help\` for details.` + ) + process.exit(1) + } + + let bin: string = './gradlew' + if (cli.flags['bin']) { + bin = cli.flags['bin'] as string + } + + let out: string = './socket.pom.xml' + if (cli.flags['out']) { + out = cli.flags['out'] as string + } + if (cli.flags['stdout']) { + out = '-' + } + + // TODO: I'm not sure it's feasible to parse source file from stdin. We could try, store contents in a file in some folder, target that folder... what would the file name be? + if (target === '-') { + new Spinner() + .start('Parsing...') + .error( + `Failure: Currently source code from stdin is not supported. See \`${name} --help\` for details.` + ) + process.exit(1) + } + + const verbose = (cli.flags['verbose'] as boolean) ?? false + + let gradleOpts: Array = [] + if (cli.flags['gradleOpts']) { + gradleOpts = (cli.flags['gradleOpts'] as string) + .split(' ') + .map(s => s.trim()) + .filter(Boolean) + } + + await gradleToMaven(target, bin, out, verbose, gradleOpts) +} diff --git a/src/commands/manifest/cmd-kotlin.ts b/src/commands/manifest/cmd-kotlin.ts new file mode 100644 index 000000000..ae16efd79 --- /dev/null +++ b/src/commands/manifest/cmd-kotlin.ts @@ -0,0 +1,165 @@ +import meow from 'meow' + +import { Spinner } from '@socketsecurity/registry/lib/spinner' + +import { gradleToMaven } from './gradle-to-maven.ts' +import { commonFlags } from '../../flags.ts' +import { getFlagListOutput } from '../../utils/output-formatting.ts' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +// TODO: we may want to dedupe some pieces for all gradle languages. I think it +// makes sense to have separate commands for them and I think it makes +// sense for the help panels to note the requested language, rather than +// `socket manifest kotlin` to print help screens with `gradle` as the +// command. Room for improvement. +const config: CliCommandConfig = { + commandName: 'kotlin', + description: + 'Use Gradle to generate a manifest file (`pom.xml`) for a Kotlin project', + hidden: false, + flags: { + ...commonFlags, + bin: { + type: 'string', + default: 'DIR/gradlew', + description: 'Location of gradlew binary to use' + }, + gradleOpts: { + type: 'string', + default: '', + description: + 'Additional options to pass on to ./gradlew, see `./gradlew --help`' + }, + out: { + type: 'string', + default: './socket.pom.xml', + description: + 'Path of output file; where to store the resulting manifest, see also --stdout' + }, + stdout: { + type: 'boolean', + description: 'Print resulting pom.xml to stdout (supersedes --out)' + }, + task: { + type: 'string', + default: 'all', + description: 'Task to target. By default targets all.' + }, + verbose: { + type: 'boolean', + description: 'Print debug messages' + } + }, + help: (parentName, config) => ` + Usage + $ ${parentName} ${config.commandName} [--gradle=path/to/gradle/binary] [--out=path/to/result] DIR + + Options + ${getFlagListOutput(config.flags, 6)} + + Uses gradle, preferably through your local project \`gradlew\`, to generate a + \`pom.xml\` file for each task. If you have no \`gradlew\` you can try the + global \`gradle\` binary but that may not work (hard to predict). + + The \`pom.xml\` is a manifest file similar to \`package.json\` for npm or + or requirements.txt for PyPi), but specifically for Maven, which is Java's + dependency repository. Languages like Kotlin and Scala piggy back on it too. + + There are some caveats with the gradle to \`pom.xml\` conversion: + + - each task will generate its own xml file and by default it generates one xml + for every task. (This may be a good thing!) + + - it's possible certain features don't translate well into the xml. If you + think something is missing that could be supported please reach out. + + - it works with your \`gradlew\` from your repo and local settings and config + + Examples + + $ ${parentName} ${config.commandName} . + $ ${parentName} ${config.commandName} --gradlew=../gradlew . + ` +} + +export const cmdManifestKotlin = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + // note: meow will exit if it prints the --help screen + const cli = meow(config.help(parentName, config), { + flags: config.flags, + argv: argv.length === 0 ? ['--help'] : argv, + description: config.description, + allowUnknownFlags: false, + importMeta + }) + + if (cli.flags['verbose']) { + console.log('[VERBOSE] cli.flags:', cli.flags, ', cli.input:', cli.input) + } + + const target = cli.input[0] + if (!target) { + // will exit. + new Spinner() + .start('Parsing...') + .error( + `Failure: Missing DIR argument. See \`${parentName} ${config.commandName} --help\` for details.` + ) + process.exit(1) + } + + if (cli.input.length > 1) { + // will exit. + new Spinner() + .start('Parsing...') + .error( + `Failure: Can only accept one FILE or DIR, received ${cli.input.length} (make sure to escape spaces!). See \`${parentName} ${config.commandName} --help\` for details.` + ) + process.exit(1) + } + + let bin: string = './gradlew' + if (cli.flags['bin']) { + bin = cli.flags['bin'] as string + } + + let out: string = './socket.pom.xml' + if (cli.flags['out']) { + out = cli.flags['out'] as string + } + if (cli.flags['stdout']) { + out = '-' + } + + // TODO: I'm not sure it's feasible to parse source file from stdin. We could try, store contents in a file in some folder, target that folder... what would the file name be? + if (target === '-') { + new Spinner() + .start('Parsing...') + .error( + `Failure: Currently source code from stdin is not supported. See \`${parentName} ${config.commandName} --help\` for details.` + ) + process.exit(1) + } + + const verbose = (cli.flags['verbose'] as boolean) ?? false + + let gradleOpts: Array = [] + if (cli.flags['gradleOpts']) { + gradleOpts = (cli.flags['gradleOpts'] as string) + .split(' ') + .map(s => s.trim()) + .filter(Boolean) + } + + await gradleToMaven(target, bin, out, verbose, gradleOpts) +} diff --git a/src/commands/manifest/cmd-manifest.ts b/src/commands/manifest/cmd-manifest.ts new file mode 100644 index 000000000..8d4c3b33f --- /dev/null +++ b/src/commands/manifest/cmd-manifest.ts @@ -0,0 +1,76 @@ +import { cmdManifestAuto } from './cmd-auto.ts' +import { cmdManifestGradle } from './cmd-gradle.ts' +import { cmdManifestKotlin } from './cmd-kotlin.ts' +import { cmdManifestScala } from './cmd-scala.ts' +import { commonFlags } from '../../flags.ts' +import { + type CliCommandConfig, + meowWithSubcommands +} from '../../utils/meow-with-subcommands' + +const config: CliCommandConfig = { + commandName: 'manifest', + description: 'Generate a dependency manifest for given file or dir', + hidden: false, + flags: { + ...commonFlags + }, + help: (parentName, config) => ` + Usage + + $ ${parentName} ${config.commandName} + + Generates a declarative dependency manifest (like a package.json for Node.JS + or requirements.txt for PyPi), but for certain supported ecosystems + where it's common to use a dynamic manifest, like Scala's sbt. + + Only certain languages are supported and there may be language specific + configurations available. See \`manifest --help\` for usage details + per language. + + Currently supported language: scala, gradle + + Examples + + $ ${parentName} ${config.commandName} scala . + + To have it auto-detect and attempt to run: + + $ ${parentName} ${config.commandName} yolo + ` +} + +export const cmdManifest = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + await meowWithSubcommands( + { + auto: cmdManifestAuto, + scala: cmdManifestScala, + gradle: cmdManifestGradle, + kotlin: cmdManifestKotlin + }, + { + argv, + aliases: { + yolo: { + description: config.description, + hidden: true, + argv: ['auto'] + } + }, + description: config.description, + importMeta, + flags: config.flags, + name: `${parentName} ${config.commandName}` + } + ) +} diff --git a/src/commands/manifest/cmd-scala.ts b/src/commands/manifest/cmd-scala.ts new file mode 100644 index 000000000..772e1dc01 --- /dev/null +++ b/src/commands/manifest/cmd-scala.ts @@ -0,0 +1,157 @@ +import meow from 'meow' + +import { Spinner } from '@socketsecurity/registry/lib/spinner' + +import { sbtToMaven } from './sbt-to-maven.ts' +import { commonFlags } from '../../flags.ts' +import { getFlagListOutput } from '../../utils/output-formatting.ts' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +const config: CliCommandConfig = { + commandName: 'kotlin', + description: + "Generate a manifest file (`pom.xml`) from Scala's `build.sbt` file", + hidden: false, + flags: { + ...commonFlags, + bin: { + type: 'string', + default: 'sbt', + description: 'Location of sbt binary to use' + }, + out: { + type: 'string', + default: './socket.pom.xml', + description: + 'Path of output file; where to store the resulting manifest, see also --stdout' + }, + stdout: { + type: 'boolean', + description: 'Print resulting pom.xml to stdout (supersedes --out)' + }, + sbtOpts: { + type: 'string', + default: '', + description: 'Additional options to pass on to sbt, as per `sbt --help`' + }, + verbose: { + type: 'boolean', + description: 'Print debug messages' + } + }, + help: (parentName, config) => ` + Usage + $ ${parentName} ${config.commandName} [--sbt=path/to/sbt/binary] [--out=path/to/result] FILE|DIR + + Options + ${getFlagListOutput(config.flags, 6)} + + Uses \`sbt makePom\` to generate a \`pom.xml\` from your \`build.sbt\` file. + This xml file is the dependency manifest (like a package.json + for Node.js or requirements.txt for PyPi), but specifically for Scala. + + There are some caveats with \`build.sbt\` to \`pom.xml\` conversion: + + - the xml is exported as socket.pom.xml as to not confuse existing build tools + but it will first hit your /target/sbt folder (as a different name) + + - the pom.xml format (standard by Scala) does not support certain sbt features + - \`excludeAll()\`, \`dependencyOverrides\`, \`force()\`, \`relativePath\` + - For details: https://www.scala-sbt.org/1.x/docs/Library-Management.html + + - it uses your sbt settings and local configuration verbatim + + - it can only export one target per run, so if you have multiple targets like + development and production, you must run them separately. + + You can optionally configure the path to the \`sbt\` bin to invoke. + + Examples + + $ ${parentName} ${config.commandName} ./build.sbt + $ ${parentName} ${config.commandName} --bin=/usr/bin/sbt ./build.sbt + ` +} + +export const cmdManifestScala = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + // console.log('scala', argv, parentName) + // note: meow will exit if it prints the --help screen + const cli = meow(config.help(parentName, config), { + flags: config.flags, + argv: argv.length === 0 ? ['--help'] : argv, + description: config.description, + allowUnknownFlags: false, + importMeta + }) + + if (cli.flags['verbose']) { + console.log('[VERBOSE] cli.flags:', cli.flags, ', cli.input:', cli.input) + } + + const target = cli.input[0] + if (!target) { + // will exit. + new Spinner() + .start('Parsing...') + .error( + `Failure: Missing FILE|DIR argument. See \`${parentName} ${config.commandName} --help\` for details.` + ) + process.exit(1) + } + + if (cli.input.length > 1) { + // will exit. + new Spinner() + .start('Parsing...') + .error( + `Failure: Can only accept one FILE or DIR, received ${cli.input.length} (make sure to escape spaces!). See \`${parentName} ${config.commandName} --help\` for details.` + ) + process.exit(1) + } + + let bin: string = 'sbt' + if (cli.flags['bin']) { + bin = cli.flags['bin'] as string + } + + let out: string = './socket.pom.xml' + if (cli.flags['out']) { + out = cli.flags['out'] as string + } + if (cli.flags['stdout']) { + out = '-' + } + + // TODO: we can make `-` (accept from stdin) work by storing it into /tmp + if (target === '-') { + new Spinner() + .start('Parsing...') + .error( + `Failure: Currently source code from stdin is not supported. See \`${parentName} ${config.commandName} --help\` for details.` + ) + process.exit(1) + } + + const verbose = (cli.flags['verbose'] as boolean) ?? false + + let sbtOpts: Array = [] + if (cli.flags['sbtOpts']) { + sbtOpts = (cli.flags['sbtOpts'] as string) + .split(' ') + .map(s => s.trim()) + .filter(Boolean) + } + + await sbtToMaven(target, bin, out, verbose, sbtOpts) +} diff --git a/src/commands/manifest/gradle-to-maven.ts b/src/commands/manifest/gradle-to-maven.ts new file mode 100644 index 000000000..f61244e0d --- /dev/null +++ b/src/commands/manifest/gradle-to-maven.ts @@ -0,0 +1,104 @@ +import fs from 'node:fs' +import path from 'node:path' +import util from 'node:util' + +import spawn from '@npmcli/promise-spawn' + +import { Spinner } from '@socketsecurity/registry/lib/spinner' + +import { safeReadFile } from '../../utils/fs.ts' + +// TODO: find better consolidation around fs abstraction (like fs-extra) throughout cli +const renamep = util.promisify(fs.rename) + +export async function gradleToMaven( + target: string, + bin: string, + out: string, + verbose: boolean, + gradleOpts: Array +) { + const rbin = path.resolve(bin) + const rtarget = path.resolve(target) + const rout = out === '-' ? '-' : path.resolve(out) + + if (verbose) { + console.log(`[VERBOSE] - Absolute bin path: \`${rbin}\``) + console.log(`[VERBOSE] - Absolute target path: \`${rtarget}\``) + console.log(`[VERBOSE] - Absolute out path: \`${rout}\``) + } else { + console.log(`- executing: \`${bin}\``) + console.log(`- src dir: \`${target}\``) + console.log(`- dst dir: \`${out}\``) + } + + const spinner = new Spinner() + + spinner.start(`Running gradle from \`${bin}\` on \`${target}\`...`) + + try { + // Run gradlew with the init script we provide which should yield zero or more pom files. + // We have to figure out where to store those pom files such that we can upload them and predict them through the GitHub API. + // We could do a .socket folder. We could do a socket.pom.gz with all the poms, although I'd prefer something plain-text if it is to be committed. + + const output = await spawn(bin, ['makePom'].concat(gradleOpts), { + cwd: target || '.' + }) + spinner.success() + if (verbose) { + console.group('[VERBOSE] sbt stdout:') + console.log(output) + console.groupEnd() + } + + if (output.stderr) { + spinner.error('There were errors while running sbt') + // (In verbose mode, stderr was printed above, no need to repeat it) + if (!verbose) { + console.group('[VERBOSE] stderr:') + console.error(output.stderr) + console.groupEnd() + } + process.exit(1) + } + + const loc = output.stdout?.match(/Wrote (.*?.pom)\n/)?.[1]?.trim() + if (!loc) { + spinner.error( + 'There were no errors from sbt but could not find the location of resulting .pom file either' + ) + process.exit(1) + } + + // Move the pom file to ...? initial cwd? loc will be an absolute path, or dump to stdout + if (out === '-') { + spinner.start('Result:\n```').success() + console.log(await safeReadFile(loc, 'utf8')) + console.log('```') + spinner.start().success(`OK`) + } else { + if (verbose) { + spinner.start( + `Moving manifest file from \`${loc.replace(/^\/home\/[^/]*?\//, '~/')}\` to \`${out}\`` + ) + } else { + spinner.start('Moving output pom file') + } + // TODO: do we prefer fs-extra? renaming can be gnarly on windows and fs-extra's version is better + await renamep(loc, out) + spinner.success() + spinner.start().success(`OK. File should be available in \`${out}\``) + } + } catch (e) { + spinner.error( + 'There was an unexpected error while running this' + + (verbose ? '' : ' (use --verbose for details)') + ) + if (verbose) { + console.group('[VERBOSE] error:') + console.log(e) + console.groupEnd() + } + process.exit(1) + } +} diff --git a/src/commands/manifest/index.ts b/src/commands/manifest/index.ts deleted file mode 100644 index cbc178069..000000000 --- a/src/commands/manifest/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import meow from 'meow' - -import { auto } from './auto.ts' -import { scala } from './scala' -import { meowWithSubcommands } from '../../utils/meow-with-subcommands' - -import type { CliSubcommand } from '../../utils/meow-with-subcommands' - -const description = 'Generate a dependency manifest for given file or dir' -const help = (name: string) => ` - Usage - - $ ${name} - - Generates a declarative dependency manifest (like a package.json for Node.JS - or requirements.txt for PyPi), but for certain supported ecosystems - where it's common to use a dynamic manifest, like Scala's sbt. - - Only certain languages are supported and there may be language specific - configurations available. See \`manifest --help\` for usage details - per language. - - Currently supported language: scala - - Examples - - $ ${name} scala . - - To have it auto-detect and attempt to run: - - $ ${name} yolo -` - -export const manifestCommand: CliSubcommand = { - description, - hidden: true, - async run(argv, importMeta, { parentName }) { - const name = `${parentName} manifest` - - // Note: this won't catch `socket manifest -xyz --help` sort of cases which - // would fallback to the default meow help behavior. That's fine. - if (argv.length === 0 || argv[0] === '--help') { - meow(help(name), { - argv: ['--help'] as const, // meow will exit() when --help is passed - description, - importMeta - }) - } - - await meowWithSubcommands( - { - scala, - auto - }, - { - argv, - aliases: { - yolo: { - description: auto.description, - hidden: true, - argv: ['auto'] - } - }, - description, - importMeta, - name - } - ) - } -} diff --git a/src/commands/manifest/init.gradle b/src/commands/manifest/init.gradle new file mode 100644 index 000000000..3ccb37d38 --- /dev/null +++ b/src/commands/manifest/init.gradle @@ -0,0 +1,250 @@ +// This is a Gradle initialization script that generates Maven POM files for projects +// A POM file describes a project's dependencies and other metadata in XML format + +// This script: +// - Generates Maven POM files for Java/Kotlin/Android projects +// - Handles different types of dependencies (direct, project, version catalog) +// - Supports different project types (Java, Android, root project) +// - Can be invoked with `./gradlew --init-script /path/to/this/script pom` to generate POM files +// - Copies the generated POM to a target location (default: pom.xml) + +initscript { + repositories { + // We need these repositories for Gradle's plugin resolution system + // TODO: it's not clear if we actually need them. + gradlePluginPortal() + mavenCentral() + google() + } + + dependencies { + // No external dependencies needed as we only use Gradle's built-in maven-publish plugin + } +} + +// Apply these configurations to all projects in the build +cmdGradle.allprojects { project -> + // Create a unique name for the Maven publication + // Example: project ':foo:bar' becomes 'maven-foo-bar' + def publicationName = "maven-${project.path.replace(':', '-')}" + if (publicationName.startsWith('maven--')) { + publicationName = 'maven-root' // Special case for root project + } + + // Apply the Maven Publish plugin if not already applied + if (!project.plugins.hasPlugin('maven-publish')) { + project.plugins.apply('maven-publish') + } + + // Register a new task called 'pom' that will generate the POM file. + // This is what allows us to do `gradlew pom`. We could rename it to + // something like socket-generate-pom instead. It should be invisible + // to the user because this script is not part of their repo. + project.tasks.register('pom') { + group = 'publishing' // Group tasks are shown together in ./gradlew tasks (irrelevant) + description = 'Generates a POM file' + // Force task to run every time. Otherwise caching would cause + // subsequent runs without changes to do anything. + // There may be room for improvement; I think this may cause + // everything to run which is theorietically not necessary. + outputs.upToDateWhen { false } + + // Define where POM files will be generated and copied + def defaultPomFile = project.file("build/publications/${publicationName}/pom-default.xml") + def targetPomFile = project.hasProperty('pomPath') ? + project.file(project.property('pomPath')) : // Custom location if specified. You can use `./gradlew pom -PpomPath=path/to/pom.xml` to specify a custom location. + project.file('pom.xml') // Default location + + // Declare task inputs and outputs for Gradle's incremental build system + inputs.file(defaultPomFile) + outputs.file(targetPomFile) + + // The actual work of copying the POM file happens here + doLast { + if (defaultPomFile.exists()) { + // Print the generated POM for inspection + println "\nGenerated POM file for ${publicationName}:" + println "==================================" + println defaultPomFile.text + println "==================================" + + // Copy the POM file to its target location + targetPomFile.parentFile.mkdirs() + targetPomFile.text = defaultPomFile.text + println "\nPOM file copied to: ${targetPomFile.absolutePath}" + } else { + println "No POM file generated at ${defaultPomFile.absolutePath}" + } + } + } + + // Wait for project evaluation to complete before configuring publication + project.afterEvaluate { p -> + p.plugins.withId('maven-publish') { + // Gather project information + def projectPath = p.path + def projectName = p.name + def projectDesc = p.description ?: p.name + def isRootProject = p.path == ':' && !p.subprojects.isEmpty() + def isAndroidProject = p.plugins?.hasPlugin('com.android.library') || + p.plugins?.hasPlugin('com.android.application') + def hasJavaComponent = p.extensions?.findByName('components')?.findByName('java') != null + + // Store all dependencies we find here + def projectDependencies = [] + + // Find all relevant dependency configurations + // We care about implementation, api, compile, and runtime configurations + // TODO: anything we're missing here? tests maybe? + def relevantConfigs = p.configurations.findAll { config -> + !config.name.toLowerCase().contains('test') && + (config.name.endsWith('Implementation') || + config.name.endsWith('Api') || + config.name == 'implementation' || + config.name == 'api' || + config.name == 'compile' || + config.name == 'runtime') + } + + // Process each configuration to find dependencies + relevantConfigs.each { config -> + config.dependencies.each { dep -> + if (dep instanceof ProjectDependency) { + // Handle project dependencies (e.g., implementation(project(":other-module"))) + def depProjectPath = dep.dependencyProject.path + def depProjectName = depProjectPath.substring(depProjectPath.lastIndexOf(':') + 1) + projectDependencies << [ + group: p.group ?: p.rootProject.name, + name: depProjectName, + version: p.version ?: 'unspecified', + scope: config.name.contains('api') ? 'compile' : 'runtime' + ] + } else { + // Handle all other types of dependencies + try { + def group = dep.group + def name = dep.name + def version = dep.version + + // Handle version catalog dependencies (e.g., implementation(libs.some.library)) + if (!group && p.findProperty('libs')) { + def depString = dep.toString() + + // Skip bundles and file dependencies as they need special handling + if (!depString.contains('Bundle') && !dep.toString().contains('DefaultFileCollectionDependency')) { + try { + // Extract library name from version catalog reference + def libName = depString.contains('libs.') ? + depString.substring(depString.indexOf('libs.') + 5) : + depString + def libProvider = p.libs.findLibrary(libName) + if (libProvider.present) { + def dependency = libProvider.get() + projectDependencies << [ + group: dependency.get().module.group, + name: dependency.get().module.name, + version: dependency.versionConstraint.requiredVersion, + scope: config.name.contains('api') ? 'compile' : 'runtime' + ] + } + } catch (Exception e) { + println " - Skipping non-catalog dependency: ${dep}" + } + } + } else if (group && name) { + // Handle regular dependencies (e.g., implementation("group:name:version")) + projectDependencies << [ + group: group, + name: name, + version: version ?: 'unspecified', + scope: config.name.contains('api') ? 'compile' : 'runtime' + ] + } + } catch (Exception e) { + println " - Failed to process dependency: ${e.message}" + } + } + } + } + + // Configure the Maven publication + p.publishing { + publications { + if (!publications.findByName(publicationName)) { + create(publicationName, MavenPublication) { + // Handle different project types + if (isAndroidProject) { + // For Android libraries, we need to wait for the Android plugin to set up + afterEvaluate { + def android = p.extensions.findByName('android') + if (android) { + // Try to get the release variant component + def components = p.components + def componentNames = components.names + + // Look for specific variant components + // Prefer release over debug + if (components.findByName("release")) { + from components.release + } else if (components.findByName("debug")) { + from components.debug + } else { + println "Warning: No release or debug component found for Android project ${p.name}" + // Skip the component for now, will still generate POM + } + } else { + println "Warning: Android extension not found for project ${p.name}" + } + } + } else if (!isRootProject && hasJavaComponent) { + // For Java libraries, use the java component + from components.java + } + // Root project doesn't need a 'from' clause as it's just a POM + + // Configure the POM file content + pom { + // Set packaging type based on project type (why is this necessary?) + packaging = isRootProject ? 'pom' : (isAndroidProject ? 'aar' : 'jar') + name = projectName + description = projectDesc + + // Customize the POM XML + withXml { xml -> + def root = xml.asNode() + def dependencies = root.appendNode('dependencies') + + // Add all collected dependencies to the POM + projectDependencies.each { dep -> + def dependency = dependencies.appendNode('dependency') + // Ensure all values are strings + dependency.appendNode('groupId', String.valueOf(dep.group)) + dependency.appendNode('artifactId', String.valueOf(dep.name)) + dependency.appendNode('version', String.valueOf(dep.version ?: 'unspecified')) + dependency.appendNode('scope', String.valueOf(dep.scope)) + } + + // Add standard properties for root project + if (isRootProject) { + def properties = root.appendNode('properties') + properties.appendNode('kotlin.version', String.valueOf('1.9.0')) + properties.appendNode('java.version', String.valueOf('11')) + properties.appendNode('project.build.sourceEncoding', String.valueOf('UTF-8')) + } + } + } + } + } + } + } + + // Make our pom task depend on the actual POM generation task + project.tasks.named('pom') { + def pomTask = "generatePomFileFor${publicationName.capitalize()}Publication" + if (project.tasks?.findByName(pomTask)) { + dependsOn(pomTask) + } + } + } + } +} diff --git a/src/commands/manifest/sbt-to-maven.ts b/src/commands/manifest/sbt-to-maven.ts new file mode 100644 index 000000000..46da832a1 --- /dev/null +++ b/src/commands/manifest/sbt-to-maven.ts @@ -0,0 +1,102 @@ +import fs from 'node:fs' +import path from 'node:path' +import util from 'node:util' + +import spawn from '@npmcli/promise-spawn' + +import { Spinner } from '@socketsecurity/registry/lib/spinner' + +import { safeReadFile } from '../../utils/fs.ts' + +const renamep = util.promisify(fs.rename) + +export async function sbtToMaven( + target: string, + bin: string, + out: string, + verbose: boolean, + sbtOpts: Array +) { + const rbin = path.resolve(bin) + const rtarget = path.resolve(target) + const rout = out === '-' ? '-' : path.resolve(out) + + if (verbose) { + console.log(`[VERBOSE] - Absolute bin path: \`${rbin}\``) + console.log(`[VERBOSE] - Absolute target path: \`${rtarget}\``) + console.log(`[VERBOSE] - Absolute out path: \`${rout}\``) + } else { + console.log(`- executing: \`${bin}\``) + console.log(`- src dir: \`${target}\``) + console.log(`- dst dir: \`${out}\``) + } + + const spinner = new Spinner() + + spinner.start(`Running sbt from \`${bin}\` on \`${target}\`...`) + + try { + // We must now run sbt, pick the generated xml from the /target folder (the stdout should tell you the location upon success) and store it somewhere else. + // TODO: Not sure what this somewhere else might be tbh. + + const output = await spawn(bin, ['makePom'].concat(sbtOpts), { + cwd: target || '.' + }) + spinner.success() + if (verbose) { + console.group('[VERBOSE] sbt stdout:') + console.log(output) + console.groupEnd() + } + + if (output.stderr) { + spinner.error('There were errors while running sbt') + // (In verbose mode, stderr was printed above, no need to repeat it) + if (!verbose) { + console.group('[VERBOSE] stderr:') + console.error(output.stderr) + console.groupEnd() + } + process.exit(1) + } + + const loc = output.stdout?.match(/Wrote (.*?.pom)\n/)?.[1]?.trim() + if (!loc) { + spinner.error( + 'There were no errors from sbt but could not find the location of resulting .pom file either' + ) + process.exit(1) + } + + // Move the pom file to ...? initial cwd? loc will be an absolute path, or dump to stdout + if (out === '-') { + spinner.start('Result:\n```').success() + console.log(await safeReadFile(loc, 'utf8')) + console.log('```') + spinner.start().success(`OK`) + } else { + if (verbose) { + spinner.start( + `Moving manifest file from \`${loc.replace(/^\/home\/[^/]*?\//, '~/')}\` to \`${out}\`` + ) + } else { + spinner.start('Moving output pom file') + } + // TODO: do we prefer fs-extra? renaming can be gnarly on windows and fs-extra's version is better + await renamep(loc, out) + spinner.success() + spinner.start().success(`OK. File should be available in \`${out}\``) + } + } catch (e) { + spinner.error( + 'There was an unexpected error while running this' + + (verbose ? '' : ' (use --verbose for details)') + ) + if (verbose) { + console.group('[VERBOSE] error:') + console.log(e) + console.groupEnd() + } + process.exit(1) + } +} diff --git a/src/commands/manifest/scala.ts b/src/commands/manifest/scala.ts deleted file mode 100644 index 52b283017..000000000 --- a/src/commands/manifest/scala.ts +++ /dev/null @@ -1,249 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' -import util from 'node:util' - -import spawn from '@npmcli/promise-spawn' -import meow from 'meow' - -import { Spinner } from '@socketsecurity/registry/lib/spinner' - -import { safeReadFile } from '../../utils/fs' -import { getFlagListOutput } from '../../utils/output-formatting.ts' - -import type { CliSubcommand } from '../../utils/meow-with-subcommands' - -type ListDescription = - | string - | { description: string; type?: string; default?: string } - -const renamep = util.promisify(fs.rename) - -const description = - "Generate a manifest file (`pom.xml`) from Scala's `build.sbt` file" - -const scalaCmdFlags: Record = { - bin: { - type: 'string', - default: 'sbt', - description: 'Location of sbt binary to use' - }, - out: { - type: 'string', - default: './socket.pom.xml', - description: - 'Path of output file; where to store the resulting manifest, see also --stdout' - }, - stdout: { - type: 'boolean', - description: 'Print resulting pom.xml to stdout (supersedes --out)' - }, - sbtOpts: { - type: 'string', - default: '', - description: 'Additional options to pass on to sbt, as per `sbt --help`' - }, - verbose: { - type: 'boolean', - description: 'Print debug messages' - } -} - -const help = (name: string, flags: Record) => ` - Usage - $ ${name} [--sbt=path/to/sbt/binary] [--out=path/to/result] FILE|DIR - - Options - ${getFlagListOutput(flags, 6)} - - Uses \`sbt makePom\` to generate a \`pom.xml\` from your \`build.sbt\` file. - This xml file is the dependency manifest (like a package.json - for Node.js or requirements.txt for PyPi), but specifically for Scala. - - There are some caveats with \`build.sbt\` to \`pom.xml\` conversion: - - - the xml is exported as socket.pom.xml as to not confuse existing build tools - but it will first hit your /target/sbt folder (as a different name) - - - the pom.xml format (standard by Scala) does not support certain sbt features - - \`excludeAll()\`, \`dependencyOverrides\`, \`force()\`, \`relativePath\` - - For details: https://www.scala-sbt.org/1.x/docs/Library-Management.html - - - it uses your sbt settings and local configuration verbatim - - - it can only export one target per run, so if you have multiple targets like - development and production, you must run them separately. - - You can optionally configure the path to the \`sbt\` bin to invoke. - - Examples - - $ ${name} ./build.sbt - $ ${name} --bin=/usr/bin/sbt ./build.sbt -` - -export const scala: CliSubcommand = { - description, - async run(argv, importMeta, { parentName }) { - // console.log('scala', argv, parentName) - const name = `${parentName} scala` - // note: meow will exit if it prints the --help screen - const cli = meow(help(name, scalaCmdFlags), { - flags: <{ [key: string]: any }>scalaCmdFlags, - argv: argv.length === 0 ? ['--help'] : argv, - description, - allowUnknownFlags: false, - importMeta - }) - - if (cli.flags['verbose']) { - console.log('[VERBOSE] cli.flags:', cli.flags, ', cli.input:', cli.input) - } - - const target = cli.input[0] - if (!target) { - // will exit. - new Spinner() - .start('Parsing...') - .error( - `Failure: Missing FILE|DIR argument. See \`${name} --help\` for details.` - ) - process.exit(1) - } - - if (cli.input.length > 1) { - // will exit. - new Spinner() - .start('Parsing...') - .error( - `Failure: Can only accept one FILE or DIR, received ${cli.input.length} (make sure to escape spaces!). See \`${name} --help\` for details.` - ) - process.exit(1) - } - - let bin: string = 'sbt' - if (cli.flags['bin']) { - bin = cli.flags['bin'] as string - } - - let out: string = './socket.pom.xml' - if (cli.flags['out']) { - out = cli.flags['out'] as string - } - if (cli.flags['stdout']) { - out = '-' - } - - // TODO: we can make `-` (accept from stdin) work by storing it into /tmp - if (target === '-') { - new Spinner() - .start('Parsing...') - .error( - `Failure: Currently source code from stdin is not supported. See \`${name} --help\` for details.` - ) - process.exit(1) - } - - const verbose = (cli.flags['verbose'] as boolean) ?? false - - let sbtOpts: Array = [] - if (cli.flags['sbtOpts']) { - sbtOpts = (cli.flags['sbtOpts'] as string) - .split(' ') - .map(s => s.trim()) - .filter(Boolean) - } - - await startConversion(target, bin, out, verbose, sbtOpts) - } -} - -async function startConversion( - target: string, - bin: string, - out: string, - verbose: boolean, - sbtOpts: Array -) { - const rbin = path.resolve(bin) - const rtarget = path.resolve(target) - const rout = out === '-' ? '-' : path.resolve(out) - - if (verbose) { - console.log(`[VERBOSE] - Absolute bin path: \`${rbin}\``) - console.log(`[VERBOSE] - Absolute target path: \`${rtarget}\``) - console.log(`[VERBOSE] - Absolute out path: \`${rout}\``) - } else { - console.log(`- executing: \`${bin}\``) - console.log(`- src dir: \`${target}\``) - console.log(`- dst dir: \`${out}\``) - } - - const spinner = new Spinner() - - spinner.start(`Running sbt from \`${bin}\` on \`${target}\`...`) - - try { - // We must now run sbt, pick the generated xml from the /target folder (the stdout should tell you the location upon success) and store it somewhere else. - // TODO: Not sure what this somewhere else might be tbh. - - const output = await spawn(bin, ['makePom'].concat(sbtOpts), { - cwd: target || '.' - }) - spinner.success() - if (verbose) { - console.group('[VERBOSE] sbt stdout:') - console.log(output) - console.groupEnd() - } - - if (output.stderr) { - spinner.error('There were errors while running sbt') - // (In verbose mode, stderr was printed above, no need to repeat it) - if (!verbose) { - console.group('[VERBOSE] stderr:') - console.error(output.stderr) - console.groupEnd() - } - process.exit(1) - } - - const loc = output.stdout?.match(/Wrote (.*?.pom)\n/)?.[1]?.trim() - if (!loc) { - spinner.error( - 'There were no errors from sbt but could not find the location of resulting .pom file either' - ) - process.exit(1) - } - - // Move the pom file to ...? initial cwd? loc will be an absolute path, or dump to stdout - if (out === '-') { - spinner.start('Result:\n```').success() - console.log(await safeReadFile(loc, 'utf8')) - console.log('```') - spinner.start().success(`OK`) - } else { - if (verbose) { - spinner.start( - `Moving manifest file from \`${loc.replace(/^\/home\/[^/]*?\//, '~/')}\` to \`${out}\`` - ) - } else { - spinner.start('Moving output pom file') - } - // TODO: do we prefer fs-extra? renaming can be gnarly on windows and fs-extra's version is better - await renamep(loc, out) - spinner.success() - spinner.start().success(`OK. File should be available in \`${out}\``) - } - } catch (e) { - spinner.error( - 'There was an unexpected error while running this' + - (verbose ? '' : ' (use --verbose for details)') - ) - if (verbose) { - console.group('[VERBOSE] error:') - console.log(e) - console.groupEnd() - } - process.exit(1) - } -} From 8759db53647a35e9db0ab8872988c6576b0298a7 Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Fri, 14 Feb 2025 16:34:15 +0100 Subject: [PATCH 2/5] polish --- .config/rollup.dist.config.mjs | 16 ++- src/commands/manifest/cmd-auto.ts | 47 ++++--- src/commands/manifest/cmd-gradle.ts | 42 ++++-- src/commands/manifest/cmd-kotlin.ts | 35 +++-- src/commands/manifest/cmd-scala.ts | 28 +++- .../manifest/convert_gradle_to_maven.ts | 122 ++++++++++++++++++ ...le-to-maven.ts => convert_sbt_to_maven.ts} | 72 +++++++---- src/commands/manifest/init.gradle | 8 +- src/commands/manifest/sbt-to-maven.ts | 102 --------------- 9 files changed, 285 insertions(+), 187 deletions(-) create mode 100644 src/commands/manifest/convert_gradle_to_maven.ts rename src/commands/manifest/{gradle-to-maven.ts => convert_sbt_to_maven.ts} (53%) delete mode 100644 src/commands/manifest/sbt-to-maven.ts diff --git a/.config/rollup.dist.config.mjs b/.config/rollup.dist.config.mjs index 4777a90f0..f10df5e26 100644 --- a/.config/rollup.dist.config.mjs +++ b/.config/rollup.dist.config.mjs @@ -67,6 +67,12 @@ function moveDtsFilesSync(namePattern, srcPath, destPath) { } } +function copyInitGradle() { + const filepath = path.join(rootSrcPath, 'commands', 'manifest', 'init.gradle') + const destPath = path.join(rootDistPath, 'init.gradle') + copyFileSync(filepath, destPath) +} + function removeDtsFilesSync(namePattern, srcPath) { for (const filepath of tinyGlobSync([`**/${namePattern}.d.ts{.map,}`], { absolute: true, @@ -81,16 +87,11 @@ function updateDepStatsSync(depStats) { const oldDepStats = existsSync(depStatsPath) ? readJsonSync(depStatsPath) : undefined - Object.assign(depStats.dependencies, { - // Add existing package.json dependencies without old transitives. This - // preserves dependencies like '@cyclonedx/cdxgen' and 'synp' that are - // indirectly referenced through spawned processes and not directly imported. - ...Object.fromEntries( + Object.assign(depStats.dependencies, Object.fromEntries( Object.entries(pkgJson.dependencies).filter( ({ 0: key }) => !oldDepStats?.transitives?.[key] ) - ) - }) + )) // Remove transitives from dependencies. for (const key of Object.keys(oldDepStats?.transitives ?? {})) { if (pkgJson.dependencies[key]) { @@ -212,6 +213,7 @@ export default () => { }, writeBundle() { moveDtsFilesSync(CONSTANTS, distRequirePath, rootDistPath) + copyInitGradle() removeDtsFilesSync('*', distRequirePath) updateDepStatsSync(requireConfig.meta.depStats) } diff --git a/src/commands/manifest/cmd-auto.ts b/src/commands/manifest/cmd-auto.ts index 547bad319..e8662732e 100644 --- a/src/commands/manifest/cmd-auto.ts +++ b/src/commands/manifest/cmd-auto.ts @@ -6,6 +6,7 @@ import meow from 'meow' import { cmdManifestGradle } from './cmd-gradle.ts' import { cmdManifestScala } from './cmd-scala.ts' import { commonFlags } from '../../flags.ts' +import { getFlagListOutput } from '../../utils/output-formatting.ts' import type { CliCommandConfig } from '../../utils/meow-with-subcommands' @@ -15,6 +16,10 @@ const config: CliCommandConfig = { hidden: false, flags: { ...commonFlags, + cwd: { + type: 'string', + description: 'Set the cwd, defaults to process.cwd()' + }, verbose: { type: 'boolean', default: false, @@ -26,11 +31,12 @@ const config: CliCommandConfig = { Usage $ ${parentName} ${config.commandName} + Options + ${getFlagListOutput(config.flags, 6)} + Tries to figure out what language your current repo uses. If it finds a supported case then it will try to generate the manifest file for that language with the default or detected settings. - - This command takes no arguments except --verbose. ` } @@ -45,35 +51,35 @@ async function run( importMeta: ImportMeta, { parentName }: { parentName: string } ): Promise { - // Allow `--verbose` to pass through - let verbose = false - const args = argv.filter(arg => { - if (arg === '--verbose') { - verbose = true - return false - } - return true + const cli = meow(config.help(parentName, config), { + argv, + description: config.description, + importMeta, + flags: config.flags, + allowUnknownFlags: false }) - if (args.length) { - meow(config.help(parentName, config), { - argv: ['--help'], - description: config.description, - importMeta, - flags: config.flags - }) - return + const verbose = cli.flags['verbose'] ?? false + const cwd = String(cli.flags['cwd']) || false + if (verbose) { + console.group('- ', parentName, config.commandName, ':') + console.group('- flags:', cli.flags) + console.groupEnd() + console.log('- input:', cli.input) + console.log('- cwd:', cwd || process.cwd()) + console.groupEnd() } const subArgs = [] - if (verbose) subArgs.push('--verbose', '1') + if (verbose) subArgs.push('--verbose') - const dir = '.' + const dir = cwd || '.' if (fs.existsSync(path.join(dir, 'build.sbt'))) { console.log( 'Detected a Scala sbt build, running default Scala generator...' ) + if (cwd) subArgs.push('--cwd', cwd) subArgs.push(dir) await cmdManifestScala.run(subArgs, importMeta, { parentName }) return @@ -81,6 +87,7 @@ async function run( if (fs.existsSync(path.join(dir, 'gradlew'))) { console.log('Detected a gradle build, running default gradle generator...') + if (cwd) subArgs.push(cwd) // This command takes the cwd as first arg await cmdManifestGradle.run(subArgs, importMeta, { parentName }) return } diff --git a/src/commands/manifest/cmd-gradle.ts b/src/commands/manifest/cmd-gradle.ts index 6141f6548..50f0fb81a 100644 --- a/src/commands/manifest/cmd-gradle.ts +++ b/src/commands/manifest/cmd-gradle.ts @@ -1,8 +1,10 @@ +import path from 'node:path' + import meow from 'meow' import { Spinner } from '@socketsecurity/registry/lib/spinner' -import { gradleToMaven } from './gradle-to-maven.ts' +import { convertGradleToMaven } from './convert_gradle_to_maven.ts' import { commonFlags } from '../../flags.ts' import { getFlagListOutput } from '../../utils/output-formatting.ts' @@ -17,8 +19,11 @@ const config: CliCommandConfig = { ...commonFlags, bin: { type: 'string', - default: 'DIR/gradlew', - description: 'Location of gradlew binary to use' + description: 'Location of gradlew binary to use, default: CWD/gradlew' + }, + cwd: { + type: 'string', + description: 'Set the cwd, defaults to process.cwd()' }, gradleOpts: { type: 'string', @@ -89,7 +94,6 @@ async function run( importMeta: ImportMeta, { parentName }: { parentName: string } ): Promise { - const name = `${parentName} gradle` // note: meow will exit if it prints the --help screen const cli = meow(config.help(parentName, config), { flags: config.flags, @@ -99,8 +103,14 @@ async function run( importMeta }) - if (cli.flags['verbose']) { - console.log('[VERBOSE] cli.flags:', cli.flags, ', cli.input:', cli.input) + const verbose = Boolean(cli.flags['verbose']) + + if (verbose) { + console.group('- ', parentName, config.commandName, ':') + console.group('- flags:', cli.flags) + console.groupEnd() + console.log('- input:', cli.input) + console.groupEnd() } const target = cli.input[0] @@ -109,7 +119,7 @@ async function run( new Spinner() .start('Parsing...') .error( - `Failure: Missing DIR argument. See \`${name} --help\` for details.` + `Failure: Missing DIR argument. See \`${parentName} ${config.commandName} --help\` for details.` ) process.exit(1) } @@ -119,14 +129,16 @@ async function run( new Spinner() .start('Parsing...') .error( - `Failure: Can only accept one FILE or DIR, received ${cli.input.length} (make sure to escape spaces!). See \`${name} --help\` for details.` + `Failure: Can only accept one FILE or DIR, received ${cli.input.length} (make sure to escape spaces!). See \`${parentName} ${config.commandName} --help\` for details.` ) process.exit(1) } - let bin: string = './gradlew' + let bin: string if (cli.flags['bin']) { bin = cli.flags['bin'] as string + } else { + bin = path.join(target, './gradlew') } let out: string = './socket.pom.xml' @@ -142,12 +154,18 @@ async function run( new Spinner() .start('Parsing...') .error( - `Failure: Currently source code from stdin is not supported. See \`${name} --help\` for details.` + `Failure: Currently source code from stdin is not supported. See \`${parentName} ${config.commandName} --help\` for details.` ) process.exit(1) } - const verbose = (cli.flags['verbose'] as boolean) ?? false + if (verbose) { + console.group() + console.log('- target:', target) + console.log('- gradle bin:', bin) + console.log('- out:', out) + console.groupEnd() + } let gradleOpts: Array = [] if (cli.flags['gradleOpts']) { @@ -157,5 +175,5 @@ async function run( .filter(Boolean) } - await gradleToMaven(target, bin, out, verbose, gradleOpts) + await convertGradleToMaven(target, bin, out, verbose, gradleOpts) } diff --git a/src/commands/manifest/cmd-kotlin.ts b/src/commands/manifest/cmd-kotlin.ts index ae16efd79..4ac197fb2 100644 --- a/src/commands/manifest/cmd-kotlin.ts +++ b/src/commands/manifest/cmd-kotlin.ts @@ -1,8 +1,10 @@ +import path from 'node:path' + import meow from 'meow' import { Spinner } from '@socketsecurity/registry/lib/spinner' -import { gradleToMaven } from './gradle-to-maven.ts' +import { convertGradleToMaven } from './convert_gradle_to_maven.ts' import { commonFlags } from '../../flags.ts' import { getFlagListOutput } from '../../utils/output-formatting.ts' @@ -22,8 +24,11 @@ const config: CliCommandConfig = { ...commonFlags, bin: { type: 'string', - default: 'DIR/gradlew', - description: 'Location of gradlew binary to use' + description: 'Location of gradlew binary to use, default: CWD/gradlew' + }, + cwd: { + type: 'string', + description: 'Set the cwd, defaults to process.cwd()' }, gradleOpts: { type: 'string', @@ -103,8 +108,14 @@ async function run( importMeta }) - if (cli.flags['verbose']) { - console.log('[VERBOSE] cli.flags:', cli.flags, ', cli.input:', cli.input) + const verbose = Boolean(cli.flags['verbose']) + + if (verbose) { + console.group('- ', parentName, config.commandName, ':') + console.group('- flags:', cli.flags) + console.groupEnd() + console.log('- input:', cli.input) + console.groupEnd() } const target = cli.input[0] @@ -128,9 +139,11 @@ async function run( process.exit(1) } - let bin: string = './gradlew' + let bin: string if (cli.flags['bin']) { bin = cli.flags['bin'] as string + } else { + bin = path.join(target, './gradlew') } let out: string = './socket.pom.xml' @@ -151,7 +164,13 @@ async function run( process.exit(1) } - const verbose = (cli.flags['verbose'] as boolean) ?? false + if (verbose) { + console.group() + console.log('- target:', target) + console.log('- gradle bin:', bin) + console.log('- out:', out) + console.groupEnd() + } let gradleOpts: Array = [] if (cli.flags['gradleOpts']) { @@ -161,5 +180,5 @@ async function run( .filter(Boolean) } - await gradleToMaven(target, bin, out, verbose, gradleOpts) + await convertGradleToMaven(target, bin, out, verbose, gradleOpts) } diff --git a/src/commands/manifest/cmd-scala.ts b/src/commands/manifest/cmd-scala.ts index 772e1dc01..81a6e1dc8 100644 --- a/src/commands/manifest/cmd-scala.ts +++ b/src/commands/manifest/cmd-scala.ts @@ -2,7 +2,7 @@ import meow from 'meow' import { Spinner } from '@socketsecurity/registry/lib/spinner' -import { sbtToMaven } from './sbt-to-maven.ts' +import { convertSbtToMaven } from './convert_sbt_to_maven.ts' import { commonFlags } from '../../flags.ts' import { getFlagListOutput } from '../../utils/output-formatting.ts' @@ -20,6 +20,10 @@ const config: CliCommandConfig = { default: 'sbt', description: 'Location of sbt binary to use' }, + cwd: { + type: 'string', + description: 'Set the cwd, defaults to process.cwd()' + }, out: { type: 'string', default: './socket.pom.xml', @@ -95,8 +99,14 @@ async function run( importMeta }) - if (cli.flags['verbose']) { - console.log('[VERBOSE] cli.flags:', cli.flags, ', cli.input:', cli.input) + const verbose = Boolean(cli.flags['verbose']) + + if (verbose) { + console.group('- ', parentName, config.commandName, ':') + console.group('- flags:', cli.flags) + console.groupEnd() + console.log('- input:', cli.input) + console.groupEnd() } const target = cli.input[0] @@ -133,6 +143,14 @@ async function run( out = '-' } + if (verbose) { + console.group() + console.log('- target:', target) + console.log('- gradle bin:', bin) + console.log('- out:', out) + console.groupEnd() + } + // TODO: we can make `-` (accept from stdin) work by storing it into /tmp if (target === '-') { new Spinner() @@ -143,8 +161,6 @@ async function run( process.exit(1) } - const verbose = (cli.flags['verbose'] as boolean) ?? false - let sbtOpts: Array = [] if (cli.flags['sbtOpts']) { sbtOpts = (cli.flags['sbtOpts'] as string) @@ -153,5 +169,5 @@ async function run( .filter(Boolean) } - await sbtToMaven(target, bin, out, verbose, sbtOpts) + await convertSbtToMaven(target, bin, out, verbose, sbtOpts) } diff --git a/src/commands/manifest/convert_gradle_to_maven.ts b/src/commands/manifest/convert_gradle_to_maven.ts new file mode 100644 index 000000000..3a200e30b --- /dev/null +++ b/src/commands/manifest/convert_gradle_to_maven.ts @@ -0,0 +1,122 @@ +import path from 'node:path' + +import spawn from '@npmcli/promise-spawn' + +import { Spinner } from '@socketsecurity/registry/lib/spinner' + +import constants from '../../constants.ts' + +export async function convertGradleToMaven( + target: string, + bin: string, + _out: string, + verbose: boolean, + gradleOpts: Array +) { + const rbin = path.resolve(bin) + const rtarget = path.resolve(target) + // const rout = out === '-' ? '-' : path.resolve(out) + + if (verbose) { + console.group('gradle2maven:') + console.log(`[VERBOSE] - Absolute bin path: \`${rbin}\``) + console.log(`[VERBOSE] - Absolute target path: \`${rtarget}\``) + // console.log(`[VERBOSE] - Absolute out path: \`${rout}\``) + console.groupEnd() + } else { + console.group('gradle2maven:') + console.log(`- executing: \`${bin}\``) + console.log(`- src dir: \`${target}\``) + // console.log(`- dst dir: \`${out}\``) + console.groupEnd() + } + + const spinner = new Spinner() + + spinner.start( + `Converting gradle to maven from \`${bin}\` on \`${target}\`...` + ) + + try { + // Run sbt with the init script we provide which should yield zero or more pom files. + // We have to figure out where to store those pom files such that we can upload them and predict them through the GitHub API. + // We could do a .socket folder. We could do a socket.pom.gz with all the poms, although I'd prefer something plain-text if it is to be committed. + + // Note: init.gradle will be exported by .config/rollup.dist.config.mjs + const initLocation = path.join(constants.rootDistPath, 'init.gradle') + const commandArgs = ['--init-script', initLocation, ...gradleOpts, 'pom'] + + if (verbose) { + console.log('\n[VERBOSE] Executing:', bin, commandArgs) + } + + const output = await spawn(bin, commandArgs, { + cwd: target || '.' + }) + spinner.success() + if (verbose) { + console.group('[VERBOSE] gradle stdout:') + console.log(output) + console.groupEnd() + } + + if (output.stderr) { + spinner.error('There were errors while running gradle') + // (In verbose mode, stderr was printed above, no need to repeat it) + if (!verbose) { + console.group('[VERBOSE] stderr:') + console.error(output.stderr) + console.groupEnd() + } + process.exit(1) + } + + console.log('Reported exports:') + output.stdout.replace( + /^POM file copied to: (.*)/gm, + (_all: string, fn: string) => { + console.log('- ', fn) + return fn + } + ) + + // const loc = output.stdout?.match(/Wrote (.*?.pom)\n/)?.[1]?.trim() + // if (!loc) { + // spinner.error( + // 'There were no errors from sbt but could not find the location of resulting .pom file either' + // ) + // process.exit(1) + // } + // + // // Move the pom file to ...? initial cwd? loc will be an absolute path, or dump to stdout + // if (out === '-') { + // spinner.start('Result:\n```').success() + // console.log(await safeReadFile(loc, 'utf8')) + // console.log('```') + // spinner.start().success(`OK`) + // } else { + // if (verbose) { + // spinner.start( + // `Moving manifest file from \`${loc.replace(/^\/home\/[^/]*?\//, '~/')}\` to \`${out}\`` + // ) + // } else { + // spinner.start('Moving output pom file') + // } + // // TODO: do we prefer fs-extra? renaming can be gnarly on windows and fs-extra's version is better + // await renamep(loc, out) + // spinner.success() + // spinner.start().success(`OK. File should be available in \`${out}\``) + // } + } catch (e) { + spinner.error( + 'There was an unexpected error while running this' + + (verbose ? '' : ' (use --verbose for details)') + ) + if (verbose) { + console.group('[VERBOSE] error:') + console.log(e) + console.groupEnd() + } + process.exit(1) + } +} diff --git a/src/commands/manifest/gradle-to-maven.ts b/src/commands/manifest/convert_sbt_to_maven.ts similarity index 53% rename from src/commands/manifest/gradle-to-maven.ts rename to src/commands/manifest/convert_sbt_to_maven.ts index f61244e0d..b7b213488 100644 --- a/src/commands/manifest/gradle-to-maven.ts +++ b/src/commands/manifest/convert_sbt_to_maven.ts @@ -1,6 +1,4 @@ -import fs from 'node:fs' import path from 'node:path' -import util from 'node:util' import spawn from '@npmcli/promise-spawn' @@ -8,40 +6,41 @@ import { Spinner } from '@socketsecurity/registry/lib/spinner' import { safeReadFile } from '../../utils/fs.ts' -// TODO: find better consolidation around fs abstraction (like fs-extra) throughout cli -const renamep = util.promisify(fs.rename) - -export async function gradleToMaven( +export async function convertSbtToMaven( target: string, bin: string, out: string, verbose: boolean, - gradleOpts: Array + sbtOpts: Array ) { const rbin = path.resolve(bin) const rtarget = path.resolve(target) - const rout = out === '-' ? '-' : path.resolve(out) + // const rout = out === '-' ? '-' : path.resolve(out) if (verbose) { + console.group('sbt2maven:') console.log(`[VERBOSE] - Absolute bin path: \`${rbin}\``) console.log(`[VERBOSE] - Absolute target path: \`${rtarget}\``) - console.log(`[VERBOSE] - Absolute out path: \`${rout}\``) + // console.log(`[VERBOSE] - Absolute out path: \`${rout}\``) + console.groupEnd() } else { + console.group('sbt2maven:') console.log(`- executing: \`${bin}\``) console.log(`- src dir: \`${target}\``) - console.log(`- dst dir: \`${out}\``) + // console.log(`- dst dir: \`${out}\``) + console.groupEnd() } const spinner = new Spinner() - spinner.start(`Running gradle from \`${bin}\` on \`${target}\`...`) + spinner.start(`Converting sbt to maven from \`${bin}\` on \`${target}\`...`) try { - // Run gradlew with the init script we provide which should yield zero or more pom files. + // Run sbt with the init script we provide which should yield zero or more pom files. // We have to figure out where to store those pom files such that we can upload them and predict them through the GitHub API. // We could do a .socket folder. We could do a socket.pom.gz with all the poms, although I'd prefer something plain-text if it is to be committed. - const output = await spawn(bin, ['makePom'].concat(gradleOpts), { + const output = await spawn(bin, ['makePom'].concat(sbtOpts), { cwd: target || '.' }) spinner.success() @@ -62,32 +61,49 @@ export async function gradleToMaven( process.exit(1) } - const loc = output.stdout?.match(/Wrote (.*?.pom)\n/)?.[1]?.trim() - if (!loc) { + const poms: Array = [] + output.stdout.replace(/Wrote (.*?.pom)\n/g, (_all: string, fn: string) => { + poms.push(fn) + return fn + }) + + if (!poms.length) { spinner.error( - 'There were no errors from sbt but could not find the location of resulting .pom file either' + 'There were no errors from sbt but it seems to not have generated any poms either' ) process.exit(1) } // Move the pom file to ...? initial cwd? loc will be an absolute path, or dump to stdout - if (out === '-') { + // TODO: what to do with multiple output files? Do we want to dump them to stdout? Raw or with separators or ? + // TODO: maybe we can add an option to target a specific file to dump to stdout + if (out === '-' && poms.length === 1) { spinner.start('Result:\n```').success() - console.log(await safeReadFile(loc, 'utf8')) + console.log(await safeReadFile(poms[0] as string, 'utf8')) console.log('```') spinner.start().success(`OK`) - } else { - if (verbose) { - spinner.start( - `Moving manifest file from \`${loc.replace(/^\/home\/[^/]*?\//, '~/')}\` to \`${out}\`` + } else if (out === '-') { + spinner + .start() + .error( + 'Requested out target was stdout but there are multiple generated files' ) - } else { - spinner.start('Moving output pom file') - } + poms.forEach(fn => console.error('-', fn)) + console.error('Exiting now...') + process.exit(1) + } else { + // if (verbose) { + // spinner.start( + // `Moving manifest file from \`${loc.replace(/^\/home\/[^/]*?\//, '~/')}\` to \`${out}\`` + // ) + // } else { + // spinner.start('Moving output pom file') + // } // TODO: do we prefer fs-extra? renaming can be gnarly on windows and fs-extra's version is better - await renamep(loc, out) - spinner.success() - spinner.start().success(`OK. File should be available in \`${out}\``) + // await renamep(loc, out) + spinner.start().success(`Generated ${poms.length} pom files`) + poms.forEach(fn => console.log('-', fn)) + spinner.start().success(`OK`) } } catch (e) { spinner.error( diff --git a/src/commands/manifest/init.gradle b/src/commands/manifest/init.gradle index 3ccb37d38..a64e5d93c 100644 --- a/src/commands/manifest/init.gradle +++ b/src/commands/manifest/init.gradle @@ -23,7 +23,7 @@ initscript { } // Apply these configurations to all projects in the build -cmdGradle.allprojects { project -> +gradle.allprojects { project -> // Create a unique name for the Maven publication // Example: project ':foo:bar' becomes 'maven-foo-bar' def publicationName = "maven-${project.path.replace(':', '-')}" @@ -64,9 +64,9 @@ cmdGradle.allprojects { project -> if (defaultPomFile.exists()) { // Print the generated POM for inspection println "\nGenerated POM file for ${publicationName}:" - println "==================================" - println defaultPomFile.text - println "==================================" +// println "==================================" +// println defaultPomFile.text +// println "==================================" // Copy the POM file to its target location targetPomFile.parentFile.mkdirs() diff --git a/src/commands/manifest/sbt-to-maven.ts b/src/commands/manifest/sbt-to-maven.ts deleted file mode 100644 index 46da832a1..000000000 --- a/src/commands/manifest/sbt-to-maven.ts +++ /dev/null @@ -1,102 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' -import util from 'node:util' - -import spawn from '@npmcli/promise-spawn' - -import { Spinner } from '@socketsecurity/registry/lib/spinner' - -import { safeReadFile } from '../../utils/fs.ts' - -const renamep = util.promisify(fs.rename) - -export async function sbtToMaven( - target: string, - bin: string, - out: string, - verbose: boolean, - sbtOpts: Array -) { - const rbin = path.resolve(bin) - const rtarget = path.resolve(target) - const rout = out === '-' ? '-' : path.resolve(out) - - if (verbose) { - console.log(`[VERBOSE] - Absolute bin path: \`${rbin}\``) - console.log(`[VERBOSE] - Absolute target path: \`${rtarget}\``) - console.log(`[VERBOSE] - Absolute out path: \`${rout}\``) - } else { - console.log(`- executing: \`${bin}\``) - console.log(`- src dir: \`${target}\``) - console.log(`- dst dir: \`${out}\``) - } - - const spinner = new Spinner() - - spinner.start(`Running sbt from \`${bin}\` on \`${target}\`...`) - - try { - // We must now run sbt, pick the generated xml from the /target folder (the stdout should tell you the location upon success) and store it somewhere else. - // TODO: Not sure what this somewhere else might be tbh. - - const output = await spawn(bin, ['makePom'].concat(sbtOpts), { - cwd: target || '.' - }) - spinner.success() - if (verbose) { - console.group('[VERBOSE] sbt stdout:') - console.log(output) - console.groupEnd() - } - - if (output.stderr) { - spinner.error('There were errors while running sbt') - // (In verbose mode, stderr was printed above, no need to repeat it) - if (!verbose) { - console.group('[VERBOSE] stderr:') - console.error(output.stderr) - console.groupEnd() - } - process.exit(1) - } - - const loc = output.stdout?.match(/Wrote (.*?.pom)\n/)?.[1]?.trim() - if (!loc) { - spinner.error( - 'There were no errors from sbt but could not find the location of resulting .pom file either' - ) - process.exit(1) - } - - // Move the pom file to ...? initial cwd? loc will be an absolute path, or dump to stdout - if (out === '-') { - spinner.start('Result:\n```').success() - console.log(await safeReadFile(loc, 'utf8')) - console.log('```') - spinner.start().success(`OK`) - } else { - if (verbose) { - spinner.start( - `Moving manifest file from \`${loc.replace(/^\/home\/[^/]*?\//, '~/')}\` to \`${out}\`` - ) - } else { - spinner.start('Moving output pom file') - } - // TODO: do we prefer fs-extra? renaming can be gnarly on windows and fs-extra's version is better - await renamep(loc, out) - spinner.success() - spinner.start().success(`OK. File should be available in \`${out}\``) - } - } catch (e) { - spinner.error( - 'There was an unexpected error while running this' + - (verbose ? '' : ' (use --verbose for details)') - ) - if (verbose) { - console.group('[VERBOSE] error:') - console.log(e) - console.groupEnd() - } - process.exit(1) - } -} From 971f1f94529cfaceb41f567c1f55c7d14ac78ff0 Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Fri, 14 Feb 2025 16:47:53 +0100 Subject: [PATCH 3/5] Add quick readme to manifest --- src/commands/manifest/README.md | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/commands/manifest/README.md diff --git a/src/commands/manifest/README.md b/src/commands/manifest/README.md new file mode 100644 index 000000000..c6c883920 --- /dev/null +++ b/src/commands/manifest/README.md @@ -0,0 +1,41 @@ +# Manifest + +(At the time of writing...) + +## Dev + +First build the bundle: + +``` +npm run build:dist +``` + +Then run it like these examples: + +``` +# Scala: +npm exec socket manifest scala -- --bin ~/apps/sbt/bin/sbt ~/socket/repos/scala/akka +# Gradle/Kotlin +npm exec socket manifest yolo -- --cwd ~/socket/repos/kotlin/kotlinx.coroutines +``` + +And upload with this: + +``` +npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/scala/akka socketdev . +npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/kotlin/kotlinx.coroutines . +``` + +(The `cwd` option for `create` is necessary because we can't go to the dir and run `npm exec`). + +## Prod + +User flow look something like this: + +``` +socket manifest scala . +socket manifest kotlin . +socket manifest yolo + +socket scan create --repo=depscantmp --branch=mastertmp --tmp socketdev . +``` From 7722259fd8fc57b939d9a04d90d38e659b5c2b68 Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Fri, 14 Feb 2025 17:07:37 +0100 Subject: [PATCH 4/5] Put comment back in --- .config/rollup.dist.config.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.config/rollup.dist.config.mjs b/.config/rollup.dist.config.mjs index f10df5e26..dcad1c6bd 100644 --- a/.config/rollup.dist.config.mjs +++ b/.config/rollup.dist.config.mjs @@ -87,7 +87,11 @@ function updateDepStatsSync(depStats) { const oldDepStats = existsSync(depStatsPath) ? readJsonSync(depStatsPath) : undefined - Object.assign(depStats.dependencies, Object.fromEntries( + Object.assign(depStats.dependencies, + // Add existing package.json dependencies without old transitives. This + // preserves dependencies like '@cyclonedx/cdxgen' and 'synp' that are + // indirectly referenced through spawned processes and not directly imported. + Object.fromEntries( Object.entries(pkgJson.dependencies).filter( ({ 0: key }) => !oldDepStats?.transitives?.[key] ) From 10d4a437feba414002a9a7698fdd9511085a3e09 Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Fri, 14 Feb 2025 17:25:26 +0100 Subject: [PATCH 5/5] Slap a beta tag on it --- src/commands/manifest/cmd-gradle.ts | 4 +++- src/commands/manifest/cmd-kotlin.ts | 4 +++- src/commands/manifest/cmd-manifest.ts | 3 ++- src/commands/manifest/cmd-scala.ts | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/commands/manifest/cmd-gradle.ts b/src/commands/manifest/cmd-gradle.ts index 50f0fb81a..cbc9acfe5 100644 --- a/src/commands/manifest/cmd-gradle.ts +++ b/src/commands/manifest/cmd-gradle.ts @@ -13,7 +13,7 @@ import type { CliCommandConfig } from '../../utils/meow-with-subcommands' const config: CliCommandConfig = { commandName: 'gradle', description: - 'Use Gradle to generate a manifest file (`pom.xml`) for a Gradle/Java/Kotlin/etc project', + '[beta] Use Gradle to generate a manifest file (`pom.xml`) for a Gradle/Java/Kotlin/etc project', hidden: false, flags: { ...commonFlags, @@ -76,6 +76,8 @@ const config: CliCommandConfig = { - it works with your \`gradlew\` from your repo and local settings and config + Support is beta. Please report issues or give us feedback on what's missing. + Examples $ ${parentName} ${config.commandName} . diff --git a/src/commands/manifest/cmd-kotlin.ts b/src/commands/manifest/cmd-kotlin.ts index 4ac197fb2..32cad6cd4 100644 --- a/src/commands/manifest/cmd-kotlin.ts +++ b/src/commands/manifest/cmd-kotlin.ts @@ -18,7 +18,7 @@ import type { CliCommandConfig } from '../../utils/meow-with-subcommands' const config: CliCommandConfig = { commandName: 'kotlin', description: - 'Use Gradle to generate a manifest file (`pom.xml`) for a Kotlin project', + '[beta] Use Gradle to generate a manifest file (`pom.xml`) for a Kotlin project', hidden: false, flags: { ...commonFlags, @@ -81,6 +81,8 @@ const config: CliCommandConfig = { - it works with your \`gradlew\` from your repo and local settings and config + Support is beta. Please report issues or give us feedback on what's missing. + Examples $ ${parentName} ${config.commandName} . diff --git a/src/commands/manifest/cmd-manifest.ts b/src/commands/manifest/cmd-manifest.ts index 8d4c3b33f..a2eeb18e0 100644 --- a/src/commands/manifest/cmd-manifest.ts +++ b/src/commands/manifest/cmd-manifest.ts @@ -28,7 +28,8 @@ const config: CliCommandConfig = { configurations available. See \`manifest --help\` for usage details per language. - Currently supported language: scala, gradle + Currently supported language: scala [beta], gradle [beta], kotlin (through + gradle) [beta]. Examples diff --git a/src/commands/manifest/cmd-scala.ts b/src/commands/manifest/cmd-scala.ts index 81a6e1dc8..7d2379f3f 100644 --- a/src/commands/manifest/cmd-scala.ts +++ b/src/commands/manifest/cmd-scala.ts @@ -11,7 +11,7 @@ import type { CliCommandConfig } from '../../utils/meow-with-subcommands' const config: CliCommandConfig = { commandName: 'kotlin', description: - "Generate a manifest file (`pom.xml`) from Scala's `build.sbt` file", + "[beta] Generate a manifest file (`pom.xml`) from Scala's `build.sbt` file", hidden: false, flags: { ...commonFlags, @@ -71,6 +71,8 @@ const config: CliCommandConfig = { You can optionally configure the path to the \`sbt\` bin to invoke. + Support is beta. Please report issues or give us feedback on what's missing. + Examples $ ${parentName} ${config.commandName} ./build.sbt