swallowkit 2.0.0-alpha.1 → 2.0.0-alpha.10
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.ja.md +14 -0
- package/README.md +14 -0
- package/dist/cli/commands/add-auth.js +37 -6
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/configure-mcp.d.ts +12 -0
- package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
- package/dist/cli/commands/configure-mcp.js +42 -0
- package/dist/cli/commands/configure-mcp.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +2 -2
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +39 -27
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +8 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +67 -132
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/migrations.d.ts.map +1 -1
- package/dist/cli/commands/migrations.js +25 -0
- package/dist/cli/commands/migrations.js.map +1 -1
- package/dist/cli/commands/provision.d.ts +5 -0
- package/dist/cli/commands/provision.d.ts.map +1 -1
- package/dist/cli/commands/provision.js +278 -55
- package/dist/cli/commands/provision.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +4 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +85 -3
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +43 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/core/operations/create-model.d.ts +2 -0
- package/dist/core/operations/create-model.d.ts.map +1 -1
- package/dist/core/operations/create-model.js +60 -8
- package/dist/core/operations/create-model.js.map +1 -1
- package/dist/core/operations/migration-generator.d.ts.map +1 -1
- package/dist/core/operations/migration-generator.js +12 -1
- package/dist/core/operations/migration-generator.js.map +1 -1
- package/dist/core/operations/model-migration.d.ts +22 -0
- package/dist/core/operations/model-migration.d.ts.map +1 -0
- package/dist/core/operations/model-migration.js +36 -0
- package/dist/core/operations/model-migration.js.map +1 -0
- package/dist/core/operations/provision-deployment.d.ts +52 -0
- package/dist/core/operations/provision-deployment.d.ts.map +1 -0
- package/dist/core/operations/provision-deployment.js +164 -0
- package/dist/core/operations/provision-deployment.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +62 -6
- package/dist/core/operations/provision-operations.d.ts.map +1 -1
- package/dist/core/operations/provision-operations.js +653 -115
- package/dist/core/operations/provision-operations.js.map +1 -1
- package/dist/core/operations/scaffold-apply.d.ts +1 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
- package/dist/core/operations/scaffold-apply.js +1 -0
- package/dist/core/operations/scaffold-apply.js.map +1 -1
- package/dist/core/operations/scaffold-machine.d.ts +1 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
- package/dist/core/operations/scaffold-machine.js.map +1 -1
- package/dist/core/operations/scaffold-plan.d.ts +2 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
- package/dist/core/operations/scaffold-plan.js +3 -1
- package/dist/core/operations/scaffold-plan.js.map +1 -1
- package/dist/core/project/agent-instructions.d.ts.map +1 -1
- package/dist/core/project/agent-instructions.js +4 -1
- package/dist/core/project/agent-instructions.js.map +1 -1
- package/dist/core/project/boundaries.d.ts +5 -2
- package/dist/core/project/boundaries.d.ts.map +1 -1
- package/dist/core/project/boundaries.js +51 -20
- package/dist/core/project/boundaries.js.map +1 -1
- package/dist/core/project/capabilities.d.ts +10 -0
- package/dist/core/project/capabilities.d.ts.map +1 -1
- package/dist/core/project/capabilities.js +38 -6
- package/dist/core/project/capabilities.js.map +1 -1
- package/dist/core/project/home-page.d.ts +4 -0
- package/dist/core/project/home-page.d.ts.map +1 -0
- package/dist/core/project/home-page.js +113 -0
- package/dist/core/project/home-page.js.map +1 -0
- package/dist/core/project/mcp-config.d.ts +26 -0
- package/dist/core/project/mcp-config.d.ts.map +1 -0
- package/dist/core/project/mcp-config.js +117 -0
- package/dist/core/project/mcp-config.js.map +1 -0
- package/dist/core/project/react-project.d.ts +1 -1
- package/dist/core/project/react-project.d.ts.map +1 -1
- package/dist/core/project/react-project.js +46 -25
- package/dist/core/project/react-project.js.map +1 -1
- package/dist/core/project/state.d.ts +7 -0
- package/dist/core/project/state.d.ts.map +1 -1
- package/dist/core/project/state.js +86 -0
- package/dist/core/project/state.js.map +1 -1
- package/dist/core/project/workflows.d.ts.map +1 -1
- package/dist/core/project/workflows.js +53 -7
- package/dist/core/project/workflows.js.map +1 -1
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +26 -27
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +23 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +407 -137
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
- package/dist/core/scaffold/native-schema-generator.js +294 -91
- package/dist/core/scaffold/native-schema-generator.js.map +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +5 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
- package/dist/core/scaffold/openapi-generator.js +53 -3
- package/dist/core/scaffold/openapi-generator.js.map +1 -1
- package/dist/core/scaffold/ui-generator.d.ts +0 -3
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +68 -7
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +4 -3
- package/dist/core/verify/index.d.ts.map +1 -1
- package/dist/core/verify/index.js +122 -5
- package/dist/core/verify/index.js.map +1 -1
- package/dist/machine/contracts.d.ts +1 -0
- package/dist/machine/contracts.d.ts.map +1 -1
- package/dist/machine/errors.d.ts +3 -2
- package/dist/machine/errors.d.ts.map +1 -1
- package/dist/machine/errors.js +2 -1
- package/dist/machine/errors.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +59 -28
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts +6 -4
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +210 -25
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/version-selection.d.ts +55 -0
- package/dist/mcp/version-selection.d.ts.map +1 -0
- package/dist/mcp/version-selection.js +152 -0
- package/dist/mcp/version-selection.js.map +1 -0
- package/dist/utils/azure-cli.d.ts +56 -0
- package/dist/utils/azure-cli.d.ts.map +1 -0
- package/dist/utils/azure-cli.js +326 -0
- package/dist/utils/azure-cli.js.map +1 -0
- package/dist/utils/azure-diagnostics.d.ts +16 -0
- package/dist/utils/azure-diagnostics.d.ts.map +1 -0
- package/dist/utils/azure-diagnostics.js +68 -0
- package/dist/utils/azure-diagnostics.js.map +1 -0
- package/dist/utils/package-manager.js +2 -2
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +5 -7
- package/dist/utils/python-uv.d.ts.map +1 -1
- package/dist/utils/python-uv.js +50 -24
- package/dist/utils/python-uv.js.map +1 -1
- package/dist/utils/react-dev.d.ts +4 -0
- package/dist/utils/react-dev.d.ts.map +1 -0
- package/dist/utils/react-dev.js +35 -0
- package/dist/utils/react-dev.js.map +1 -0
- package/dist/utils/semver.d.ts +14 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +61 -0
- package/dist/utils/semver.js.map +1 -0
- package/package.json +6 -3
- package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
- package/src/__tests__/agent-instructions.test.ts +5 -0
- package/src/__tests__/agent-loop-phase34.test.ts +143 -8
- package/src/__tests__/auth.test.ts +9 -0
- package/src/__tests__/azure-cli.test.ts +202 -0
- package/src/__tests__/boundaries.test.ts +42 -0
- package/src/__tests__/csharp-schema.test.ts +261 -0
- package/src/__tests__/dev.test.ts +13 -6
- package/src/__tests__/fixtures/az wrapper.cmd +2 -0
- package/src/__tests__/fixtures/azure-cli.cjs +153 -0
- package/src/__tests__/fixtures/provision-runner.cjs +28 -0
- package/src/__tests__/generated-project-matrix.test.ts +789 -0
- package/src/__tests__/home-page.test.ts +50 -0
- package/src/__tests__/init.test.ts +116 -0
- package/src/__tests__/mcp-version.test.ts +262 -0
- package/src/__tests__/mcp.test.ts +120 -3
- package/src/__tests__/multi-auth.test.ts +115 -4
- package/src/__tests__/package-manager.test.ts +17 -2
- package/src/__tests__/plan-apply.test.ts +179 -0
- package/src/__tests__/provision-runtime.test.ts +722 -0
- package/src/__tests__/python-uv.test.ts +50 -14
- package/src/__tests__/python-venv.test.ts +48 -0
- package/src/__tests__/react-dev.test.ts +72 -0
- package/src/__tests__/react.test.ts +19 -0
- package/src/__tests__/schema-analysis.test.ts +329 -0
- package/src/cli/commands/add-auth.ts +36 -6
- package/src/cli/commands/configure-mcp.ts +54 -0
- package/src/cli/commands/dev.ts +40 -31
- package/src/cli/commands/init.ts +73 -138
- package/src/cli/commands/migrations.ts +22 -0
- package/src/cli/commands/provision.ts +294 -56
- package/src/cli/commands/scaffold.ts +105 -3
- package/src/cli/index.ts +40 -7
- package/src/core/operations/create-model.ts +51 -13
- package/src/core/operations/migration-generator.ts +14 -1
- package/src/core/operations/model-migration.ts +52 -0
- package/src/core/operations/provision-deployment.ts +183 -0
- package/src/core/operations/provision-operations.ts +662 -154
- package/src/core/operations/scaffold-apply.ts +2 -0
- package/src/core/operations/scaffold-machine.ts +1 -0
- package/src/core/operations/scaffold-plan.ts +5 -1
- package/src/core/project/agent-instructions.ts +4 -1
- package/src/core/project/agent-templates/AGENTS.md +89 -6
- package/src/core/project/agent-templates/CLAUDE.md +14 -1
- package/src/core/project/agent-templates/copilot-instructions.md +15 -1
- package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
- package/src/core/project/boundaries.ts +62 -20
- package/src/core/project/capabilities.ts +49 -6
- package/src/core/project/home-page.ts +114 -0
- package/src/core/project/mcp-config.ts +84 -0
- package/src/core/project/react-project.ts +50 -28
- package/src/core/project/state.ts +70 -0
- package/src/core/project/workflows.ts +53 -7
- package/src/core/scaffold/auth-generator.ts +26 -27
- package/src/core/scaffold/model-parser.ts +413 -129
- package/src/core/scaffold/native-schema-generator.ts +319 -103
- package/src/core/scaffold/openapi-generator.ts +63 -5
- package/src/core/scaffold/ui-generator.ts +70 -8
- package/src/core/verify/index.ts +118 -6
- package/src/machine/contracts.ts +1 -0
- package/src/machine/errors.ts +4 -2
- package/src/machine/index.ts +65 -34
- package/src/mcp/index.ts +221 -31
- package/src/mcp/version-selection.ts +163 -0
- package/src/utils/azure-cli.ts +315 -0
- package/src/utils/azure-diagnostics.ts +75 -0
- package/src/utils/package-manager.ts +2 -2
- package/src/utils/python-uv.ts +53 -33
- package/src/utils/react-dev.ts +31 -0
- package/src/utils/semver.ts +59 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FieldInfo, ModelInfo, toCamelCase } from "./model-parser";
|
|
1
|
+
import { FieldInfo, ModelInfo, toCamelCase, toPascalCase } from "./model-parser";
|
|
2
2
|
|
|
3
3
|
type OpenApiSchema =
|
|
4
4
|
| {
|
|
@@ -11,9 +11,59 @@ type OpenApiSchema =
|
|
|
11
11
|
additionalProperties?: boolean;
|
|
12
12
|
nullable?: boolean;
|
|
13
13
|
$ref?: string;
|
|
14
|
+
minLength?: number;
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
pattern?: string;
|
|
17
|
+
minimum?: number;
|
|
18
|
+
maximum?: number;
|
|
19
|
+
exclusiveMinimum?: boolean;
|
|
20
|
+
exclusiveMaximum?: boolean;
|
|
21
|
+
minItems?: number;
|
|
22
|
+
maxItems?: number;
|
|
14
23
|
};
|
|
15
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Promote inline `z.object({...})` fields to named models (e.g. ProbeItem.nested -> ProbeItemNested)
|
|
27
|
+
* so OpenAPI components and native classes keep their shape. Expanded fields reference the new model.
|
|
28
|
+
*/
|
|
29
|
+
export function expandInlineObjectModels(models: ModelInfo[]): ModelInfo[] {
|
|
30
|
+
const names = new Set(models.map((model) => model.name));
|
|
31
|
+
const synthetic: ModelInfo[] = [];
|
|
32
|
+
|
|
33
|
+
const expand = (owner: ModelInfo): ModelInfo => ({
|
|
34
|
+
...owner,
|
|
35
|
+
fields: owner.fields.map((field) => {
|
|
36
|
+
if (!field.objectFields || field.isNestedSchema) return field;
|
|
37
|
+
let name = `${owner.name}${toPascalCase(field.name)}`;
|
|
38
|
+
while (names.has(name)) name = `${name}Object`;
|
|
39
|
+
names.add(name);
|
|
40
|
+
const nested = expand({
|
|
41
|
+
...owner,
|
|
42
|
+
name,
|
|
43
|
+
displayName: name,
|
|
44
|
+
schemaName: name,
|
|
45
|
+
fields: field.objectFields,
|
|
46
|
+
hasId: field.objectFields.some((child) => child.name === "id"),
|
|
47
|
+
hasCreatedAt: field.objectFields.some((child) => child.name === "createdAt"),
|
|
48
|
+
hasUpdatedAt: field.objectFields.some((child) => child.name === "updatedAt"),
|
|
49
|
+
nestedSchemaRefs: [],
|
|
50
|
+
partitionKey: "/id",
|
|
51
|
+
connectorConfig: undefined,
|
|
52
|
+
authPolicy: undefined,
|
|
53
|
+
});
|
|
54
|
+
synthetic.push(nested);
|
|
55
|
+
const rest = { ...field };
|
|
56
|
+
delete rest.objectFields;
|
|
57
|
+
return { ...rest, isNestedSchema: true, nestedModelName: name };
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const expanded = models.map(expand);
|
|
62
|
+
return [...expanded, ...synthetic];
|
|
63
|
+
}
|
|
64
|
+
|
|
16
65
|
function toScalarSchema(field: FieldInfo): OpenApiSchema {
|
|
66
|
+
const constraints = field.constraints ?? {};
|
|
17
67
|
if (field.enumValues?.length) {
|
|
18
68
|
return {
|
|
19
69
|
type: "string",
|
|
@@ -25,7 +75,10 @@ function toScalarSchema(field: FieldInfo): OpenApiSchema {
|
|
|
25
75
|
case "string":
|
|
26
76
|
return {
|
|
27
77
|
type: "string",
|
|
28
|
-
format
|
|
78
|
+
...(field.format && field.format !== "time" ? { format: field.format } : {}),
|
|
79
|
+
...(constraints.minLength !== undefined ? { minLength: constraints.minLength } : {}),
|
|
80
|
+
...(constraints.maxLength !== undefined ? { maxLength: constraints.maxLength } : {}),
|
|
81
|
+
...(constraints.pattern !== undefined ? { pattern: constraints.pattern } : {}),
|
|
29
82
|
};
|
|
30
83
|
case "date":
|
|
31
84
|
return {
|
|
@@ -34,7 +87,11 @@ function toScalarSchema(field: FieldInfo): OpenApiSchema {
|
|
|
34
87
|
};
|
|
35
88
|
case "number":
|
|
36
89
|
return {
|
|
37
|
-
type: "number",
|
|
90
|
+
...(field.isInteger ? { type: "integer", format: "int64" } : { type: "number", format: "double" }),
|
|
91
|
+
...(constraints.minimum !== undefined ? { minimum: constraints.minimum } : {}),
|
|
92
|
+
...(constraints.exclusiveMinimum ? { exclusiveMinimum: true } : {}),
|
|
93
|
+
...(constraints.maximum !== undefined ? { maximum: constraints.maximum } : {}),
|
|
94
|
+
...(constraints.exclusiveMaximum ? { exclusiveMaximum: true } : {}),
|
|
38
95
|
};
|
|
39
96
|
case "boolean":
|
|
40
97
|
return {
|
|
@@ -59,6 +116,8 @@ function toFieldSchema(field: FieldInfo): OpenApiSchema {
|
|
|
59
116
|
return {
|
|
60
117
|
type: "array",
|
|
61
118
|
items: schema,
|
|
119
|
+
...(field.constraints?.minItems !== undefined ? { minItems: field.constraints.minItems } : {}),
|
|
120
|
+
...(field.constraints?.maxItems !== undefined ? { maxItems: field.constraints.maxItems } : {}),
|
|
62
121
|
};
|
|
63
122
|
}
|
|
64
123
|
|
|
@@ -82,11 +141,10 @@ function createComponentSchema(model: ModelInfo): OpenApiSchema {
|
|
|
82
141
|
required,
|
|
83
142
|
};
|
|
84
143
|
}
|
|
85
|
-
|
|
86
144
|
export function generateOpenApiDocument(models: ModelInfo[], rootModel: ModelInfo): string {
|
|
87
145
|
const rootModelRoute = toCamelCase(rootModel.name);
|
|
88
146
|
const components = Object.fromEntries(
|
|
89
|
-
models.map((model) => [model.name, createComponentSchema(model)])
|
|
147
|
+
expandInlineObjectModels(models).map((model) => [model.name, createComponentSchema(model)])
|
|
90
148
|
);
|
|
91
149
|
|
|
92
150
|
const document = {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* CRUD 画面(一覧、詳細、新規作成、編集)を生成する
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ModelInfo, toCamelCase, toKebabCase } from "./model-parser";
|
|
6
|
+
import { FieldInfo, ModelInfo, toCamelCase, toKebabCase } from "./model-parser";
|
|
7
7
|
import { ModelAuthPolicy } from "../../types";
|
|
8
8
|
|
|
9
9
|
/** auth が有効かつ authPolicy が存在する場合に渡されるオプション */
|
|
@@ -18,6 +18,21 @@ export interface UIAuthOptions {
|
|
|
18
18
|
/**
|
|
19
19
|
* 認証付きCRUDルートを、ページ側のuseAuthと同じAuthProviderで囲むレイアウトを生成する。
|
|
20
20
|
*/
|
|
21
|
+
/** Lookup state and fetches are per referenced model; several fields may reference the same model. */
|
|
22
|
+
function uniqueByReferencedModel(fields: FieldInfo[]): FieldInfo[] {
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
return fields.filter((field) => {
|
|
25
|
+
if (!field.referencedModel || seen.has(field.referencedModel)) return false;
|
|
26
|
+
seen.add(field.referencedModel);
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Untyped or inline z.object fields (not references to another model) are edited and shown as JSON. */
|
|
32
|
+
function isJsonField(f: FieldInfo): boolean {
|
|
33
|
+
return f.type === "object" && !f.isNestedSchema;
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
export function generateAuthLayout(model: ModelInfo, authOptions?: UIAuthOptions): string | undefined {
|
|
22
37
|
if (!authOptions) return undefined;
|
|
23
38
|
|
|
@@ -59,14 +74,14 @@ export function generateListPage(model: ModelInfo, sharedPackageName: string, au
|
|
|
59
74
|
const hasForeignKeys = foreignKeyFields.length > 0;
|
|
60
75
|
|
|
61
76
|
// 外部キー用のstate定義を生成
|
|
62
|
-
const foreignKeyStates = foreignKeyFields.map(f => {
|
|
77
|
+
const foreignKeyStates = uniqueByReferencedModel(foreignKeyFields).map(f => {
|
|
63
78
|
const refModel = f.referencedModel!;
|
|
64
79
|
const refModelCamel = toCamelCase(refModel);
|
|
65
80
|
return ` const [${refModelCamel}Map, set${refModel}Map] = useState<Record<string, string>>({});`;
|
|
66
81
|
}).join('\n');
|
|
67
82
|
|
|
68
83
|
// 外部キーデータのフェッチロジックを生成
|
|
69
|
-
const foreignKeyFetches = foreignKeyFields.map(f => {
|
|
84
|
+
const foreignKeyFetches = uniqueByReferencedModel(foreignKeyFields).map(f => {
|
|
70
85
|
const refModel = f.referencedModel!;
|
|
71
86
|
const refModelCamel = toCamelCase(refModel);
|
|
72
87
|
return ` fetch('/api/${refModelCamel}')
|
|
@@ -234,6 +249,10 @@ ${displayFields.map(f => {
|
|
|
234
249
|
return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
235
250
|
{${refModelCamel}Map[item.${f.name}] || item.${f.name}}
|
|
236
251
|
</td>`;
|
|
252
|
+
} else if (isJsonField(f)) {
|
|
253
|
+
return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
254
|
+
{item.${f.name} === undefined ? '-' : JSON.stringify(item.${f.name})}
|
|
255
|
+
</td>`;
|
|
237
256
|
} else {
|
|
238
257
|
return ` <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
239
258
|
{String(item.${f.name})}
|
|
@@ -305,14 +324,14 @@ export function generateDetailPage(model: ModelInfo, sharedPackageName: string,
|
|
|
305
324
|
const hasForeignKeys = foreignKeyFields.length > 0;
|
|
306
325
|
|
|
307
326
|
// 外部キー用のstate定義を生成
|
|
308
|
-
const foreignKeyStates = foreignKeyFields.map(f => {
|
|
327
|
+
const foreignKeyStates = uniqueByReferencedModel(foreignKeyFields).map(f => {
|
|
309
328
|
const refModel = f.referencedModel!;
|
|
310
329
|
const refModelCamel = toCamelCase(refModel);
|
|
311
330
|
return ` const [${refModelCamel}Map, set${refModel}Map] = useState<Record<string, string>>({});`;
|
|
312
331
|
}).join('\n');
|
|
313
332
|
|
|
314
333
|
// 外部キーデータのフェッチロジックを生成
|
|
315
|
-
const foreignKeyFetches = foreignKeyFields.map(f => {
|
|
334
|
+
const foreignKeyFetches = uniqueByReferencedModel(foreignKeyFields).map(f => {
|
|
316
335
|
const refModel = f.referencedModel!;
|
|
317
336
|
const refModelCamel = toCamelCase(refModel);
|
|
318
337
|
return ` fetch('/api/${refModelCamel}')
|
|
@@ -476,6 +495,11 @@ ${model.fields.map(f => {
|
|
|
476
495
|
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${refModel}</dt>
|
|
477
496
|
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-100">{${refModelCamel}Map[${modelCamel}.${f.name}] || ${modelCamel}.${f.name}}</dd>
|
|
478
497
|
</div>`;
|
|
498
|
+
} else if (isJsonField(f)) {
|
|
499
|
+
return ` <div>
|
|
500
|
+
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${f.name}</dt>
|
|
501
|
+
<dd className="mt-1 text-sm text-gray-900 dark:text-gray-100"><pre className="whitespace-pre-wrap">{${modelCamel}.${f.name} === undefined ? '-' : JSON.stringify(${modelCamel}.${f.name}, null, 2)}</pre></dd>
|
|
502
|
+
</div>`;
|
|
479
503
|
} else {
|
|
480
504
|
return ` <div>
|
|
481
505
|
<dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${f.name}</dt>
|
|
@@ -573,7 +597,7 @@ export default function ${modelName}Form({ initialData, isEdit = false }: ${mode
|
|
|
573
597
|
const router = useRouter();
|
|
574
598
|
const [loading, setLoading] = useState(false);
|
|
575
599
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
576
|
-
${hasForignKeys ? foreignKeyFields.map(f => ` const [${toCamelCase(f.referencedModel!)}Options, set${f.referencedModel}Options] = useState<Array<{ id: string; name: string }>>([]);`).join('\n') : ''}
|
|
600
|
+
${hasForignKeys ? uniqueByReferencedModel(foreignKeyFields).map(f => ` const [${toCamelCase(f.referencedModel!)}Options, set${f.referencedModel}Options] = useState<Array<{ id: string; name: string }>>([]);`).join('\n') : ''}
|
|
577
601
|
${hasNestedSchemas ? nestedSchemaStates : ''}
|
|
578
602
|
|
|
579
603
|
const [formData, setFormData] = useState({
|
|
@@ -587,6 +611,11 @@ ${formFields.map(f => {
|
|
|
587
611
|
}
|
|
588
612
|
}
|
|
589
613
|
|
|
614
|
+
// インラインオブジェクトは JSON テキストとして編集する
|
|
615
|
+
if (isJsonField(f)) {
|
|
616
|
+
return ` ${f.name}: initialData?.${f.name} !== undefined ? JSON.stringify(initialData.${f.name}, null, 2) : '',`;
|
|
617
|
+
}
|
|
618
|
+
|
|
590
619
|
let defaultValue = "''";
|
|
591
620
|
if (f.isArray) {
|
|
592
621
|
// Array の場合は空文字列(カンマ区切りで入力するため)
|
|
@@ -612,7 +641,7 @@ ${formFields.map(f => {
|
|
|
612
641
|
${needsUseEffect ? `
|
|
613
642
|
// 参照データの選択肢を取得
|
|
614
643
|
useEffect(() => {
|
|
615
|
-
${hasForignKeys ? foreignKeyFields.map(f => ` // ${f.referencedModel} の一覧を取得
|
|
644
|
+
${hasForignKeys ? uniqueByReferencedModel(foreignKeyFields).map(f => ` // ${f.referencedModel} の一覧を取得
|
|
616
645
|
fetch('/api/${toKebabCase(f.referencedModel!)}')
|
|
617
646
|
.then(res => res.json())
|
|
618
647
|
.then(data => set${f.referencedModel}Options(data.map((item: Record<string, string>) => ({ id: item.id, name: item.name || item.title || item.id }))))
|
|
@@ -628,7 +657,19 @@ ${hasNestedSchemas ? nestedSchemaFetches : ''}
|
|
|
628
657
|
try {
|
|
629
658
|
// Array フィールドをカンマ区切りから配列に変換
|
|
630
659
|
const submitData: Record<string, unknown> = { ...formData };
|
|
631
|
-
${formFields.filter(
|
|
660
|
+
${formFields.filter(isJsonField).length > 0 ? ` // JSON フィールド(インラインオブジェクト)を解析
|
|
661
|
+
${formFields.filter(isJsonField).map(f => ` if (submitData.${f.name} === '') {
|
|
662
|
+
submitData.${f.name} = undefined;
|
|
663
|
+
} else if (typeof submitData.${f.name} === 'string') {
|
|
664
|
+
try {
|
|
665
|
+
submitData.${f.name} = JSON.parse(submitData.${f.name});
|
|
666
|
+
} catch {
|
|
667
|
+
setErrors({ ${f.name}: 'Invalid JSON' });
|
|
668
|
+
setLoading(false);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
}`).join('\n')}
|
|
672
|
+
` : ''}${formFields.filter(f => f.isArray && !f.isNestedSchema && !isJsonField(f)).map(f => ` if (typeof submitData.${f.name} === 'string') {
|
|
632
673
|
submitData.${f.name} = submitData.${f.name}.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0);
|
|
633
674
|
}`).join('\n')}
|
|
634
675
|
${formFields.filter(f => f.enumValues && f.enumValues.length > 0).length > 0 ? `
|
|
@@ -833,6 +874,27 @@ ${f.enumValues.map(v => ` <option value="${v}">${v}</option>`).join('\n
|
|
|
833
874
|
</div>`;
|
|
834
875
|
}
|
|
835
876
|
|
|
877
|
+
// インラインオブジェクトは JSON テキストエリア
|
|
878
|
+
if (isJsonField(f)) {
|
|
879
|
+
return ` <div>
|
|
880
|
+
<label htmlFor="${f.name}" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
881
|
+
${f.name}${!f.isOptional ? ' *' : ''} <span className="text-xs text-gray-500">(JSON)</span>
|
|
882
|
+
</label>
|
|
883
|
+
<textarea
|
|
884
|
+
id="${f.name}"
|
|
885
|
+
name="${f.name}"
|
|
886
|
+
rows={4}
|
|
887
|
+
value={formData.${f.name}}
|
|
888
|
+
onChange={(e) => setFormData({ ...formData, ${f.name}: e.target.value })}
|
|
889
|
+
className="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm px-3 py-2 font-mono focus:border-blue-500 focus:ring-blue-500 dark:focus:border-blue-400 dark:focus:ring-blue-400"
|
|
890
|
+
${!f.isOptional ? 'required' : ''}
|
|
891
|
+
/>
|
|
892
|
+
{errors.${f.name} && (
|
|
893
|
+
<p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.${f.name}}</p>
|
|
894
|
+
)}
|
|
895
|
+
</div>`;
|
|
896
|
+
}
|
|
897
|
+
|
|
836
898
|
// Array の場合はカンマ区切りテキスト
|
|
837
899
|
if (f.isArray) {
|
|
838
900
|
return ` <div>
|
package/src/core/verify/index.ts
CHANGED
|
@@ -5,16 +5,17 @@
|
|
|
5
5
|
* - structure: プロジェクト構造規約 (validateProject)
|
|
6
6
|
* - drift: 生成物と現在状態の乖離 (detectDrift)
|
|
7
7
|
* - typecheck: TypeScript 型チェック (tsc --noEmit / typecheck script)
|
|
8
|
+
* - backend-build: standalone Functions のコンパイル (C#/TypeScript) または構文検証 (Python)
|
|
8
9
|
* - build/lint/test: package.json scripts に同名 script がある場合のみ実行 (なければ skip)
|
|
9
10
|
* - custom: swallowkit.config の verify.checks で定義されたプロジェクト固有チェック
|
|
10
11
|
*
|
|
11
|
-
* 既定実行セットは structure/drift/typecheck + custom。build/lint/test は --checks で明示指定。
|
|
12
|
+
* 既定実行セットは structure/drift/typecheck/backend-build + custom。build/lint/test は --checks で明示指定。
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import * as fs from "fs";
|
|
15
16
|
import * as path from "path";
|
|
16
17
|
import { spawnSync } from "child_process";
|
|
17
|
-
import { loadConfig } from "../config";
|
|
18
|
+
import { loadConfig, loadConfigFromEnv } from "../config";
|
|
18
19
|
import { detectDrift, DriftFinding } from "../project/drift";
|
|
19
20
|
import { saveLastVerifyState } from "../project/state";
|
|
20
21
|
import { ProjectViolation, validateProject } from "../project/validation";
|
|
@@ -60,11 +61,11 @@ export interface VerifyResult {
|
|
|
60
61
|
verifiedAt: string;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
export const AVAILABLE_VERIFY_CHECKS = ["structure", "drift", "typecheck", "build", "lint", "test"] as const;
|
|
64
|
+
export const AVAILABLE_VERIFY_CHECKS = ["structure", "drift", "typecheck", "backend-build", "build", "lint", "test"] as const;
|
|
64
65
|
export type VerifyCheckId = (typeof AVAILABLE_VERIFY_CHECKS)[number];
|
|
65
66
|
|
|
66
67
|
/** 既定で実行される built-in チェック。build/lint/test は明示指定時のみ。 */
|
|
67
|
-
export const DEFAULT_VERIFY_CHECKS = ["structure", "drift", "typecheck"] as const;
|
|
68
|
+
export const DEFAULT_VERIFY_CHECKS = ["structure", "drift", "typecheck", "backend-build"] as const;
|
|
68
69
|
|
|
69
70
|
async function runStructureCheck(projectRoot: string): Promise<VerifyCheckResult> {
|
|
70
71
|
const startedAt = Date.now();
|
|
@@ -137,6 +138,7 @@ interface CommandCheckSpec {
|
|
|
137
138
|
args: string[];
|
|
138
139
|
timeoutMs?: number;
|
|
139
140
|
failAction: string;
|
|
141
|
+
shell?: boolean;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
function runCommandCheck(projectRoot: string, spec: CommandCheckSpec): VerifyCheckResult {
|
|
@@ -145,9 +147,10 @@ function runCommandCheck(projectRoot: string, spec: CommandCheckSpec): VerifyChe
|
|
|
145
147
|
try {
|
|
146
148
|
const result = spawnSync(spec.command, spec.args, {
|
|
147
149
|
cwd: projectRoot,
|
|
148
|
-
shell: true,
|
|
150
|
+
shell: spec.shell ?? true,
|
|
149
151
|
encoding: "utf-8",
|
|
150
152
|
timeout: spec.timeoutMs ?? 5 * 60 * 1000,
|
|
153
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
151
154
|
});
|
|
152
155
|
|
|
153
156
|
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
@@ -180,6 +183,11 @@ function runCommandCheck(projectRoot: string, spec: CommandCheckSpec): VerifyChe
|
|
|
180
183
|
command: commandLabel,
|
|
181
184
|
exitCode,
|
|
182
185
|
logTail: tailLines(output, 50),
|
|
186
|
+
...(spec.id === "backend-build" ? {
|
|
187
|
+
diagnostics: [...new Set(output.split(/\r?\n/).filter((line) =>
|
|
188
|
+
/\(\d+,\d+\):\s*error\s+\w+/.test(line) ||
|
|
189
|
+
/^\s*(File ".+", line \d+|(?:Sorry: )?(?:Syntax|Indentation|Tab)Error:|\*\*\* Error compiling)/.test(line)))],
|
|
190
|
+
} : {}),
|
|
183
191
|
},
|
|
184
192
|
suggestedActions: exitCode === 0 ? [] : [spec.failAction],
|
|
185
193
|
};
|
|
@@ -201,6 +209,86 @@ function runCommandCheck(projectRoot: string, spec: CommandCheckSpec): VerifyChe
|
|
|
201
209
|
}
|
|
202
210
|
}
|
|
203
211
|
|
|
212
|
+
function backendUnavailable(reason: string): VerifyCheckResult {
|
|
213
|
+
return {
|
|
214
|
+
id: "backend-build",
|
|
215
|
+
title: "Standalone Functions compilation",
|
|
216
|
+
status: "skip",
|
|
217
|
+
durationMs: 0,
|
|
218
|
+
fixable: false,
|
|
219
|
+
evidence: { logTail: [reason], diagnostics: [reason] },
|
|
220
|
+
suggestedActions: ["Install or restore the backend toolchain, then re-run verify with --checks \"backend-build\". A skipped check is not backend validation."],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function runBackendBuildCheck(projectRoot: string): VerifyCheckResult {
|
|
225
|
+
const functionsRoot = path.join(projectRoot, "functions");
|
|
226
|
+
if (!fs.existsSync(functionsRoot)) return backendUnavailable("functions/ not found; no standalone backend was checked.");
|
|
227
|
+
const configPath = ["swallowkit.config.json", "swallowkit.config.js", ".swallowkitrc.json"]
|
|
228
|
+
.map((name) => path.join(projectRoot, name)).find((file) => fs.existsSync(file));
|
|
229
|
+
if (!configPath) return backendUnavailable("SwallowKit configuration not found; backend language is unknown.");
|
|
230
|
+
// Same effective language as generation (getBackendLanguage): env override, then file, invalid -> typescript.
|
|
231
|
+
const configured = loadConfigFromEnv().backend?.language ?? loadConfig(configPath, true, true).backend?.language;
|
|
232
|
+
const language = configured && ["typescript", "csharp", "python"].includes(configured) ? configured : "typescript";
|
|
233
|
+
let command: string;
|
|
234
|
+
let args: string[];
|
|
235
|
+
if (language === "csharp") {
|
|
236
|
+
command = "dotnet";
|
|
237
|
+
args = ["build", "--nologo"];
|
|
238
|
+
const probe = spawnSync(command, ["--version"], { cwd: functionsRoot, encoding: "utf-8", timeout: 30_000 });
|
|
239
|
+
if (probe.error && "code" in probe.error && probe.error.code === "ENOENT") return backendUnavailable("dotnet SDK is not installed or is not on PATH; C# compilation was skipped.");
|
|
240
|
+
} else if (language === "python") {
|
|
241
|
+
const venvPython = process.platform === "win32"
|
|
242
|
+
? path.join(functionsRoot, ".venv", "Scripts", "python.exe")
|
|
243
|
+
: path.join(functionsRoot, ".venv", "bin", "python");
|
|
244
|
+
const candidates = fs.existsSync(venvPython)
|
|
245
|
+
? [{ command: venvPython, args: [] as string[] }]
|
|
246
|
+
: process.platform === "win32"
|
|
247
|
+
? [{ command: "python", args: [] }, { command: "python3", args: [] }, { command: "py", args: ["-3"] }]
|
|
248
|
+
: [{ command: "python3", args: [] }, { command: "python", args: [] }];
|
|
249
|
+
const python = candidates.find((candidate) => {
|
|
250
|
+
const probe = spawnSync(candidate.command, [...candidate.args, "-c", "import sys; sys.exit(0 if sys.version_info.major == 3 else 1)"], {
|
|
251
|
+
cwd: functionsRoot, encoding: "utf-8", timeout: 30_000,
|
|
252
|
+
});
|
|
253
|
+
// Preserve execution errors for an installed interpreter instead of silently falling back.
|
|
254
|
+
return probe.status === 0 || (probe.error && (!("code" in probe.error) || probe.error.code !== "ENOENT"));
|
|
255
|
+
});
|
|
256
|
+
if (!python) return backendUnavailable("Python 3 is not available in functions/.venv or on PATH; Python syntax compilation was skipped.");
|
|
257
|
+
command = python.command;
|
|
258
|
+
// -f: never trust cached bytecode, which can survive same-size edits within one mtime second.
|
|
259
|
+
args = [...python.args, "-m", "compileall", "-q", "-f", "-x", "[/\\\\](?:\\.venv|\\.codegen-venv|node_modules|__pycache__)(?:[/\\\\]|$)", "."];
|
|
260
|
+
} else {
|
|
261
|
+
let tscPath: string;
|
|
262
|
+
try {
|
|
263
|
+
tscPath = require.resolve("typescript/bin/tsc", { paths: [functionsRoot] });
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND") {
|
|
266
|
+
return backendUnavailable("The project's TypeScript compiler is not installed; restore dependencies before verifying functions/.");
|
|
267
|
+
}
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
const sharedBuild = runSharedBuildIfPresent(projectRoot);
|
|
271
|
+
if (sharedBuild.ran && !sharedBuild.ok) {
|
|
272
|
+
return {
|
|
273
|
+
id: "backend-build", title: "Standalone Functions compilation", status: sharedBuild.unavailable ? "error" : "fail",
|
|
274
|
+
durationMs: 0, fixable: !sharedBuild.unavailable,
|
|
275
|
+
evidence: { command: sharedBuild.command, logTail: sharedBuild.logTail,
|
|
276
|
+
...(sharedBuild.unavailable ? { errorCode: "verification-command-unavailable" as const } : {}) },
|
|
277
|
+
suggestedActions: ["Fix the shared package build before compiling the TypeScript backend."],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
command = process.execPath;
|
|
281
|
+
args = [tscPath, "--project", "tsconfig.json", "--noEmit"];
|
|
282
|
+
}
|
|
283
|
+
const result = runCommandCheck(functionsRoot, {
|
|
284
|
+
id: "backend-build", title: "Standalone Functions compilation", command, args, shell: false,
|
|
285
|
+
timeoutMs: 10 * 60 * 1000,
|
|
286
|
+
failAction: "Fix the backend compilation errors in evidence.diagnostics / evidence.logTail, then re-run verify.",
|
|
287
|
+
});
|
|
288
|
+
result.evidence.command = `${result.evidence.command} (in functions/)`;
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
204
292
|
function readPackageScripts(projectRoot: string): Record<string, string> {
|
|
205
293
|
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
206
294
|
if (!fs.existsSync(packageJsonPath)) return {};
|
|
@@ -253,6 +341,8 @@ function runTypecheckCheck(projectRoot: string): VerifyCheckResult {
|
|
|
253
341
|
|
|
254
342
|
const scripts = readPackageScripts(projectRoot);
|
|
255
343
|
const pm = detectFromProject(projectRoot);
|
|
344
|
+
// Next.js App Router templates use generated route types (e.g. LayoutProps); create them before tsc.
|
|
345
|
+
const typegenNotes = runNextTypegenIfPresent(projectRoot, pm);
|
|
256
346
|
let command: string;
|
|
257
347
|
let args: string[];
|
|
258
348
|
if (typeof scripts.typecheck === "string") {
|
|
@@ -263,13 +353,33 @@ function runTypecheckCheck(projectRoot: string): VerifyCheckResult {
|
|
|
263
353
|
args = pm === "pnpm" ? ["exec", "tsc", "--noEmit"] : ["exec", "tsc", "--", "--noEmit"];
|
|
264
354
|
}
|
|
265
355
|
|
|
266
|
-
|
|
356
|
+
const result = runCommandCheck(projectRoot, {
|
|
267
357
|
id: "typecheck",
|
|
268
358
|
title: "TypeScript type check",
|
|
269
359
|
command,
|
|
270
360
|
args,
|
|
271
361
|
failAction: "Fix the TypeScript errors listed in evidence.logTail, then re-run verify.",
|
|
272
362
|
});
|
|
363
|
+
if (result.status !== "pass" && typegenNotes.length > 0) {
|
|
364
|
+
result.evidence.logTail = [...typegenNotes, ...(result.evidence.logTail ?? [])];
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const NEXT_CONFIG_FILES = ["next.config.ts", "next.config.js", "next.config.mjs", "next.config.cjs"];
|
|
370
|
+
|
|
371
|
+
function runNextTypegenIfPresent(projectRoot: string, pm: ReturnType<typeof detectFromProject>): string[] {
|
|
372
|
+
if (!NEXT_CONFIG_FILES.some((file) => fs.existsSync(path.join(projectRoot, file)))) return [];
|
|
373
|
+
if (!fs.existsSync(path.join(projectRoot, "node_modules", "next", "package.json"))) return [];
|
|
374
|
+
const args = pm === "pnpm" ? ["exec", "next", "typegen"] : ["exec", "--", "next", "typegen"];
|
|
375
|
+
const result = spawnSync(getCommands(pm).name, args, {
|
|
376
|
+
cwd: projectRoot,
|
|
377
|
+
shell: true,
|
|
378
|
+
encoding: "utf-8",
|
|
379
|
+
timeout: 2 * 60 * 1000,
|
|
380
|
+
});
|
|
381
|
+
if (!result.error && result.status === 0) return [];
|
|
382
|
+
return [`next typegen did not complete (${result.error?.message ?? `exit ${result.status}`}); route types such as LayoutProps may be missing.`];
|
|
273
383
|
}
|
|
274
384
|
|
|
275
385
|
const SCRIPT_CHECK_TITLES: Record<string, string> = {
|
|
@@ -408,6 +518,8 @@ export async function runVerify(
|
|
|
408
518
|
checks.push(await runDriftCheck(projectRoot));
|
|
409
519
|
} else if (id === "typecheck") {
|
|
410
520
|
checks.push(runTypecheckCheck(projectRoot));
|
|
521
|
+
} else if (id === "backend-build") {
|
|
522
|
+
checks.push(runBackendBuildCheck(projectRoot));
|
|
411
523
|
} else if (id === "build" || id === "lint" || id === "test") {
|
|
412
524
|
checks.push(runScriptCheck(projectRoot, id));
|
|
413
525
|
} else {
|
package/src/machine/contracts.ts
CHANGED
package/src/machine/errors.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import type { MachineOperationStatus } from "./contracts";
|
|
1
|
+
import type { MachineNextAction, MachineOperationStatus } from "./contracts";
|
|
2
2
|
|
|
3
3
|
export class MachineCommandError extends Error {
|
|
4
4
|
readonly code: string;
|
|
5
5
|
readonly details?: unknown;
|
|
6
6
|
readonly status?: MachineOperationStatus;
|
|
7
|
+
readonly nextActions?: MachineNextAction[];
|
|
7
8
|
|
|
8
|
-
constructor(code: string, message: string, details?: unknown, status?: MachineOperationStatus) {
|
|
9
|
+
constructor(code: string, message: string, details?: unknown, status?: MachineOperationStatus, nextActions?: MachineNextAction[]) {
|
|
9
10
|
super(message);
|
|
10
11
|
this.name = "MachineCommandError";
|
|
11
12
|
this.code = code;
|
|
12
13
|
this.details = details;
|
|
13
14
|
this.status = status;
|
|
15
|
+
this.nextActions = nextActions;
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|