@pactor-app/module-pack 1.55.0 → 1.56.0

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/build.ts
2
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, renameSync, rmSync as rmSync2 } from "fs";
3
- import { dirname as dirname3, join as join9, resolve as resolve4 } from "path";
2
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync2, renameSync, rmSync as rmSync2 } from "fs";
3
+ import { dirname as dirname3, join as join10, resolve as resolve5 } from "path";
4
4
 
5
5
  // src/bundle-client.ts
6
6
  import { existsSync, mkdirSync, readFileSync } from "fs";
@@ -117,9 +117,60 @@ function parseJsonc(text) {
117
117
  return JSON.parse(out.replace(/,(\s*[}\]])/g, "$1"));
118
118
  }
119
119
 
120
+ // src/bundle-server.ts
121
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
122
+ import { join as join2, resolve as resolve2, sep } from "path";
123
+ import { buildSync as buildSync2 } from "esbuild";
124
+ var SERVER_EXTERNALS = [
125
+ "react",
126
+ "react-dom",
127
+ "react-dom/*",
128
+ "next",
129
+ "next/*",
130
+ "pactor-app",
131
+ "pactor-app/*",
132
+ "@pactor-app/*"
133
+ ];
134
+ var SERVER_BUNDLE_FILE = "index.mjs";
135
+ var ServerBundleError = class extends Error {
136
+ };
137
+ function bundleServer(options) {
138
+ const projectRoot = resolve2(options.projectDir);
139
+ const entry = resolve2(projectRoot, options.serverEntry);
140
+ if (!entry.startsWith(`${projectRoot}${sep}`)) {
141
+ throw new ServerBundleError(
142
+ `server \u5165\u53E3\u5FC5\u987B\u4F4D\u4E8E\u5DE5\u7A0B\u6839\u5185: ${options.serverEntry}`
143
+ );
144
+ }
145
+ if (!existsSync2(entry)) {
146
+ throw new ServerBundleError(`server \u5165\u53E3\u4E0D\u5B58\u5728: ${options.serverEntry}`);
147
+ }
148
+ mkdirSync2(options.outDir, { recursive: true });
149
+ buildSync2({
150
+ entryPoints: [entry],
151
+ bundle: true,
152
+ format: "esm",
153
+ platform: "node",
154
+ target: "node20",
155
+ outdir: options.outDir,
156
+ entryNames: "index",
157
+ outExtension: { ".js": ".mjs" },
158
+ sourcemap: true,
159
+ alias: tsconfigAliases(projectRoot),
160
+ external: [...SERVER_EXTERNALS],
161
+ logLevel: "silent"
162
+ });
163
+ if (!existsSync2(join2(options.outDir, SERVER_BUNDLE_FILE))) {
164
+ throw new ServerBundleError(
165
+ `server \u6253\u5305\u672A\u4EA7\u51FA ${SERVER_BUNDLE_FILE}\uFF08\u8BF7\u68C0\u67E5 serverEntry \u4E0E esbuild \u914D\u7F6E\uFF09`
166
+ );
167
+ }
168
+ return { bundled: true, entry: SERVER_BUNDLE_FILE };
169
+ }
170
+
120
171
  // src/collect.ts
121
172
  import { readdirSync } from "fs";
