From cc274186d9394cbc68e4120ce4680a7e6944ee86 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Fri, 8 May 2026 10:15:38 -0400 Subject: [PATCH 1/2] Add --method flag to import-uploads and fix got.stream crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds --method diff|legacy option to the import-uploads command, threading it through to the REST endpoint added in vantage-backend#2443. The diff method skips unchanged files for significantly faster re-imports. Also fixes a crash in stream.js where got.default.stream was called — got is already the default export so got.stream is the correct call. Co-Authored-By: Claude Sonnet 4.6 --- lib/commands/stack/import-uploads.js | 135 +++++++++++++++++++++++++++ lib/stream.js | 2 +- 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 lib/commands/stack/import-uploads.js diff --git a/lib/commands/stack/import-uploads.js b/lib/commands/stack/import-uploads.js new file mode 100644 index 0000000..528166e --- /dev/null +++ b/lib/commands/stack/import-uploads.js @@ -0,0 +1,135 @@ +import chalk from 'chalk'; +import inquirer from 'inquirer'; +import { format, parse } from 'url'; +import { getStack, streamLog } from './util.js'; +import Vantage from '../../vantage.js'; + +function startImport( vantage, stack, args ) { + console.log( `Importing uploads into ${ stack } from ${ args.from_application }...` ); + + const urlBits = parse( `stack/applications/${ stack }/import-uploads`, true ); + + urlBits.query = Object.assign( + {}, + { + stream: 'true', + from_application: args.from_application, + } + ); + + if ( args.path ) { + urlBits.query.path = args.path; + } + + if ( args.method ) { + urlBits.query.method = args.method; + } + + const url = format( urlBits ); + + const opts = { method: 'POST' }; + return vantage.fetch( url, opts ).then( resp => { + return resp.text().then( text => { + if ( ! resp.ok ) { + throw new Error( text ); + } + + return text; + } ); + } ); +} + +const handler = function ( argv ) { + const { config, debug } = argv; + + getStack( argv ).then( stack => { + const v = new Vantage( config ); + v.debug = debug; + + if ( argv.resume ) { + streamLog( v, stack, argv.resume, debug ); + return; + } + + if ( ! argv.from ) { + console.error( chalk.red( 'Error: --from is required (source application to import from)' ) ); + process.exit( 1 ); + } + + v.fetch( `stack/applications/${ stack }` ) + .then( resp => resp.json() ) + .then( data => { + if ( data['environment-type'] === 'production' ) { + console.log( chalk.red.bold( `\n WARNING: You are importing uploads into a PRODUCTION application (${ stack })` ) ); + console.log( chalk.red( ' This will overwrite files in the production uploads bucket. This action cannot be undone.\n' ) ); + + return inquirer.prompt( { + type: 'confirm', + name: 'confirm', + message: 'Are you sure you want to continue?', + default: false, + } ).then( answers => { + if ( ! answers.confirm ) { + console.log( 'Aborted.' ); + process.exit( 0 ); + } + } ); + } + } ) + .then( () => { + const args = { + from_application: argv.from, + }; + + if ( argv.path ) { + args.path = argv.path; + } + + if ( argv.method ) { + args.method = argv.method; + } + + return startImport( v, stack, args ); + } ) + .then( id => { + console.log( chalk.yellow( `Import started, resume later with...` ) ); + console.log( chalk.yellow( ` altis-cli stack import-uploads ${ stack } --resume ${ id }\n` ) ); + streamLog( v, stack, id, debug ); + } ) + .catch( err => { + console.error( chalk.red( `Error: ${ err.message }` ) ); + process.exit( 1 ); + } ); + } ); +}; + +export default { + command: 'import-uploads [stack]', + description: 'Import uploads from another application.', + builder: subcommand => { + subcommand.option( 'from', { + description: 'Source application to import uploads from.', + type: 'string', + demandOption: true, + } ); + subcommand.option( 'path', { + description: 'Relative path within uploads to sync (e.g. "2024/01" or "sites/2/2024").', + type: 'string', + } ); + subcommand.option( 'method', { + description: 'Import method: "diff" skips unchanged files (faster for re-imports), "legacy" copies everything.', + type: 'string', + choices: [ 'diff', 'legacy' ], + } ); + subcommand.option( 'resume', { + description: 'Log ID for resuming an existing import.', + type: 'string', + } ); + subcommand.option( 'debug', { + description: 'Enable internal debugging information.', + type: 'boolean', + default: false, + } ); + }, + handler, +}; diff --git a/lib/stream.js b/lib/stream.js index bfd533b..878e5d9 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -45,7 +45,7 @@ export default function(url, opts) { buf = ''; - stream = got.default.stream(url, reqOpts); + stream = got.stream(url, reqOpts); onclose = once(() => { if (destroyed) return; From 8763d420004992b212cca77812148d7daafab552 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Fri, 8 May 2026 10:22:20 -0400 Subject: [PATCH 2/2] Add --method flag to import uploads and fix got.stream crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds --method diff|legacy option to `altis-cli stack import uploads`, wiring it to the REST endpoint from vantage-backend#2443. The diff method skips unchanged files for significantly faster re-imports. Also fixes a crash in stream.js where got.default.stream was called — got is already the default export so got.stream is the correct call. Co-Authored-By: Claude Sonnet 4.6 --- lib/commands/stack/import-uploads.js | 135 --------------------------- lib/commands/stack/import.js | 10 +- 2 files changed, 8 insertions(+), 137 deletions(-) delete mode 100644 lib/commands/stack/import-uploads.js diff --git a/lib/commands/stack/import-uploads.js b/lib/commands/stack/import-uploads.js deleted file mode 100644 index 528166e..0000000 --- a/lib/commands/stack/import-uploads.js +++ /dev/null @@ -1,135 +0,0 @@ -import chalk from 'chalk'; -import inquirer from 'inquirer'; -import { format, parse } from 'url'; -import { getStack, streamLog } from './util.js'; -import Vantage from '../../vantage.js'; - -function startImport( vantage, stack, args ) { - console.log( `Importing uploads into ${ stack } from ${ args.from_application }...` ); - - const urlBits = parse( `stack/applications/${ stack }/import-uploads`, true ); - - urlBits.query = Object.assign( - {}, - { - stream: 'true', - from_application: args.from_application, - } - ); - - if ( args.path ) { - urlBits.query.path = args.path; - } - - if ( args.method ) { - urlBits.query.method = args.method; - } - - const url = format( urlBits ); - - const opts = { method: 'POST' }; - return vantage.fetch( url, opts ).then( resp => { - return resp.text().then( text => { - if ( ! resp.ok ) { - throw new Error( text ); - } - - return text; - } ); - } ); -} - -const handler = function ( argv ) { - const { config, debug } = argv; - - getStack( argv ).then( stack => { - const v = new Vantage( config ); - v.debug = debug; - - if ( argv.resume ) { - streamLog( v, stack, argv.resume, debug ); - return; - } - - if ( ! argv.from ) { - console.error( chalk.red( 'Error: --from is required (source application to import from)' ) ); - process.exit( 1 ); - } - - v.fetch( `stack/applications/${ stack }` ) - .then( resp => resp.json() ) - .then( data => { - if ( data['environment-type'] === 'production' ) { - console.log( chalk.red.bold( `\n WARNING: You are importing uploads into a PRODUCTION application (${ stack })` ) ); - console.log( chalk.red( ' This will overwrite files in the production uploads bucket. This action cannot be undone.\n' ) ); - - return inquirer.prompt( { - type: 'confirm', - name: 'confirm', - message: 'Are you sure you want to continue?', - default: false, - } ).then( answers => { - if ( ! answers.confirm ) { - console.log( 'Aborted.' ); - process.exit( 0 ); - } - } ); - } - } ) - .then( () => { - const args = { - from_application: argv.from, - }; - - if ( argv.path ) { - args.path = argv.path; - } - - if ( argv.method ) { - args.method = argv.method; - } - - return startImport( v, stack, args ); - } ) - .then( id => { - console.log( chalk.yellow( `Import started, resume later with...` ) ); - console.log( chalk.yellow( ` altis-cli stack import-uploads ${ stack } --resume ${ id }\n` ) ); - streamLog( v, stack, id, debug ); - } ) - .catch( err => { - console.error( chalk.red( `Error: ${ err.message }` ) ); - process.exit( 1 ); - } ); - } ); -}; - -export default { - command: 'import-uploads [stack]', - description: 'Import uploads from another application.', - builder: subcommand => { - subcommand.option( 'from', { - description: 'Source application to import uploads from.', - type: 'string', - demandOption: true, - } ); - subcommand.option( 'path', { - description: 'Relative path within uploads to sync (e.g. "2024/01" or "sites/2/2024").', - type: 'string', - } ); - subcommand.option( 'method', { - description: 'Import method: "diff" skips unchanged files (faster for re-imports), "legacy" copies everything.', - type: 'string', - choices: [ 'diff', 'legacy' ], - } ); - subcommand.option( 'resume', { - description: 'Log ID for resuming an existing import.', - type: 'string', - } ); - subcommand.option( 'debug', { - description: 'Enable internal debugging information.', - type: 'boolean', - default: false, - } ); - }, - handler, -}; diff --git a/lib/commands/stack/import.js b/lib/commands/stack/import.js index c2bffe9..226be80 100644 --- a/lib/commands/stack/import.js +++ b/lib/commands/stack/import.js @@ -56,8 +56,13 @@ const handler = async argv => { if (argv.postSync) { body.post_sync = true; } - } else if (argv.uploadsPath) { - body.uploads_path = argv.uploadsPath; + } else { + if (argv.uploadsPath) { + body.uploads_path = argv.uploadsPath; + } + if (argv.method) { + body.method = argv.method; + } } const data = await fetchJSON(v, `stack/applications/${target}/${route}?stream=true`, { @@ -78,6 +83,7 @@ export default { .option('from', { type: 'string', description: 'Source application id.' }) .option('tables', { type: 'string', description: 'Comma-separated database table names.' }) .option('uploads-path', { type: 'string', description: 'Uploads prefix to import.' }) + .option('method', { type: 'string', choices: ['diff', 'legacy'], description: 'Import method for uploads: diff skips unchanged files, legacy copies everything.' }) .option('replace', { type: 'string', array: true, description: 'Search-replace mapping from=to.' }) .option('use-mydumper', { type: 'boolean', description: 'Use mydumper/myloader.' }) .option('post-sync', { type: 'boolean', description: 'Run post-sync after database import.' })