@uipath/packager-tool-datafabric 1.201.0-preview.134

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/src/init.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Entity project scaffolding. A separate entry (like `@uipath/flow-tool/init`)
3
+ * because it needs the filesystem and solution-sdk — node-only machinery the
4
+ * root module must not pull into browser bundles.
5
+ */
6
+
7
+ export type {
8
+ AutoCreatedSolution,
9
+ EntityInitOptions,
10
+ EntityInitResult,
11
+ EntityProjectRegistration,
12
+ } from "./init/entity-init-service.js";
13
+ export { entityInitAsync } from "./init/entity-init-service.js";
@@ -0,0 +1,20 @@
1
+ /** The entity stub extension. Its own module so every caller shares one
2
+ * literal. */
3
+ export const ENTITY_EXTENSION_CONST = ".entity";
4
+
5
+ /** Error code on the `ToolResult` when entity validation fails. */
6
+ export const ENTITY_VALIDATION_FAILED = "ENTITY_VALIDATION_FAILED";
7
+
8
+ /**
9
+ * Diagnostic codes pack raises about the PROJECT rather than a single entity.
10
+ * The per-entity codes live with the checks that raise them: pair failures in
11
+ * `verify-pair.ts` (`PairFailureCode`) and schema failures in
12
+ * `validate-entity.ts` (`DIAGNOSTIC_CODES`).
13
+ */
14
+ export const PACK_DIAGNOSTIC_CODES = {
15
+ /** A main file belonging to another project type sits in an Entity project. */
16
+ misplacedMainFile: "MISPLACED_MAIN_FILE",
17
+ /** The project has entities but no parent solution, so their
18
+ * solution-relative resource pointers cannot resolve. */
19
+ noParentSolution: "NO_PARENT_SOLUTION",
20
+ } as const;
@@ -0,0 +1,226 @@
1
+ import {
2
+ type IProjectPackOptions,
3
+ type IProjectValidateOptions,
4
+ ProjectTool,
5
+ ToolResult,
6
+ } from "@uipath/solutionpackager-tool-core";
7
+ import { validateEntity } from "../schema/validate-entity.js";
8
+ import {
9
+ ENTITY_EXTENSION_CONST as ENTITY_EXTENSION,
10
+ ENTITY_VALIDATION_FAILED,
11
+ PACK_DIAGNOSTIC_CODES,
12
+ } from "./constants.js";
13
+ import { findSolutionRoot, verifyPair } from "./verify-pair.js";
14
+
15
+ export { ENTITY_VALIDATION_FAILED } from "./constants.js";
16
+
17
+ /** One pack finding. Codes come from verifyPair, validateEntity, or the
18
+ * project-level checks below. */
19
+ export interface EntityPackDiagnostic {
20
+ file: string;
21
+ code: string;
22
+ message: string;
23
+ fieldName?: string;
24
+ }
25
+
26
+ /**
27
+ * Validate-only. Entity projects emit no package — their definition resources
28
+ * under `resources/solution_folder/entity/native/` are the deployable payload —
29
+ * so packing means proving every stub↔resource pair is intact and every schema
30
+ * valid, then returning `packages: []`. Never writes.
31
+ *
32
+ * The same checks run behind `validateAsync` and `packAsync`, so solution
33
+ * validate and solution pack can never disagree.
34
+ */
35
+ export class EntityProjectTool extends ProjectTool {
36
+ override async validateAsync(
37
+ options: IProjectValidateOptions,
38
+ _cancellationToken?: AbortSignal,
39
+ ): Promise<ToolResult> {
40
+ return this.checkProjectAsync(options.projectPath);
41
+ }
42
+
43
+ override async packAsync(
44
+ options: IProjectPackOptions,
45
+ _cancellationToken?: AbortSignal,
46
+ ): Promise<ToolResult> {
47
+ const result = await this.checkProjectAsync(options.projectPath);
48
+ if (result.isSuccess) {
49
+ this.logger.info(
50
+ "Entity project is valid — no package emitted; the entity resources are the deployable payload.",
51
+ );
52
+ }
53
+ return result;
54
+ }
55
+
56
+ private async checkProjectAsync(projectPath: string): Promise<ToolResult> {
57
+ const fs = this.fileSystem;
58
+ const diagnostics: EntityPackDiagnostic[] = [];
59
+
60
+ let entries: string[] = [];
61
+ try {
62
+ entries = await fs.readdir(projectPath);
63
+ } catch {
64
+ return new ToolResult(
65
+ ENTITY_VALIDATION_FAILED,
66
+ `Entity project directory '${projectPath}' could not be read.`,
67
+ [],
68
+ "Check that the project path in the solution manifest points at an existing directory.",
69
+ );
70
+ }
71
+
72
+ const stubs = sorted(
73
+ entries.filter((name) => name.endsWith(ENTITY_EXTENSION)),
74
+ );
75
+
76
+ // A `.flow` inside an Entity project belongs to a Flow project: fail
77
+ // with move guidance rather than ignore a file the explorer renders.
78
+ for (const foreign of sorted(
79
+ entries.filter((name) => name.endsWith(".flow")),
80
+ )) {
81
+ diagnostics.push({
82
+ file: fs.path.join(projectPath, foreign),
83
+ code: PACK_DIAGNOSTIC_CODES.misplacedMainFile,
84
+ message: `'${foreign}' is a Flow main file inside an Entity project — move it to a Flow project.`,
85
+ });
86
+ }
87
+
88
+ const solutionDir = await findSolutionRoot(projectPath, fs);
89
+ if (solutionDir === undefined) {
90
+ if (stubs.length > 0) {
91
+ diagnostics.push({
92
+ file: projectPath,
93
+ code: PACK_DIAGNOSTIC_CODES.noParentSolution,
94
+ message:
95
+ "Entity project has no parent solution (.uipx not found) — the stubs' solution-relative resource pointers cannot resolve.",
96
+ });
97
+ }
98
+ } else {
99
+ diagnostics.push(
100
+ ...(await this.checkStubsAsync(
101
+ projectPath,
102
+ solutionDir,
103
+ stubs,
104
+ )),
105
+ );
106
+ }
107
+
108
+ if (diagnostics.length === 0) {
109
+ return ToolResult.success();
110
+ }
111
+
112
+ const result = new ToolResult(
113
+ ENTITY_VALIDATION_FAILED,
114
+ `Entity validation failed with ${diagnostics.length} issue(s). First: ${diagnostics[0]?.message}`,
115
+ [],
116
+ "Fix the listed entities (open them in the entity editor, or restore missing resources) and pack again.",
117
+ );
118
+ result.details = { diagnostics };
119
+ return result;
120
+ }
121
+
122
+ /** Verify every pair and validate each schema it resolves to, in stub
123
+ * order. */
124
+ private async checkStubsAsync(
125
+ projectPath: string,
126
+ solutionDir: string,
127
+ stubs: readonly string[],
128
+ ): Promise<EntityPackDiagnostic[]> {
129
+ const fs = this.fileSystem;
130
+ const diagnostics: EntityPackDiagnostic[] = [];
131
+ const allEntityNames = await collectSolutionEntityNames(
132
+ solutionDir,
133
+ fs,
134
+ );
135
+
136
+ for (const stubName of stubs) {
137
+ const stubFile = fs.path.join(projectPath, stubName);
138
+ const pair = await verifyPair(stubFile, solutionDir, fs);
139
+ if (!pair.ok) {
140
+ diagnostics.push({
141
+ file: stubFile,
142
+ code: pair.failure.code,
143
+ message: pair.failure.message,
144
+ });
145
+ continue;
146
+ }
147
+ for (const diagnostic of validateEntity(pair.entity, {
148
+ siblingNames: withoutOneOccurrence(
149
+ allEntityNames,
150
+ pair.entityName,
151
+ ),
152
+ })) {
153
+ diagnostics.push({
154
+ file: pair.resourceFile,
155
+ code: diagnostic.code,
156
+ message: diagnostic.message,
157
+ fieldName: diagnostic.fieldName,
158
+ });
159
+ }
160
+ }
161
+ return diagnostics;
162
+ }
163
+ }
164
+
165
+ /** Code-unit ordering, stated explicitly: diagnostics come out in this order, so
166
+ * it must not shift between environments. `localeCompare` would hand that to
167
+ * the runtime's ICU data and default locale. */
168
+ function sorted(names: readonly string[]): string[] {
169
+ return [...names].sort(compareCodeUnits);
170
+ }
171
+
172
+ function compareCodeUnits(left: string, right: string): number {
173
+ if (left < right) {
174
+ return -1;
175
+ }
176
+ return left > right ? 1 : 0;
177
+ }
178
+
179
+ /** An entity's siblings are every name in the solution except one occurrence of
180
+ * its own — names are solution-wide unique. */
181
+ function withoutOneOccurrence(
182
+ names: readonly string[],
183
+ name: string,
184
+ ): string[] {
185
+ const siblings = [...names];
186
+ const self = siblings.indexOf(name);
187
+ if (self >= 0) {
188
+ siblings.splice(self, 1);
189
+ }
190
+ return siblings;
191
+ }
192
+
193
+ /** Every entity name in the solution, read from stub filenames one directory
194
+ * deep. Duplicates are kept so cross-project collisions surface. */
195
+ async function collectSolutionEntityNames(
196
+ solutionDir: string,
197
+ fs: Parameters<typeof findSolutionRoot>[1],
198
+ ): Promise<string[]> {
199
+ const names: string[] = [];
200
+ let topLevel: string[] = [];
201
+ try {
202
+ topLevel = await fs.readdir(solutionDir);
203
+ } catch {
204
+ return names;
205
+ }
206
+ for (const entry of topLevel) {
207
+ if (entry === "resources" || entry.startsWith(".")) {
208
+ continue;
209
+ }
210
+ const dir = fs.path.join(solutionDir, entry);
211
+ try {
212
+ const stat = await fs.stat(dir);
213
+ if (!stat?.isDirectory()) {
214
+ continue;
215
+ }
216
+ for (const file of await fs.readdir(dir)) {
217
+ if (file.endsWith(ENTITY_EXTENSION)) {
218
+ names.push(fs.path.basename(file, ENTITY_EXTENSION));
219
+ }
220
+ }
221
+ } catch {
222
+ /* unreadable entry — skip */
223
+ }
224
+ }
225
+ return names;
226
+ }
@@ -0,0 +1,26 @@
1
+ import type {
2
+ IFileSystem,
3
+ IProjectToolFactory,
4
+ IToolLogger,
5
+ ProjectTool,
6
+ ProjectType,
7
+ } from "@uipath/solutionpackager-tool-core";
8
+ import { EntityProjectTool } from "./entity-project-tool.js";
9
+
10
+ /**
11
+ * Factory the packer's registry stores for project type `Entity`. Registered by
12
+ * `@uipath/data-fabric-tool/packager-tool` (CLI) and at activation by the
13
+ * Maestro VS Code extension; registering the same factory twice is a no-op.
14
+ * `"Entity"` is a string `ProjectType` — the tool-core enum is extensible, so
15
+ * no core change was needed.
16
+ */
17
+ export class EntityToolFactory implements IProjectToolFactory {
18
+ readonly supportedTypes: readonly ProjectType[] = ["Entity"];
19
+
20
+ async createAsync(
21
+ logger: IToolLogger,
22
+ fileSystem: IFileSystem,
23
+ ): Promise<ProjectTool> {
24
+ return new EntityProjectTool(fileSystem, logger);
25
+ }
26
+ }
@@ -0,0 +1,208 @@
1
+ import type { EntityJSON } from "@uipath/entity-modeler/schema";
2
+ import type { IFileSystem } from "@uipath/solutionpackager-tool-core";
3
+ import {
4
+ EntityResourceError,
5
+ unwrapResource,
6
+ } from "../resource/resource-io.js";
7
+ import { ENTITY_EXTENSION_CONST as ENTITY_EXTENSION } from "./constants.js";
8
+
9
+ /** Why a stub↔resource pair failed. Stable codes: pack output and the editor's
10
+ * failure states key on these. */
11
+ export type PairFailureCode =
12
+ | "StubUnparsable"
13
+ | "StubFileNameMismatch"
14
+ | "ResourcePathEscapes"
15
+ | "ResourceMissing"
16
+ | "ResourceInvalid"
17
+ | "NameCoupling";
18
+
19
+ export interface PairFailure {
20
+ code: PairFailureCode;
21
+ message: string;
22
+ }
23
+
24
+ export type PairResult =
25
+ | { ok: true; entityName: string; resourceFile: string; entity: EntityJSON }
26
+ | { ok: false; failure: PairFailure };
27
+
28
+ /**
29
+ * Does the stub still point at a real, well-formed resource whose name matches?
30
+ * Checks the stub parses, its filename equals the entity name, the resolved
31
+ * resource stays inside the solution (stubs can come from unpacked archives, so
32
+ * a `../../` pointer must not escape), the resource exists and parses, and its
33
+ * `Name` couples back to the stub. Reads only.
34
+ */
35
+ export async function verifyPair(
36
+ stubFile: string,
37
+ solutionDir: string,
38
+ fs: IFileSystem,
39
+ ): Promise<PairResult> {
40
+ const parsedStub = await readStub(stubFile, fs);
41
+ if ("failure" in parsedStub) {
42
+ return { ok: false, failure: parsedStub.failure };
43
+ }
44
+ const stub = parsedStub.stub;
45
+
46
+ const baseName = fs.path.basename(stubFile, ENTITY_EXTENSION);
47
+ if (baseName !== stub.name) {
48
+ return fail(
49
+ "StubFileNameMismatch",
50
+ `Stub file '${baseName}${ENTITY_EXTENSION}' names entity '${stub.name}' — the filename and the entity name must match.`,
51
+ );
52
+ }
53
+
54
+ const solutionRoot = fs.path.resolve(solutionDir);
55
+ const resourceFile = fs.path.resolve(
56
+ solutionRoot,
57
+ ...stub.resourcePath.split("/"),
58
+ );
59
+ // No IPath.sep, so "inside" means starts with root + a separator.
60
+ const boundary = resourceFile.charAt(solutionRoot.length);
61
+ if (
62
+ !resourceFile.startsWith(solutionRoot) ||
63
+ (boundary !== "/" && boundary !== "\\")
64
+ ) {
65
+ return fail(
66
+ "ResourcePathEscapes",
67
+ `'${stub.name}': resourcePath '${stub.resourcePath}' points outside the solution.`,
68
+ );
69
+ }
70
+
71
+ const parsedResource = await readResource(
72
+ resourceFile,
73
+ stub.name,
74
+ stub.resourcePath,
75
+ fs,
76
+ );
77
+ if ("failure" in parsedResource) {
78
+ return { ok: false, failure: parsedResource.failure };
79
+ }
80
+ const resourceRaw = parsedResource.raw;
81
+
82
+ let entity: EntityJSON;
83
+ try {
84
+ entity = unwrapResource(resourceRaw);
85
+ } catch (error) {
86
+ const detail =
87
+ error instanceof EntityResourceError ? ` (${error.failure})` : "";
88
+ return fail(
89
+ "ResourceInvalid",
90
+ `'${stub.name}': resource '${stub.resourcePath}' is not a valid entity resource${detail}.`,
91
+ );
92
+ }
93
+
94
+ if (entity.Name !== stub.name) {
95
+ return fail(
96
+ "NameCoupling",
97
+ `Stub '${stub.name}' points at a resource defining entity '${entity.Name}' — the names must match.`,
98
+ );
99
+ }
100
+
101
+ return { ok: true, entityName: stub.name, resourceFile, entity };
102
+ }
103
+
104
+ function fail(code: PairFailureCode, message: string): PairResult {
105
+ return { ok: false, failure: { code, message } };
106
+ }
107
+
108
+ interface ParsedStub {
109
+ name: string;
110
+ resourcePath: string;
111
+ }
112
+
113
+ /** Unreadable, unparsable and mis-shaped stubs all collapse to one
114
+ * `StubUnparsable` failure. */
115
+ async function readStub(
116
+ stubFile: string,
117
+ fs: IFileSystem,
118
+ ): Promise<{ stub: ParsedStub } | { failure: PairFailure }> {
119
+ let parsed: { name?: unknown; resourcePath?: unknown };
120
+ try {
121
+ const raw = await fs.readFile(stubFile);
122
+ if (raw === null) {
123
+ return failure(
124
+ "StubUnparsable",
125
+ `'${stubFile}' could not be read.`,
126
+ );
127
+ }
128
+ parsed = JSON.parse(decode(raw)) as typeof parsed;
129
+ } catch {
130
+ return failure("StubUnparsable", `'${stubFile}' is not valid JSON.`);
131
+ }
132
+ if (
133
+ typeof parsed !== "object" ||
134
+ parsed === null ||
135
+ typeof parsed.name !== "string" ||
136
+ typeof parsed.resourcePath !== "string"
137
+ ) {
138
+ return failure(
139
+ "StubUnparsable",
140
+ `'${stubFile}' must be an object with string 'name' and 'resourcePath'.`,
141
+ );
142
+ }
143
+ return { stub: { name: parsed.name, resourcePath: parsed.resourcePath } };
144
+ }
145
+
146
+ /** Missing and corrupt are different failures: the first is fixed by restoring
147
+ * the file, the second by editing it. */
148
+ async function readResource(
149
+ resourceFile: string,
150
+ entityName: string,
151
+ resourcePath: string,
152
+ fs: IFileSystem,
153
+ ): Promise<{ raw: unknown } | { failure: PairFailure }> {
154
+ const missing = failure(
155
+ "ResourceMissing",
156
+ `'${entityName}': definition resource '${resourcePath}' is missing. Restore it (check trash/backup) or delete the entity.`,
157
+ );
158
+ try {
159
+ const raw = await fs.readFile(resourceFile);
160
+ if (raw === null) {
161
+ return missing;
162
+ }
163
+ return { raw: JSON.parse(decode(raw)) };
164
+ } catch (error) {
165
+ if (error instanceof SyntaxError) {
166
+ return failure(
167
+ "ResourceInvalid",
168
+ `'${entityName}': resource '${resourcePath}' is not valid JSON.`,
169
+ );
170
+ }
171
+ return missing;
172
+ }
173
+ }
174
+
175
+ function decode(raw: string | Uint8Array): string {
176
+ return typeof raw === "string" ? raw : new TextDecoder().decode(raw);
177
+ }
178
+
179
+ function failure(
180
+ code: PairFailureCode,
181
+ message: string,
182
+ ): { failure: PairFailure } {
183
+ return { failure: { code, message } };
184
+ }
185
+
186
+ /** The owning solution dir, or undefined when outside any solution. */
187
+ export async function findSolutionRoot(
188
+ startDir: string,
189
+ fs: IFileSystem,
190
+ ): Promise<string | undefined> {
191
+ let dir = fs.path.resolve(startDir);
192
+ for (;;) {
193
+ let entries: string[] = [];
194
+ try {
195
+ entries = await fs.readdir(dir);
196
+ } catch {
197
+ entries = [];
198
+ }
199
+ if (entries.some((name) => name.endsWith(".uipx"))) {
200
+ return dir;
201
+ }
202
+ const parent = fs.path.dirname(dir);
203
+ if (parent === dir) {
204
+ return undefined;
205
+ }
206
+ dir = parent;
207
+ }
208
+ }
@@ -0,0 +1,142 @@
1
+ import type { EntityJSON } from "@uipath/entity-modeler/schema";
2
+ import {
3
+ ENTITY_RESOURCE_API_VERSION,
4
+ ENTITY_RESOURCE_DOC_VERSION,
5
+ ENTITY_RESOURCE_KIND,
6
+ ENTITY_RESOURCE_TYPE,
7
+ SOLUTION_FOLDER,
8
+ type UnifiedResourceFile,
9
+ } from "./unified-resource-file.js";
10
+
11
+ /**
12
+ * Deterministic bytes for everything this library writes: 2-space indent, LF,
13
+ * trailing newline, property order = construction order. One serializer shared
14
+ * with the editor in the vsix, so a re-save can't produce phantom diffs.
15
+ */
16
+ export function serialize(value: object): string {
17
+ return `${JSON.stringify(value, null, 2)}\n`;
18
+ }
19
+
20
+ /** The inner document is stored compact (single line) inside `resourceJson`. */
21
+ export function serializeEntityJson(entity: EntityJSON): string {
22
+ return JSON.stringify(entity);
23
+ }
24
+
25
+ /** Why `unwrapResource` failed. The editor maps these onto its "definition is
26
+ * corrupted" states; pack reports them as diagnostics. */
27
+ export type ResourceParseFailure =
28
+ | "NotAnObject"
29
+ | "MissingResource"
30
+ | "MissingSpec"
31
+ | "NotAnEntityResource"
32
+ | "ResourceJsonUnparsable";
33
+
34
+ export class EntityResourceError extends Error {
35
+ constructor(
36
+ public readonly failure: ResourceParseFailure,
37
+ message: string,
38
+ ) {
39
+ super(message);
40
+ this.name = "EntityResourceError";
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Wrap a schema into a brand-new resource file. For edits use
46
+ * {@link updateResourceSchema}, which preserves the existing wrapper.
47
+ */
48
+ export function wrapEntityJson(entity: EntityJSON): UnifiedResourceFile {
49
+ return {
50
+ docVersion: ENTITY_RESOURCE_DOC_VERSION,
51
+ resource: {
52
+ name: entity.Name,
53
+ kind: ENTITY_RESOURCE_KIND,
54
+ type: ENTITY_RESOURCE_TYPE,
55
+ apiVersion: ENTITY_RESOURCE_API_VERSION,
56
+ isOverridable: true,
57
+ dependencies: [],
58
+ runtimeDependencies: [],
59
+ folders: [{ fullyQualifiedName: SOLUTION_FOLDER }],
60
+ spec: {
61
+ resourceJson: serializeEntityJson(entity),
62
+ name: entity.Name,
63
+ displayName: entity.DisplayName,
64
+ description: entity.Description,
65
+ },
66
+ locks: [],
67
+ key: entity.Id,
68
+ files: [],
69
+ },
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Read the schema back out of a parsed resource file. Throws
75
+ * {@link EntityResourceError} with a stable failure kind.
76
+ */
77
+ export function unwrapResource(file: unknown): EntityJSON {
78
+ if (typeof file !== "object" || file === null || Array.isArray(file)) {
79
+ throw new EntityResourceError(
80
+ "NotAnObject",
81
+ "Resource file is not a JSON object.",
82
+ );
83
+ }
84
+ const resource = (file as UnifiedResourceFile).resource;
85
+ if (typeof resource !== "object" || resource === null) {
86
+ throw new EntityResourceError(
87
+ "MissingResource",
88
+ "Resource file has no 'resource' object.",
89
+ );
90
+ }
91
+ if (resource.kind !== ENTITY_RESOURCE_KIND) {
92
+ throw new EntityResourceError(
93
+ "NotAnEntityResource",
94
+ `Resource kind is '${String(resource.kind)}', expected '${ENTITY_RESOURCE_KIND}'.`,
95
+ );
96
+ }
97
+ const spec = resource.spec;
98
+ if (
99
+ typeof spec !== "object" ||
100
+ spec === null ||
101
+ typeof spec.resourceJson !== "string"
102
+ ) {
103
+ throw new EntityResourceError(
104
+ "MissingSpec",
105
+ "Resource file has no 'spec.resourceJson' string.",
106
+ );
107
+ }
108
+ try {
109
+ return JSON.parse(spec.resourceJson) as EntityJSON;
110
+ } catch {
111
+ throw new EntityResourceError(
112
+ "ResourceJsonUnparsable",
113
+ "'spec.resourceJson' is not valid JSON.",
114
+ );
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Replace the schema inside an existing resource file. Returns a new object for
120
+ * the caller to persist via {@link serialize}. Wrapper properties this function
121
+ * doesn't own — including ones other tools added — are preserved, and `key` is
122
+ * left alone since the entity Id is minted once at creation.
123
+ */
124
+ export function updateResourceSchema(
125
+ file: UnifiedResourceFile,
126
+ entity: EntityJSON,
127
+ ): UnifiedResourceFile {
128
+ return {
129
+ ...file,
130
+ resource: {
131
+ ...file.resource,
132
+ name: entity.Name,
133
+ spec: {
134
+ ...file.resource.spec,
135
+ resourceJson: serializeEntityJson(entity),
136
+ name: entity.Name,
137
+ displayName: entity.DisplayName,
138
+ description: entity.Description,
139
+ },
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,56 @@
1
+ // The solution resource file that stores an entity definition:
2
+ // `resources/solution_folder/entity/native/<Name>.json`. Every resource kind in
3
+ // a packed solution shares this wrapper; only `spec.resourceJson` (the
4
+ // stringified EntityJSON) is entity-specific.
5
+ //
6
+ // Index signatures throughout, so a round-trip preserves properties other tools
7
+ // may have stamped on the file.
8
+
9
+ export interface ResourceFolderRef {
10
+ fullyQualifiedName: string;
11
+ [extra: string]: unknown;
12
+ }
13
+
14
+ export interface ResourceSpec {
15
+ /** The entity definition (EntityJSON), stringified. The single copy. */
16
+ resourceJson: string;
17
+ name: string;
18
+ displayName: string;
19
+ description: string;
20
+ [extra: string]: unknown;
21
+ }
22
+
23
+ export interface SolutionResource {
24
+ name: string;
25
+ kind: string;
26
+ type: string;
27
+ apiVersion: string;
28
+ isOverridable: boolean;
29
+ dependencies: unknown[];
30
+ runtimeDependencies: unknown[];
31
+ folders: ResourceFolderRef[];
32
+ spec: ResourceSpec;
33
+ locks: unknown[];
34
+ /** The entity's own Id GUID — reused, never minted here. */
35
+ key: string;
36
+ files: unknown[];
37
+ [extra: string]: unknown;
38
+ }
39
+
40
+ export interface UnifiedResourceFile {
41
+ docVersion: string;
42
+ resource: SolutionResource;
43
+ [extra: string]: unknown;
44
+ }
45
+
46
+ export const ENTITY_RESOURCE_KIND = "entity";
47
+ export const ENTITY_RESOURCE_TYPE = "native";
48
+ export const ENTITY_RESOURCE_API_VERSION = "dataservice.uipath.com/v2";
49
+ export const ENTITY_RESOURCE_DOC_VERSION = "1.0.0";
50
+ export const SOLUTION_FOLDER = "solution_folder";
51
+
52
+ /** Solution-root-relative path of an entity's resource file. One convention
53
+ * shared by scaffolding, the editor's pairing and pack. */
54
+ export function entityResourcePath(entityName: string): string {
55
+ return `resources/${SOLUTION_FOLDER}/entity/native/${entityName}.json`;
56
+ }