@lark-project/cli
Version:
飞书项目插件开发工具
137 lines (136 loc) • 8.56 kB
JavaScript
;
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;