openxiangda 1.0.159 → 1.0.161

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.
Files changed (62) hide show
  1. package/README.md +39 -13
  2. package/lib/change-baseline.js +1077 -0
  3. package/lib/cli.js +12002 -6723
  4. package/lib/config.js +272 -7
  5. package/lib/http.js +2 -0
  6. package/lib/js-code-build.js +61 -0
  7. package/lib/publish-context.js +98 -0
  8. package/lib/publish-lease-heartbeat.js +182 -0
  9. package/lib/publish-lease.js +190 -0
  10. package/lib/release-telemetry.js +182 -0
  11. package/lib/runtime-lineage.js +297 -0
  12. package/lib/sdd.js +567 -35
  13. package/lib/source-dependencies.js +742 -0
  14. package/lib/workspace-init.js +6 -1
  15. package/lib/worktree-owner.js +219 -0
  16. package/openxiangda-skills/SKILL.md +103 -228
  17. package/openxiangda-skills/references/architecture-design.md +2 -1
  18. package/openxiangda-skills/references/openxiangda-api.md +27 -0
  19. package/openxiangda-skills/references/pages/page-sdk.md +11 -1
  20. package/openxiangda-skills/references/resource-manifest-cheatsheet.md +12 -2
  21. package/openxiangda-skills/references/workspace-state.md +2 -0
  22. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +77 -243
  23. package/openxiangda-skills/skills/openxiangda-form/SKILL.md +8 -0
  24. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +18 -0
  25. package/package.json +20 -1
  26. package/packages/sdk/dist/{ProcessPreview-DMkzccq4.d.mts → ProcessPreview-Dux73lY_.d.mts} +5 -0
  27. package/packages/sdk/dist/{ProcessPreview-DMkzccq4.d.ts → ProcessPreview-Dux73lY_.d.ts} +5 -0
  28. package/packages/sdk/dist/components/index.cjs +29 -1
  29. package/packages/sdk/dist/components/index.cjs.map +1 -1
  30. package/packages/sdk/dist/components/index.d.mts +4 -4
  31. package/packages/sdk/dist/components/index.d.ts +4 -4
  32. package/packages/sdk/dist/components/index.mjs +29 -1
  33. package/packages/sdk/dist/components/index.mjs.map +1 -1
  34. package/packages/sdk/dist/{dataManagementApi-C9O-Bb0j.d.ts → dataManagementApi-BNLH2o7f.d.ts} +2 -1
  35. package/packages/sdk/dist/{dataManagementApi-gpZkgRDM.d.mts → dataManagementApi-BOcIx4U-.d.mts} +2 -1
  36. package/packages/sdk/dist/runtime/index.cjs +150 -22
  37. package/packages/sdk/dist/runtime/index.cjs.map +1 -1
  38. package/packages/sdk/dist/runtime/index.d.mts +3 -3
  39. package/packages/sdk/dist/runtime/index.d.ts +3 -3
  40. package/packages/sdk/dist/runtime/index.mjs +150 -22
  41. package/packages/sdk/dist/runtime/index.mjs.map +1 -1
  42. package/packages/sdk/dist/runtime/react.cjs +121 -21
  43. package/packages/sdk/dist/runtime/react.cjs.map +1 -1
  44. package/packages/sdk/dist/runtime/react.d.mts +15 -2
  45. package/packages/sdk/dist/runtime/react.d.ts +15 -2
  46. package/packages/sdk/dist/runtime/react.mjs +121 -21
  47. package/packages/sdk/dist/runtime/react.mjs.map +1 -1
  48. package/packages/sdk/src/build-source/scripts/register.mjs +54 -23
  49. package/packages/sdk/src/build-source/scripts/sync-schema.mjs +59 -19
  50. package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +242 -2
  51. package/templates/openxiangda-react-spa/.cursor/rules/openxiangda-resources.mdc +7 -3
  52. package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +11 -4
  53. package/templates/openxiangda-react-spa/.qoder/rules/openxiangda-resources.md +7 -3
  54. package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +10 -4
  55. package/templates/openxiangda-react-spa/AGENTS.md +25 -12
  56. package/templates/openxiangda-react-spa/scripts/build-js-code.mjs +344 -60
  57. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +5 -1
  58. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +8 -2
  59. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +8 -4
  60. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +8 -2
  61. package/templates/sy-lowcode-app-workspace/AGENTS.md +14 -4
  62. package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +344 -60
@@ -16,7 +16,12 @@ const TEMPLATE_IGNORE_NAMES = new Set([
16
16
  '.cache',
17
17
  'coverage',
18
18
  ]);
