UNPKG

@curvenote/cli

Version:
232 lines (231 loc) 11.8 kB
import { keyFromTransferFile, writeKeyToConfig } from './utils.transfer.js'; import { confirmOrExit, writeJsonLogs, addOxaTransformersToOpts } from '../utils/utils.js'; import chalk from 'chalk'; import { postNewCliCheckJob, patchUpdateCliCheckJob, exitOnInvalidKeyOption } from './utils.js'; import { ensureVenue, checkVenueExists, confirmUpdateToExistingSubmission, updateExistingSubmission, createNewSubmission, checkForSubmissionKeyInUse, determineCollectionAndKind, collectionMoniker, promptForNewKey, getAllSubmissionsUsingKey, getSubmissionToUpdate, checkVenueSubmitAccess, getVenueCollections, } from './submit.utils.js'; import { submissionRuleChecks } from '@curvenote/check-implementations'; import { logCheckReport, runChecks } from '../check/index.js'; import path from 'node:path'; import fs from 'node:fs'; import { prepareChecksForSubmission } from './check.js'; import { getGitRepoInfo } from './utils.git.js'; import * as uploads from '../uploads/index.js'; import { workKeyFromConfig } from '../works/utils.js'; import { buildSite, clean, collectAllBuildExportOptions, localArticleExport } from 'myst-cli'; export const CDN_KEY_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; export const DEV_CDN_KEY = 'ad7fa60f-5460-4bf9-96ea-59be87944e41'; export async function performCleanRebuild(session, opts) { session.log.info('\n\n\t✨✨✨ performing a clean re-build of your work ✨✨✨\n\n'); await clean(session, [], { site: true, html: true, temp: true, exports: true, yes: true }); const exportOptionsList = await collectAllBuildExportOptions(session, [], { all: true }); const exportLogList = exportOptionsList.map((exportOptions) => { return `${path.relative('.', exportOptions.$file)} -> ${exportOptions.output}`; }); session.log.info(`📬 Performing exports:\n ${exportLogList.join('\n ')}`); await localArticleExport(session, exportOptionsList, {}); session.log.info(`⛴ Exports complete`); // Build the files in the content folder and process them await buildSite(session, addOxaTransformersToOpts(session, opts !== null && opts !== void 0 ? opts : {})); session.log.info(`✅ Work rebuild complete`); } export async function submit(session, venue, opts) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const submitLog = { input: { venue, opts, }, }; if (session.isAnon) { throw new Error('⛔️ You must be authenticated for this command. Use `curvenote token set [token]`'); } // TODO upload preflight checks // TODO check the venue allows for submissions & updates to the submission // TODO check user has permission to submit / update a submission venue = await ensureVenue(session, venue, opts); await checkVenueExists(session, venue); await checkVenueSubmitAccess(session, venue); const collections = await getVenueCollections(session, venue); let key = workKeyFromConfig(session); // Deprecation step to handle old transfer.yml files key = (_a = (await keyFromTransferFile(session, venue, key, opts))) !== null && _a !== void 0 ? _a : key; if (!key) { key = await promptForNewKey(session, opts); await writeKeyToConfig(session, key); } exitOnInvalidKeyOption(session, key); const gitInfo = await getGitRepoInfo(); const source = { repo: gitInfo === null || gitInfo === void 0 ? void 0 : gitInfo.repo, branch: gitInfo === null || gitInfo === void 0 ? void 0 : gitInfo.branch, path: gitInfo === null || gitInfo === void 0 ? void 0 : gitInfo.path, commit: gitInfo === null || gitInfo === void 0 ? void 0 : gitInfo.commit, }; session.log.info(`📍 Submitting using key: ${chalk.bold(key)}`); let existing; // Only check for submissions to update if we are not creating a new draft if (!(opts === null || opts === void 0 ? void 0 : opts.draft) && !(opts === null || opts === void 0 ? void 0 : opts.new)) { session.log.info(`📡 Checking submission status...`); const allExisting = await getAllSubmissionsUsingKey(session, venue, key); if (!(allExisting === null || allExisting === void 0 ? void 0 : allExisting.length)) { const exists = await checkForSubmissionKeyInUse(session, venue, key); if (exists) { session.log.warn(`⛔️ This work has already been submitted to a Curvenote site, but you don't have permission to access that submission.`); session.log.info('If you still want to make a new submission, you may explicitly add flag "--new"'); process.exit(1); } else { session.log.info(`🔍 No existing submission found at "${venue}" using the key "${key}"`); } } else { existing = await getSubmissionToUpdate(session, allExisting); session.log.info(`🔍 Found an existing submission using this key, the existing submission will be updated.`); } } // // Options, checks and prompts // let kind; let collection; if (existing) { const confirmed = await confirmUpdateToExistingSubmission(session, venue, collections, existing, key, opts); kind = confirmed.kind; collection = confirmed.collection; } else { // // NEW SUBMISSIONS // session.log.debug('Making a new submission...'); const determined = await determineCollectionAndKind(session, venue, collections, opts); kind = determined.kind; collection = determined.collection; session.log.info(`📚 Submitting a "${kind === null || kind === void 0 ? void 0 : kind.name}" to the "${collectionMoniker(collection)}"`); if (opts === null || opts === void 0 ? void 0 : opts.draft) session.log.info(`📝 ${chalk.bold.yellow(`Making a draft submission, this will not be processed by "${venue}".`)}`); await confirmOrExit((opts === null || opts === void 0 ? void 0 : opts.draft) ? `Submit your draft to "${venue}" based on the contents of your local folder?` : `Start a new submission to "${venue}" based on the contents of your local folder?`, opts); } const checks = prepareChecksForSubmission(session, venue, kind); // // Process local folder and upload stuff // await performCleanRebuild(session, opts); session.log.info('🪩 Successfully built your work!'); // // run checks // let report; if (checks && checks.length > 0) { session.log.info(`🕵️‍♀️ Running checks...`); report = await runChecks(session, checks, submissionRuleChecks); const reportFilename = path.join(session.buildPath(), 'site', 'checks.json'); session.log.debug(`💼 adding check report to ${reportFilename} for upload...`); fs.writeFileSync(reportFilename, JSON.stringify({ venue, kind, report }, null, 2)); logCheckReport(session, report, false); session.log.info(`🏁 Checks completed`); } if (!(opts === null || opts === void 0 ? void 0 : opts.draft)) { await confirmOrExit(checks ? `Build and submission checks completed, are you happy to proceed with submission to "${venue}"?` : `Build completed, are you happy to proceed with submission to "${venue}"?`, opts); } // // Create a job to track the build and checks // session.log.info(`⛴ OK! Starting the submission process...`); let job = await postNewCliCheckJob(session, { site: venue, collection, kind, source, key, }, { checks: { venue, kind, report, }, }); try { job = await patchUpdateCliCheckJob(session, job.id, 'RUNNING', 'Uploading work files to cdn', { ...job.results, }); const cdn = (opts === null || opts === void 0 ? void 0 : opts.draft) ? session.config.tempCdnUrl : session.config.privateCdnUrl; let cdnKey; if (!process.env.DEV_CDN || process.env.DEV_CDN === 'false') { const uploadResult = await uploads.uploadToCdn(session, cdn, opts); cdnKey = uploadResult.cdnKey; } else if (process.env.DEV_CDN.match(CDN_KEY_RE)) { cdnKey = process.env.DEV_CDN; } else { cdnKey = DEV_CDN_KEY; } session.log.info(`🚀 ${chalk.bold.green(`Content uploaded with key ${cdnKey}`)}.`); job = await patchUpdateCliCheckJob(session, job.id, 'RUNNING', 'Creating new work and submission entry', { ...job.results, cdnKey, }); const buildUrl = `${session.config.adminUrl}/build/${job.id}`; session.log.info(`🤖 created a job to track this build: ${buildUrl}`); // // Create work and submission // if (existing) { await updateExistingSubmission(session, submitLog, venue, cdnKey, existing, job.id); } else { if (opts === null || opts === void 0 ? void 0 : opts.draft) { session.log.info(`${chalk.bold(`🖐 Making a draft submission`)}`); } else { session.log.info(`✨ Making a new submission`); } if (!kind) { session.log.error('🚨 No submission kind found.'); process.exit(1); } await createNewSubmission(session, submitLog, venue, collection, kind, cdn, cdnKey, job.id, key, opts); } session.log.debug(`generating a build artifact for the submission...`); if (!((_b = submitLog.work) === null || _b === void 0 ? void 0 : _b.id) || !((_c = submitLog.workVersion) === null || _c === void 0 ? void 0 : _c.id) || !((_d = submitLog.submission) === null || _d === void 0 ? void 0 : _d.id) || !((_e = submitLog.submissionVersion) === null || _e === void 0 ? void 0 : _e.id)) { // This is just a safety net - it will not be encountered unless we change // implementation of create/update submission functions or change the shape // of successful API responses. throw new Error(`work/submission ids not found from submission response`); } job = await patchUpdateCliCheckJob(session, job.id, 'COMPLETED', 'Submission completed', { ...job.results, submissionId: (_f = submitLog.submission) === null || _f === void 0 ? void 0 : _f.id, submissionVersionId: (_g = submitLog.submissionVersion) === null || _g === void 0 ? void 0 : _g.id, workId: (_h = submitLog.work) === null || _h === void 0 ? void 0 : _h.id, workVersionId: (_j = submitLog.workVersion) === null || _j === void 0 ? void 0 : _j.id, }); submitLog.key = key; submitLog.venue = venue; submitLog.kind = kind; submitLog.source = source; submitLog.report = report; submitLog.job = job; submitLog.buildUrl = buildUrl; session.log.info(chalk.bold.green(`🔗 build report url: ${buildUrl}`)); writeJsonLogs(session, 'curvenote.submit.json', submitLog); } catch (err) { await patchUpdateCliCheckJob(session, job.id, 'FAILED', 'Submission from CLI failed', { ...job.results, error: err.message, }); session.log.error(`📣 ${chalk.bold.red(err.message)}`); session.log.info('📨 Please contact support@curvenote.com'); writeJsonLogs(session, 'curvenote.submit.json', submitLog); process.exit(1); } }