122
- import { join as join2, relative } from "path";
173
+ import { join as join3, relative } from "path";
123
174
  function collectModuleSource(yamlRoot) {
124
175
  const pages = /* @__PURE__ */ new Map();
125
176
  const layouts = [];
@@ -127,7 +178,7 @@ function collectModuleSource(yamlRoot) {
127
178
  const warnings = [];
128
179
  const walk = (dir) => {
129
180
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
130
- const full = join2(dir, entry.name);
181
+ const full = join3(dir, entry.name);
131
182
  if (entry.isDirectory()) {
132
183
  walk(full);
133
184
  continue;
@@ -164,26 +215,26 @@ function collectModuleSource(yamlRoot) {
164
215
  import { execFileSync } from "child_process";
165
216
  import {
166
217
  appendFileSync,
167
- existsSync as existsSync2,
168
- mkdirSync as mkdirSync2,
218
+ existsSync as existsSync3,
219
+ mkdirSync as mkdirSync3,
169
220
  mkdtempSync,
170
221
  readFileSync as readFileSync2,
171
222
  rmSync,
172
223
  writeFileSync
173
224
  } from "fs";
174
225
  import { tmpdir } from "os";
175
- import { dirname as dirname2, join as join3 } from "path";
226
+ import { dirname as dirname2, join as join4 } from "path";
176
227
  function compileTailwind(options) {
177
228
  const warnings = [];
178
229
  if (!options.tailwind.enabled) {
179
230
  return { compiled: false, warnings };
180
231
  }
181
- const work = mkdtempSync(join3(tmpdir(), "pactor-tailwind-"));
232
+ const work = mkdtempSync(join4(tmpdir(), "pactor-tailwind-"));
182
233
  try {
183
234
  let input;
184
235
  if (options.tailwind.input !== void 0) {
185
- input = join3(options.projectDir, options.tailwind.input);
186
- if (!existsSync2(input)) {
236
+ input = join4(options.projectDir, options.tailwind.input);
237
+ if (!existsSync3(input)) {
187
238
  warnings.push(`tailwind.input \u4E0D\u5B58\u5728: ${options.tailwind.input}\uFF0C\u8DF3\u8FC7\u9884\u7F16\u8BD1`);
188
239
  return { compiled: false, warnings };
189
240
  }
@@ -191,21 +242,21 @@ function compileTailwind(options) {
191
242
  const scan = (options.tailwind.scan ?? options.scanDirs).map(
192
243
  (dir) => `@source "${dir.replace(/\\/g, "/")}";`
193
244
  );
194
- input = join3(work, "input.css");
245
+ input = join4(work, "input.css");
195
246
  writeFileSync(input, `@import "tailwindcss";
196
247
  ${scan.join("\n")}
197
248
  `, "utf8");
198
249
  }
199
- const out = join3(work, "tailwind.css");
250
+ const out = join4(work, "tailwind.css");
200
251
  execFileSync(
201
252
  "npx",
202
253
  ["--no-install", "tailwindcss", "--input", input, "--output", out],
203
254
  { cwd: options.projectDir, stdio: "pipe" }
204
255
  );
205
- if (existsSync2(out)) {
256
+ if (existsSync3(out)) {
206
257
  const css = readFileSync2(out, "utf8");
207
258
  if (css.trim().length > 0) {
208
- mkdirSync2(dirname2(options.cssFile), { recursive: true });
259
+ mkdirSync3(dirname2(options.cssFile), { recursive: true });
209
260
  appendFileSync(
210
261
  options.cssFile,
211
262
  `
@@ -228,8 +279,8 @@ ${css}`,
228
279
  }
229
280
 
230
281
  // src/config.ts
231
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
232
- import { join as join4, resolve as resolve2 } from "path";
282
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
283
+ import { join as join5, resolve as resolve3 } from "path";
233
284
 
234
285
  // src/profiles/innospots-web-shell.ts
235
286
  var base = {
@@ -238,7 +289,8 @@ var base = {
238
289
  stripMenuPaths: true,
239
290
  rewriteNavigation: { basePath: true, excludeApiUrls: true },
240
291
  rewriteAssetUrls: (id, version) => `/modules/${id}/${version}/`,
241
- forbidRootBasePath: true
292
+ forbidRootBasePath: true,
293
+ moduleServerApi: false
242
294
  };
243
295
  var innospotsWebShellV1 = {
244
296
  ...base,
@@ -250,15 +302,22 @@ var innospotsWebShellV2 = {
250
302
  version: 2,
251
303
  forceInlinePages: false
252
304
  };
305
+ var innospotsWebShellV3 = {
306
+ ...base,
307
+ version: 3,
308
+ forceInlinePages: false,
309
+ moduleServerApi: true
310
+ };
253
311
 
254
312
  // src/profiles/index.ts
255
313
  var ProfileError = class extends Error {
256
314
  };
257
315
  var registry = /* @__PURE__ */ new Map([
258
316
  [`${innospotsWebShellV1.name}@${innospotsWebShellV1.version}`, innospotsWebShellV1],
259
- [`${innospotsWebShellV2.name}@${innospotsWebShellV2.version}`, innospotsWebShellV2]
317
+ [`${innospotsWebShellV2.name}@${innospotsWebShellV2.version}`, innospotsWebShellV2],
318
+ [`${innospotsWebShellV3.name}@${innospotsWebShellV3.version}`, innospotsWebShellV3]
260
319
  ]);
261
- var DEFAULT_TARGET = `${innospotsWebShellV2.name}@${innospotsWebShellV2.version}`;
320
+ var DEFAULT_TARGET = `${innospotsWebShellV3.name}@${innospotsWebShellV3.version}`;
262
321
  function listProfiles() {
263
322
  return [...registry.keys()];
264
323
  }
@@ -282,8 +341,8 @@ var CONFIG_DEFAULTS = {
282
341
  outDir: "dist-module"
283
342
  };
284
343
  function loadModuleConfig(projectDir, configPath) {
285
- const file = configPath ? resolve2(projectDir, configPath) : join4(projectDir, CONFIG_FILE);
286
- if (!existsSync3(file)) {
344
+ const file = configPath ? resolve3(projectDir, configPath) : join5(projectDir, CONFIG_FILE);
345
+ if (!existsSync4(file)) {
287
346
  throw new ConfigError(`\u7F3A\u5C11\u914D\u7F6E\u6587\u4EF6: ${file}\uFF08\u53EF\u5148\u6267\u884C pactor module init \u751F\u6210\uFF09`);
288
347
  }
289
348
  let raw;
@@ -304,19 +363,32 @@ function loadModuleConfig(projectDir, configPath) {
304
363
  if (!basePath.startsWith("/")) basePath = `/${basePath}`;
305
364
  if (basePath.length > 1 && basePath.endsWith("/")) basePath = basePath.slice(0, -1);
306
365
  const actions = raw.actions === void 0 ? [] : readStringArray(raw.actions, "actions", file);
307
- const demo = isRecord(raw.demo) ? raw.demo : {};
366
+ if (raw.demo !== void 0) {
367
+ throw new ConfigError(
368
+ `${file}: demo.public \u5DF2\u79FB\u9664\uFF0C\u8BF7\u6539\u7528 "access": { "default": "public" }\uFF08\u7F3A\u7701\u586B\u5145\u8BED\u4E49\uFF0C\u4E0D\u8986\u76D6\u663E\u5F0F permission\uFF09`
369
+ );
370
+ }
371
+ const access = isRecord(raw.access) ? raw.access : {};
372
+ const accessDefault = readString(access, "default", file, "access") ?? "authenticated";
373
+ if (accessDefault !== "public" && accessDefault !== "authenticated") {
374
+ throw new ConfigError(`${file}: access.default \u53EA\u652F\u6301 "public" | "authenticated"`);
375
+ }
308
376
  const tailwind = isRecord(raw.tailwind) ? raw.tailwind : {};
309
377
  const rewrite = isRecord(raw.rewrite) ? raw.rewrite : {};
378
+ const serverEntry = readString(raw, "serverEntry", file);
379
+ const apiPrefixes = raw.apiPrefixes === void 0 ? void 0 : readStringArray(raw.apiPrefixes, "apiPrefixes", file);
380
+ validateServerConfig({ id, serverEntry, apiPrefixes, file });
310
381
  return {
311
382
  id,
312
383
  name,
313
384
  basePath,
314
385
  yamlRoot: readString(raw, "yamlRoot", file) ?? CONFIG_DEFAULTS.yamlRoot,
315
386
  clientEntry: readString(raw, "clientEntry", file) ?? CONFIG_DEFAULTS.clientEntry,
387
+ ...serverEntry !== void 0 ? { serverEntry, apiPrefixes } : {},
316
388
  actions,
317
389
  publicDir: readString(raw, "publicDir", file) ?? CONFIG_DEFAULTS.publicDir,
318
390
  target: readString(raw, "target", file) ?? CONFIG_DEFAULTS.target,
319
- demo: { public: readBoolean(demo, "public", file, "demo") ?? false },
391
+ access: { default: accessDefault },
320
392
  tailwind: {
321
393
  enabled: readBoolean(tailwind, "enabled", file, "tailwind") ?? false,
322
394
  input: readString(tailwind, "input", file, "tailwind"),
@@ -354,20 +426,67 @@ function readStringArray(value, label, file) {
354
426
  }
355
427
  return value;
356
428
  }
429
+ var URL_SAFE_MODULE_ID_RE = /^[A-Za-z0-9._~-]+$/;
430
+ var API_PREFIX_RE = /^\/api\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/;
431
+ var RESERVED_API_PREFIXES = ["/api/modules", "/api/runtime"];
432
+ function validateServerConfig(input) {
433
+ const { id, serverEntry, apiPrefixes, file } = input;
434
+ if (serverEntry === void 0 && apiPrefixes === void 0) return;
435
+ if (serverEntry === void 0 || apiPrefixes === void 0) {
436
+ throw new ConfigError(`${file}: serverEntry \u4E0E apiPrefixes \u5FC5\u987B\u540C\u65F6\u914D\u7F6E\uFF08\u7F3A\u4E00\u4E0D\u53EF\uFF09`);
437
+ }
438
+ if (apiPrefixes.length === 0) {
439
+ throw new ConfigError(`${file}: \u914D\u7F6E serverEntry \u65F6 apiPrefixes \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4`);
440
+ }
441
+ if (!URL_SAFE_MODULE_ID_RE.test(id)) {
442
+ throw new ConfigError(
443
+ `${file}: \u542F\u7528 serverEntry \u65F6 id \u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001. _ ~ -\uFF08\u5F53\u524D: ${JSON.stringify(id)}\uFF09`
444
+ );
445
+ }
446
+ for (const prefix of apiPrefixes) {
447
+ if (!API_PREFIX_RE.test(prefix)) {
448
+ throw new ConfigError(
449
+ `${file}: apiPrefixes \u542B\u975E\u6CD5\u524D\u7F00 ${JSON.stringify(prefix)}\uFF08\u8981\u6C42\u5F62\u5982 /api/risk-analysis\uFF0C\u4E0D\u80FD\u542B query / hash / \u5C3E\u659C\u6760 / \u901A\u914D / :param\uFF09`
450
+ );
451
+ }
452
+ const reserved = RESERVED_API_PREFIXES.find(
453
+ (item) => prefix === item || prefix.startsWith(`${item}/`)
454
+ );
455
+ if (reserved) {
456
+ throw new ConfigError(
457
+ `${file}: apiPrefixes \u4E0D\u80FD\u4F7F\u7528\u4FDD\u7559\u524D\u7F00 ${reserved}\uFF08Shell \u81EA\u5E26\u8DEF\u7531\uFF09: ${prefix}`
458
+ );
459
+ }
460
+ }
461
+ for (let i = 0; i < apiPrefixes.length; i += 1) {
462
+ for (let j = i + 1; j < apiPrefixes.length; j += 1) {
463
+ const a = apiPrefixes[i];
464
+ const b = apiPrefixes[j];
465
+ if (a === b) {
466
+ throw new ConfigError(`${file}: apiPrefixes \u5B58\u5728\u91CD\u590D\u524D\u7F00: ${a}`);
467
+ }
468
+ if (a.startsWith(`${b}/`) || b.startsWith(`${a}/`)) {
469
+ throw new ConfigError(
470
+ `${file}: apiPrefixes \u524D\u7F00\u4E92\u76F8\u5305\u542B\uFF08\u540C\u4E00\u6A21\u5757\u5185\u65E0\u6CD5\u786E\u5B9A\u5F52\u5C5E\uFF09: ${a} / ${b}`
471
+ );
472
+ }
473
+ }
474
+ }
475
+ }
357
476
 
358
477
  // src/emit-manifest.ts
359
478
  import { createHash } from "crypto";
360
479
  import {
361
480
  cpSync,
362
- existsSync as existsSync4,
363
- mkdirSync as mkdirSync3,
481
+ existsSync as existsSync5,
482
+ mkdirSync as mkdirSync4,
364
483
  readdirSync as readdirSync2,
365
484
  readFileSync as readFileSync4,
366
485
  statSync,
367
486
  writeFileSync as writeFileSync2
368
487
  } from "fs";
369
488
  import { createRequire } from "module";
370
- import { join as join5 } from "path";
489
+ import { join as join6 } from "path";
371
490
  import { stringify } from "yaml";
372
491
  function emitModule(input) {
373
492
  const dslResult = emitDsl(input);
@@ -376,22 +495,22 @@ function emitModule(input) {
376
495
  }
377
496
  function emitDsl(input) {
378
497
  const { config, projectDir, outDir, site, pages } = input;
379
- const dslDir = join5(outDir, "dsl");
380
- mkdirSync3(dslDir, { recursive: true });
381
- writeFileSync2(join5(dslDir, "sitemap.yaml"), stringify(site, { lineWidth: 0 }), "utf8");
498
+ const dslDir = join6(outDir, "dsl");
499
+ mkdirSync4(dslDir, { recursive: true });
500
+ writeFileSync2(join6(dslDir, "sitemap.yaml"), stringify(site, { lineWidth: 0 }), "utf8");
382
501
  for (const [id, dsl] of pages) {
383
- writeFileSync2(join5(dslDir, `${id}.yaml`), stringify(dsl, { lineWidth: 0 }), "utf8");
502
+ writeFileSync2(join6(dslDir, `${id}.yaml`), stringify(dsl, { lineWidth: 0 }), "utf8");
384
503
  }
385
- const publicDir = join5(projectDir, config.publicDir);
386
- const assetsDir = join5(outDir, "assets");
387
- if (existsSync4(publicDir)) {
504
+ const publicDir = join6(projectDir, config.publicDir);
505
+ const assetsDir = join6(outDir, "assets");
506
+ if (existsSync5(publicDir)) {
388
507
  cpSync(publicDir, assetsDir, { recursive: true });
389
508
  } else {
390
- mkdirSync3(assetsDir, { recursive: true });
509
+ mkdirSync4(assetsDir, { recursive: true });
391
510
  }
392
511
  const assets = hashAssets(assetsDir, assetsDir);
393
512
  writeFileSync2(
394
- join5(outDir, "module-assets.json"),
513
+ join6(outDir, "module-assets.json"),
395
514
  `${JSON.stringify({ module: config.id, assets }, null, 2)}
396
515
  `,
397
516
  "utf8"
@@ -412,10 +531,12 @@ function writeManifest(input) {
412
531
  dslRoot: "dsl",
413
532
  assetsRoot: "assets",
414
533
  ...input.clientEntry ? { clientEntry: input.clientEntry } : {},
415
- ...input.styles ? { styles: input.styles } : {}
534
+ ...input.styles ? { styles: input.styles } : {},
535
+ ...input.serverEntry ? { serverEntry: input.serverEntry } : {},
536
+ ...input.apiPrefixes && input.apiPrefixes.length > 0 ? { apiPrefixes: input.apiPrefixes } : {}
416
537
  };
417
538
  writeFileSync2(
418
- join5(outDir, "manifest.json"),
539
+ join6(outDir, "manifest.json"),
419
540
  `${JSON.stringify(manifest, null, 2)}
420
541
  `,
421
542
  "utf8"
@@ -423,8 +544,8 @@ function writeManifest(input) {
423
544
  return manifest;
424
545
  }
425
546
  function readProjectVersion(projectDir) {
426
- const pkgFile = join5(projectDir, "package.json");
427
- if (!existsSync4(pkgFile)) return "0.0.0";
547
+ const pkgFile = join6(projectDir, "package.json");
548
+ if (!existsSync5(pkgFile)) return "0.0.0";
428
549
  try {
429
550
  const pkg = JSON.parse(readFileSync4(pkgFile, "utf8"));
430
551
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "0.0.0";
@@ -434,7 +555,7 @@ function readProjectVersion(projectDir) {
434
555
  }
435
556
  function resolvePactorVersionRange(projectDir, warnings) {
436
557
  try {
437
- const require2 = createRequire(join5(projectDir, "package.json"));
558
+ const require2 = createRequire(join6(projectDir, "package.json"));
438
559
  for (const name of ["pactor-app/package.json", "@pactor-app/app/package.json"]) {
439
560
  try {
440
561
  const pkgFile = require2.resolve(name);
@@ -445,7 +566,7 @@ function resolvePactorVersionRange(projectDir, warnings) {
445
566
  } catch {
446
567
  }
447
568
  }
448
- const pkg = JSON.parse(readFileSync4(join5(projectDir, "package.json"), "utf8"));
569
+ const pkg = JSON.parse(readFileSync4(join6(projectDir, "package.json"), "utf8"));
449
570
  for (const section of ["dependencies", "peerDependencies", "devDependencies"]) {
450
571
  const range = pkg[section]?.["pactor-app"];
451
572
  if (typeof range === "string" && range.length > 0) {
@@ -459,11 +580,11 @@ function resolvePactorVersionRange(projectDir, warnings) {
459
580
  }
460
581
  function hashAssets(dir, root) {
461
582
  const entries = [];
462
- if (!existsSync4(dir)) return entries;
583
+ if (!existsSync5(dir)) return entries;
463
584
  for (const entry of readdirSync2(dir, { withFileTypes: true }).sort(
464
585
  (a, b) => a.name.localeCompare(b.name)
465
586
  )) {
466
- const full = join5(dir, entry.name);
587
+ const full = join6(dir, entry.name);
467
588
  if (entry.isDirectory()) {
468
589
  entries.push(...hashAssets(full, root));
469
590
  continue;
@@ -481,7 +602,7 @@ function hashAssets(dir, root) {
481
602
 
482
603
  // src/expand-include.ts
483
604
  import { readFileSync as readFileSync5 } from "fs";
484
- import { join as join6 } from "path";
605
+ import { join as join7 } from "path";
485
606
  import { parseDocument } from "yaml";
486
607
  var INCLUDE_KEY = "$include";
487
608
  var IncludeError = class extends Error {
@@ -499,7 +620,7 @@ function loadYamlExpanded(yamlRoot, relPath, stack = []) {
499
620
  if (stack.includes(relPath)) {
500
621
  throw new IncludeError(`\u5FAA\u73AF\u5F15\u7528: ${[...stack, relPath].join(" -> ")}`);
501
622
  }
502
- const value = parseYamlFile(join6(yamlRoot, relPath));
623
+ const value = parseYamlFile(join7(yamlRoot, relPath));
503
624
  return expandIncludes(value, { yamlRoot, current: relPath, stack: [...stack, relPath] });
504
625
  }
505
626
  function expandIncludes(value, ctx) {
@@ -526,8 +647,8 @@ function expandIncludes(value, ctx) {
526
647
  }
527
648
 
528
649
  // src/rewrite-urls.ts
529
- import { existsSync as existsSync5, statSync as statSync2 } from "fs";
530
- import { join as join7 } from "path";
650
+ import { existsSync as existsSync6, statSync as statSync2 } from "fs";
651
+ import { join as join8 } from "path";
531
652
  var NAVIGATION_ACTIONS = /* @__PURE__ */ new Set(["navigate", "app.reload"]);
532
653
  var ABSOLUTE_API_RE = /^https?:\/\/[^/]+(\/api\/.+)$/;
533
654
  function rewriteDslUrls(value, ctx) {
@@ -582,11 +703,14 @@ function matchPathPattern(pattern, value) {
582
703
  function rewriteString(value, ctx, warnings) {
583
704
  const absoluteApi = ABSOLUTE_API_RE.exec(value);
584
705
  if (absoluteApi) {
585
- warnings.push(
586
- `\u7EDD\u5BF9 API \u5730\u5740: ${value}${ctx.absoluteApiToRelative ? "\uFF08\u5DF2\u91CD\u5199\u4E3A\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u9700\u5E95\u5EA7 BFF/\u4EE3\u7406\u627F\u63A5\uFF09" : ""}`
587
- );
588
- return ctx.absoluteApiToRelative ? absoluteApi[1] : value;
706
+ const relative3 = absoluteApi[1];
707
+ const namespaced2 = ctx.absoluteApiToRelative ? namespaceModuleApi(relative3, ctx) : void 0;
708
+ const action = ctx.absoluteApiToRelative ? namespaced2 !== void 0 ? "\uFF08\u5DF2\u91CD\u5199\u4E3A\u6A21\u5757 API \u76F8\u5BF9\u8DEF\u5F84\uFF09" : "\uFF08\u5DF2\u91CD\u5199\u4E3A\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u9700\u5E95\u5EA7 BFF/\u4EE3\u7406\u627F\u63A5\uFF09" : "\uFF08absoluteApiToRelative=false\uFF0C\u4FDD\u6301\u7EDD\u5BF9\u5730\u5740\uFF0C\u4E0D\u4F1A\u8D70\u6A21\u5757 API \u8DEF\u7531\uFF09";
709
+ warnings.push(`\u7EDD\u5BF9 API \u5730\u5740: ${value}${action}`);
710
+ return ctx.absoluteApiToRelative ? namespaced2 ?? relative3 : value;
589
711
  }
712
+ const namespaced = namespaceModuleApi(value, ctx);
713
+ if (namespaced !== void 0) return namespaced;
590
714
  if (!ctx.rewriteAssets) return value;
591
715
  if (!value.startsWith("/") || value.startsWith("//")) return value;
592
716
  if (value.startsWith("/api/")) return value;
@@ -598,10 +722,18 @@ function rewriteString(value, ctx, warnings) {
598
722
  if (!isPublicFile(ctx.publicDir, value)) return value;
599
723
  return `${ctx.assetPrefix}${value}`;
600
724
  }
725
+ function namespaceModuleApi(value, ctx) {
726
+ const { moduleId, apiPrefixes } = ctx;
727
+ if (!moduleId || !apiPrefixes || apiPrefixes.length === 0) return void 0;
728
+ const pathOnly = value.split(/[?#]/, 1)[0];
729
+ const hit = [...apiPrefixes].sort((a, b) => b.length - a.length).some((prefix) => pathOnly === prefix || pathOnly.startsWith(`${prefix}/`));
730
+ if (!hit) return void 0;
731
+ return `/api/modules/${encodeURIComponent(moduleId)}${value.slice("/api".length)}`;
732
+ }
601
733
  function isPublicFile(publicDir, urlPath) {
602
- const file = join7(publicDir, urlPath);
734
+ const file = join8(publicDir, urlPath);
603
735
  try {
604
- return existsSync5(file) && statSync2(file).isFile();
736
+ return existsSync6(file) && statSync2(file).isFile();
605
737
  } catch {
606
738
  return false;
607
739
  }
@@ -612,7 +744,8 @@ function isRecord2(value) {
612
744
 
613
745
  // src/transform-sitemap.ts
614
746
  function transformSitemap(site, options) {
615
- const { profile, demoPublic, loadLayout } = options;
747
+ const { profile, accessDefault, loadLayout } = options;
748
+ const fillPublic = accessDefault === "public";
616
749
  const next = { ...site };
617
750
  const rawLayouts = isRecord3(site.layouts) ? site.layouts : {};
618
751
  const layouts = {};
@@ -626,7 +759,7 @@ function transformSitemap(site, options) {
626
759
  if (profile.forceInlinePages) {
627
760
  delete nextPage.lazy;
628
761
  }
629
- if (demoPublic) {
762
+ if (fillPublic && isAccessUndeclared(nextPage)) {
630
763
  nextPage.public = true;
631
764
  }
632
765
  return nextPage;
@@ -637,7 +770,7 @@ function transformSitemap(site, options) {
637
770
  if (profile.stripMenuPaths) {
638
771
  delete nextMenu.path;
639
772
  }
640
- if (demoPublic) {
773
+ if (fillPublic && isAccessUndeclared(nextMenu)) {
641
774
  nextMenu.public = true;
642
775
  }
643
776
  if (Array.isArray(nextMenu.children)) {
@@ -658,10 +791,13 @@ function transformSitemap(site, options) {
658
791
  function isRecord3(value) {
659
792
  return value !== null && typeof value === "object" && !Array.isArray(value);
660
793
  }
794
+ function isAccessUndeclared(entry) {
795
+ return entry.public === void 0 && entry.permission === void 0;
796
+ }
661
797
 
662
798
  // src/validate.ts
663
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
664
- import { join as join8, relative as relative2, resolve as resolve3 } from "path";
799
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
800
+ import { join as join9, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
665
801
  import { parseDocument as parseDocument2 } from "yaml";
666
802
  var REQUIRED_MANIFEST_FIELDS = [
667
803
  "schemaVersion",
@@ -675,7 +811,7 @@ var REQUIRED_MANIFEST_FIELDS = [
675
811
  "assetsRoot"
676
812
  ];
677
813
  function validateModule(options = {}) {
678
- const projectDir = resolve3(options.dir ?? process.cwd());
814
+ const projectDir = resolve4(options.dir ?? process.cwd());
679
815
  const errors = [];
680
816
  const warnings = [];
681
817
  let config;
@@ -693,9 +829,9 @@ function validateModule(options = {}) {
693
829
  config = void 0;
694
830
  }
695
831
  }
696
- const moduleDir = options.moduleDir ?? join8(projectDir, config?.outDir ?? CONFIG_DEFAULTS.outDir);
697
- const manifestFile = join8(moduleDir, "manifest.json");
698
- if (!existsSync6(manifestFile)) {
832
+ const moduleDir = options.moduleDir ?? join9(projectDir, config?.outDir ?? CONFIG_DEFAULTS.outDir);
833
+ const manifestFile = join9(moduleDir, "manifest.json");
834
+ if (!existsSync7(manifestFile)) {
699
835
  errors.push(`\u4EA7\u7269\u4E0D\u5B58\u5728: ${manifestFile}\uFF08\u5148\u6267\u884C pactor module build\uFF09`);
700
836
  return { ok: false, errors, warnings };
701
837
  }
@@ -716,8 +852,8 @@ function validateModule(options = {}) {
716
852
  }
717
853
  const dslRoot = typeof manifest.dslRoot === "string" ? manifest.dslRoot : "dsl";
718
854
  const sitemapRel = typeof manifest.sitemap === "string" ? manifest.sitemap : "dsl/sitemap.yaml";
719
- const sitemapFile = join8(moduleDir, sitemapRel);
720
- if (!existsSync6(sitemapFile)) {
855
+ const sitemapFile = join9(moduleDir, sitemapRel);
856
+ if (!existsSync7(sitemapFile)) {
721
857
  errors.push(`sitemap \u4E0D\u5B58\u5728: ${sitemapRel}`);
722
858
  } else {
723
859
  const doc = parseDocument2(readFileSync6(sitemapFile, "utf8"));
@@ -732,7 +868,7 @@ function validateModule(options = {}) {
732
868
  errors.push("sitemap.pages \u5B58\u5728\u7F3A\u5C11 id \u7684\u9875\u9762\u9879");
733
869
  continue;
734
870
  }
735
- if (findPageDslFile(join8(moduleDir, dslRoot), id) === null) {
871
+ if (findPageDslFile(join9(moduleDir, dslRoot), id) === null) {
736
872
  errors.push(`\u9875\u9762 ${id} \u7F3A\u5C11 DSL \u6587\u4EF6\uFF08${dslRoot}/ \u4E0B\u672A\u627E\u5230 ${id}.yaml\uFF09`);
737
873
  }
738
874
  }
@@ -746,15 +882,15 @@ function validateModule(options = {}) {
746
882
  }
747
883
  }
748
884
  }
749
- const dslDir = join8(moduleDir, dslRoot);
885
+ const dslDir = join9(moduleDir, dslRoot);
750
886
  for (const file of listFiles(dslDir)) {
751
887
  if (readFileSync6(file, "utf8").includes(INCLUDE_KEY)) {
752
888
  errors.push(`$include \u6B8B\u7559: ${relative2(moduleDir, file)}`);
753
889
  }
754
890
  }
755
891
  if (typeof manifest.clientEntry === "string") {
756
- const clientFile = join8(moduleDir, manifest.clientEntry);
757
- if (!existsSync6(clientFile)) {
892
+ const clientFile = join9(moduleDir, manifest.clientEntry);
893
+ if (!existsSync7(clientFile)) {
758
894
  errors.push(`client bundle \u4E0D\u5B58\u5728: ${manifest.clientEntry}`);
759
895
  } else {
760
896
  const text = readFileSync6(clientFile, "utf8");
@@ -770,8 +906,34 @@ function validateModule(options = {}) {
770
906
  }
771
907
  }
772
908
  }
773
- const pkgFile = join8(projectDir, "package.json");
774
- if (existsSync6(pkgFile)) {
909
+ const hasServerEntry = typeof manifest.serverEntry === "string";
910
+ const hasApiPrefixes = Array.isArray(manifest.apiPrefixes);
911
+ if (hasServerEntry !== hasApiPrefixes) {
912
+ errors.push("manifest.serverEntry \u4E0E apiPrefixes \u5FC5\u987B\u6210\u5BF9\u51FA\u73B0");
913
+ }
914
+ if (hasServerEntry) {
915
+ const serverRel = manifest.serverEntry;
916
+ const serverFile = resolve4(moduleDir, serverRel);
917
+ if (!isInsideDir(moduleDir, serverFile)) {
918
+ errors.push(`server bundle \u8DEF\u5F84\u8D8A\u754C: ${serverRel}`);
919
+ } else if (!existsSync7(serverFile)) {
920
+ errors.push(`server bundle \u4E0D\u5B58\u5728: ${serverRel}`);
921
+ } else {
922
+ const text = readFileSync6(serverFile, "utf8");
923
+ if (/node_modules\/(?:next|react|react-dom|@pactor-app|pactor-app)\//.test(text)) {
924
+ warnings.push(
925
+ "server bundle \u7591\u4F3C\u5185\u5D4C next / react / pactor\uFF08\u5E94\u4FDD\u6301 external\uFF0C\u7531\u5BBF\u4E3B\u63D0\u4F9B\uFF09"
926
+ );
927
+ }
928
+ }
929
+ if (!Array.isArray(manifest.apiPrefixes) || manifest.apiPrefixes.length === 0) {
930
+ errors.push("manifest.apiPrefixes \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
931
+ } else if (manifest.apiPrefixes.some((prefix) => typeof prefix !== "string")) {
932
+ errors.push("manifest.apiPrefixes \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4");
933
+ }
934
+ }
935
+ const pkgFile = join9(projectDir, "package.json");
936
+ if (existsSync7(pkgFile)) {
775
937
  try {
776
938
  const pkg = JSON.parse(readFileSync6(pkgFile, "utf8"));
777
939
  const exportsMap = pkg.exports ?? {};
@@ -781,6 +943,9 @@ function validateModule(options = {}) {
781
943
  if (typeof manifest.clientEntry === "string" && exportsMap["./client"] === void 0) {
782
944
  errors.push('package.json exports \u7F3A\u5C11 "./client"\uFF08client-registry \u9759\u6001 import "<pkg>/client"\uFF09');
783
945
  }
946
+ if (typeof manifest.serverEntry === "string" && exportsMap["./server"] === void 0) {
947
+ errors.push('package.json exports \u7F3A\u5C11 "./server"\uFF08Shell \u88C5\u914D\u5668\u7ECF require.resolve("<pkg>/server") \u5B9A\u4F4D\uFF09');
948
+ }
784
949
  if (typeof pkg.version === "string" && typeof manifest.version === "string" && pkg.version !== manifest.version) {
785
950
  warnings.push(
786
951
  `manifest.version (${manifest.version}) \u4E0E package.json version (${pkg.version}) \u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0 build`
@@ -794,13 +959,17 @@ function validateModule(options = {}) {
794
959
  }
795
960
  return { ok: errors.length === 0, errors, warnings };
796
961
  }
962
+ function isInsideDir(dir, file) {
963
+ const root = resolve4(dir);
964
+ return file.startsWith(`${root}${sep2}`);
965
+ }
797
966
  function findPageDslFile(dslRootDir, pageId) {
798
- if (!existsSync6(dslRootDir)) return null;
967
+ if (!existsSync7(dslRootDir)) return null;
799
968
  const queue = [dslRootDir];
800
969
  while (queue.length > 0) {
801
970
  const dir = queue.pop();
802
971
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
803
- const full = join8(dir, entry.name);
972
+ const full = join9(dir, entry.name);
804
973
  if (entry.isDirectory()) {
805
974
  queue.push(full);
806
975
  continue;
@@ -811,11 +980,11 @@ function findPageDslFile(dslRootDir, pageId) {
811
980
  return null;
812
981
  }
813
982
  function listFiles(dir) {
814
- if (!existsSync6(dir)) return [];
983
+ if (!existsSync7(dir)) return [];
815
984
  const files = [];
816
985
  const walk = (target) => {
817
986
  for (const entry of readdirSync3(target, { withFileTypes: true })) {
818
- const full = join8(target, entry.name);
987
+ const full = join9(target, entry.name);
819
988
  if (entry.isDirectory()) {
820
989
  walk(full);
821
990
  } else if (entry.isFile()) {
@@ -835,7 +1004,7 @@ var ModuleBuildError = class extends Error {
835
1004
  }
836
1005
  };
837
1006
  function buildModule(options = {}) {
838
- const projectDir = resolve4(options.dir ?? process.cwd());
1007
+ const projectDir = resolve5(options.dir ?? process.cwd());
839
1008
  const config = loadModuleConfig(projectDir, options.config);
840
1009
  const profile = getProfile(config.target);
841
1010
  if (profile.forbidRootBasePath && config.basePath === "/") {
@@ -844,8 +1013,17 @@ function buildModule(options = {}) {
844
1013
  [`basePath: "/" \u4E0E profile ${config.target} \u7684 forbidRootBasePath \u51B2\u7A81\uFF0C\u8BF7\u6539\u7528\u5982 /${config.id} \u7684\u524D\u7F00`]
845
1014
  );
846
1015
  }
847
- const yamlRoot = join9(projectDir, config.yamlRoot);
848
- if (!existsSync7(yamlRoot)) {
1016
+ if (config.serverEntry !== void 0 && !profile.moduleServerApi) {
1017
+ throw new ModuleBuildError(
1018
+ `target ${config.target} \u4E0D\u652F\u6301\u6A21\u5757 server entry\uFF08\u9700\u8981 innospots-web-shell@3\uFF09`,
1019
+ [
1020
+ `\u914D\u7F6E\u4E86 serverEntry / apiPrefixes\uFF0C\u4F46 profile ${config.target} \u7684 moduleServerApi=false`,
1021
+ '\u8BF7\u628A pactor.module.json \u7684 target \u5347\u5230 "innospots-web-shell@3" \u540E\u91CD\u65B0\u6784\u5EFA'
1022
+ ]
1023
+ );
1024
+ }
1025
+ const yamlRoot = join10(projectDir, config.yamlRoot);
1026
+ if (!existsSync8(yamlRoot)) {
849
1027
  throw new ModuleBuildError(`yamlRoot \u4E0D\u5B58\u5728: ${config.yamlRoot}`, [
850
1028
  `yamlRoot \u4E0D\u5B58\u5728: ${yamlRoot}`
851
1029
  ]);
@@ -885,7 +1063,7 @@ function buildModule(options = {}) {
885
1063
  }
886
1064
  const transformed = transformSitemap(site, {
887
1065
  profile,
888
- demoPublic: config.demo.public,
1066
+ accessDefault: config.access.default,
889
1067
  loadLayout: (rel) => loadYamlExpanded(yamlRoot, rel)
890
1068
  });
891
1069
  const version = readProjectVersion(projectDir);
@@ -894,11 +1072,13 @@ function buildModule(options = {}) {
894
1072
  basePath: config.basePath,
895
1073
  pagePaths: sitePages.map((page) => page.path).filter((path) => typeof path === "string"),
896
1074
  loginPath: typeof siteAuth.loginPath === "string" ? siteAuth.loginPath : "/login",
897
- publicDir: join9(projectDir, config.publicDir),
1075
+ publicDir: join10(projectDir, config.publicDir),
898
1076
  rewriteNavigation: profile.rewriteNavigation.basePath,
899
1077
  rewriteAssets: true,
900
1078
  assetPrefix: profile.rewriteAssetUrls(config.id, version).replace(/\/+$/, ""),
901
- absoluteApiToRelative: config.rewrite.absoluteApiToRelative
1079
+ absoluteApiToRelative: config.rewrite.absoluteApiToRelative,
1080
+ moduleId: config.id,
1081
+ apiPrefixes: config.apiPrefixes
902
1082
  };
903
1083
  const sitemapRewrite = rewriteDslUrls(transformed.site, rewriteCtx);
904
1084
  warnings.push(...sitemapRewrite.warnings);
@@ -908,17 +1088,22 @@ function buildModule(options = {}) {
908
1088
  warnings.push(...result.warnings.map((warning) => `${id}: ${warning}`));
909
1089
  rewrittenPages.set(id, result.value);
910
1090
  }
911
- const outDir = join9(projectDir, config.outDir);
912
- const temp = mkdtempSync2(join9(projectDir, ".module-pack-"));
1091
+ const outDir = join10(projectDir, config.outDir);
1092
+ const temp = mkdtempSync2(join10(projectDir, ".module-pack-"));
913
1093
  try {
914
1094
  const bundle = bundleClient({
915
1095
  projectDir,
916
1096
  clientEntry: config.clientEntry,
917
- outDir: join9(temp, "client")
1097
+ outDir: join10(temp, "client")
918
1098
  });
919
1099
  if (bundle.message !== void 0) {
920
1100
  warnings.push(bundle.message);
921
1101
  }
1102
+ const server = config.serverEntry ? bundleServer({
1103
+ projectDir,
1104
+ serverEntry: config.serverEntry,
1105
+ outDir: join10(temp, "server")
1106
+ }) : void 0;
922
1107
  const emitted = emitDsl({
923
1108
  config,
924
1109
  projectDir,
@@ -930,18 +1115,20 @@ function buildModule(options = {}) {
930
1115
  const tailwind = compileTailwind({
931
1116
  projectDir,
932
1117
  tailwind: config.tailwind,
933
- scanDirs: [join9(temp, "dsl"), dirname3(join9(projectDir, config.clientEntry))],
934
- cssFile: join9(temp, "client", "index.css")
1118
+ scanDirs: [join10(temp, "dsl"), dirname3(join10(projectDir, config.clientEntry))],
1119
+ cssFile: join10(temp, "client", "index.css")
935
1120
  });
936
1121
  warnings.push(...tailwind.warnings);
937
1122
  }
938
- const hasCss = existsSync7(join9(temp, "client", "index.css"));
1123
+ const hasCss = existsSync8(join10(temp, "client", "index.css"));
939
1124
  writeManifest({
940
1125
  config,
941
1126
  projectDir,
942
1127
  outDir: temp,
943
1128
  clientEntry: bundle.bundled ? "client/index.js" : void 0,
944
1129
  styles: bundle.bundled && hasCss ? ["client/index.css"] : void 0,
1130
+ serverEntry: server ? "server/index.mjs" : void 0,
1131
+ apiPrefixes: server ? config.apiPrefixes : void 0,
945
1132
  warnings
946
1133
  });
947
1134
  const validation = validateModule({ dir: projectDir, config: options.config, moduleDir: temp });
@@ -953,7 +1140,7 @@ function buildModule(options = {}) {
953
1140
  );
954
1141
  }
955
1142
  rmSync2(outDir, { recursive: true, force: true });
956
- mkdirSync4(dirname3(outDir), { recursive: true });
1143
+ mkdirSync5(dirname3(outDir), { recursive: true });
957
1144
  renameSync(temp, outDir);
958
1145
  return {
959
1146
  outDir,
@@ -964,6 +1151,7 @@ function buildModule(options = {}) {
964
1151
  assets: emitted.assetCount
965
1152
  },
966
1153
  clientBundled: bundle.bundled,
1154
+ serverBundled: server !== void 0,
967
1155
  warnings
968
1156
  };
969
1157
  } catch (error) {
@@ -973,67 +1161,131 @@ function buildModule(options = {}) {
973
1161
  }
974
1162
 
975
1163
  // src/init.ts
976
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync3 } from "fs";
977
- import { join as join10, resolve as resolve5 } from "path";
1164
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync3 } from "fs";
1165
+ import { dirname as dirname4, join as join11, resolve as resolve6 } from "path";
1166
+ var CLIENT_STYLES_ENTRY = "module/module.css";
1167
+ var SERVER_ENTRY = "module/server.ts";
978
1168
  function initModule(options = {}) {
979
- const projectDir = resolve5(options.dir ?? process.cwd());
980
- const result = { created: [], skipped: [], warnings: [] };
981
- const pkgFile = join10(projectDir, "package.json");
982
- if (!existsSync8(pkgFile)) {
1169
+ const projectDir = resolve6(options.dir ?? process.cwd());
1170
+ const overwrite = options.overwrite ?? false;
1171
+ const server = options.server ?? false;
1172
+ const result = { created: [], overwritten: [], skipped: [], warnings: [] };
1173
+ const pkgFile = join11(projectDir, "package.json");
1174
+ if (!existsSync9(pkgFile)) {
983
1175
  throw new Error(`\u672A\u627E\u5230 package.json: ${projectDir}\uFF08\u8BF7\u5728\u76EE\u6807\u5DE5\u7A0B\u6839\u6267\u884C\uFF09`);
984
1176
  }
985
1177
  const pkg = JSON.parse(readFileSync7(pkgFile, "utf8"));
986
1178
  const id = deriveModuleId(pkg.name);
987
1179
  const actions = discoverActions(projectDir);
988
1180
  const hasComponents = discoverComponents(projectDir);
989
- const configFile = join10(projectDir, CONFIG_FILE);
990
- if (existsSync8(configFile)) {
1181
+ const configFile = join11(projectDir, CONFIG_FILE);
1182
+ const renderConfig = () => ({
1183
+ id,
1184
+ name: typeof pkg.name === "string" ? pkg.name : id,
1185
+ basePath: `/${id}`,
1186
+ yamlRoot: CONFIG_DEFAULTS.yamlRoot,
1187
+ clientEntry: CONFIG_DEFAULTS.clientEntry,
1188
+ // 只有显式 --server 才写入,避免纯 DSL 模块被迫启用 server entry
1189
+ ...server ? { serverEntry: SERVER_ENTRY, apiPrefixes: [deriveApiPrefix(id)] } : {},
1190
+ actions: actions.map((action) => action.specifier.replace(/^\.\.\//, "")),
1191
+ publicDir: CONFIG_DEFAULTS.publicDir,
1192
+ target: DEFAULT_TARGET
1193
+ });
1194
+ const configExisted = existsSync9(configFile);
1195
+ if (configExisted && !overwrite) {
991
1196
  result.skipped.push(`${CONFIG_FILE}\uFF08\u5DF2\u5B58\u5728\uFF09`);
992
1197
  } else {
993
- const config = {
994
- id,
995
- name: typeof pkg.name === "string" ? pkg.name : id,
996
- basePath: `/${id}`,
997
- yamlRoot: CONFIG_DEFAULTS.yamlRoot,
998
- clientEntry: CONFIG_DEFAULTS.clientEntry,
999
- actions: actions.map((action) => action.specifier.replace(/^\.\.\//, "")),
1000
- publicDir: CONFIG_DEFAULTS.publicDir,
1001
- target: DEFAULT_TARGET,
1002
- demo: { public: false }
1003
- };
1004
- writeFileSync3(configFile, `${JSON.stringify(config, null, 2)}
1198
+ writeFileSync3(configFile, `${JSON.stringify(renderConfig(), null, 2)}
1005
1199
  `, "utf8");
1006
- result.created.push(CONFIG_FILE);
1200
+ if (configExisted) {
1201
+ result.overwritten.push(CONFIG_FILE);
1202
+ } else {
1203
+ result.created.push(CONFIG_FILE);
1204
+ }
1007
1205
  }
1008
- const clientFile = join10(projectDir, CONFIG_DEFAULTS.clientEntry);
1009
- if (existsSync8(clientFile)) {
1206
+ const clientFile = join11(projectDir, CONFIG_DEFAULTS.clientEntry);
1207
+ if (existsSync9(clientFile) && !overwrite) {
1010
1208
  result.skipped.push(`${CONFIG_DEFAULTS.clientEntry}\uFF08\u5DF2\u5B58\u5728\uFF09`);
1011
1209
  } else {
1012
- mkdirSync5(join10(projectDir, "module"), { recursive: true });
1210
+ const clientExisted = existsSync9(clientFile);
1211
+ mkdirSync6(dirname4(clientFile), { recursive: true });
1013
1212
  writeFileSync3(clientFile, renderClientEntry(hasComponents, actions), "utf8");
1014
- result.created.push(CONFIG_DEFAULTS.clientEntry);
1213
+ if (clientExisted) {
1214
+ result.overwritten.push(CONFIG_DEFAULTS.clientEntry);
1215
+ } else {
1216
+ result.created.push(CONFIG_DEFAULTS.clientEntry);
1217
+ }
1218
+ }
1219
+ const stylesFile = join11(projectDir, CLIENT_STYLES_ENTRY);
1220
+ if (existsSync9(stylesFile) && !overwrite) {
1221
+ result.skipped.push(`${CLIENT_STYLES_ENTRY}\uFF08\u5DF2\u5B58\u5728\uFF09`);
1222
+ } else {
1223
+ const stylesExisted = existsSync9(stylesFile);
1224
+ mkdirSync6(dirname4(stylesFile), { recursive: true });
1225
+ writeFileSync3(stylesFile, renderModuleStyles(), "utf8");
1226
+ if (stylesExisted) {
1227
+ result.overwritten.push(CLIENT_STYLES_ENTRY);
1228
+ } else {
1229
+ result.created.push(CLIENT_STYLES_ENTRY);
1230
+ }
1231
+ }
1232
+ if (server) {
1233
+ const serverFile = join11(projectDir, SERVER_ENTRY);
1234
+ if (existsSync9(serverFile) && !overwrite) {
1235
+ result.skipped.push(`${SERVER_ENTRY}\uFF08\u5DF2\u5B58\u5728\uFF09`);
1236
+ } else {
1237
+ const serverExisted = existsSync9(serverFile);
1238
+ mkdirSync6(dirname4(serverFile), { recursive: true });
1239
+ writeFileSync3(serverFile, renderServerEntry(id), "utf8");
1240
+ if (serverExisted) {
1241
+ result.overwritten.push(SERVER_ENTRY);
1242
+ } else {
1243
+ result.created.push(SERVER_ENTRY);
1244
+ }
1245
+ }
1246
+ }
1247
+ const hasServerEntry = existsSync9(join11(projectDir, SERVER_ENTRY));
1248
+ injectPackageJson(pkg, result, hasServerEntry);
1249
+ if (hasServerEntry && !configDeclaresServerEntry(configFile)) {
1250
+ result.warnings.push(
1251
+ '\u68C0\u6D4B\u5230 module/server.ts\uFF1A\u5DF2\u6CE8\u5165 "./server" export\uFF1B\u8BF7\u5728 pactor.module.json \u4E2D\u540C\u65F6\u914D\u7F6E serverEntry \u4E0E apiPrefixes \u540E\u518D\u6784\u5EFA\uFF08\u7F3A\u4E00\u6784\u5EFA\u4F1A\u5931\u8D25\uFF09'
1252
+ );
1015
1253
  }
1016
- injectPackageJson(pkg, result);
1017
1254
  writeFileSync3(pkgFile, `${JSON.stringify(pkg, null, 2)}
1018
1255
  `, "utf8");
1019
1256
  result.created.push("package.json\uFF08\u6CE8\u5165 files / exports / scripts / peerDependencies\uFF09");
1020
1257
  return result;
1021
1258
  }
1259
+ function configDeclaresServerEntry(configFile) {
1260
+ if (!existsSync9(configFile)) return false;
1261
+ try {
1262
+ const raw = JSON.parse(readFileSync7(configFile, "utf8"));
1263
+ return raw.serverEntry === SERVER_ENTRY;
1264
+ } catch {
1265
+ return false;
1266
+ }
1267
+ }
1268
+ var MODULE_ID_PREFIX = "pactor-app-";
1022
1269
  function deriveModuleId(name) {
1023
1270
  const base2 = typeof name === "string" && name.length > 0 ? name : "module";
1024
1271
  const withoutScope = base2.startsWith("@") ? base2.slice(base2.indexOf("/") + 1) : base2;
1025
1272
  const kebab = withoutScope.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1026
- return kebab.length > 0 ? kebab : "module";
1273
+ const id = kebab.length > 0 ? kebab : "module";
1274
+ return id.startsWith(MODULE_ID_PREFIX) ? id : `${MODULE_ID_PREFIX}${id}`;
1275
+ }
1276
+ function deriveApiPrefix(moduleId) {
1277
+ const slug = moduleId.startsWith(MODULE_ID_PREFIX) ? moduleId.slice(MODULE_ID_PREFIX.length) : moduleId;
1278
+ return `/api/${slug.length > 0 ? slug : "module"}`;
1027
1279
  }
1028
1280
  function discoverActions(projectDir) {
1029
- const dir = join10(projectDir, "src/pactor");
1030
- if (!existsSync8(dir)) return [];
1281
+ const dir = join11(projectDir, "src/pactor");
1282
+ if (!existsSync9(dir)) return [];
1031
1283
  const sources = [];
1032
1284
  for (const entry of readdirSync4(dir, { withFileTypes: true }).sort(
1033
1285
  (a, b) => a.name.localeCompare(b.name)
1034
1286
  )) {
1035
1287
  if (!entry.isFile() || !entry.name.endsWith("-actions.ts")) continue;
1036
- const file = join10(dir, entry.name);
1288
+ const file = join11(dir, entry.name);
1037
1289
  const content = readFileSync7(file, "utf8");
1038
1290
  const exports = [...content.matchAll(/export\s+const\s+([A-Za-z_$][\w$]*)/g)].map(
1039
1291
  (match) => match[1]
@@ -1047,8 +1299,8 @@ function discoverActions(projectDir) {
1047
1299
  return sources;
1048
1300
  }
1049
1301
  function discoverComponents(projectDir) {
1050
- const file = join10(projectDir, "src/pactor/components/index.ts");
1051
- if (!existsSync8(file)) return false;
1302
+ const file = join11(projectDir, "src/pactor/components/index.ts");
1303
+ if (!existsSync9(file)) return false;
1052
1304
  return /export\s+(const|\{[^}]*)\s*pactorComponents/.test(readFileSync7(file, "utf8"));
1053
1305
  }
1054
1306
  function renderClientEntry(hasComponents, actions) {
@@ -1065,18 +1317,48 @@ function renderClientEntry(hasComponents, actions) {
1065
1317
  }
1066
1318
  const components = hasComponents ? "pactorComponents" : "{}";
1067
1319
  const actionsExpr = spreads.length > 0 ? `{ ${spreads.join(", ")} }` : "{}";
1320
+ const importsBlock = imports.length > 0 ? `${imports.join("\n")}
1321
+ ` : "";
1068
1322
  return `// \u7531 \`pactor module init\` \u751F\u6210\uFF1A\u6A21\u5757 client \u5165\u53E3\u3002
1069
1323
  // Shell \u88C5\u914D\u5668\u751F\u6210 client-registry.ts \u65F6\u9759\u6001 import "<pkg>/client"\uFF0C
1070
1324
  // \u8981\u6C42\u6B64\u5904\u5177\u540D\u5BFC\u51FA extensions\uFF08components / actions \u6CE8\u518C\u8868\uFF09\u3002
1071
- ${imports.join("\n")}
1072
-
1325
+ // module.css \u7531 esbuild \u62BD\u6210 client/index.css\uFF0CShell \u6309 manifest.styles \u52A0\u8F7D\u3002
1326
+ import './module.css';
1327
+ ${importsBlock}
1073
1328
  export const extensions = {
1074
1329
  components: ${components},
1075
1330
  actions: ${actionsExpr},
1076
1331
  };
1077
1332
  `;
1078
1333
  }
1079
- function injectPackageJson(pkg, result) {
1334
+ function renderModuleStyles() {
1335
+ return `/**
1336
+ * \u6A21\u5757\u81EA\u5E26\u6837\u5F0F\uFF08\u7531 \`pactor module init\` \u751F\u6210\uFF09\u3002
1337
+ *
1338
+ * \u672C\u6587\u4EF6\u7531 module/client.ts import\uFF1Aesbuild \u6253\u5305\u65F6\u4F1A\u62BD\u53D6\u4E3A
1339
+ * dist-module/client/index.css \u5E76\u5199\u5165 manifest.styles\uFF0CShell \u636E\u6B64\u81EA\u52A8\u52A0\u8F7D\u3002
1340
+ * Tailwind \u57FA\u7840\u6837\u5F0F\u3001pactor-app \u805A\u5408\u6837\u5F0F\u4E0E\u4E3B\u9898\u53D8\u91CF\u7531 Shell \u7EDF\u4E00\u63D0\u4F9B\uFF0C
1341
+ * \u6A21\u5757\u5185\u4E0D\u8981\u91CD\u590D\u5F15\u5165\uFF0C\u907F\u514D\u4EA7\u751F\u7B2C\u4E8C\u4EFD reset / \u4EE4\u724C\u3002
1342
+ */
1343
+
1344
+ /* \u6A21\u5757\u7EA7\u6837\u5F0F\u5199\u5728\u8FD9\u91CC\uFF1A\u5EFA\u8BAE\u4F7F\u7528\u5E26\u6A21\u5757\u524D\u7F00\u7684\u7C7B\u540D\uFF0C\u907F\u514D\u4E0E Shell \u5168\u5C40\u6837\u5F0F\u51B2\u7A81\u3002 */
1345
+ `;
1346
+ }
1347
+ function renderServerEntry(moduleId) {
1348
+ const prefix = deriveApiPrefix(moduleId);
1349
+ return `// \u7531 \`pactor module init --server\` \u751F\u6210\uFF1A\u6A21\u5757\u670D\u52A1\u7AEF\u5165\u53E3\u3002
1350
+ // \u5BFC\u51FA\u300C\u903B\u8F91\u8DEF\u5F84 \u2192 \u65B9\u6CD5\u5904\u7406\u51FD\u6570\u300D\u6620\u5C04\uFF1A\u8DEF\u5F84\u952E\u4F7F\u7528\u539F\u59CB /api/... \u903B\u8F91\u8DEF\u5F84\uFF0C
1351
+ // Shell \u901A\u8FC7 /api/modules/<moduleId>/* \u7EDF\u4E00\u5206\u53D1\uFF08\u89C1 pactor.module.json \u7684 apiPrefixes\uFF09\u3002
1352
+ // handler \u53EA\u4F9D\u8D56 Web \u6807\u51C6 Request / Response\uFF0Ccontext.url \u4E3A\u903B\u8F91 URL\uFF08\u542B query\uFF09\u3002
1353
+
1354
+ export const handlers = {
1355
+ // '${prefix}/hello': {
1356
+ // GET: () => Response.json({ code: 0, data: { ok: true }, message: 'ok' }),
1357
+ // },
1358
+ };
1359
+ `;
1360
+ }
1361
+ function injectPackageJson(pkg, result, hasServerEntry) {
1080
1362
  const outDir = CONFIG_DEFAULTS.outDir;
1081
1363
  const files = Array.isArray(pkg.files) ? pkg.files : [];
1082
1364
  if (!files.includes(outDir)) {
@@ -1086,7 +1368,8 @@ function injectPackageJson(pkg, result) {
1086
1368
  const wantedExports = {
1087
1369
  "./manifest": `./${outDir}/manifest.json`,
1088
1370
  "./client": `./${outDir}/client/index.js`,
1089
- "./styles.css": `./${outDir}/client/index.css`
1371
+ "./styles.css": `./${outDir}/client/index.css`,
1372
+ ...hasServerEntry ? { "./server": `./${outDir}/server/index.mjs` } : {}
1090
1373
  };
1091
1374
  for (const [key, value] of Object.entries(wantedExports)) {
1092
1375
  if (exportsMap[key] === void 0) {
@@ -1099,7 +1382,8 @@ function injectPackageJson(pkg, result) {
1099
1382
  const scripts = isRecord4(pkg.scripts) ? { ...pkg.scripts } : {};
1100
1383
  const wantedScripts = {
1101
1384
  "build:module": "pactor module build",
1102
- "pack:module": "pactor module pack"
1385
+ "pack:module": "pactor module pack",
1386
+ "publish:module": "pactor module publish"
1103
1387
  };
1104
1388
  for (const [key, value] of Object.entries(wantedScripts)) {
1105
1389
  if (scripts[key] === void 0) {
@@ -1127,20 +1411,71 @@ function injectPackageJson(pkg, result) {
1127
1411
  function isRecord4(value) {
1128
1412
  return value !== null && typeof value === "object" && !Array.isArray(value);
1129
1413
  }
1414
+
1415
+ // src/version.ts
1416
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
1417
+ import { resolve as resolve7 } from "path";
1418
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/;
1419
+ function bumpVersion(current, release) {
1420
+ if (release === "major" || release === "minor" || release === "patch") {
1421
+ const match = SEMVER_RE.exec(current);
1422
+ if (!match) {
1423
+ throw new ConfigError(`package.json \u7248\u672C\u53F7\u975E\u6CD5: ${current}\uFF08\u671F\u671B x.y.z\uFF09`);
1424
+ }
1425
+ const [, major, minor, patch] = match;
1426
+ if (release === "major") return `${Number(major) + 1}.0.0`;
1427
+ if (release === "minor") return `${major}.${Number(minor) + 1}.0`;
1428
+ return `${major}.${minor}.${Number(patch) + 1}`;
1429
+ }
1430
+ if (!SEMVER_RE.test(release)) {
1431
+ throw new ConfigError(
1432
+ `--bump \u53D6\u503C\u975E\u6CD5: ${release}\uFF08\u652F\u6301 patch | minor | major | \u663E\u5F0F\u7248\u672C\u53F7 x.y.z\uFF09`
1433
+ );
1434
+ }
1435
+ return release;
1436
+ }
1437
+ function bumpPackageVersion(options) {
1438
+ const dir = resolve7(options.dir ?? process.cwd());
1439
+ const pkgFile = resolve7(dir, "package.json");
1440
+ if (!existsSync10(pkgFile)) {
1441
+ throw new ConfigError(`\u672A\u627E\u5230 package.json: ${dir}\uFF08\u8BF7\u5728\u76EE\u6807\u5DE5\u7A0B\u6839\u6267\u884C\uFF09`);
1442
+ }
1443
+ const pkg = JSON.parse(readFileSync8(pkgFile, "utf8"));
1444
+ const name = typeof pkg.name === "string" ? pkg.name : "(\u672A\u547D\u540D)";
1445
+ if (typeof pkg.version !== "string" || pkg.version === "") {
1446
+ throw new ConfigError(`package.json \u7F3A\u5C11 version \u5B57\u6BB5: ${pkgFile}`);
1447
+ }
1448
+ const from = pkg.version;
1449
+ const to = bumpVersion(from, options.release);
1450
+ pkg.version = to;
1451
+ writeFileSync4(pkgFile, `${JSON.stringify(pkg, null, 2)}
1452
+ `, "utf8");
1453
+ return { name, from, to };
1454
+ }
1130
1455
  export {
1131
1456
  CLIENT_EXTERNALS,
1457
+ CLIENT_STYLES_ENTRY,
1132
1458
  CONFIG_DEFAULTS,
1133
1459
  CONFIG_FILE,
1134
1460
  ConfigError,
1135
1461
  DEFAULT_TARGET,
1136
1462
  INCLUDE_KEY,
1137
1463
  IncludeError,
1464
+ MODULE_ID_PREFIX,
1138
1465
  ModuleBuildError,
1139
1466
  ProfileError,
1467
+ SERVER_BUNDLE_FILE,
1468
+ SERVER_ENTRY,
1469
+ SERVER_EXTERNALS,
1470
+ ServerBundleError,
1140
1471
  buildModule,
1472
+ bumpPackageVersion,
1473
+ bumpVersion,
1141
1474
  bundleClient,
1475
+ bundleServer,
1142
1476
  collectModuleSource,
1143
1477
  compileTailwind,
1478
+ deriveApiPrefix,
1144
1479
  deriveModuleId,
1145
1480
  discoverActions,
1146
1481
  discoverComponents,
@@ -1151,6 +1486,7 @@ export {
1151
1486
  initModule,
1152
1487
  innospotsWebShellV1,
1153
1488
  innospotsWebShellV2,
1489
+ innospotsWebShellV3,
1154
1490
  listProfiles,
1155
1491
  loadModuleConfig,
1156
1492
  loadYamlExpanded,
@@ -1159,6 +1495,8 @@ export {
1159
1495
  parseYamlFile,
1160
1496
  readProjectVersion,
1161
1497
  renderClientEntry,
1498
+ renderModuleStyles,
1499
+ renderServerEntry,
1162
1500
  resolvePactorVersionRange,
1163
1501
  rewriteDslUrls,
1164
1502
  rewriteNavigationTo,