19
- const TEMPLATE_IGNORE_PATHS = new Set(['.openxiangda/build-cache.json']);
19
+ const TEMPLATE_IGNORE_PATHS = new Set([
20
+ '.openxiangda/build-cache.json',
21
+ '.openxiangda/build-cache.json.lock',
22
+ '.openxiangda/worktree-owner.json',
23
+ '.openxiangda/worktree-owner.json.lock',
24
+ ]);
20
25
 
21
26
  function initWorkspace(options = {}) {
22
27
  const targetDir = path.resolve(options.dir || process.cwd());
@@ -0,0 +1,219 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const { readWorkspaceIdentity } = require('./publish-lease');
5
+
6
+ const OWNER_RELATIVE_FILE = path.join('.openxiangda', 'worktree-owner.json');
7
+ const DEFAULT_OWNER_TTL_SECONDS = 12 * 60 * 60;
8
+ const LOCK_STALE_MS = 30_000;
9
+ const LOCK_RETRIES = 80;
10
+ const LOCK_RETRY_MS = 25;
11
+
12
+ function getWorktreeOwnerStatus(options = {}) {
13
+ const cwd = path.resolve(options.cwd || process.cwd());
14
+ const identity = readWorkspaceIdentity(cwd);
15
+ const file = path.join(cwd, OWNER_RELATIVE_FILE);
16
+ const owner = readOwnerFile(file);
17
+ const now = options.now === undefined ? Date.now() : Number(options.now);
18
+ const active = isOwnerActive(owner, now);
19
+ return {
20
+ enforced: Boolean(identity.threadId),
21
+ currentThreadId: identity.threadId || null,
22
+ active,
23
+ ownedByCurrentThread: Boolean(
24
+ active && identity.threadId && owner?.threadId === identity.threadId
25
+ ),
26
+ owner: owner || null,
27
+ file,
28
+ identity,
29
+ };
30
+ }
31
+
32
+ function claimWorktreeOwner(options = {}) {
33
+ const cwd = path.resolve(options.cwd || process.cwd());
34
+ const identity = readWorkspaceIdentity(cwd);
35
+ if (!identity.threadId) {
36
+ return {
37
+ enforced: false,
38
+ claimed: false,
39
+ reason: 'CODEX_THREAD_ID is not available',
40
+ identity,
41
+ };
42
+ }
43
+ const file = path.join(cwd, OWNER_RELATIVE_FILE);
44
+ const ttlSeconds = normalizeTtl(options.ttlSeconds);
45
+ return withOwnerLock(file, () => {
46
+ const now = options.now === undefined ? Date.now() : Number(options.now);
47
+ const current = readOwnerFile(file);
48
+ const active = isOwnerActive(current, now);
49
+ const foreign = active && current.threadId !== identity.threadId;
50
+ if (foreign && !options.force) {
51
+ throw ownershipError(current, identity);
52
+ }
53
+ if (foreign && !String(options.reason || '').trim()) {
54
+ throw new Error('强制接管 worktree 必须提供 --reason');
55
+ }
56
+ const acquiredAt =
57
+ active && current.threadId === identity.threadId
58
+ ? current.acquiredAt
59
+ : new Date(now).toISOString();
60
+ const owner = {
61
+ version: 1,
62
+ threadId: identity.threadId,
63
+ changeId: text(options.changeId) || text(current?.changeId) || null,
64
+ cwd: identity.cwd,
65
+ worktreeRoot: identity.worktreeRoot || identity.cwd,
66
+ branch: identity.branch || null,
67
+ commit: identity.commit || null,
68
+ acquiredAt,
69
+ heartbeatAt: new Date(now).toISOString(),
70
+ expiresAt: new Date(now + ttlSeconds * 1000).toISOString(),
71
+ ...(foreign
72
+ ? {
73
+ takeover: {
74
+ previousThreadId: current.threadId,
75
+ reason: String(options.reason).trim(),
76
+ at: new Date(now).toISOString(),
77
+ },
78
+ }
79
+ : {}),
80
+ };
81
+ writeOwnerFile(file, owner);
82
+ return {
83
+ enforced: true,
84
+ claimed: true,
85
+ renewed: Boolean(active && current.threadId === identity.threadId),
86
+ owner,
87
+ file,
88
+ };
89
+ });
90
+ }
91
+
92
+ function releaseWorktreeOwner(options = {}) {
93
+ const cwd = path.resolve(options.cwd || process.cwd());
94
+ const identity = readWorkspaceIdentity(cwd);
95
+ const file = path.join(cwd, OWNER_RELATIVE_FILE);
96
+ if (!fs.existsSync(file)) {
97
+ return { released: false, active: false, file };
98
+ }
99
+ return withOwnerLock(file, () => {
100
+ const current = readOwnerFile(file);
101
+ if (!current) return { released: false, active: false, file };
102
+ const foreign =
103
+ identity.threadId &&
104
+ current.threadId &&
105
+ current.threadId !== identity.threadId &&
106
+ isOwnerActive(current, Date.now());
107
+ if (foreign && !options.force) throw ownershipError(current, identity);
108
+ if (foreign && !String(options.reason || '').trim()) {
109
+ throw new Error('强制释放其他任务的 worktree 所有权必须提供 --reason');
110
+ }
111
+ fs.rmSync(file, { force: true });
112
+ return { released: true, active: false, previousOwner: current, file };
113
+ });
114
+ }
115
+
116
+ function assertOrClaimWorktreeOwner(options = {}) {
117
+ return claimWorktreeOwner(options);
118
+ }
119
+
120
+ function ownershipError(owner, identity) {
121
+ const error = new Error(
122
+ [
123
+ `当前 worktree 已由另一个 Codex 任务占用: ${owner.threadId}`,
124
+ owner.changeId ? `change=${owner.changeId}` : '',
125
+ owner.branch ? `branch=${owner.branch}` : '',
126
+ `请为任务 ${identity.threadId} 创建独立 git worktree 后继续;不要在共享目录覆盖对方源码。`,
127
+ '确认原任务已结束时,可执行 workspace ownership claim --force --reason "..." 显式接管。',
128
+ ]
129
+ .filter(Boolean)
130
+ .join(' ')
131
+ );
132
+ error.code = 'WORKTREE_OWNED_BY_ANOTHER_TASK';
133
+ error.owner = owner;
134
+ return error;
135
+ }
136
+
137
+ function readOwnerFile(file) {
138
+ if (!fs.existsSync(file)) return null;
139
+ try {
140
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ function writeOwnerFile(file, value) {
147
+ fs.mkdirSync(path.dirname(file), { recursive: true });
148
+ const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
149
+ fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
150
+ fs.renameSync(temp, file);
151
+ }
152
+
153
+ function withOwnerLock(file, operation) {
154
+ fs.mkdirSync(path.dirname(file), { recursive: true });
155
+ const lockFile = `${file}.lock`;
156
+ let fd = null;
157
+ for (let attempt = 0; attempt < LOCK_RETRIES; attempt += 1) {
158
+ try {
159
+ fd = fs.openSync(lockFile, 'wx', 0o600);
160
+ break;
161
+ } catch (error) {
162
+ if (error?.code !== 'EEXIST') throw error;
163
+ try {
164
+ const age = Date.now() - fs.statSync(lockFile).mtimeMs;
165
+ if (age > LOCK_STALE_MS) {
166
+ fs.rmSync(lockFile, { force: true });
167
+ continue;
168
+ }
169
+ } catch {}
170
+ synchronousWait(LOCK_RETRY_MS);
171
+ }
172
+ }
173
+ if (fd === null) throw new Error(`无法获取 worktree ownership lock: ${lockFile}`);
174
+ try {
175
+ return operation();
176
+ } finally {
177
+ try {
178
+ fs.closeSync(fd);
179
+ } finally {
180
+ fs.rmSync(lockFile, { force: true });
181
+ }
182
+ }
183
+ }
184
+
185
+ function synchronousWait(milliseconds) {
186
+ const signal = new Int32Array(new SharedArrayBuffer(4));
187
+ Atomics.wait(signal, 0, 0, milliseconds);
188
+ }
189
+
190
+ function isOwnerActive(owner, now = Date.now()) {
191
+ if (!owner?.threadId || !owner?.expiresAt) return false;
192
+ const expiresAt = new Date(owner.expiresAt).getTime();
193
+ return Number.isFinite(expiresAt) && expiresAt > Number(now);
194
+ }
195
+
196
+ function normalizeTtl(value) {
197
+ if (value === undefined || value === null || value === '') {
198
+ return DEFAULT_OWNER_TTL_SECONDS;
199
+ }
200
+ const ttl = Number(value);
201
+ if (!Number.isSafeInteger(ttl) || ttl < 300 || ttl > 86_400) {
202
+ throw new Error('worktree ownership ttl 必须是 300-86400 秒之间的整数');
203
+ }
204
+ return ttl;
205
+ }
206
+
207
+ function text(value) {
208
+ return String(value || '').trim();
209
+ }
210
+
211
+ module.exports = {
212
+ DEFAULT_OWNER_TTL_SECONDS,
213
+ OWNER_RELATIVE_FILE,
214
+ assertOrClaimWorktreeOwner,
215
+ claimWorktreeOwner,
216
+ getWorktreeOwnerStatus,
217
+ isOwnerActive,
218
+ releaseWorktreeOwner,
219
+ };