openxiangda-cli 2.0.0-alpha.98 → 2.0.0

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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -8
  3. package/bin/run.js +2 -11
  4. package/dist/base.d.ts +30 -6
  5. package/dist/base.d.ts.map +1 -1
  6. package/dist/base.js +120 -22
  7. package/dist/base.js.map +1 -1
  8. package/dist/commands/accept.d.ts +28 -0
  9. package/dist/commands/accept.d.ts.map +1 -0
  10. package/dist/commands/accept.js +21 -0
  11. package/dist/commands/accept.js.map +1 -0
  12. package/dist/commands/admin.d.ts +32 -0
  13. package/dist/commands/admin.d.ts.map +1 -0
  14. package/dist/commands/admin.js +27 -0
  15. package/dist/commands/admin.js.map +1 -0
  16. package/dist/commands/auth.d.ts +30 -0
  17. package/dist/commands/auth.d.ts.map +1 -0
  18. package/dist/commands/auth.js +13 -0
  19. package/dist/commands/auth.js.map +1 -0
  20. package/dist/commands/cancel.d.ts +30 -0
  21. package/dist/commands/cancel.d.ts.map +1 -0
  22. package/dist/commands/cancel.js +12 -0
  23. package/dist/commands/cancel.js.map +1 -0
  24. package/dist/commands/check.d.ts +12 -3
  25. package/dist/commands/check.d.ts.map +1 -1
  26. package/dist/commands/check.js +21 -4
  27. package/dist/commands/check.js.map +1 -1
  28. package/dist/commands/context.d.ts +27 -0
  29. package/dist/commands/context.d.ts.map +1 -0
  30. package/dist/commands/context.js +10 -0
  31. package/dist/commands/context.js.map +1 -0
  32. package/dist/commands/create.d.ts +20 -3
  33. package/dist/commands/create.d.ts.map +1 -1
  34. package/dist/commands/create.js +396 -22
  35. package/dist/commands/create.js.map +1 -1
  36. package/dist/commands/deploy.d.ts +13 -3
  37. package/dist/commands/deploy.d.ts.map +1 -1
  38. package/dist/commands/deploy.js +17 -16
  39. package/dist/commands/deploy.js.map +1 -1
  40. package/dist/commands/dev.d.ts +10 -3
  41. package/dist/commands/dev.d.ts.map +1 -1
  42. package/dist/commands/dev.js +7 -4
  43. package/dist/commands/dev.js.map +1 -1
  44. package/dist/commands/docs.d.ts +30 -0
  45. package/dist/commands/docs.d.ts.map +1 -0
  46. package/dist/commands/docs.js +20 -0
  47. package/dist/commands/docs.js.map +1 -0
  48. package/dist/commands/login.d.ts +8 -3
  49. package/dist/commands/login.d.ts.map +1 -1
  50. package/dist/commands/login.js +1 -1
  51. package/dist/commands/login.js.map +1 -1
  52. package/dist/commands/logs.d.ts +10 -3
  53. package/dist/commands/logs.d.ts.map +1 -1
  54. package/dist/commands/logs.js +7 -3
  55. package/dist/commands/logs.js.map +1 -1
  56. package/dist/commands/retry.d.ts +30 -0
  57. package/dist/commands/retry.d.ts.map +1 -0
  58. package/dist/commands/retry.js +12 -0
  59. package/dist/commands/retry.js.map +1 -0
  60. package/dist/commands/rollback.d.ts +11 -3
  61. package/dist/commands/rollback.d.ts.map +1 -1
  62. package/dist/commands/rollback.js +9 -3
  63. package/dist/commands/rollback.js.map +1 -1
  64. package/dist/commands/skill.d.ts +9 -3
  65. package/dist/commands/skill.d.ts.map +1 -1
  66. package/dist/commands/skill.js +9 -2
  67. package/dist/commands/skill.js.map +1 -1
  68. package/dist/commands/spec.d.ts +42 -0
  69. package/dist/commands/spec.d.ts.map +1 -0
  70. package/dist/commands/spec.js +123 -0
  71. package/dist/commands/spec.js.map +1 -0
  72. package/dist/commands/start.d.ts +28 -0
  73. package/dist/commands/start.d.ts.map +1 -0
  74. package/dist/commands/start.js +18 -0
  75. package/dist/commands/start.js.map +1 -0
  76. package/dist/commands/status.d.ts +9 -3
  77. package/dist/commands/status.d.ts.map +1 -1
  78. package/dist/commands/status.js +10 -3
  79. package/dist/commands/status.js.map +1 -1
  80. package/dist/commands/stop.d.ts +28 -0
  81. package/dist/commands/stop.d.ts.map +1 -0
  82. package/dist/commands/stop.js +18 -0
  83. package/dist/commands/stop.js.map +1 -0
  84. package/dist/create-workspace.d.ts +36 -0
  85. package/dist/create-workspace.d.ts.map +1 -1
  86. package/dist/create-workspace.js +366 -8
  87. package/dist/create-workspace.js.map +1 -1
  88. package/dist/studio-events.d.ts +9 -0
  89. package/dist/studio-events.d.ts.map +1 -0
  90. package/dist/studio-events.js +30 -0
  91. package/dist/studio-events.js.map +1 -0
  92. package/package.json +15 -6
  93. package/template/AGENTS.md +25 -20
  94. package/template/README.md +31 -12
  95. package/template/apps/web/e2e/README.md +7 -0
  96. package/template/apps/web/package.json +4 -2
  97. package/template/apps/web/playwright.config.ts +4 -3
  98. package/template/apps/web/src/document.css +5 -0
  99. package/template/apps/web/src/main.tsx +48 -3
  100. package/template/apps/web/src/user-end/tailwind.css +5 -0
  101. package/template/apps/web/test/loopback.test.ts +8 -2
  102. package/template/apps/web/test/standard-user-surfaces.typecheck.tsx +27 -0
  103. package/template/apps/web/vite.config.ts +5 -2
  104. package/template/appspec/app.md +56 -0
  105. package/template/openxiangda.config.ts +50 -25
  106. package/template/package.json +4 -4
  107. package/template/packages/contracts/package.json +1 -1
  108. package/template/packages/contracts/src/generated.ts +143 -2
  109. package/template/apps/server/Dockerfile +0 -20
  110. package/template/apps/server/package.json +0 -27
  111. package/template/apps/server/src/app.module.ts +0 -43
  112. package/template/apps/server/src/main.ts +0 -5
  113. package/template/apps/server/test/smoke.test.ts +0 -16
  114. package/template/apps/server/tsconfig.json +0 -9
  115. package/template/apps/web/data-api-live.e2e.html +0 -15
  116. package/template/apps/web/e2e/client-fixture.ts +0 -5
  117. package/template/apps/web/e2e/data-api-live-fixture.tsx +0 -146
  118. package/template/apps/web/e2e/data-api-live.spec.ts +0 -138
  119. package/template/apps/web/e2e/field-protocol-fixture.tsx +0 -347
  120. package/template/apps/web/e2e/field-protocol.spec.ts +0 -186
  121. package/template/apps/web/e2e/resources.spec.ts +0 -285
  122. package/template/apps/web/e2e/workflow.spec.ts +0 -329
  123. package/template/apps/web/field-protocol.e2e.html +0 -15
  124. package/template/apps/web/scripts/check.mjs +0 -43
  125. package/template/apps/web/test/contracts.test.ts +0 -99
  126. package/template/scripts/verify-template-budget.mjs +0 -49
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancel.js","sourceRoot":"","sources":["../../src/commands/cancel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,kBAAkB;IACpD,MAAM,CAAC,OAAO,GAAG,2BAA2B,CAAC;IAC7C,MAAM,CAAC,IAAI,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAChE,MAAM,CAAC,KAAK,GAAG,cAAc,CAAC;IAE9B,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,OAAO,CACjB,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,CACzD,CAAC;IACJ,CAAC"}
@@ -2,21 +2,30 @@ import { OpenXiangdaCommand } from '../base.js';
2
2
  export default class Check extends OpenXiangdaCommand {
3
3
  static summary: string;
4
4
  static flags: {
5
+ local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
+ environment: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ "json-events": import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ "run-id": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
5
9
  cwd: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
10
  };
7
11
  run(): Promise<import("openxiangda-contracts").DevkitResult<unknown> | {
8
- schemaVersion: string;
12
+ schemaVersion: "openxiangda.cli-result/v2";
9
13
  ok: boolean;
10
14
  operation: string;
11
- workspace: import("openxiangda-contracts").WorkspaceIdentity;
15
+ workspace: import("openxiangda-contracts").WorkspaceIdentity | {
16
+ appCode: string;
17
+ name: string | undefined;
18
+ };
12
19
  data: {} | null;
13
20
  error: {
21
+ details?: Record<string, unknown>;
22
+ pointer?: string;
14
23
  code: string;
15
24
  message: string;
16
25
  retryable: boolean;
17
26
  remediation: string;
18
27
  nextCommand: string | null;
19
28
  } | null;
20
- }>;
29
+ } | undefined>;
21
30
  }
