openxiangda 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -13,8 +13,9 @@ import minimist from "minimist";
13
13
  import { rootDir } from "./utils/load-config.mjs";
14
14
  import {
15
15
  discoverWorkspaceModules,
16
+ commitIncrementalPublish,
16
17
  normalizeOnly,
17
- planIncrementalBuild,
18
+ planIncrementalPublish,
18
19
  printPlan,
19
20
  } from "./utils/incremental.mjs";
20
21
 
@@ -99,7 +100,7 @@ const maybeDryRun = dryRun ? ["--dry-run"] : [];
99
100
  const maybeForce = force ? ["--force"] : [];
100
101
 
101
102
  const allModules = discoverWorkspaceModules();
102
- const plan = planIncrementalBuild(allModules, { force, only });
103
+ const plan = planIncrementalPublish(allModules, { force, only });
103
104
  printPlan(plan, "publish-all");
104
105
  if (plan.changed.length === 0 && !force) {
105
106
  console.log("✅ 没有检测到变更,跳过发布");
@@ -110,18 +111,26 @@ console.log(`🚀 发布应用工作区 (${dryRun ? "DRY RUN" : "LIVE"})`);
110
111
  console.log(` APP_BUILD_ID=${buildId}`);
111
112
  console.log("");
112
113
 
114
+ const publishedModules = [];
115
+
113
116
  for (const moduleItem of plan.changed) {
114
117
  if (moduleItem.kind === "forms") {
115
118
  await run("sync-schema.mjs", ["--form", moduleItem.name, ...maybeDryRun]);
116
119
  await run("build-forms.mjs", ["--form", moduleItem.name, ...maybeForce]);
117
120
  await run("publish-oss.mjs", ["--form", moduleItem.name, ...maybeDryRun]);
118
121
  await run("register.mjs", ["--form", moduleItem.name, ...maybeDryRun]);
122
+ publishedModules.push(moduleItem);
119
123
  } else if (moduleItem.kind === "pages") {
120
124
  await run("build-pages.mjs", ["--page", moduleItem.name, ...maybeForce]);
121
125
  await run("publish-oss.mjs", ["--page", moduleItem.name, ...maybeDryRun]);
122
126
  await run("register.mjs", ["--page", moduleItem.name, ...maybeDryRun]);
127
+ publishedModules.push(moduleItem);
123
128
  }
124
129
  }
125
130
 
131
+ if (!dryRun) {
132
+ commitIncrementalPublish(plan, publishedModules);
133
+ }
134
+
126
135
  console.log("");
127
136
  console.log(`✅ 发布流程完成,buildId=${buildId}`);
@@ -5,6 +5,11 @@ import { rootDir } from "./load-config.mjs";
5
5
 
6
6
  const CACHE_VERSION = 1;
7
7
  export const CACHE_FILE = path.join(rootDir, ".openxiangda", "build-cache.json");
8
+ export const PUBLISH_CACHE_FILE = path.join(
9
+ rootDir,
10
+ ".openxiangda",
11
+ "publish-cache.json",
12
+ );
8
13
 
9
14
  function toPosix(filePath) {
10
15
  return filePath.split(path.sep).join("/");
@@ -89,15 +94,23 @@ export function computeConfigHash() {
89
94
  return hash.digest("hex");
90
95
  }
91
96
 
92
- export function loadBuildCache() {
97
+ function loadCache(cacheFile) {
93
98
  try {
94
- const cache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf-8"));
99
+ const cache = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
95
100
  return cache.version === CACHE_VERSION ? cache : null;
96
101
  } catch {
97
102
  return null;
98
103
  }
99
104
  }
100
105
 
106
+ export function loadBuildCache() {
107
+ return loadCache(CACHE_FILE);
108
+ }
109
+
110
+ export function loadPublishCache() {
111
+ return loadCache(PUBLISH_CACHE_FILE);
112
+ }
113
+
101
114
  export function cleanBuildCache() {
102
115
  fs.rmSync(CACHE_FILE, { force: true });
103
116
  }
@@ -111,7 +124,7 @@ export function normalizeOnly(value) {
111
124
  .filter(Boolean);
112
125
  }
113
126
 
114
- export function planIncrementalBuild(modules, options = {}) {
127
+ function createIncrementalPlan(modules, options = {}, cache = null) {
115
128
  const only = normalizeOnly(options.only);
116
129
  const selected = only.length
117
130
  ? modules.filter((item) => only.includes(item.key))
@@ -125,7 +138,6 @@ export function planIncrementalBuild(modules, options = {}) {
125
138
  configHash: computeConfigHash(),
126
139
  moduleHashes,
127
140
  };
128
- const cache = loadBuildCache();
129
141
  const fullRebuild =
130
142
  Boolean(options.force) ||
131
143
  !cache ||
@@ -145,6 +157,14 @@ export function planIncrementalBuild(modules, options = {}) {
145
157
  };
146
158
  }
147
159
 
160
+ export function planIncrementalBuild(modules, options = {}) {
161
+ return createIncrementalPlan(modules, options, loadBuildCache());
162
+ }
163
+
164
+ export function planIncrementalPublish(modules, options = {}) {
165
+ return createIncrementalPlan(modules, options, loadPublishCache());
166
+ }
167
+
148
168
  export function discoverWorkspaceModules() {
149
169
  const modules = [];
150
170
  for (const [kind, requiredFile] of [
@@ -168,24 +188,23 @@ export function discoverWorkspaceModules() {
168
188
  return modules.sort((left, right) => left.key.localeCompare(right.key));
169
189
  }
170
190
 
171
- export function commitIncrementalBuild(plan, builtModules) {
172
- const previous = loadBuildCache();
191
+ function commitIncrementalCache(cacheFile, previous, plan, modules) {
173
192
  const entries = { ...(previous?.entries || {}) };
174
193
  const now = new Date().toISOString();
175
194
  const isPartialBuild = plan.selected.length !== plan.all.length;
176
195
  const preserveGlobalSnapshot = Boolean(
177
196
  previous && isPartialBuild && plan.fullRebuild,
178
197
  );
179
- for (const item of builtModules) {
198
+ for (const item of modules) {
180
199
  entries[item.key] = {
181
200
  contentHash: plan.snapshot.moduleHashes[item.key],
182
201
  lastBuildTime: now,
183
202
  success: true,
184
203
  };
185
204
  }
186
- fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
205
+ fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
187
206
  fs.writeFileSync(
188
- CACHE_FILE,
207
+ cacheFile,
189
208
  `${JSON.stringify(
190
209
  {
191
210
  version: CACHE_VERSION,
@@ -207,6 +226,19 @@ export function commitIncrementalBuild(plan, builtModules) {
207
226
  );
208
227
  }
209
228
 
229
+ export function commitIncrementalBuild(plan, builtModules) {
230
+ commitIncrementalCache(CACHE_FILE, loadBuildCache(), plan, builtModules);
231
+ }
232
+
233
+ export function commitIncrementalPublish(plan, publishedModules) {
234
+ commitIncrementalCache(
235
+ PUBLISH_CACHE_FILE,
236
+ loadPublishCache(),
237
+ plan,
238
+ publishedModules,
239
+ );
240
+ }
241
+
210
242
  export function printPlan(plan, label) {
211
243
  if (plan.changed.length === 0) {
212
244
  console.log(`[build] ${label}: No changes detected`);
@@ -0,0 +1,78 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+
7
+ let tempDirs: string[] = [];
8
+
9
+ function createWorkspace() {
10
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "workspace-incremental-"));
11
+ tempDirs.push(dir);
12
+ fs.mkdirSync(path.join(dir, "src", "pages", "dashboard"), {
13
+ recursive: true,
14
+ });
15
+ fs.writeFileSync(
16
+ path.join(dir, "package.json"),
17
+ JSON.stringify({ name: "test-workspace", dependencies: { openxiangda: "1.0.5" } }),
18
+ "utf-8",
19
+ );
20
+ fs.writeFileSync(path.join(dir, "pnpm-lock.yaml"), "lockfileVersion: 9.0\n", "utf-8");
21
+ fs.writeFileSync(
22
+ path.join(dir, "src", "pages", "dashboard", "page.config.ts"),
23
+ "export default { code: 'dashboard', name: 'Dashboard' };\n",
24
+ "utf-8",
25
+ );
26
+ fs.writeFileSync(
27
+ path.join(dir, "src", "pages", "dashboard", "index.tsx"),
28
+ "export default function Dashboard() { return null; }\n",
29
+ "utf-8",
30
+ );
31
+ return dir;
32
+ }
33
+
34
+ async function loadIncremental(rootDir: string) {
35
+ const previousRoot = process.env.LOWCODE_WORKSPACE_ROOT;
36
+ process.env.LOWCODE_WORKSPACE_ROOT = rootDir;
37
+ vi.resetModules();
38
+ const module = await import("./incremental.mjs");
39
+ if (previousRoot === undefined) {
40
+ delete process.env.LOWCODE_WORKSPACE_ROOT;
41
+ } else {
42
+ process.env.LOWCODE_WORKSPACE_ROOT = previousRoot;
43
+ }
44
+ return module;
45
+ }
46
+
47
+ afterEach(() => {
48
+ for (const dir of tempDirs) {
49
+ fs.rmSync(dir, { recursive: true, force: true });
50
+ }
51
+ tempDirs = [];
52
+ vi.resetModules();
53
+ });
54
+
55
+ describe("incremental publish cache", () => {
56
+ it("does not treat a local build cache entry as an already published module", async () => {
57
+ const workspaceRoot = createWorkspace();
58
+ const incremental = await loadIncremental(workspaceRoot);
59
+ const modules = incremental.discoverWorkspaceModules();
60
+
61
+ const buildPlan = incremental.planIncrementalBuild(modules);
62
+ expect(buildPlan.changed.map((item: any) => item.key)).toEqual([
63
+ "pages/dashboard",
64
+ ]);
65
+
66
+ incremental.commitIncrementalBuild(buildPlan, buildPlan.changed);
67
+
68
+ expect(incremental.planIncrementalBuild(modules).changed).toHaveLength(0);
69
+ expect(incremental.planIncrementalPublish(modules).changed.map((item: any) => item.key)).toEqual([
70
+ "pages/dashboard",
71
+ ]);
72
+
73
+ const publishPlan = incremental.planIncrementalPublish(modules);
74
+ incremental.commitIncrementalPublish(publishPlan, publishPlan.changed);
75
+
76
+ expect(incremental.planIncrementalPublish(modules).changed).toHaveLength(0);
77
+ });
78
+ });