openxiangda 1.0.162 → 1.0.164
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/README.md +6 -2
- package/lib/change-baseline.js +88 -2
- package/lib/cli.js +565 -73
- package/lib/js-code-build.js +120 -8
- package/lib/publish-lease.js +13 -1
- package/lib/release-mainline.js +545 -0
- package/lib/runtime-lineage.js +19 -12
- package/lib/sdd.js +137 -0
- package/lib/workspace-bootstrap.js +36 -7
- package/lib/workspace-init.js +12 -3
- package/lib/worktree-owner.js +13 -0
- package/openxiangda-skills/SKILL.md +9 -3
- package/openxiangda-skills/references/automation-v3.md +1 -1
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +1 -1
- package/openxiangda-skills/references/workflow-v3.md +1 -1
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +8 -2
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +2 -2
- package/package.json +3 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda-resources.mdc +2 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +2 -2
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda-resources.md +2 -1
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +3 -3
- package/templates/openxiangda-react-spa/AGENTS.md +8 -6
- package/templates/openxiangda-react-spa/scripts/build-js-code.mjs +172 -20
- package/templates/openxiangda-react-spa/scripts/deploy.mjs +19 -39
- package/templates/openxiangda-react-spa/scripts/guard-publish.mjs +4 -5
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +3 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +1 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +3 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +1 -1
- package/templates/sy-lowcode-app-workspace/AGENTS.md +4 -4
- package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +172 -20
- package/templates/sy-lowcode-app-workspace/scripts/guard-publish.mjs +4 -5
|
@@ -19,13 +19,29 @@ import path from "node:path";
|
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { build } from "vite";
|
|
21
21
|
|
|
22
|
-
const rootDir = fileURLToPath(new URL("..", import.meta.url));
|
|
23
22
|
const scriptFile = fileURLToPath(import.meta.url);
|
|
23
|
+
const defaultRootDir = fileURLToPath(new URL("..", import.meta.url));
|
|
24
|
+
const rootDir = path.resolve(process.env.OPENXIANGDA_WORKSPACE_ROOT || defaultRootDir);
|
|
24
25
|
const args = process.argv.slice(2);
|
|
25
26
|
const require = createRequire(import.meta.url);
|
|
26
|
-
const
|
|
27
|
-
const
|
|
27
|
+
const ts = require("typescript");
|
|
28
|
+
const CACHE_VERSION = 3;
|
|
29
|
+
const builderFingerprint = crypto
|
|
30
|
+
.createHash("sha256")
|
|
31
|
+
.update(readFileSync(scriptFile))
|
|
32
|
+
.digest("hex");
|
|
33
|
+
const workspaceBuilderFile = path.join(rootDir, "scripts", "build-js-code.mjs");
|
|
34
|
+
const workspaceBuilderMatches =
|
|
35
|
+
existsSync(workspaceBuilderFile) &&
|
|
36
|
+
crypto.createHash("sha256").update(readFileSync(workspaceBuilderFile)).digest("hex") ===
|
|
37
|
+
builderFingerprint;
|
|
38
|
+
const cacheFileName =
|
|
39
|
+
path.resolve(defaultRootDir) !== rootDir && !workspaceBuilderMatches
|
|
40
|
+
? "build-cache.cli-v3.json"
|
|
41
|
+
: "build-cache.json";
|
|
42
|
+
const CACHE_FILE = path.join(rootDir, ".openxiangda", cacheFileName);
|
|
28
43
|
const CACHE_LOCK_FILE = `${CACHE_FILE}.lock`;
|
|
44
|
+
const TSCONFIG_FILE = path.join(rootDir, "tsconfig.js-code-nodes.json");
|
|
29
45
|
const forceBuild = args.includes("--force") || args.includes("--no-cache");
|
|
30
46
|
|
|
31
47
|
const sourceKinds = {
|
|
@@ -84,11 +100,24 @@ function assertScriptCode(scriptCode) {
|
|
|
84
100
|
function listScriptCodes(kind) {
|
|
85
101
|
if (!existsSync(kind.sourceRoot)) return [];
|
|
86
102
|
return readdirSync(kind.sourceRoot, { withFileTypes: true })
|
|
87
|
-
.filter(
|
|
103
|
+
.filter(
|
|
104
|
+
(entry) =>
|
|
105
|
+
entry.isDirectory() &&
|
|
106
|
+
["index.ts", "index.tsx"].some((name) =>
|
|
107
|
+
existsSync(path.join(kind.sourceRoot, entry.name, name)),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
88
110
|
.map((entry) => entry.name)
|
|
89
111
|
.sort();
|
|
90
112
|
}
|
|
91
113
|
|
|
114
|
+
function targetEntryFile(target) {
|
|
115
|
+
const base = path.join(target.kind.sourceRoot, target.scriptCode);
|
|
116
|
+
return ["index.ts", "index.tsx"]
|
|
117
|
+
.map((name) => path.join(base, name))
|
|
118
|
+
.find((file) => existsSync(file)) || path.join(base, "index.ts");
|
|
119
|
+
}
|
|
120
|
+
|
|
92
121
|
function parseScriptSpec(rawSpec, defaultKind) {
|
|
93
122
|
const prefixed = String(rawSpec).match(/^(js-code-nodes|automations|functions)[:/](.+)$/);
|
|
94
123
|
const requestedKind = prefixed?.[1] || defaultKind?.name;
|
|
@@ -101,7 +130,9 @@ function parseScriptSpec(rawSpec, defaultKind) {
|
|
|
101
130
|
if (requestedKind) return { kind: sourceKinds[requestedKind], scriptCode };
|
|
102
131
|
|
|
103
132
|
const matches = Object.values(sourceKinds).filter((kind) =>
|
|
104
|
-
|
|
133
|
+
["index.ts", "index.tsx"].some((name) =>
|
|
134
|
+
existsSync(path.join(kind.sourceRoot, scriptCode, name)),
|
|
135
|
+
),
|
|
105
136
|
);
|
|
106
137
|
if (matches.length > 1) {
|
|
107
138
|
console.warn(
|
|
@@ -111,7 +142,7 @@ function parseScriptSpec(rawSpec, defaultKind) {
|
|
|
111
142
|
return { kind: matches[0] || sourceKinds["js-code-nodes"], scriptCode };
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
function
|
|
145
|
+
function resolveBuildSelection() {
|
|
115
146
|
const sourceArgs = readArgs("source");
|
|
116
147
|
const sourceNames = [...new Set(sourceArgs)];
|
|
117
148
|
if (sourceNames.length > 1) throw new Error("--source may only select one source kind");
|
|
@@ -128,7 +159,10 @@ function resolveBuildTargets() {
|
|
|
128
159
|
: (selectedKind ? [selectedKind] : Object.values(sourceKinds)).flatMap((kind) =>
|
|
129
160
|
listScriptCodes(kind).map((scriptCode) => ({ kind, scriptCode })),
|
|
130
161
|
);
|
|
131
|
-
return
|
|
162
|
+
return {
|
|
163
|
+
scoped: specs.length > 0,
|
|
164
|
+
targets: [...new Map(targets.map((target) => [`${target.kind.name}/${target.scriptCode}`, target])).values()],
|
|
165
|
+
};
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
function packageVersion(name) {
|
|
@@ -144,6 +178,7 @@ const toolFingerprint = crypto
|
|
|
144
178
|
.update(
|
|
145
179
|
JSON.stringify({
|
|
146
180
|
cacheVersion: CACHE_VERSION,
|
|
181
|
+
builder: builderFingerprint,
|
|
147
182
|
node: process.version,
|
|
148
183
|
platform: process.platform,
|
|
149
184
|
arch: process.arch,
|
|
@@ -157,14 +192,25 @@ const toolFingerprint = crypto
|
|
|
157
192
|
.digest("hex");
|
|
158
193
|
|
|
159
194
|
function emptyCache() {
|
|
160
|
-
return {
|
|
195
|
+
return {
|
|
196
|
+
version: CACHE_VERSION,
|
|
197
|
+
toolFingerprint,
|
|
198
|
+
typecheck: null,
|
|
199
|
+
typechecks: {},
|
|
200
|
+
targets: {},
|
|
201
|
+
};
|
|
161
202
|
}
|
|
162
203
|
|
|
163
204
|
function readCache(file = CACHE_FILE) {
|
|
164
205
|
try {
|
|
165
206
|
const cache = JSON.parse(readFileSync(file, "utf8"));
|
|
166
207
|
if (cache.version !== CACHE_VERSION) return emptyCache();
|
|
167
|
-
return {
|
|
208
|
+
return {
|
|
209
|
+
...emptyCache(),
|
|
210
|
+
...cache,
|
|
211
|
+
typechecks: cache.typechecks || {},
|
|
212
|
+
targets: cache.targets || {},
|
|
213
|
+
};
|
|
168
214
|
} catch {
|
|
169
215
|
return emptyCache();
|
|
170
216
|
}
|
|
@@ -231,7 +277,7 @@ function typecheckInputHash() {
|
|
|
231
277
|
}
|
|
232
278
|
|
|
233
279
|
function targetInputHash(target, dependencies) {
|
|
234
|
-
const entry = relativeWorkspaceFile(
|
|
280
|
+
const entry = relativeWorkspaceFile(targetEntryFile(target));
|
|
235
281
|
return hashFileSet(
|
|
236
282
|
[...configInputFiles(), entry, ...(dependencies || [])].filter(Boolean),
|
|
237
283
|
`target:${target.kind.name}/${target.scriptCode}`,
|
|
@@ -258,11 +304,94 @@ function isTargetCacheHit(cache, target) {
|
|
|
258
304
|
);
|
|
259
305
|
}
|
|
260
306
|
|
|
261
|
-
function
|
|
307
|
+
function formatTypeScriptDiagnostics(diagnostics) {
|
|
308
|
+
return ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
309
|
+
getCanonicalFileName: (file) => file,
|
|
310
|
+
getCurrentDirectory: () => rootDir,
|
|
311
|
+
getNewLine: () => "\n",
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function scopedTypecheckKey(targets) {
|
|
316
|
+
return targets
|
|
317
|
+
.map((target) => `${target.kind.name}/${target.scriptCode}`)
|
|
318
|
+
.sort()
|
|
319
|
+
.join(",");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function scopedAmbientDeclarationFiles() {
|
|
323
|
+
return walkFiles(path.join(rootDir, "src"))
|
|
324
|
+
.filter((file) => file.endsWith(".d.ts"))
|
|
325
|
+
.map(relativeWorkspaceFile)
|
|
326
|
+
.filter(Boolean);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function createScopedTypecheckProgram(targets) {
|
|
330
|
+
const loaded = ts.readConfigFile(TSCONFIG_FILE, ts.sys.readFile);
|
|
331
|
+
if (loaded.error) {
|
|
332
|
+
process.stderr.write(formatTypeScriptDiagnostics([loaded.error]));
|
|
333
|
+
throw new Error("JS_CODE scoped TypeScript config validation failed");
|
|
334
|
+
}
|
|
335
|
+
const files = [
|
|
336
|
+
...targets.map((target) =>
|
|
337
|
+
relativeWorkspaceFile(targetEntryFile(target)),
|
|
338
|
+
),
|
|
339
|
+
...scopedAmbientDeclarationFiles(),
|
|
340
|
+
].filter(Boolean);
|
|
341
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
342
|
+
{ ...loaded.config, files, include: [] },
|
|
343
|
+
ts.sys,
|
|
344
|
+
rootDir,
|
|
345
|
+
{ noEmit: true },
|
|
346
|
+
TSCONFIG_FILE,
|
|
347
|
+
);
|
|
348
|
+
if (parsed.errors.length > 0) {
|
|
349
|
+
process.stderr.write(formatTypeScriptDiagnostics(parsed.errors));
|
|
350
|
+
throw new Error("JS_CODE scoped TypeScript config validation failed");
|
|
351
|
+
}
|
|
352
|
+
return ts.createProgram({
|
|
353
|
+
rootNames: parsed.fileNames,
|
|
354
|
+
options: parsed.options,
|
|
355
|
+
projectReferences: parsed.projectReferences,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function scopedTypecheckInputHash(program, cacheKey) {
|
|
360
|
+
const sourceFiles = program
|
|
361
|
+
.getSourceFiles()
|
|
362
|
+
.map((sourceFile) => relativeWorkspaceFile(sourceFile.fileName))
|
|
363
|
+
.filter(Boolean);
|
|
364
|
+
return hashFileSet([...configInputFiles(), ...sourceFiles], `typecheck:${cacheKey}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function typecheckSelectedScripts(cache, targets) {
|
|
368
|
+
const cacheKey = scopedTypecheckKey(targets);
|
|
369
|
+
const program = createScopedTypecheckProgram(targets);
|
|
370
|
+
const inputHash = scopedTypecheckInputHash(program, cacheKey);
|
|
371
|
+
if (
|
|
372
|
+
!forceBuild &&
|
|
373
|
+
cache.toolFingerprint === toolFingerprint &&
|
|
374
|
+
cache.typechecks?.[cacheKey]?.inputHash === inputHash
|
|
375
|
+
) {
|
|
376
|
+
console.log(`[build-js-code] scoped TypeScript validation cache hit (${targets.length} selected)`);
|
|
377
|
+
return { cacheHit: true, cacheKey, inputHash, mode: "scoped" };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log(`[build-js-code] validating TypeScript for ${targets.length} selected target(s)...`);
|
|
381
|
+
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
382
|
+
if (diagnostics.length > 0) {
|
|
383
|
+
process.stderr.write(formatTypeScriptDiagnostics(diagnostics));
|
|
384
|
+
throw new Error("JS_CODE scoped TypeScript validation failed");
|
|
385
|
+
}
|
|
386
|
+
console.log("[build-js-code] scoped TypeScript validation passed");
|
|
387
|
+
return { cacheHit: false, cacheKey, inputHash, mode: "scoped" };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function typecheckAllScripts(cache) {
|
|
262
391
|
const inputHash = typecheckInputHash();
|
|
263
392
|
if (!forceBuild && cache.toolFingerprint === toolFingerprint && cache.typecheck?.inputHash === inputHash) {
|
|
264
393
|
console.log("[build-js-code] TypeScript validation cache hit");
|
|
265
|
-
return { cacheHit: true, inputHash };
|
|
394
|
+
return { cacheHit: true, inputHash, mode: "full" };
|
|
266
395
|
}
|
|
267
396
|
|
|
268
397
|
console.log("[build-js-code] validating TypeScript once for this batch...");
|
|
@@ -275,7 +404,13 @@ function typecheckScripts(cache) {
|
|
|
275
404
|
console.log("[build-js-code] TypeScript validation passed");
|
|
276
405
|
// pnpm may materialize/update a lockfile on the first invocation. Record the
|
|
277
406
|
// post-validation inputs so the next process can reuse this successful tsc.
|
|
278
|
-
return { cacheHit: false, inputHash: typecheckInputHash() };
|
|
407
|
+
return { cacheHit: false, inputHash: typecheckInputHash(), mode: "full" };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function typecheckScripts(cache, targets, scoped) {
|
|
411
|
+
return scoped
|
|
412
|
+
? typecheckSelectedScripts(cache, targets)
|
|
413
|
+
: typecheckAllScripts(cache);
|
|
279
414
|
}
|
|
280
415
|
|
|
281
416
|
function collectBuildDependencies(buildResult, entry) {
|
|
@@ -296,7 +431,7 @@ function collectBuildDependencies(buildResult, entry) {
|
|
|
296
431
|
|
|
297
432
|
async function buildScript(target) {
|
|
298
433
|
const { kind, scriptCode } = target;
|
|
299
|
-
const entry =
|
|
434
|
+
const entry = targetEntryFile(target);
|
|
300
435
|
if (!existsSync(entry)) throw new Error(`${kind.label} script not found: ${entry}`);
|
|
301
436
|
const outDir = path.join(kind.outputRoot, scriptCode);
|
|
302
437
|
const external = Array.from(
|
|
@@ -361,14 +496,31 @@ function saveCache(typecheck, updatedTargets) {
|
|
|
361
496
|
if (typecheck.cacheHit && updatedTargets.size === 0) return;
|
|
362
497
|
const lockFd = acquireCacheLock();
|
|
363
498
|
try {
|
|
364
|
-
const
|
|
499
|
+
const stored = readCache();
|
|
500
|
+
// A scoped build updates only part of the cache. Never carry metadata from
|
|
501
|
+
// a different compiler/builder fingerprint into the new cache, otherwise
|
|
502
|
+
// untouched targets could become false hits after this process stamps the
|
|
503
|
+
// cache with the current fingerprint.
|
|
504
|
+
const latest = stored.toolFingerprint === toolFingerprint
|
|
505
|
+
? stored
|
|
506
|
+
: emptyCache();
|
|
507
|
+
const validatedAt = new Date().toISOString();
|
|
508
|
+
const mergedTypechecks = { ...latest.typechecks };
|
|
509
|
+
if (!typecheck.cacheHit && typecheck.mode === "scoped") {
|
|
510
|
+
mergedTypechecks[typecheck.cacheKey] = {
|
|
511
|
+
inputHash: typecheck.inputHash,
|
|
512
|
+
validatedAt,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
365
515
|
const merged = {
|
|
366
516
|
...latest,
|
|
367
517
|
version: CACHE_VERSION,
|
|
368
518
|
toolFingerprint,
|
|
369
|
-
typecheck:
|
|
370
|
-
|
|
371
|
-
|
|
519
|
+
typecheck:
|
|
520
|
+
!typecheck.cacheHit && typecheck.mode === "full"
|
|
521
|
+
? { inputHash: typecheck.inputHash, validatedAt }
|
|
522
|
+
: latest.typecheck,
|
|
523
|
+
typechecks: mergedTypechecks,
|
|
372
524
|
targets: { ...latest.targets, ...Object.fromEntries(updatedTargets) },
|
|
373
525
|
};
|
|
374
526
|
const tempFile = `${CACHE_FILE}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
|
|
@@ -398,7 +550,7 @@ function saveCache(typecheck, updatedTargets) {
|
|
|
398
550
|
}
|
|
399
551
|
}
|
|
400
552
|
|
|
401
|
-
const targets =
|
|
553
|
+
const { scoped, targets } = resolveBuildSelection();
|
|
402
554
|
if (targets.length === 0) {
|
|
403
555
|
console.log(
|
|
404
556
|
"no JS_CODE scripts found under src/js-code-nodes/<scriptCode>/index.ts, src/automations/<scriptCode>/index.ts, or src/functions/<functionCode>/index.ts",
|
|
@@ -407,7 +559,7 @@ if (targets.length === 0) {
|
|
|
407
559
|
}
|
|
408
560
|
|
|
409
561
|
const cache = readCache();
|
|
410
|
-
const typecheck = typecheckScripts(cache);
|
|
562
|
+
const typecheck = typecheckScripts(cache, targets, scoped);
|
|
411
563
|
const updatedTargets = new Map();
|
|
412
564
|
let builtCount = 0;
|
|
413
565
|
let cachedCount = 0;
|
|
@@ -7,17 +7,16 @@
|
|
|
7
7
|
if (!process.env.OPENXIANGDA_PROFILE) {
|
|
8
8
|
const lines = [
|
|
9
9
|
'',
|
|
10
|
-
'❌
|
|
10
|
+
'❌ 错误:请使用带 change 与精确 scope 的 openxiangda workspace publish',
|
|
11
11
|
'',
|
|
12
12
|
'此 npm script(publish:all / publish:oss / register / publish:changed / openxiangda:publish 等)',
|
|
13
13
|
'是工作区内部实现,直接调用会缺少 token / profile / appType / OSS 密钥的注入,',
|
|
14
14
|
'可能发到错误的环境,或彻底发不出去。',
|
|
15
15
|
'',
|
|
16
16
|
'正确入口:',
|
|
17
|
-
' openxiangda workspace publish --profile <name>
|
|
18
|
-
' openxiangda workspace publish --profile <name> --
|
|
19
|
-
'
|
|
20
|
-
' openxiangda workspace publish --profile <name> --form Y # 单表',
|
|
17
|
+
' openxiangda workspace publish --change <change> --profile <name> --only pages/X,forms/Y --dry-run',
|
|
18
|
+
' openxiangda workspace publish --change <change> --profile <name> --only pages/X,forms/Y',
|
|
19
|
+
' 发布后 merge/fast-forward 并 push 冻结 SHA,再运行 release integration-status 与 release end',
|
|
21
20
|
'',
|
|
22
21
|
'更多说明详见工作区根目录的 AGENTS.md。',
|
|
23
22
|
'',
|