22
31
  //# sourceMappingURL=check.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAkB,MAAM,YAAY,CAAC;AAChE,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,kBAAkB;IACnD,MAAM,CAAC,OAAO,SAAe;IAC7B,MAAM,CAAC,KAAK;;MAAkB;IACxB,GAAG;;;;;;;;;;;;;;CACV"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAwB,MAAM,YAAY,CAAC;AAGtE,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,kBAAkB;IACnD,MAAM,CAAC,OAAO,SAAqB;IACnC,MAAM,CAAC,KAAK;;;;;;MAQV;IAEI,GAAG;;;;;;;;;;;;;;;;;;;CAQV"}
@@ -1,7 +1,24 @@
1
- import { OpenXiangdaCommand, workspaceFlags } from '../base.js';
1
+ import { Flags } from '@oclif/core';
2
+ import { OpenXiangdaCommand, studioWorkspaceFlags } from '../base.js';
3
+ import { checkApplication, DEVELOPER_ENVIRONMENTS } from 'openxiangda-devkit-core';
2
4
  export default class Check extends OpenXiangdaCommand {
3
- static summary = '确定性检查完整应用';
4
- static flags = workspaceFlags;
5
- async run() { const { flags } = await this.parse(Check); return this.present(await this.services.check(flags.cwd)); }
5
+ static summary = '针对目标平台确定性检查完整应用';
6
+ static flags = {
7
+ ...studioWorkspaceFlags,
8
+ local: Flags.boolean({ summary: '只执行本地完整校验与构建,不验证目标平台;适用于 CI 和候选包验证', default: false }),
9
+ environment: Flags.string({
10
+ options: [...DEVELOPER_ENVIRONMENTS],
11
+ default: 'test',
12
+ summary: '兼容性预检目标;默认为 test',
13
+ }),
14
+ };
15
+ async run() {
16
+ const { flags } = await this.parse(Check);
17
+ this.beginStudioEvents('check');
18
+ return this.present(await checkApplication(this.services, {
19
+ onProgress: event => this.presentOperationProgress(event),
20
+ ...(flags.cwd ? { root: flags.cwd } : {}), environment: flags.environment, local: flags.local,
21
+ }));
22
+ }
6
23
  }
