openxiangda 1.0.5 → 1.0.7

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.7",
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
+ });
@@ -73,6 +73,7 @@ export function isWorkspaceTailwindConfigCurrent(content) {
73
73
 
74
74
  export function isWorkspaceTailwindCssCurrent(content) {
75
75
  return (
76
+ /@import\s+["']openxiangda\/styles\/tokens\.css["'];/.test(content) &&
76
77
  /@layer\s+tailwind-base\s*\{\s*@tailwind\s+base\s*;\s*\}/s.test(content) &&
77
78
  !/@layer\s+tailwind-base\s*,\s*antd\s*;/.test(content) &&
78
79
  tailwindDirectives
@@ -270,7 +271,7 @@ export function validateWorkspaceTailwindConfig(workspaceRoot) {
270
271
  : "";
271
272
  if (!isWorkspaceTailwindCssCurrent(cssContent)) {
272
273
  return [
273
- "src/index.css must keep Tailwind base in @layer tailwind-base and avoid declaring antd layer",
274
+ "src/index.css must import openxiangda/styles/tokens.css, keep Tailwind base in @layer tailwind-base, and avoid declaring antd layer",
274
275
  ];
275
276
  }
276
277
  return [];
@@ -60,6 +60,7 @@ describe("workspace Tailwind config helpers", () => {
60
60
  expect(cssContent).toContain("@tailwind utilities;");
61
61
  expect(postcssContent).toContain("createOpenXiangdaNamespaceCssPlugin");
62
62
  expect(postcssContent).toContain('require("openxiangda/build")');
63
+ expect(cssContent).toContain('@import "openxiangda/styles/tokens.css";');
63
64
  expect(validateWorkspaceTailwindConfig(workspaceRoot)).toEqual([]);
64
65
  });
65
66
 
@@ -128,7 +129,9 @@ module.exports = {
128
129
  });
129
130
 
130
131
  it("keeps current Tailwind base layer index css unchanged", () => {
131
- const source = `@layer tailwind-base {
132
+ const source = `@import "openxiangda/styles/tokens.css";
133
+
134
+ @layer tailwind-base {
132
135
  @tailwind base;
133
136
  }
134
137
 
@@ -143,6 +146,20 @@ module.exports = {
143
146
  expect(patchWorkspaceTailwindCss(source)).toBe(source);
144
147
  });
145
148
 
149
+ it("adds missing OpenXiangda design tokens import to otherwise current index css", () => {
150
+ const nextContent = patchWorkspaceTailwindCss(`@layer tailwind-base {
151
+ @tailwind base;
152
+ }
153
+
154
+ @tailwind components;
155
+ @tailwind utilities;
156
+ `);
157
+
158
+ expect(nextContent).toContain('@import "openxiangda/styles/tokens.css";');
159
+ expect(nextContent.match(/openxiangda\/styles\/tokens\.css/g)).toHaveLength(1);
160
+ expect(nextContent).toContain("@layer tailwind-base");
161
+ });
162
+
146
163
  it("patches standard PostCSS config with openxiangda namespace css plugin", () => {
147
164
  const nextContent = patchWorkspacePostcssConfig(`module.exports = {
148
165
  plugins: {
@@ -210,7 +227,7 @@ module.exports = { plugins: [custom()] };
210
227
  );
211
228
 
212
229
  expect(validateWorkspaceTailwindConfig(workspaceRoot)).toEqual([
213
- "src/index.css must keep Tailwind base in @layer tailwind-base and avoid declaring antd layer",
230
+ "src/index.css must import openxiangda/styles/tokens.css, keep Tailwind base in @layer tailwind-base, and avoid declaring antd layer",
214
231
  ]);
215
232
  });
216
233
  });