UNPKG

@lark-project/cli

Version:

飞书项目插件开发工具

137 lines (136 loc) 8.56 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateProject = void 0; const path_1 = __importDefault(require("path")); const fs_extra_1 = require("fs-extra"); const get_plugin_info_1 = require("../../../api/get-plugin-info"); const local_plugin_config_1 = require("../../../local-plugin-config"); const with_loading_spinner_1 = require("../../../utils/with-loading-spinner"); const download_codebase_1 = require("../init/download-codebase"); const logger_1 = require("../../../utils/logger"); const utils_1 = require("../../../v1/utils"); const file_1 = require("../utils/file"); const apply_local_point_config_1 = require("../../../api/tools/apply-local-point-config"); const write_local_point_config_1 = require("../../../utils/write-local-point-config"); const validate_runtime_urls_1 = require("../../../utils/validate-runtime-urls"); const local_config_1 = require("../local-config"); const downloadTemplate = async (templateTargetDir) => { await (0, with_loading_spinner_1.withLoadingSpinner)(download_codebase_1.downloadCodeBase, 'Code template downloading...', templateTargetDir, 'react-initialized'); }; const updateProject = async (payload) => { // Track local-sync success so the "next: publish" hint emits only after the // whole command (including fall-through template pull) finishes — emitting // mid-flow risks a misleading next-step if downstream steps then fail. let syncedLocal = false; const emitPublishHintIfSynced = () => { if (syncedLocal) { process.stderr.write('next: run `lpm publish` to ship a new version with this configuration.\n'); } }; if ((payload === null || payload === void 0 ? void 0 : payload.source_type) === 'local') { logger_1.logger.info('Syncing local configuration to remote...'); try { const localPluginConfig = (0, local_plugin_config_1.getLocalPluginConfig)(); if (!localPluginConfig) { logger_1.logger.error('Please init project first'); return; } const localFeatures = await (0, write_local_point_config_1.getLocalPointConfig)(); // Runtime URL check before pushing to remote. Fabricated-looking // placeholders are NOT blocked — we only warn (the user swaps in the real // URL later / in the developer console). Structurally invalid URLs // (missing / empty / not http(s)) still hard-block. const { invalid, placeholders } = (0, validate_runtime_urls_1.splitViolations)((0, validate_runtime_urls_1.validateRuntimeUrls)(localFeatures).violations); if (placeholders.length > 0) { process.stderr.write((0, validate_runtime_urls_1.formatPlaceholderNotice)(placeholders) + '\n'); } if (invalid.length > 0) { process.stderr.write((0, validate_runtime_urls_1.formatViolations)(invalid) + '\n'); process.exit(1); } // Pre-push deletion gate (the irreversible action). If the local config drops // points still present on remote, pushing would delete them — block unless the // caller explicitly confirmed with --allow-delete. This is the load-bearing gate: // it does not trust that `local-config diff` was run first. If the remote diff // can't be computed (network), it stays advisory — the push itself hits the same // backend and will fail there rather than silently deleting. let deletions = []; try { deletions = (await (0, local_config_1.computeLocalVsRemoteDiff)()).filter(d => d.status === 'deleted'); } catch (diffErr) { // Diff fetch is best-effort; a network failure here must not block the push // (the push hits the same backend and will surface the error itself). logger_1.logger.debug('Pre-push deletion check failed (non-fatal):', diffErr); } // exit outside the fetch try/catch so the block is never swallowed as a "fetch failure". if (deletions.length > 0 && !payload.allow_delete) { (0, local_config_1.writeDeletionBlockNotice)(deletions); process.exit(2); } await (0, apply_local_point_config_1.applyLocalPointConfig)({ pluginId: localPluginConfig.pluginId, siteDomain: localPluginConfig.siteDomain, pointInfoMap: localFeatures, saveSnapshot: payload.save_snapshot, caseId: payload.case_id, }); logger_1.logger.success('Local configuration synced successfully.'); syncedLocal = true; // fall through to remote pull logic: pushes + pulls templates in one command // (the "next: publish" hint is emitted at the end of this function, after // fall-through completes — so a downstream failure doesn't leave a stale hint.) } catch (error) { logger_1.logger.error('Failed to sync local configuration:', error.message); process.exit(1); } } // config-only workspace (no ./src — no frontend toolchain): nothing to scaffold // or pull. Skip the template download + resource-code generation fall-through so // we don't write .template/ or src/features/ into a config-only handle. Detection // is automatic (a normal v2 project always has src/), so the caller never needs a // flag — keeps the skill's push step a single command. // cwd is the project root here (the dispatcher's plugin-project guard enforces it), // so this cwd-based check is not subdir-safe by design and doesn't need to be. if (!(0, fs_extra_1.existsSync)(path_1.default.join(process.cwd(), 'src'))) { emitPublishHintIfSynced(); return; } const templateTargetDir = path_1.default.join(process.cwd(), '.template'); await downloadTemplate(templateTargetDir); logger_1.logger.info('Start fetching project latest data...'); const localConfig = await (0, local_plugin_config_1.getLocalPluginConfig)(); if (!localConfig) { // No publish hint here even if the local sync succeeded above: reaching this // means the plugin config vanished mid-command — an anomalous state where a // "next: publish" hint would mislead more than help. logger_1.logger.error('Please init project first'); return; } const remoteConfig = await (0, get_plugin_info_1.getPluginInfo)(localConfig.pluginId, localConfig.pluginSecret, localConfig.siteDomain); const { list, newResourceRelation, syncResourceIds, unSyncResourceIds, needRemoveResourceIds } = await (0, file_1.generateResourceCode)({ remoteConfig, localConfig, targetDir: process.cwd(), siteDomain: localConfig.siteDomain, }); if (!list.length) { logger_1.logger.info('No new features'); emitPublishHintIfSynced(); return; } await (0, with_loading_spinner_1.withLoadingSpinner)(async () => { await Promise.all(list); (0, local_plugin_config_1.setLocalPluginConfig)(Object.assign(Object.assign({}, localConfig), { pluginSecret: (0, utils_1.encrypt)(localConfig.pluginSecret), resources: localConfig.resources.concat(newResourceRelation) })); }, 'Pulling code templates for the new features is in progress...'); await (0, fs_extra_1.remove)(templateTargetDir); (syncResourceIds === null || syncResourceIds === void 0 ? void 0 : syncResourceIds.length) && logger_1.logger.info(`Some new features were synchronized successfully and sample templates were generated:\n${syncResourceIds.join('\n')}`); (unSyncResourceIds === null || unSyncResourceIds === void 0 ? void 0 : unSyncResourceIds.length) && logger_1.logger.error(`Some features need to manually specify the entry file, which needs to be handled in plugin.config.json:\n${unSyncResourceIds.join('\n')}`); (needRemoveResourceIds === null || needRemoveResourceIds === void 0 ? void 0 : needRemoveResourceIds.length) && logger_1.logger.warn(`Some features were removed and need to be manually removed from plugin.config.json:\n${needRemoveResourceIds.join('\n')}`); emitPublishHintIfSynced(); }; exports.updateProject = updateProject;