7
24
  //# sourceMappingURL=check.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAChE,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,kBAAkB;IACnD,MAAM,CAAC,OAAO,GAAG,WAAW,CAAC;IAC7B,MAAM,CAAC,KAAK,GAAG,cAAc,CAAC;IAC9B,KAAK,CAAC,GAAG,KAAK,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEnF,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,kBAAkB;IACnD,MAAM,CAAC,OAAO,GAAG,iBAAiB,CAAC;IACnC,MAAM,CAAC,KAAK,GAAG;QACb,GAAG,oBAAoB;QACvB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,oCAAoC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACvF,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC;YACxB,OAAO,EAAE,CAAC,GAAG,sBAAsB,CAAC;YACpC,OAAO,EAAE,MAAM;YACf,OAAO,EAAE,kBAAkB;SAC5B,CAAC;KACH,CAAC;IAEF,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE;YACxD,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC;YACzD,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK;SAC9F,CAAC,CAAC,CAAC;IACN,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { OpenXiangdaCommand } from '../base.js';
2
+ export default class Context extends OpenXiangdaCommand {
3
+ static summary: string;
4
+ static flags: {
5
+ cwd: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ };
7
+ run(): Promise<import("openxiangda-contracts").DevkitResult<unknown> | {
8
+ schemaVersion: "openxiangda.cli-result/v2";
9
+ ok: boolean;
10
+ operation: string;
11
+ workspace: import("openxiangda-contracts").WorkspaceIdentity | {
12
+ appCode: string;
13
+ name: string | undefined;
14
+ };
15
+ data: {} | null;
16
+ error: {
17
+ details?: Record<string, unknown>;
18
+ pointer?: string;
19
+ code: string;
20
+ message: string;
21
+ retryable: boolean;
22
+ remediation: string;
23
+ nextCommand: string | null;
24
+ } | null;
25
+ } | undefined>;
26
+ }
27
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAkB,MAAM,YAAY,CAAC;AAEhE,MAAM,CAAC,OAAO,OAAO,OAAQ,SAAQ,kBAAkB;IACrD,MAAM,CAAC,OAAO,SAA0B;IACxC,MAAM,CAAC,KAAK;;MAAkB;IACxB,GAAG;;;;;;;;;;;;;;;;;;;CAIV"}
@@ -0,0 +1,10 @@
1
+ import { OpenXiangdaCommand, workspaceFlags } from '../base.js';
2
+ export default class Context extends OpenXiangdaCommand {
3
+ static summary = '只读查看当前工作区、工具链版本与平台绑定';
4
+ static flags = workspaceFlags;
5
+ async run() {
6
+ const { flags } = await this.parse(Context);
7
+ return this.present(await this.services.workspaceContext(flags.cwd));
8
+ }
9
+ }
10
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,CAAC,OAAO,OAAO,OAAQ,SAAQ,kBAAkB;IACrD,MAAM,CAAC,OAAO,GAAG,sBAAsB,CAAC;IACxC,MAAM,CAAC,KAAK,GAAG,cAAc,CAAC;IAC9B,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACvE,CAAC"}
@@ -4,20 +4,37 @@ export default class Create extends OpenXiangdaCommand {
4
4
  static args: {
5
5
  directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
6
  };
7
+ static flags: {
8
+ "base-url": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ "app-code": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ "template-ref": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ "template-digest": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ "studio-project-id": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ "provisioning-run-id": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ "json-events": import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ "run-id": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ };
7
18
  run(): Promise<import("openxiangda-contracts").DevkitResult<unknown> | {
8
- schemaVersion: string;
19
+ schemaVersion: "openxiangda.cli-result/v2";
9
20
  ok: boolean;
10
21
  operation: string;
11
- workspace: import("openxiangda-contracts").WorkspaceIdentity;
22
+ workspace: import("openxiangda-contracts").WorkspaceIdentity | {
23
+ appCode: string;
24
+ name: string | undefined;
25
+ };
12
26
  data: {} | null;
13
27
  error: {
28
+ details?: Record<string, unknown>;
29
+ pointer?: string;
14
30
  code: string;
15
31
  message: string;
16
32
  retryable: boolean;
17
33
  remediation: string;
18
34
  nextCommand: string | null;
19
35
  } | null;
20
- }>;
36
+ } | undefined>;
37
+ private studioCompilerSummary;
21
38
  private reuseWorkspace;
