@mdfriday/foundry 26.3.7 → 26.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -118,6 +118,7 @@ var init_project_metadata = __esm({
118
118
  contentLinks;
119
119
  staticLink;
120
120
  fileLink;
121
+ baseURLStructure;
121
122
  constructor(data) {
122
123
  this.id = data.id;
123
124
  this.name = data.name;
@@ -129,6 +130,7 @@ var init_project_metadata = __esm({
129
130
  this.contentLinks = data.contentLinks || [];
130
131
  this.staticLink = data.staticLink || null;
131
132
  this.fileLink = data.fileLink || null;
133
+ this.baseURLStructure = data.baseURLStructure || null;
132
134
  }
133
135
  static create(data) {
134
136
  if (!data.id || !data.name) {
@@ -168,6 +170,9 @@ var init_project_metadata = __esm({
168
170
  if (this.fileLink) {
169
171
  result.fileLink = this.fileLink;
170
172
  }
173
+ if (this.baseURLStructure) {
174
+ result.baseURLStructure = this.baseURLStructure;
175
+ }
171
176
  return result;
172
177
  }
173
178
  };
@@ -1337,6 +1342,80 @@ var init_project = __esm({
1337
1342
  }
1338
1343
  return linkDirs;
1339
1344
  }
1345
+ // ============================================================================
1346
+ // baseURL Structure Management
1347
+ // ============================================================================
1348
+ /**
1349
+ * 获取 baseURL(从 Hugo config.json)
1350
+ */
1351
+ async getBaseURL() {
1352
+ const config = await this.loadConfig();
1353
+ return config.baseURL || "/";
1354
+ }
1355
+ /**
1356
+ * 设置 baseURL(更新 Hugo config.json)
1357
+ */
1358
+ async setBaseURL(baseURL) {
1359
+ const config = await this.loadConfig();
1360
+ config.baseURL = baseURL;
1361
+ const configPath = import_path2.default.join(this.projectPath, "config.json");
1362
+ await import_fs.promises.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
1363
+ this.config = config;
1364
+ }
1365
+ /**
1366
+ * 解析 baseURL 路径信息
1367
+ */
1368
+ parseBaseURLPath(baseURL) {
1369
+ let normalized = baseURL.trim();
1370
+ if (normalized.startsWith("/")) {
1371
+ normalized = normalized.slice(1);
1372
+ }
1373
+ if (normalized.endsWith("/")) {
1374
+ normalized = normalized.slice(0, -1);
1375
+ }
1376
+ if (normalized === "") {
1377
+ return {
1378
+ isRoot: true,
1379
+ pathParts: [],
1380
+ parentParts: [],
1381
+ finalDirName: "",
1382
+ relativeToPublic: "."
1383
+ };
1384
+ }
1385
+ const parts = normalized.split("/").filter((p2) => p2 !== "");
1386
+ if (parts.length === 0) {
1387
+ return {
1388
+ isRoot: true,
1389
+ pathParts: [],
1390
+ parentParts: [],
1391
+ finalDirName: "",
1392
+ relativeToPublic: "."
1393
+ };
1394
+ }
1395
+ const depth = parts.length;
1396
+ const relativeToPublic = depth === 1 ? "." : "../".repeat(depth - 1).slice(0, -1);
1397
+ return {
1398
+ isRoot: false,
1399
+ pathParts: parts,
1400
+ parentParts: parts.slice(0, -1),
1401
+ finalDirName: parts[parts.length - 1],
1402
+ relativeToPublic
1403
+ };
1404
+ }
1405
+ /**
1406
+ * 获取当前 baseURL 结构信息
1407
+ */
1408
+ getBaseURLStructure() {
1409
+ return this.metadata.baseURLStructure || null;
1410
+ }
1411
+ /**
1412
+ * 更新 baseURL 结构记录
1413
+ */
1414
+ updateBaseURLStructure(info) {
1415
+ this.metadata = this.metadata.update({
1416
+ baseURLStructure: info
1417
+ });
1418
+ }
1340
1419
  };
1341
1420
  }
1342
1421
  });
@@ -3303,7 +3382,8 @@ var init_workspace_factory = __esm({
3303
3382
  });
3304
3383
  const defaultTheme = options.theme || "https://gohugo.net/quartz-theme.zip?version=1.2";
3305
3384
  const config = {
3306
- baseURL: "/",
3385
+ baseURL: options.baseURL || "/",
3386
+ // 新增:使用传入的 baseURL
3307
3387
  title: name,
3308
3388
  contentDir,
3309
3389
  publishDir,
@@ -3792,6 +3872,154 @@ var init_workspace_factory = __esm({
3792
3872
  // ============================================================================
3793
3873
  // Private Helpers
3794
3874
  // ============================================================================
3875
+ // ============================================================================
3876
+ // baseURL Structure Management
3877
+ // ============================================================================
3878
+ /**
3879
+ * 在 public/ 目录下创建 baseURL 访问路径结构
3880
+ *
3881
+ * 例如:baseURL = "/blog/" 时,在 public/ 下创建 blog/ -> . (symlink)
3882
+ */
3883
+ async setupBaseURLStructure(project) {
3884
+ if (!this.fileSystemRepo) {
3885
+ throw new Error("FileSystemRepository is required for baseURL structure");
3886
+ }
3887
+ const baseURL = await project.getBaseURL();
3888
+ const pathInfo = project.parseBaseURLPath(baseURL);
3889
+ const projectRoot = project.getPath();
3890
+ const publicDir = import_path3.default.join(projectRoot, "public");
3891
+ if (pathInfo.isRoot) {
3892
+ log5.debug("baseURL is root, no structure needed");
3893
+ return {
3894
+ serverRoot: publicDir,
3895
+ baseURL: "/",
3896
+ symlinkCreated: false
3897
+ };
3898
+ }
3899
+ log5.info("Creating baseURL structure in public/", {
3900
+ baseURL,
3901
+ pathParts: pathInfo.pathParts
3902
+ });
3903
+ let currentDir = publicDir;
3904
+ for (const dirName of pathInfo.parentParts) {
3905
+ currentDir = import_path3.default.join(currentDir, dirName);
3906
+ if (!await this.fileSystemRepo.exists(currentDir)) {
3907
+ await this.fileSystemRepo.createDirectory(currentDir, false);
3908
+ log5.debug(`Created parent directory: ${dirName}`);
3909
+ }
3910
+ }
3911
+ const symlinkPath = import_path3.default.join(currentDir, pathInfo.finalDirName);
3912
+ if (await this.fileSystemRepo.exists(symlinkPath)) {
3913
+ await this.fileSystemRepo.remove(symlinkPath, true);
3914
+ log5.debug(`Removed existing symlink: ${symlinkPath}`);
3915
+ }
3916
+ const symlinkResult = await this.fileSystemRepo.createSymlink(
3917
+ pathInfo.relativeToPublic,
3918
+ symlinkPath
3919
+ );
3920
+ if (!symlinkResult.success) {
3921
+ log5.error("Failed to create baseURL symlink", {
3922
+ target: pathInfo.relativeToPublic,
3923
+ link: symlinkPath,
3924
+ error: symlinkResult.error
3925
+ });
3926
+ throw new Error(`Failed to create baseURL structure: ${symlinkResult.error}`);
3927
+ }
3928
+ log5.info("baseURL structure created successfully", {
3929
+ symlinkPath: pathInfo.pathParts.join("/"),
3930
+ target: pathInfo.relativeToPublic
3931
+ });
3932
+ project.updateBaseURLStructure({
3933
+ baseURL,
3934
+ createdAt: Date.now(),
3935
+ pathParts: pathInfo.pathParts
3936
+ });
3937
+ await this.projectRepo.saveProjectMetadata(
3938
+ project.getPath(),
3939
+ project.getMetadata().toJSON()
3940
+ );
3941
+ return {
3942
+ serverRoot: publicDir,
3943
+ baseURL,
3944
+ symlinkCreated: true
3945
+ };
3946
+ }
3947
+ /**
3948
+ * 清理 public/ 下的 baseURL 路径结构
3949
+ */
3950
+ async cleanBaseURLStructure(project) {
3951
+ if (!this.fileSystemRepo) {
3952
+ return;
3953
+ }
3954
+ const structureInfo = project.getBaseURLStructure();
3955
+ if (!structureInfo || structureInfo.pathParts.length === 0) {
3956
+ return;
3957
+ }
3958
+ const projectRoot = project.getPath();
3959
+ const publicDir = import_path3.default.join(projectRoot, "public");
3960
+ log5.info("Cleaning baseURL structure in public/", {
3961
+ pathParts: structureInfo.pathParts
3962
+ });
3963
+ for (let i = structureInfo.pathParts.length; i > 0; i--) {
3964
+ const targetPath = import_path3.default.join(
3965
+ publicDir,
3966
+ ...structureInfo.pathParts.slice(0, i)
3967
+ );
3968
+ if (await this.fileSystemRepo.exists(targetPath)) {
3969
+ await this.fileSystemRepo.remove(targetPath, true);
3970
+ log5.debug(`Removed: ${structureInfo.pathParts.slice(0, i).join("/")}`);
3971
+ }
3972
+ if (i > 1) {
3973
+ const parentPath = import_path3.default.join(
3974
+ publicDir,
3975
+ ...structureInfo.pathParts.slice(0, i - 1)
3976
+ );
3977
+ if (await this.fileSystemRepo.exists(parentPath)) {
3978
+ const children = await this.fileSystemRepo.readDirectory(parentPath);
3979
+ if (children.length > 0) {
3980
+ break;
3981
+ }
3982
+ }
3983
+ }
3984
+ }
3985
+ project.updateBaseURLStructure(null);
3986
+ await this.projectRepo.saveProjectMetadata(
3987
+ project.getPath(),
3988
+ project.getMetadata().toJSON()
3989
+ );
3990
+ log5.info("baseURL structure cleaned");
3991
+ }
3992
+ /**
3993
+ * 确保 baseURL 结构正确
3994
+ * 检查 baseURL 是否变化,如需要则重新创建
3995
+ */
3996
+ async ensureBaseURLStructure(project) {
3997
+ const currentBaseURL = await project.getBaseURL();
3998
+ const structureInfo = project.getBaseURLStructure();
3999
+ const needRecreate = !structureInfo || structureInfo.baseURL !== currentBaseURL;
4000
+ if (needRecreate) {
4001
+ if (structureInfo) {
4002
+ log5.info("baseURL changed, recreating structure", {
4003
+ old: structureInfo.baseURL,
4004
+ new: currentBaseURL
4005
+ });
4006
+ await this.cleanBaseURLStructure(project);
4007
+ }
4008
+ const result = await this.setupBaseURLStructure(project);
4009
+ return {
4010
+ ...result,
4011
+ recreated: true
4012
+ };
4013
+ }
4014
+ return {
4015
+ serverRoot: import_path3.default.join(project.getPath(), "public"),
4016
+ baseURL: currentBaseURL,
4017
+ recreated: false
4018
+ };
4019
+ }
4020
+ // ============================================================================
4021
+ // Private Helpers
4022
+ // ============================================================================
3795
4023
  createSampleContent() {
3796
4024
  return `---
3797
4025
  title: Welcome to MDFriday
@@ -4059,6 +4287,29 @@ Please specify a project name with --project <name>`);
4059
4287
  createIdentityStorage(workspace) {
4060
4288
  return this.workspaceFactory.createIdentityStorage(workspace);
4061
4289
  }
4290
+ // ============================================================================
4291
+ // baseURL Structure Management
4292
+ // ============================================================================
4293
+ /**
4294
+ * 设置项目的 baseURL 路径结构
4295
+ * 在 public/ 目录下创建对应的 symlink 结构
4296
+ */
4297
+ async setupProjectBaseURLStructure(project) {
4298
+ return this.workspaceFactory.setupBaseURLStructure(project);
4299
+ }
4300
+ /**
4301
+ * 清理项目的 baseURL 路径结构
4302
+ */
4303
+ async cleanProjectBaseURLStructure(project) {
4304
+ return this.workspaceFactory.cleanBaseURLStructure(project);
4305
+ }
4306
+ /**
4307
+ * 确保项目的 baseURL 路径结构正确
4308
+ * 检查 baseURL 是否变化,如需要则重新创建
4309
+ */
4310
+ async ensureProjectBaseURLStructure(project) {
4311
+ return this.workspaceFactory.ensureBaseURLStructure(project);
4312
+ }
4062
4313
  };
4063
4314
  }
4064
4315
  });
@@ -8922,20 +9173,15 @@ var init_publish2 = __esm({
8922
9173
  }
8923
9174
  /**
8924
9175
  * Publish project to specified platform
9176
+ *
9177
+ * @param project - 项目实例
9178
+ * @param config - 已合并的发布配置(由调用方准备)
9179
+ * @param options - 发布选项
9180
+ * @param onProgress - 进度回调
8925
9181
  */
8926
- async publish(project, method, options, onProgress) {
8927
- log17.info(`Publishing project: ${project.getName()} to ${method}`);
9182
+ async publish(project, config, options, onProgress) {
9183
+ log17.info(`Publishing project: ${project.getName()} to ${config.type}`);
8928
9184
  try {
8929
- const workspace = await this.workspaceService.loadWorkspace();
8930
- const globalConfig = await workspace.getAllConfig();
8931
- const publishConfig = WorkspacePublishConfig.fromGlobalConfig(globalConfig);
8932
- let config;
8933
- if (options?.config) {
8934
- const mergedConfig = publishConfig.merge(options.config, method);
8935
- config = await this.getMethodConfig(mergedConfig, method);
8936
- } else {
8937
- config = await this.getMethodConfig(publishConfig, method);
8938
- }
8939
9185
  const publicDir = await project.getPublishDir();
8940
9186
  const publisher = this.publisherFactory.create(
8941
9187
  project.getId(),
@@ -8954,27 +9200,59 @@ var init_publish2 = __esm({
8954
9200
  const result = await publisher.publish(publicDir, publishOptions);
8955
9201
  return result;
8956
9202
  } catch (error) {
8957
- log17.error(`Publish failed: ${method}`, error);
9203
+ log17.error(`Publish failed: ${config.type}`, error);
8958
9204
  throw error;
8959
9205
  }
8960
9206
  }
8961
9207
  /**
8962
9208
  * Test connection
9209
+ *
9210
+ * @param project - 项目实例
9211
+ * @param config - 已合并的发布配置(由调用方准备)
8963
9212
  */
8964
- async testConnection(project, method) {
8965
- const workspace = await this.workspaceService.loadWorkspace();
9213
+ async testConnection(project, config) {
9214
+ log17.info(`Testing ${config.type} connection for project: ${project.getName()}`);
9215
+ try {
9216
+ const publisher = this.publisherFactory.create(
9217
+ project.getId(),
9218
+ project.getPath(),
9219
+ config
9220
+ );
9221
+ if (publisher.testConnection) {
9222
+ return await publisher.testConnection();
9223
+ }
9224
+ return { success: true };
9225
+ } catch (error) {
9226
+ log17.error(`Test connection failed: ${config.type}`, error);
9227
+ return {
9228
+ success: false,
9229
+ error: error.message
9230
+ };
9231
+ }
9232
+ }
9233
+ /**
9234
+ * 准备最终的发布配置
9235
+ *
9236
+ * 配置查找优先级(两级):
9237
+ * 1. 项目配置 (project.config.publish)
9238
+ * 2. 全局配置 (workspace global config)
9239
+ *
9240
+ * @param workspace - Workspace 实例
9241
+ * @param project - Project 实例
9242
+ * @param method - 发布方法
9243
+ * @returns 最终的发布配置
9244
+ */
9245
+ async prepareFinalConfig(workspace, project, method) {
8966
9246
  const globalConfig = await workspace.getAllConfig();
8967
- const publishConfig = WorkspacePublishConfig.fromGlobalConfig(globalConfig);
8968
- const config = await this.getMethodConfig(publishConfig, method);
8969
- const publisher = this.publisherFactory.create(
8970
- project.getId(),
8971
- project.getPath(),
8972
- config
8973
- );
8974
- if (publisher.testConnection) {
8975
- return await publisher.testConnection();
9247
+ let publishConfig = WorkspacePublishConfig.fromGlobalConfig(globalConfig);
9248
+ await project.loadConfig();
9249
+ const projectConfig = project.getConfig();
9250
+ if (projectConfig?.publish) {
9251
+ log17.debug("Merging project publish config");
9252
+ const projectPublishData = projectConfig.publish;
9253
+ publishConfig = publishConfig.merge(projectPublishData, method);
8976
9254
  }
8977
- return { success: true };
9255
+ return this.getMethodConfig(publishConfig, method, workspace);
8978
9256
  }
8979
9257
  // ============================================================================
8980
9258
  // Private Methods
@@ -8982,7 +9260,7 @@ var init_publish2 = __esm({
8982
9260
  /**
8983
9261
  * Get method-specific config from WorkspacePublishConfig
8984
9262
  */
8985
- async getMethodConfig(publishConfig, method) {
9263
+ async getMethodConfig(publishConfig, method, workspace) {
8986
9264
  switch (method) {
8987
9265
  case "ftp":
8988
9266
  const ftpConfig = publishConfig.getFTPConfig();
@@ -9016,15 +9294,14 @@ var init_publish2 = __esm({
9016
9294
  const licenseKey = mdConfig.licenseKey || "";
9017
9295
  if (!licenseKey) {
9018
9296
  throw new Error(
9019
- "\u274C MDFriday license key is required for publishing.\n\nDifferent licenses deploy to different servers.\nYou must explicitly configure or provide a license key:\n\n1\uFE0F\u20E3 Configure license globally (recommended):\n mdf config:set publish.mdfriday.licenseKey MDF-XXXX-XXXX-XXXX --global\n\n2\uFE0F\u20E3 Pass license via CLI:\n mdf publish mdfriday --license-key MDF-XXXX-XXXX-XXXX\n\n\u{1F4A1} Note: License login (mdf license:login) only gets TOKEN.\n Publishing requires explicitly setting a license key."
9297
+ "\u274C MDFriday license key is required for publishing.\n\nDifferent licenses deploy to different servers.\nYou must explicitly configure a license key:\n\nConfigure license globally (recommended):\n mdf config:set publish.mdfriday.licenseKey MDF-XXXX-XXXX-XXXX --global\n\nOr configure for project only:\n mdf config:set publish.mdfriday.licenseKey MDF-XXXX-XXXX-XXXX\n\n\u{1F4A1} Note: License login (mdf license:login) only gets TOKEN.\n Publishing requires explicitly setting a license key."
9020
9298
  );
9021
9299
  }
9022
- const workspace = await this.workspaceService.loadWorkspace();
9023
9300
  const { createIdentityService: createIdentityService2 } = await Promise.resolve().then(() => (init_container(), container_exports));
9024
9301
  let accessToken = null;
9025
9302
  let apiUrl = "https://app.mdfriday.com";
9026
9303
  try {
9027
- const identityService = await createIdentityService2();
9304
+ const identityService = await createIdentityService2(workspace.getInfo().path);
9028
9305
  await identityService.initialize();
9029
9306
  accessToken = identityService.getToken();
9030
9307
  const serverConfig = identityService.getServerConfig();
@@ -9037,33 +9314,30 @@ var init_publish2 = __esm({
9037
9314
  }
9038
9315
  if (!accessToken) {
9039
9316
  throw new Error(
9040
- "\u274C MDFriday access token not found.\n\nPlease login first to get an access token:\n\nOption 1 - Login with email/password:\n mdf auth:login --email <email> --password <password>\n\nOption 2 - Login with license key:\n mdf license:login --key MDF-XXXX-XXXX-XXXX"
9317
+ "\u274C Not authenticated.\n\nPlease login first:\n mdf auth:login\n\nOr use license login:\n mdf license:login --key YOUR-LICENSE-KEY"
9041
9318
  );
9042
9319
  }
9043
9320
  return {
9044
9321
  type: "mdfriday",
9045
- deploymentType: mdConfig.type || "share",
9046
- // Already merged with CLI override
9047
9322
  licenseKey,
9048
- // Required, from merged config
9049
- enabled: mdConfig.enabled,
9050
9323
  accessToken,
9051
- // From Identity Service
9052
- apiUrl
9053
- // From Identity Service (ServerConfig)
9324
+ apiUrl,
9325
+ deploymentType: mdConfig.type || "share",
9326
+ enabled: true
9054
9327
  };
9055
9328
  default:
9056
- throw new Error(`Unknown publish method: ${method}`);
9329
+ throw new Error(`Unsupported publish method: ${method}`);
9057
9330
  }
9058
9331
  }
9059
9332
  };
9060
9333
  }
9061
9334
  });
9062
9335
 
9063
- // internal/interfaces/cli/container.ts
9336
+ // internal/application/container.ts
9064
9337
  var container_exports = {};
9065
9338
  __export(container_exports, {
9066
9339
  createIdentityService: () => createIdentityService,
9340
+ createIdentityServiceForObsidian: () => createIdentityServiceForObsidian,
9067
9341
  createPublishAppService: () => createPublishAppService,
9068
9342
  createWorkspaceAppService: () => createWorkspaceAppService,
9069
9343
  createWorkspaceFactory: () => createWorkspaceFactory
@@ -9083,19 +9357,30 @@ function createWorkspaceAppService() {
9083
9357
  workspaceFactory
9084
9358
  });
9085
9359
  }
9086
- async function createIdentityService(projectPath) {
9360
+ async function createIdentityService(workspacePath) {
9087
9361
  const httpClient = new NodeHttpClient();
9088
9362
  const workspaceAppService = createWorkspaceAppService();
9089
- const workspace = await workspaceAppService.loadWorkspace(projectPath);
9363
+ const workspace = await workspaceAppService.loadWorkspace(workspacePath);
9090
9364
  const storageProvider = workspaceAppService.createIdentityStorage(workspace);
9091
9365
  const userFactory = new UserFactory({
9092
9366
  httpClient,
9093
9367
  storageProvider
9094
9368
  });
9095
- const identityAppService = new IdentityAppService({
9369
+ return new IdentityAppService({
9370
+ userFactory
9371
+ });
9372
+ }
9373
+ async function createIdentityServiceForObsidian(workspacePath, httpClient) {
9374
+ const workspaceAppService = createWorkspaceAppService();
9375
+ const workspace = await workspaceAppService.loadWorkspace(workspacePath);
9376
+ const storageProvider = workspaceAppService.createIdentityStorage(workspace);
9377
+ const userFactory = new UserFactory({
9378
+ httpClient,
9379
+ storageProvider
9380
+ });
9381
+ return new IdentityAppService({
9096
9382
  userFactory
9097
9383
  });
9098
- return identityAppService;
9099
9384
  }
9100
9385
  function createPublishAppService() {
9101
9386
  const { PublishAppService: PublishAppService2 } = (init_publish2(), __toCommonJS(publish_exports2));
@@ -9111,7 +9396,7 @@ function createPublishAppService() {
9111
9396
  );
9112
9397
  }
9113
9398
  var init_container = __esm({
9114
- "internal/interfaces/cli/container.ts"() {
9399
+ "internal/application/container.ts"() {
9115
9400
  "use strict";
9116
9401
  init_workspace2();
9117
9402
  init_workspace3();
@@ -9121,6 +9406,22 @@ var init_container = __esm({
9121
9406
  }
9122
9407
  });
9123
9408
 
9409
+ // internal/interfaces/cli/container.ts
9410
+ var container_exports2 = {};
9411
+ __export(container_exports2, {
9412
+ createIdentityService: () => createIdentityService,
9413
+ createIdentityServiceForObsidian: () => createIdentityServiceForObsidian,
9414
+ createPublishAppService: () => createPublishAppService,
9415
+ createWorkspaceAppService: () => createWorkspaceAppService,
9416
+ createWorkspaceFactory: () => createWorkspaceFactory
9417
+ });
9418
+ var init_container2 = __esm({
9419
+ "internal/interfaces/cli/container.ts"() {
9420
+ "use strict";
9421
+ init_container();
9422
+ }
9423
+ });
9424
+
9124
9425
  // internal/domain/config/type.ts
9125
9426
  var ConfigError, ErrConfigNotFound, ErrInvalidConfig, ErrWorkspaceNotFound, ErrConfigFileNotFound, ErrInvalidConfigFormat;
9126
9427
  var init_type4 = __esm({
@@ -50307,7 +50608,7 @@ __export(cli_exports, {
50307
50608
  module.exports = __toCommonJS(cli_exports);
50308
50609
 
50309
50610
  // internal/interfaces/cli/router.ts
50310
- init_container();
50611
+ init_container2();
50311
50612
 
50312
50613
  // internal/interfaces/cli/commands/workspace.ts
50313
50614
  init_log();
@@ -50508,16 +50809,20 @@ var ProjectNewCommand = class {
50508
50809
  createOptions.theme = options.theme;
50509
50810
  if (options.path)
50510
50811
  createOptions.projectPath = options.path;
50812
+ if (options.baseUrl)
50813
+ createOptions.baseURL = options.baseUrl;
50511
50814
  const project = await this.workspaceAppService.createProject(workspace, name, createOptions);
50512
50815
  const info = project.getInfo();
50513
50816
  const displayTheme = options.theme || "quartz (default)";
50817
+ const baseURL = options.baseUrl || "/";
50514
50818
  log19.info(`Created project: ${name}`);
50515
50819
  return {
50516
50820
  success: true,
50517
50821
  message: `\u2713 Project created: ${name}
50518
50822
  Path: ${info.path}
50519
50823
  Theme: ${displayTheme}
50520
- Language: ${options.language || defaultLanguage2}`,
50824
+ Language: ${options.language || defaultLanguage2}
50825
+ ` + (baseURL !== "/" ? ` Base URL: ${baseURL}` : ""),
50521
50826
  data: info
50522
50827
  };
50523
50828
  } catch (error) {
@@ -51410,11 +51715,19 @@ var BuildCommand = class {
51410
51715
  project.addBuildHistory(historyEntry);
51411
51716
  await this.workspaceAppService.saveProject(project);
51412
51717
  log82.info(`Build completed in ${duration}ms`);
51718
+ const structureInfo = await this.workspaceAppService.setupProjectBaseURLStructure(project);
51719
+ log82.debug("baseURL structure setup complete", structureInfo);
51413
51720
  let message = `\u2713 Built site in ${(duration / 1e3).toFixed(1)}s
51414
51721
  `;
51415
51722
  message += `\u2713 Project: ${project.getName()}
51416
51723
  `;
51417
51724
  message += `\u2713 Output: ${outputDir}`;
51725
+ if (structureInfo.symlinkCreated) {
51726
+ message += `
51727
+ \u2713 Base URL structure created: ${structureInfo.baseURL}`;
51728
+ message += `
51729
+ Run 'mdf serve' to preview at http://localhost:8080${structureInfo.baseURL}`;
51730
+ }
51418
51731
  if (options.parallel) {
51419
51732
  message += "\n\u2713 Used parallel processing";
51420
51733
  }
@@ -51437,7 +51750,8 @@ var BuildCommand = class {
51437
51750
  duration,
51438
51751
  outputDir,
51439
51752
  contentDirs,
51440
- snapshot: snapshotInfo
51753
+ snapshot: snapshotInfo,
51754
+ baseURL: structureInfo.baseURL
51441
51755
  }
51442
51756
  };
51443
51757
  } catch (error) {
@@ -52582,6 +52896,10 @@ var ServeCommand = class {
52582
52896
  const host = options.host || "localhost";
52583
52897
  const livereloadPort = options.livereloadPort || 35729;
52584
52898
  const enableLivereload = options.livereload !== false;
52899
+ const structureInfo = await this.workspaceAppService.ensureProjectBaseURLStructure(project);
52900
+ if (structureInfo.recreated) {
52901
+ log88.info("baseURL structure created/updated");
52902
+ }
52585
52903
  const projContentDirs = await project.getContentDirs();
52586
52904
  const contentDirs = project.getLinkDirs();
52587
52905
  const modulesDir = workspace.getModulesDir();
@@ -52602,17 +52920,7 @@ var ServeCommand = class {
52602
52920
  },
52603
52921
  onSuccess: async () => {
52604
52922
  if (options.publish && this.publishAppService) {
52605
- const publishOptions = {};
52606
- if (options.type)
52607
- publishOptions.type = options.type;
52608
- if (options.licenseKey)
52609
- publishOptions.licenseKey = options.licenseKey;
52610
- await this.autoPublish(
52611
- project,
52612
- options.publish,
52613
- options.publishDelay,
52614
- publishOptions
52615
- );
52923
+ await this.autoPublish(workspace, project, options.publish, options.publishDelay);
52616
52924
  }
52617
52925
  }
52618
52926
  };
@@ -52629,11 +52937,10 @@ var ServeCommand = class {
52629
52937
  if (!this.coordinator.isInitialized()) {
52630
52938
  throw new Error("Failed to initialize build coordinator");
52631
52939
  }
52632
- const liveReloadStatus = this.coordinator.getLiveReloadStatus();
52633
- const url = liveReloadStatus.url || `http://${host}:${port}`;
52634
- log88.info(`Server running at ${url}`);
52940
+ const serverUrl = `http://${host}:${port}${structureInfo.baseURL}`;
52941
+ log88.info(`Server running at ${serverUrl}`);
52635
52942
  if (options.open) {
52636
- await this.openBrowser(url);
52943
+ await this.openBrowser(serverUrl);
52637
52944
  }
52638
52945
  let isShuttingDown = false;
52639
52946
  const cleanup = async () => {
@@ -52662,14 +52969,14 @@ var ServeCommand = class {
52662
52969
  process.on("SIGHUP", cleanup);
52663
52970
  return {
52664
52971
  success: true,
52665
- message: `\u2713 Server running at ${url}
52972
+ message: `\u2713 Server running at ${serverUrl}
52666
52973
  ` + (enableLivereload ? `\u2713 LiveReload enabled on port ${livereloadPort}
52667
52974
  ` : "") + (options.publish ? `\u2713 Auto-publish enabled (${options.publish})
52668
52975
  ` : "") + `\u2713 Watching for changes...
52669
52976
 
52670
52977
  Press Ctrl+C to stop`,
52671
52978
  data: {
52672
- url,
52979
+ url: serverUrl,
52673
52980
  port,
52674
52981
  host,
52675
52982
  livereloadPort: enableLivereload ? livereloadPort : void 0,
@@ -52713,7 +53020,7 @@ Press Ctrl+C to stop`,
52713
53020
  /**
52714
53021
  * 自动发布(带防抖)
52715
53022
  */
52716
- async autoPublish(project, method, delayMs, options) {
53023
+ async autoPublish(workspace, project, method, delayMs) {
52717
53024
  if (this.publishTimer) {
52718
53025
  clearTimeout(this.publishTimer);
52719
53026
  }
@@ -52728,17 +53035,11 @@ Press Ctrl+C to stop`,
52728
53035
  clearOnComplete: true
52729
53036
  });
52730
53037
  const startTime = Date.now();
52731
- const publishOptions = { incremental: true };
52732
- if (options && (options.type || options.licenseKey)) {
52733
- publishOptions.config = {
52734
- type: options.type,
52735
- licenseKey: options.licenseKey
52736
- };
52737
- }
53038
+ const config = await this.publishAppService.prepareFinalConfig(workspace, project, method);
52738
53039
  const result = await this.publishAppService.publish(
52739
53040
  project,
52740
- method,
52741
- publishOptions,
53041
+ config,
53042
+ { incremental: true },
52742
53043
  (progress) => {
52743
53044
  progressBar.update(progress.percentage, progress.message);
52744
53045
  }
@@ -52919,7 +53220,7 @@ var SnapshotDeleteCommand = class {
52919
53220
  };
52920
53221
 
52921
53222
  // internal/interfaces/cli/commands/auth.ts
52922
- init_container();
53223
+ init_container2();
52923
53224
  init_log();
52924
53225
  var log90 = getDomainLogger("auth-command", { component: "cli" });
52925
53226
  async function getIdentityService() {
@@ -54068,18 +54369,22 @@ var PublishCommand = class {
54068
54369
  description = "Publish project to hosting platform";
54069
54370
  async execute(args, options) {
54070
54371
  try {
54071
- const method = args[0];
54372
+ const subcommand = args[0];
54373
+ if (subcommand === "test") {
54374
+ return await this.executeTest(args.slice(1), options);
54375
+ }
54376
+ const method = subcommand;
54072
54377
  if (!method || !["ftp", "netlify", "mdfriday"].includes(method)) {
54073
54378
  return {
54074
54379
  success: false,
54075
- message: "Please specify publish method: ftp, netlify, or mdfriday\n\nUsage:\n mdf publish ftp [options]\n mdf publish netlify [options]\n mdf publish mdfriday [options]\n\nOptions:\n --project <name> Project to publish\n --force Force full publish (ignore incremental)\n --verbose Show detailed progress\n\nFTP Options:\n --host <host> FTP server host\n --username <user> FTP username\n --password <pass> FTP password\n --remote-path <path> Remote directory path\n\nExamples:\n mdf publish ftp\n mdf publish ftp --project my-blog\n mdf publish ftp --host ftp.example.com --username admin"
54380
+ message: "Please specify publish method: ftp, netlify, or mdfriday\n\nUsage:\n mdf publish ftp [options]\n mdf publish netlify [options]\n mdf publish mdfriday [options]\n mdf publish test <method>\n\nOptions:\n --project <name> Project to publish\n --force Force full publish (ignore incremental)\n --verbose Show detailed progress\n\nConfiguration:\n All publish settings are configured via config command:\n mdf config:set publish.<method>.<key> <value> --global\n mdf config:set publish.<method>.<key> <value> (project-level)\n\nExamples:\n mdf publish ftp\n mdf publish ftp --project my-blog --force\n mdf publish test ftp"
54076
54381
  };
54077
54382
  }
54078
54383
  const { workspace, project } = await this.workspaceAppService.loadWorkspaceAndProject(
54079
54384
  options.project
54080
54385
  );
54081
54386
  log92.info(`Publishing project: ${project.getName()} to ${method}`);
54082
- const configOverride = this.parseConfigOverride(method, options);
54387
+ const config = await this.publishService.prepareFinalConfig(workspace, project, method);
54083
54388
  const progressBar = new ProgressBar({
54084
54389
  width: 30,
54085
54390
  showPercentage: true,
@@ -54087,17 +54392,10 @@ var PublishCommand = class {
54087
54392
  clearOnComplete: true
54088
54393
  });
54089
54394
  const startTime = Date.now();
54090
- const appPublishOptions = {};
54091
- if (options.force) {
54092
- appPublishOptions.force = true;
54093
- }
54094
- if (configOverride) {
54095
- appPublishOptions.config = configOverride;
54096
- }
54097
54395
  const result = await this.publishService.publish(
54098
54396
  project,
54099
- method,
54100
- appPublishOptions,
54397
+ config,
54398
+ options.force ? { force: true } : {},
54101
54399
  (progress) => {
54102
54400
  if (options.verbose) {
54103
54401
  console.log(
@@ -54157,40 +54455,48 @@ var PublishCommand = class {
54157
54455
  }
54158
54456
  }
54159
54457
  /**
54160
- * Parse config override from command line options
54458
+ * Execute test connection command
54161
54459
  */
54162
- parseConfigOverride(method, options) {
54163
- switch (method) {
54164
- case "ftp":
54165
- if (options.host || options.username) {
54166
- return {
54167
- host: options.host,
54168
- port: options.port || 21,
54169
- username: options.username,
54170
- password: options.password,
54171
- remotePath: options.remotePath || "/",
54172
- secure: options.secure !== false
54173
- };
54174
- }
54175
- break;
54176
- case "netlify":
54177
- if (options.siteId || options.token) {
54178
- return {
54179
- siteId: options.siteId,
54180
- accessToken: options.token
54181
- };
54182
- }
54183
- break;
54184
- case "mdfriday":
54185
- if (options.type || options.licenseKey) {
54186
- return {
54187
- type: options.type,
54188
- licenseKey: options.licenseKey
54189
- };
54190
- }
54191
- break;
54460
+ async executeTest(args, options) {
54461
+ try {
54462
+ const method = args[0];
54463
+ if (!method || !["ftp", "netlify", "mdfriday"].includes(method)) {
54464
+ return {
54465
+ success: false,
54466
+ message: "Please specify publish method to test: ftp, netlify, or mdfriday\n\nUsage:\n mdf publish test ftp\n mdf publish test netlify\n mdf publish test mdfriday\n\nOptions:\n --project <name> Project to test (optional)\n\nConfiguration:\n All publish settings are configured via config command:\n mdf config:set publish.<method>.<key> <value> --global\n mdf config:set publish.<method>.<key> <value> (project-level)\n\nExamples:\n mdf publish test ftp\n mdf publish test netlify"
54467
+ };
54468
+ }
54469
+ const { workspace, project } = await this.workspaceAppService.loadWorkspaceAndProject(
54470
+ options.project
54471
+ );
54472
+ log92.info(`Testing ${method} connection for project: ${project.getName()}`);
54473
+ const config = await this.publishService.prepareFinalConfig(workspace, project, method);
54474
+ console.log(`
54475
+ Testing ${method.toUpperCase()} connection...`);
54476
+ console.log("Configuration priority: Project config > Global config\n");
54477
+ const result = await this.publishService.testConnection(project, config);
54478
+ if (result.success) {
54479
+ return {
54480
+ success: true,
54481
+ message: `
54482
+ \u2713 ${method.toUpperCase()} connection test passed!`
54483
+ };
54484
+ } else {
54485
+ return {
54486
+ success: false,
54487
+ message: `
54488
+ \u2717 ${method.toUpperCase()} connection test failed: ${result.error}`,
54489
+ error: new Error(result.error)
54490
+ };
54491
+ }
54492
+ } catch (error) {
54493
+ log92.error("Test connection command failed", error);
54494
+ return {
54495
+ success: false,
54496
+ message: `Test connection failed: ${error.message}`,
54497
+ error
54498
+ };
54192
54499
  }
54193
- return void 0;
54194
54500
  }
54195
54501
  };
54196
54502
 
@@ -54213,7 +54519,7 @@ var CommandRegistry = class {
54213
54519
  this.register(new ProjectShowCommand(workspaceAppService));
54214
54520
  this.register(new ProjectDeleteCommand(workspaceAppService));
54215
54521
  this.register(new BuildCommand(workspaceAppService));
54216
- const { createPublishAppService: createPublishAppService2 } = (init_container(), __toCommonJS(container_exports));
54522
+ const { createPublishAppService: createPublishAppService2 } = (init_container2(), __toCommonJS(container_exports2));
54217
54523
  const publishAppService = createPublishAppService2();
54218
54524
  this.register(new PublishCommand(publishAppService, workspaceAppService));
54219
54525
  this.register(new ServeCommand(workspaceAppService, publishAppService));
@@ -54380,6 +54686,7 @@ Commands:
54380
54686
  --source-file <path> Create from single file (uses note theme by default)
54381
54687
  --theme <url> Theme URL (default: quartz)
54382
54688
  --language <lang> Default language (default: en)
54689
+ --base-url <url> Base URL for the site (e.g., /blog/)
54383
54690
  project list List all projects
54384
54691
  project show [name] Show project details
54385
54692
  project delete <name> Delete a project
@@ -54488,6 +54795,8 @@ Examples:
54488
54795
 
54489
54796
  # Project
54490
54797
  mdf project new my-blog Create a new project
54798
+ mdf project new my-blog --base-url /blog/
54799
+ Create project with custom base URL
54491
54800
  mdf project new my-blog --source ~/Documents/my-blog
54492
54801
  Create project from existing folder (auto-detect content/static)
54493
54802
  mdf project new my-note --source-file ~/Documents/my-note.md
@@ -54562,7 +54871,7 @@ For more information, visit: https://help.mdfriday.com
54562
54871
  * Show version
54563
54872
  */
54564
54873
  showVersion() {
54565
- const version = "26.3.7";
54874
+ const version = "26.3.9";
54566
54875
  return {
54567
54876
  success: true,
54568
54877
  message: `MDFriday CLI v${version}`