sy-lowcode-workspace-tools 0.1.0 → 0.1.1

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,10 +1,10 @@
1
1
  {
2
2
  "name": "sy-lowcode-workspace-tools",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI tools for sy lowcode app workspaces",
5
5
  "type": "module",
6
6
  "bin": {
7
- "lowcode-workspace": "./bin/lowcode-workspace.mjs"
7
+ "lowcode-workspace": "bin/lowcode-workspace.mjs"
8
8
  },
9
9
  "exports": {
10
10
  ".": "./src/cli.mjs"
@@ -198,6 +198,7 @@ async function registerPages() {
198
198
  if (dryRun) {
199
199
  console.log(" [DRY] 代码页发布 payload:");
200
200
  console.log(JSON.stringify(payload, null, 2));
201
+ writePagePublishResult(payload, null, true);
201
202
  return { succeeded: pages.length, failed: 0 };
202
203
  }
203
204
 
@@ -220,9 +221,50 @@ async function registerPages() {
220
221
  body.data?.items?.forEach((item) => {
221
222
  console.log(` ✓ 代码页 ${item.code} (${item.pageId})`);
222
223
  });
224
+ writePagePublishResult(payload, body, false);
223
225
  return { succeeded: pages.length, failed: 0 };
224
226
  }
225
227
 
228
+ function writePagePublishResult(payload, responseBody, isDryRun) {
229
+ const publishedItems = new Map(
230
+ (responseBody?.data?.items || []).map((item) => [item.code, item]),
231
+ );
232
+ const result = {
233
+ appId: config.appId || config.appType,
234
+ appType: payload.appType,
235
+ workspacePath: rootDir,
236
+ version: payload.version,
237
+ buildId: payload.buildId,
238
+ pages: payload.pages.map((page) => {
239
+ const published = publishedItems.get(page.code) || {};
240
+ return {
241
+ code: page.code,
242
+ name: page.name,
243
+ pageId: published.pageId || (isDryRun ? "<dry-run-pageId>" : ""),
244
+ routeKey:
245
+ published.routeKey ||
246
+ page.route?.pathKey ||
247
+ page.route?.path ||
248
+ page.code,
249
+ legacyFormUuid:
250
+ published.legacyFormUuid || published.formUuid || null,
251
+ entryUrl: page.runtime?.entryUrl || "",
252
+ cssUrls: page.runtime?.cssUrls || [],
253
+ menuId:
254
+ published.menuId === undefined
255
+ ? isDryRun
256
+ ? "<dry-run-menuId>"
257
+ : null
258
+ : published.menuId,
259
+ };
260
+ }),
261
+ publishedAt: new Date().toISOString(),
262
+ };
263
+ const outputPath = path.resolve(rootDir, "dist", "publish-result.json");
264
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
265
+ fs.writeFileSync(outputPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
266
+ }
267
+
226
268
  console.log(`🔗 注册应用工作区产物 (${dryRun ? "DRY RUN" : "LIVE"})`);
227
269
  console.log(` API: ${apiBase}`);
228
270
  console.log("");
package/src/cli.mjs CHANGED
@@ -39,7 +39,20 @@ const textExtensions = new Set([
39
39
  ".yml",
40
40
  ".yaml",
41
41
  ]);
42
- const ignoredDirs = new Set([".git", "node_modules", "dist", "build", "coverage", ".vite"]);
42
+ const ignoredDirs = new Set([
43
+ ".git",
44
+ "node_modules",
45
+ "dist",
46
+ "build",
47
+ "coverage",
48
+ ".vite",
49
+ "packages",
50
+ ]);
51
+ const runtimePackages = [
52
+ "sy-form-components",
53
+ "sy-page-sdk",
54
+ "sy-lowcode-workspace-tools",
55
+ ];
43
56
 
44
57
  function usage() {
45
58
  return `
@@ -49,17 +62,24 @@ Commands:
49
62
  build | build-forms | build-pages | sync-schema | publish-oss | register | publish-all
50
63
  update Update workspace runtime dependencies and managed wrappers
51
64
  migrate Convert old local SDK workspace to npm package mode
65
+ smoke Run workspace runtime smoke checks
52
66
 
53
67
  Update options:
54
68
  --workspace <path> Workspace root, defaults to cwd
55
69
  --channel <tag> npm dist-tag, defaults to latest
56
70
  --check Validate only; do not mutate
71
+ --strict-lock Compare lockfile and installed versions with npm latest
57
72
  --commit Commit update changes
58
73
  --push Push after commit
59
74
  --no-commit Do not commit, even if changes exist
60
75
  --allow-dirty Allow updates with existing worktree changes
61
76
  --skip-install Do not run pnpm install/update
62
77
  --skip-gate Do not run typecheck smoke gate
78
+ --gate <quick|full> Gate depth after update, defaults to quick
79
+
80
+ Smoke options:
81
+ --workspace <path> Workspace root, defaults to cwd
82
+ --mode <quick|full> quick validates commands/manifests; full builds shared runtimes
63
83
  `;
64
84
  }
65
85
 
@@ -72,7 +92,7 @@ function parseArgs(argv) {
72
92
  continue;
73
93
  }
74
94
  const key = arg.slice(2);
75
- if (["workspace", "channel"].includes(key)) {
95
+ if (["workspace", "channel", "mode", "gate"].includes(key)) {
76
96
  result[key] = argv[index + 1];
77
97
  index += 1;
78
98
  } else {
@@ -120,6 +140,68 @@ function writeJson(path, value) {
120
140
  writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
121
141
  }
122
142
 
143
+ function escapeRegExp(value) {
144
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
145
+ }
146
+
147
+ function readOptionalJson(path) {
148
+ if (!existsSync(path)) return null;
149
+ try {
150
+ return readJson(path);
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+
156
+ function npmLatestVersion(packageName, channel) {
157
+ const result = run("npm", ["view", `${packageName}@${channel}`, "version"], {
158
+ capture: true,
159
+ });
160
+ return String(result.stdout || "").trim();
161
+ }
162
+
163
+ function readInstalledVersion(workspaceRoot, packageName) {
164
+ const packagePath = join(workspaceRoot, "node_modules", packageName, "package.json");
165
+ const pkg = readOptionalJson(packagePath);
166
+ return typeof pkg?.version === "string" ? pkg.version : null;
167
+ }
168
+
169
+ function readLockVersion(workspaceRoot, packageName) {
170
+ const lockPath = join(workspaceRoot, "pnpm-lock.yaml");
171
+ if (!existsSync(lockPath)) return null;
172
+ const content = readFileSync(lockPath, "utf-8");
173
+ const packagePattern = new RegExp(
174
+ `(?:^|\\n)\\s{2,}/?${escapeRegExp(packageName)}@([^:\\n(]+)(?:\\([^\\n]*\\))?:`,
175
+ );
176
+ const packageMatch = content.match(packagePattern);
177
+ if (packageMatch?.[1]) return packageMatch[1].trim();
178
+
179
+ const dependencyPattern = new RegExp(
180
+ `(?:^|\\n)\\s{6}${escapeRegExp(packageName)}:\\n\\s{8}specifier:\\s*[^\\n]+\\n\\s{8}version:\\s*([^\\s(]+)`,
181
+ );
182
+ const dependencyMatch = content.match(dependencyPattern);
183
+ return dependencyMatch?.[1]?.trim() || null;
184
+ }
185
+
186
+ function validateRuntimeManifest(workspaceRoot, manifestPath, expectedProtocol, expectedMajorVersion) {
187
+ if (!existsSync(manifestPath)) return;
188
+ const manifest = readJson(manifestPath);
189
+ const errors = [];
190
+ if (manifest.protocol !== expectedProtocol) {
191
+ errors.push(`protocol must be ${expectedProtocol}`);
192
+ }
193
+ if (manifest.majorVersion !== expectedMajorVersion) {
194
+ errors.push(`majorVersion must be ${expectedMajorVersion}`);
195
+ }
196
+ if (!manifest.version) errors.push("version is required");
197
+ if (!manifest.files?.entry) errors.push("files.entry is required");
198
+ if (errors.length) {
199
+ throw new Error(
200
+ `${manifestPath.replace(`${workspaceRoot}/`, "")} is invalid: ${errors.join("; ")}`,
201
+ );
202
+ }
203
+ }
204
+
123
205
  function gitStatus(workspaceRoot) {
124
206
  const result = run("git", ["status", "--porcelain"], {
125
207
  cwd: workspaceRoot,
@@ -260,15 +342,19 @@ function removeLocalSdk(workspaceRoot) {
260
342
  }
261
343
  }
262
344
 
263
- function validateWorkspace(workspaceRoot, channel = "latest") {
345
+ function validateWorkspace(workspaceRoot, channel = "latest", options = {}) {
264
346
  const errors = [];
347
+ const warnings = [];
265
348
  const pkg = readJson(join(workspaceRoot, "package.json"));
266
349
  const deps = pkg.dependencies || {};
267
350
  if (deps["@sy/page-sdk"]) errors.push("package.json still depends on @sy/page-sdk");
268
- for (const name of ["sy-form-components", "sy-page-sdk", "sy-lowcode-workspace-tools"]) {
351
+ for (const name of runtimePackages) {
269
352
  if (deps[name] !== channel) errors.push(`package.json dependency ${name} must be ${channel}`);
270
353
  }
271
- if (existsSync(join(workspaceRoot, "packages", "page-sdk"))) {
354
+ const isWorkspaceToolsSourceRepo = existsSync(
355
+ join(workspaceRoot, "packages", "workspace-tools", "package.json"),
356
+ );
357
+ if (existsSync(join(workspaceRoot, "packages", "page-sdk")) && !isWorkspaceToolsSourceRepo) {
272
358
  errors.push("local packages/page-sdk must be removed from application workspaces");
273
359
  }
274
360
  walkFiles(workspaceRoot, (filePath) => {
@@ -284,6 +370,41 @@ function validateWorkspace(workspaceRoot, channel = "latest") {
284
370
  if (errors.length) {
285
371
  throw new Error(`workspace update check failed:\n- ${errors.join("\n- ")}`);
286
372
  }
373
+ if (options.strictLock) {
374
+ const versionRows = [];
375
+ for (const packageName of runtimePackages) {
376
+ const latest = npmLatestVersion(packageName, channel);
377
+ const installed = readInstalledVersion(workspaceRoot, packageName);
378
+ const locked = readLockVersion(workspaceRoot, packageName);
379
+ versionRows.push({ packageName, latest, installed, locked });
380
+ if (latest && locked && locked !== latest) {
381
+ errors.push(`pnpm-lock.yaml locks ${packageName}@${locked}, latest is ${latest}`);
382
+ }
383
+ if (latest && installed && installed !== latest) {
384
+ errors.push(`node_modules has ${packageName}@${installed}, latest is ${latest}`);
385
+ }
386
+ if (!locked) {
387
+ warnings.push(`pnpm-lock.yaml does not contain ${packageName}; run pnpm install after update`);
388
+ }
389
+ if (!installed) {
390
+ warnings.push(`node_modules does not contain ${packageName}; run pnpm install`);
391
+ }
392
+ }
393
+ if (versionRows.length) {
394
+ console.log("[lowcode-workspace] runtime dependency versions:");
395
+ for (const row of versionRows) {
396
+ console.log(
397
+ ` - ${row.packageName}: latest=${row.latest || "unknown"} locked=${row.locked || "missing"} installed=${row.installed || "missing"}`,
398
+ );
399
+ }
400
+ }
401
+ }
402
+ if (errors.length) {
403
+ throw new Error(`workspace update check failed:\n- ${errors.join("\n- ")}`);
404
+ }
405
+ for (const warning of warnings) {
406
+ console.warn(`[lowcode-workspace] warning: ${warning}`);
407
+ }
287
408
  }
288
409
 
289
410
  function runUpdateInstall(workspaceRoot, channel) {
@@ -299,12 +420,34 @@ function runUpdateInstall(workspaceRoot, channel) {
299
420
  run("pnpm", ["install"], { cwd: workspaceRoot });
300
421
  }
301
422
 
302
- function runQuickGate(workspaceRoot) {
423
+ function runSmoke(workspaceRoot, mode = "quick") {
424
+ const normalizedMode = String(mode || "quick").trim();
425
+ if (!["quick", "full"].includes(normalizedMode)) {
426
+ throw new Error(`unsupported smoke mode: ${mode}`);
427
+ }
303
428
  const pkg = readJson(join(workspaceRoot, "package.json"));
304
429
  if (pkg.scripts?.typecheck) {
305
430
  run("pnpm", ["typecheck"], { cwd: workspaceRoot });
306
431
  }
307
- runManagedScript("build-pages", ["--help"], workspaceRoot);
432
+ if (normalizedMode === "full") {
433
+ runManagedScript("build-pages", ["--bundle-mode", "shared"], workspaceRoot);
434
+ runManagedScript("build-forms", ["--bundle-mode", "shared"], workspaceRoot);
435
+ } else {
436
+ runManagedScript("build-pages", ["--bundle-mode", "shared", "--help"], workspaceRoot);
437
+ runManagedScript("build-forms", ["--bundle-mode", "shared", "--help"], workspaceRoot);
438
+ }
439
+ validateRuntimeManifest(
440
+ workspaceRoot,
441
+ join(workspaceRoot, "dist", "page-runtime", "manifest.json"),
442
+ "sy-page-runtime",
443
+ 1,
444
+ );
445
+ validateRuntimeManifest(
446
+ workspaceRoot,
447
+ join(workspaceRoot, "dist", "form-runtime", "manifest.json"),
448
+ "sy-form-runtime",
449
+ 2,
450
+ );
308
451
  }
309
452
 
310
453
  function commitAndMaybePush(workspaceRoot, push) {
@@ -325,12 +468,14 @@ async function updateWorkspace(argv, { migrate = false } = {}) {
325
468
  const workspaceRoot = resolve(args.workspace || process.cwd());
326
469
  const channel = String(args.channel || process.env.APP_WORKSPACE_UPDATE_CHANNEL || "latest");
327
470
  const checkOnly = Boolean(args.check);
471
+ const strictLock = Boolean(args["strict-lock"]);
328
472
  const allowDirty = Boolean(args["allow-dirty"]);
329
473
  const shouldCommit = Boolean(args.commit) && !args["no-commit"];
330
474
  const shouldPush = Boolean(args.push);
475
+ const gateMode = String(args.gate || "quick");
331
476
 
332
477
  if (checkOnly) {
333
- validateWorkspace(workspaceRoot, channel);
478
+ validateWorkspace(workspaceRoot, channel, { strictLock });
334
479
  console.log("[lowcode-workspace] update check passed");
335
480
  return;
336
481
  }
@@ -353,8 +498,8 @@ async function updateWorkspace(argv, { migrate = false } = {}) {
353
498
  }
354
499
 
355
500
  if (!args["skip-install"]) runUpdateInstall(workspaceRoot, channel);
356
- validateWorkspace(workspaceRoot, channel);
357
- if (!args["skip-gate"]) runQuickGate(workspaceRoot);
501
+ validateWorkspace(workspaceRoot, channel, { strictLock: true });
502
+ if (!args["skip-gate"]) runSmoke(workspaceRoot, gateMode);
358
503
  if (shouldCommit) commitAndMaybePush(workspaceRoot, shouldPush);
359
504
  console.log("[lowcode-workspace] workspace update completed");
360
505
  }
@@ -373,6 +518,12 @@ export async function main(argv = process.argv.slice(2)) {
373
518
  await updateWorkspace(rest, { migrate: true });
374
519
  return;
375
520
  }
521
+ if (command === "smoke") {
522
+ const args = parseArgs(rest);
523
+ runSmoke(resolve(args.workspace || process.cwd()), args.mode || "quick");
524
+ console.log("[lowcode-workspace] smoke check passed");
525
+ return;
526
+ }
376
527
  if (managedCommands.has(command)) {
377
528
  const args = parseArgs(rest);
378
529
  runManagedScript(command, rest, resolve(args.workspace || process.cwd()));