22
39
  }
23
40
  //# sourceMappingURL=create.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAOhD,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,kBAAkB;IACpD,MAAM,CAAC,OAAO,SAAmC;IACjD,MAAM,CAAC,IAAI;;MAET;IAEI,GAAG;;;;;;;;;;;;;;YAkEK,cAAc;CAsB7B"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,kBAAkB,EAAoB,MAAM,YAAY,CAAC;AAelE,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,kBAAkB;IACpD,MAAM,CAAC,OAAO,SAA8B;IAC5C,MAAM,CAAC,IAAI;;MAET;IACF,MAAM,CAAC,KAAK;;;;;;;;;;MA4BV;IAEI,GAAG;;;;;;;;;;;;;;;;;;;YAsQK,qBAAqB;YAuBrB,cAAc;CAoE7B"}
@@ -1,42 +1,245 @@
1
- import { Args } from "@oclif/core";
2
- import { existsSync, readdirSync } from "node:fs";
3
- import { basename, resolve } from "node:path";
4
- import { OpenXiangdaDeveloperSession } from "openxiangda-devkit-core";
5
- import { OpenXiangdaCommand } from "../base.js";
6
- import { createWorkspace, prepareWorkspace, } from "../create-workspace.js";
1
+ import { Args, Flags } from "@oclif/core";
2
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
+ import { basename, join, resolve } from "node:path";
4
+ import { normalizePlatformBaseUrl, OpenXiangdaDeveloperSession } from "openxiangda-devkit-core";
5
+ import { OPENXIANGDA_COMPILER_CONTRACT_VERSION, STUDIO_APPLICATION_AUTHORITY, STUDIO_WORKSPACE_INITIALIZATION_SCHEMA_VERSION, STUDIO_WORKSPACE_PROTOCOL_VERSION, } from "openxiangda-contracts";
6
+ import { OpenXiangdaCommand, studioEventFlags } from "../base.js";
7
+ import { createWorkspace, ensureStudioWorkspaceBinding, isPlatformUuid, prepareWorkspace, readStudioWorkspaceBinding, readWorkspaceTemplateBinding, resolveWorkspaceTemplate, studioWorkspaceBindingDigest, studioWorkspaceInitializationDigest, } from "../create-workspace.js";
8
+ import { resolveCliToolchainCapsule } from "../toolchain-capsule.js";
7
9
  export default class Create extends OpenXiangdaCommand {
8
- static summary = "创建、绑定并幂等初始化 React + NestJS 应用";
10
+ static summary = "创建并绑定 React 应用,按业务需要启用后端";
9
11
  static args = {
10
12
  directory: Args.string({ required: true, summary: "应用目录" }),
11
13
  };
14
+ static flags = {
15
+ ...studioEventFlags,
16
+ "base-url": Flags.string({
17
+ summary: "新应用的目标平台;已有工作区必须与原绑定一致",
18
+ helpValue: "<platform>",
19
+ }),
20
+ "app-code": Flags.string({
21
+ summary: "显式应用代码;默认从目录名推导",
22
+ helpValue: "<kebab-case>",
23
+ }),
24
+ name: Flags.string({ summary: "显式应用名称;默认使用目录名" }),
25
+ "template-ref": Flags.string({
26
+ summary: "builtin:application 或已物化的本地模板目录",
27
+ helpValue: "<ref>",
28
+ }),
29
+ "template-digest": Flags.string({
30
+ summary: "模板内容摘要 sha256:<64位小写十六进制>",
31
+ helpValue: "<sha256:digest>",
32
+ dependsOn: ["template-ref"],
33
+ }),
34
+ "studio-project-id": Flags.string({
35
+ summary: "站点 Project UUID;启用站点权威的 Studio 初始化模式",
36
+ helpValue: "<uuid>",
37
+ }),
38
+ "provisioning-run-id": Flags.string({
39
+ summary: "站点 ProjectProvisioningRun UUID",
40
+ helpValue: "<uuid>",
41
+ }),
42
+ };
12
43
  async run() {
13
- const { args } = await this.parse(Create);
44
+ const { args, flags } = await this.parse(Create);
45
+ this.beginStudioEvents("create");
14
46
  const root = resolve(args.directory);
15
47
  const directoryName = basename(root);
16
- const appCode = deriveAppCode(directoryName);
17
- const name = directoryName;
18
- const installOutput = this.jsonEnabled()
48
+ const appCode = flags["app-code"] || deriveAppCode(directoryName);
49
+ const name = flags.name || directoryName;
50
+ const studioMode = Boolean(flags["studio-project-id"] || flags["provisioning-run-id"]);
51
+ if (Boolean(flags["template-ref"]) !== Boolean(flags["template-digest"])) {
52
+ throw new Error("TEMPLATE_REF_DIGEST_PAIR_REQUIRED: --template-ref 与 --template-digest 必须一起提供");
53
+ }
54
+ const templateInput = flags["template-ref"]
55
+ ? {
56
+ templateRef: flags["template-ref"],
57
+ templateDigest: flags["template-digest"],
58
+ }
59
+ : {};
60
+ if (studioMode) {
61
+ assertStudioCreateInput({
62
+ appCode: flags["app-code"],
63
+ name: flags.name,
64
+ templateRef: flags["template-ref"],
65
+ templateDigest: flags["template-digest"],
66
+ projectId: flags["studio-project-id"],
67
+ provisioningRunId: flags["provisioning-run-id"],
68
+ jsonEvents: flags["json-events"],
69
+ runId: flags["run-id"],
70
+ });
71
+ }
72
+ const baseUrl = resolveCreatePlatform(root, appCode, flags["base-url"] || process.env.OPENXIANGDA_BASE_URL, studioMode);
73
+ const retryCommand = renderCreateRetryCommand(root, {
74
+ baseUrl,
75
+ ...(flags["app-code"] ? { appCode } : {}),
76
+ ...(flags.name ? { name } : {}),
77
+ ...templateInput,
78
+ });
79
+ const installOutput = this.machineOutputEnabled()
19
80
  ? "capture"
20
81
  : "inherit";
82
+ this.emitStudioStatus("正在验证目标平台开发者会话", { stage: "identity", baseUrl });
21
83
  const session = await OpenXiangdaDeveloperSession.load();
22
84
  if (!session) {
23
85
  throw new Error("OPENXIANGDA_AUTH_REQUIRED: 先运行 openxiangda login --base-url <platform>");
24
86
  }
87
+ session.assertPlatform(baseUrl);
88
+ if (studioMode)
89
+ assertStudioSiteBaseUrl(baseUrl);
25
90
  await session.whoami();
26
91
  const reusable = existsSync(root) && readdirSync(root).length > 0;
27
- const workspace = reusable
28
- ? await this.reuseWorkspace(root, appCode, name, installOutput)
29
- : await createWorkspace({
30
- directory: root,
31
- appCode,
32
- name,
33
- installOutput,
34
- });
92
+ this.emitStudioStatus(reusable ? "正在恢复已有工作区" : "正在生成应用工作区", { stage: "workspace", reusable });
93
+ let workspace;
94
+ if (studioMode) {
95
+ const resolvedTemplate = resolveWorkspaceTemplate(templateInput);
96
+ if (reusable) {
97
+ const storedBinding = readStudioWorkspaceBinding(root);
98
+ if (!storedBinding) {
99
+ throw Object.assign(new Error("STUDIO_WORKSPACE_BINDING_REQUIRED: 非空目录必须已有同一 Studio 初始化绑定"), {
100
+ code: "STUDIO_WORKSPACE_BINDING_REQUIRED",
101
+ retryable: false,
102
+ data: { pointer: "/studioBinding" },
103
+ });
104
+ }
105
+ ensureStudioWorkspaceBinding(root, {
106
+ siteBaseUrl: session.baseUrl,
107
+ projectId: flags["studio-project-id"],
108
+ provisioningRunId: flags["provisioning-run-id"],
109
+ appType: appCode,
110
+ appName: name,
111
+ template: resolvedTemplate.binding,
112
+ });
113
+ if (storedBinding.compiler) {
114
+ ensureStudioWorkspaceBinding(root, {
115
+ siteBaseUrl: session.baseUrl,
116
+ projectId: flags["studio-project-id"],
117
+ provisioningRunId: flags["provisioning-run-id"],
118
+ appType: appCode,
119
+ appName: name,
120
+ template: resolvedTemplate.binding,
121
+ compiler: await this.studioCompilerSummary(root),
122
+ });
123
+ }
124
+ workspace = await this.reuseWorkspace(root, appCode, name, installOutput, { explicitName: true, ...templateInput });
125
+ }
126
+ else {
127
+ const prepared = await createWorkspace({
128
+ directory: root,
129
+ appCode,
130
+ name,
131
+ install: false,
132
+ installOutput,
133
+ ...templateInput,
134
+ });
135
+ ensureStudioWorkspaceBinding(root, {
136
+ siteBaseUrl: session.baseUrl,
137
+ projectId: flags["studio-project-id"],
138
+ provisioningRunId: flags["provisioning-run-id"],
139
+ appType: appCode,
140
+ appName: name,
141
+ template: prepared.template,
142
+ });
143
+ await prepareWorkspace(root, {
144
+ installOutput,
145
+ toolchainCapsule: resolveCliToolchainCapsule(resolvedTemplate.root),
146
+ });
147
+ workspace = {
148
+ ...prepared,
149
+ installed: true,
150
+ generated: true,
151
+ generationDeferred: false,
152
+ };
153
+ }
154
+ }
155
+ else {
156
+ workspace = reusable
157
+ ? await this.reuseWorkspace(root, appCode, name, installOutput, {
158
+ explicitName: Boolean(flags.name),
159
+ ...templateInput,
160
+ })
161
+ : await createWorkspace({
162
+ directory: root,
163
+ appCode,
164
+ name,
165
+ installOutput,
166
+ ...templateInput,
167
+ });
168
+ }
169
+ this.emitStudioStatus("正在绑定平台应用", { stage: "link" });
35
170
  const link = await this.services.linkApplication(root, {
36
171
  baseUrl: session.baseUrl,
37
172
  });
38
173
  if (!link.ok)
39
174
  return this.present({ ...link, operation: "create" });
175
+ if (studioMode) {
176
+ this.emitStudioStatus("正在生成可核对的工作区摘要", {
177
+ stage: "compiler-summary",
178
+ });
179
+ const compiler = await this.studioCompilerSummary(root);
180
+ const binding = ensureStudioWorkspaceBinding(root, {
181
+ siteBaseUrl: session.baseUrl,
182
+ projectId: flags["studio-project-id"],
183
+ provisioningRunId: flags["provisioning-run-id"],
184
+ appType: appCode,
185
+ appName: name,
186
+ template: workspace.template,
187
+ compiler,
188
+ });
189
+ const initializationFacts = {
190
+ schemaVersion: STUDIO_WORKSPACE_INITIALIZATION_SCHEMA_VERSION,
191
+ applicationAuthority: STUDIO_APPLICATION_AUTHORITY,
192
+ siteBaseUrl: binding.siteBaseUrl,
193
+ projectId: binding.projectId,
194
+ provisioningRunId: binding.provisioningRunId,
195
+ appType: binding.appType,
196
+ appName: binding.appName,
197
+ workspace: {
198
+ reused: "reused" in workspace && workspace.reused === true,
199
+ },
200
+ cliVersion: this.config.version,
201
+ protocolVersion: STUDIO_WORKSPACE_PROTOCOL_VERSION,
202
+ template: binding.template,
203
+ compiler,
204
+ bindingDigest: studioWorkspaceBindingDigest(binding),
205
+ };
206
+ const initialization = {
207
+ ...initializationFacts,
208
+ workspaceDigest: studioWorkspaceInitializationDigest(initializationFacts),
209
+ };
210
+ if (!link.data) {
211
+ throw new Error("STUDIO_LINK_RESULT_MISSING: 平台绑定结果缺少 data");
212
+ }
213
+ const { path: _localLinkPath, ...linkSummary } = link.data;
214
+ return this.present({
215
+ ok: true,
216
+ operation: "create",
217
+ workspace: { appCode, name, root },
218
+ data: {
219
+ workspace: {
220
+ appType: appCode,
221
+ appName: name,
222
+ installed: workspace.installed,
223
+ generated: workspace.generated,
224
+ reused: "reused" in workspace && workspace.reused === true,
225
+ template: binding.template,
226
+ compiler,
227
+ },
228
+ link: linkSummary,
229
+ provision: null,
230
+ studioInitialization: initialization,
231
+ },
232
+ diagnostics: [],
233
+ nextActions: [
234
+ {
235
+ code: "dev",
236
+ label: "启动连接开发",
237
+ command: "openxiangda dev --cwd <workspace>",
238
+ },
239
+ ],
240
+ });
241
+ }
242
+ this.emitStudioStatus("正在幂等初始化平台应用", { stage: "provision" });
40
243
  const provision = await this.services.provisionApplication(root);
41
244
  if (!provision.ok) {
42
245
  return this.present({
@@ -46,7 +249,7 @@ export default class Create extends OpenXiangdaCommand {
46
249
  {
47
250
  code: "create.retry",
48
251
  label: "重试幂等初始化",
49
- command: `openxiangda create ${root}`,
252
+ command: retryCommand,
50
253
  },
51
254
  ],
52
255
  });
@@ -70,12 +273,59 @@ export default class Create extends OpenXiangdaCommand {
70
273
  ],
71
274
  });
72
275
  }
73
- async reuseWorkspace(root, appCode, name, installOutput) {
276
+ async studioCompilerSummary(root) {
277
+ const [context, description] = await Promise.all([
278
+ this.services.workspaceContext(root),
279
+ this.services.contractDescribe(root),
280
+ ]);
281
+ if (!context.ok || !context.data) {
282
+ throw devkitResultError("STUDIO_WORKSPACE_CONTEXT_FAILED", context);
283
+ }
284
+ if (!description.ok || !description.data) {
285
+ throw devkitResultError("STUDIO_COMPILER_SUMMARY_FAILED", description);
286
+ }
287
+ return {
288
+ toolchainVersion: context.data.toolchain.version,
289
+ contractVersion: context.data.toolchain.contractVersion,
290
+ compilerContractVersion: OPENXIANGDA_COMPILER_CONTRACT_VERSION,
291
+ configurationDigest: description.data.configDigest,
292
+ contractDigest: description.data.contractDigest,
293
+ aiCatalogDigest: description.data.aiCatalogDigest,
294
+ };
295
+ }
296
+ async reuseWorkspace(root, appCode, name, installOutput, options) {
74
297
  const context = await this.services.workspaceContext(root);
75
298
  if (context.workspace.appCode !== appCode) {
76
299
  throw new Error(`TARGET_DIRECTORY_APP_MISMATCH: 目录应用代码 ${context.workspace.appCode} 与推导值 ${appCode} 不一致`);
77
300
  }
78
- await prepareWorkspace(root, { installOutput });
301
+ if (options.explicitName &&
302
+ context.workspace.name &&
303
+ context.workspace.name !== name) {
304
+ throw new Error(`TARGET_DIRECTORY_NAME_MISMATCH: 目录应用名称 ${context.workspace.name} 与显式名称 ${name} 不一致`);
305
+ }
306
+ const storedTemplate = readWorkspaceTemplateBinding(root);
307
+ let requestedTemplate = null;
308
+ if (options.templateRef && options.templateDigest) {
309
+ requestedTemplate = resolveWorkspaceTemplate({
310
+ templateRef: options.templateRef,
311
+ templateDigest: options.templateDigest,
312
+ });
313
+ if (!storedTemplate) {
314
+ throw new Error("WORKSPACE_TEMPLATE_BINDING_REQUIRED: 已有工作区缺少可验证的模板绑定");
315
+ }
316
+ if (storedTemplate.ref !== requestedTemplate.binding.ref ||
317
+ storedTemplate.digest !== requestedTemplate.binding.digest) {
318
+ throw new Error("WORKSPACE_TEMPLATE_BINDING_MISMATCH: 已有工作区与请求模板不一致");
319
+ }
320
+ }
321
+ await prepareWorkspace(root, {
322
+ installOutput,
323
+ ...(requestedTemplate
324
+ ? {
325
+ toolchainCapsule: resolveCliToolchainCapsule(requestedTemplate.root),
326
+ }
327
+ : {}),
328
+ });
79
329
  return {
80
330
  root,
81
331
  appCode,
@@ -83,9 +333,116 @@ export default class Create extends OpenXiangdaCommand {
83
333
  installed: true,
84
334
  generated: true,
85
335
  reused: true,
336
+ template: storedTemplate,
86
337
  };
87
338
  }
88
339
  }
340
+ function resolveCreatePlatform(root, appType, requestedBaseUrl, studioMode) {
341
+ const requested = requestedBaseUrl
342
+ ? normalizePlatformBaseUrl(requestedBaseUrl)
343
+ : undefined;
344
+ const path = join(root, ".openxiangda", "link.json");
345
+ if (!existsSync(path)) {
346
+ if (requested)
347
+ return requested;
348
+ throw Object.assign(new Error("OPENXIANGDA_CREATE_PLATFORM_REQUIRED: 新建应用必须通过 --base-url <平台地址> 指定目标;先登录同一平台,不能沿用旧登录态猜测站点"), { code: "OPENXIANGDA_CREATE_PLATFORM_REQUIRED", retryable: false, data: { pointer: "/baseUrl" } });
349
+ }
350
+ let link;
351
+ try {
352
+ link = JSON.parse(readFileSync(path, "utf8"));
353
+ }
354
+ catch {
355
+ throw localLinkError("已有 OpenXiangda link 不是有效 JSON", studioMode);
356
+ }
357
+ if (!link || typeof link !== "object" || Array.isArray(link)) {
358
+ throw localLinkError("已有 OpenXiangda link 结构无效", studioMode);
359
+ }
360
+ const value = link;
361
+ if (value.schemaVersion !== 2 ||
362
+ value.appCode !== appType ||
363
+ typeof value.baseUrl !== "string" || !value.baseUrl) {
364
+ throw localLinkError("已有 OpenXiangda link 与本次应用身份不一致", studioMode);
365
+ }
366
+ const linked = normalizePlatformBaseUrl(value.baseUrl);
367
+ if (requested && requested !== linked) {
368
+ throw localLinkError(`已有工作区绑定 ${linked},与请求目标 ${requested} 不一致;create 不能重绑到其他站点`, studioMode);
369
+ }
370
+ return linked;
371
+ }
372
+ function localLinkError(message, studioMode) {
373
+ const code = studioMode ? "STUDIO_WORKSPACE_LINK_MISMATCH" : "OPENXIANGDA_WORKSPACE_LINK_MISMATCH";
374
+ return Object.assign(new Error(`${code}: ${message}`), {
375
+ code,
376
+ retryable: false,
377
+ data: { pointer: "/link" },
378
+ });
379
+ }
380
+ function devkitResultError(fallbackCode, result) {
381
+ const diagnostic = result.diagnostics.find(item => item.code) || {
382
+ code: fallbackCode,
383
+ message: fallbackCode,
384
+ retryable: false,
385
+ };
386
+ return Object.assign(new Error(`${diagnostic.code}: ${diagnostic.message}`), {
387
+ code: diagnostic.code,
388
+ retryable: diagnostic.retryable,
389
+ data: { ...(diagnostic.path ? { pointer: diagnostic.path } : {}) },
390
+ });
391
+ }
392
+ function assertStudioCreateInput(input) {
393
+ const missing = [
394
+ ["--app-code", input.appCode],
395
+ ["--name", input.name],
396
+ ["--template-ref", input.templateRef],
397
+ ["--template-digest", input.templateDigest],
398
+ ["--studio-project-id", input.projectId],
399
+ ["--provisioning-run-id", input.provisioningRunId],
400
+ ["--json-events", input.jsonEvents],
401
+ ["--run-id", input.runId],
402
+ ]
403
+ .filter(([, value]) => !value)
404
+ .map(([flag]) => flag);
405
+ if (missing.length > 0) {
406
+ throw Object.assign(new Error(`STUDIO_CREATE_FLAGS_REQUIRED: Studio 初始化缺少 ${missing.join(", ")}`), {
407
+ code: "STUDIO_CREATE_FLAGS_REQUIRED",
408
+ retryable: false,
409
+ data: { pointer: "/flags", missing },
410
+ });
411
+ }
412
+ for (const [pointer, value] of [
413
+ ["/studioProjectId", input.projectId],
414
+ ["/provisioningRunId", input.provisioningRunId],
415
+ ["/runId", input.runId],
416
+ ]) {
417
+ if (!isPlatformUuid(value)) {
418
+ throw Object.assign(new Error(`STUDIO_CREATE_UUID_INVALID: ${pointer} 必须是平台 UUID`), {
419
+ code: "STUDIO_CREATE_UUID_INVALID",
420
+ retryable: false,
421
+ data: { pointer },
422
+ });
423
+ }
424
+ }
425
+ }
426
+ function assertStudioSiteBaseUrl(value) {
427
+ try {
428
+ const url = new URL(value);
429
+ if (url.protocol === "https:" &&
430
+ !url.username &&
431
+ !url.password &&
432
+ !url.search &&
433
+ !url.hash) {
434
+ return;
435
+ }
436
+ }
437
+ catch {
438
+ // The stable error below owns malformed and insecure site URLs.
439
+ }
440
+ throw Object.assign(new Error("STUDIO_SITE_BASE_URL_INVALID: Studio 站点必须使用无 credential、query 或 fragment 的 HTTPS 地址"), {
441
+ code: "STUDIO_SITE_BASE_URL_INVALID",
442
+ retryable: false,
443
+ data: { pointer: "/siteBaseUrl" },
444
+ });
445
+ }
89
446
  function deriveAppCode(directoryName) {
90
447
  const code = directoryName
91
448
  .normalize("NFKD")
@@ -98,4 +455,21 @@ function deriveAppCode(directoryName) {
98
455
  }
99
456
  return code;
100
457
  }
458
+ function renderCreateRetryCommand(root, input) {
459
+ const parts = ["openxiangda", "create", shellArgument(root), "--base-url", shellArgument(input.baseUrl)];
460
+ if (input.appCode)
461
+ parts.push("--app-code", shellArgument(input.appCode));
462
+ if (input.name)
463
+ parts.push("--name", shellArgument(input.name));
464
+ if (input.templateRef) {
465
+ parts.push("--template-ref", shellArgument(input.templateRef));
466
+ }
467
+ if (input.templateDigest) {
468
+ parts.push("--template-digest", shellArgument(input.templateDigest));
469
+ }
470
+ return parts.join(" ");
471
+ }
472
+ function shellArgument(value) {
473
+ return `'${String(value).replaceAll("'", `'"'"'`)}'`;
474
+ }
101
475
  //# sourceMappingURL=create.js.map