mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-11 00:06:06 +00:00
feat: add fileset resource type support
Add a new "fileset" resource type that represents a collection of files stored as a relpath→content map. This enables resource types to manage multiple files (e.g., config directories, template sets) instead of just a single file. Backend: - Add is_fileset column to resource_type table - Update CRUD operations and workspace duplication to handle is_fileset - Add integration tests for fileset resource types Frontend: - Add FilesetEditor component with file explorer + Monaco editor - Extract shared FileExplorer component from RawAppSidebar (dedup) - Add fileset toggle to EditableSchemaWrapper - Show fileset editor in ResourceEditor and ApiConnectForm - Show folder icon for fileset resource types in IconedResourceType CLI: - Support fileset resources in sync pull (expand to .fileset/ directory) - Support fileset resources in sync push (reconstruct from directory) - Handle !inline_fileset YAML tag in resource resolution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
710809ac64
commit
d4dc0a088f
Generated
+87
-96
@@ -6,16 +6,11 @@
|
||||
"": {
|
||||
"name": "wmill-dev",
|
||||
"dependencies": {
|
||||
"@ayonli/jsext": "^1.9.0",
|
||||
"@cliffy/ansi": "npm:@jsr/cliffy__ansi@1.0.0",
|
||||
"@cliffy/command": "npm:@jsr/cliffy__command@1.0.0",
|
||||
"@cliffy/prompt": "npm:@jsr/cliffy__prompt@1.0.0",
|
||||
"@cliffy/table": "npm:@jsr/cliffy__table@1.0.0",
|
||||
"@std/encoding": "npm:@jsr/std__encoding@1.0.10",
|
||||
"@std/log": "npm:@jsr/std__log@0.224.14",
|
||||
"@std/path": "npm:@jsr/std__path@1.1.4",
|
||||
"@std/yaml": "npm:@jsr/std__yaml@1.0.10",
|
||||
"@windmill-labs/shared-utils": "npm:@jsr/windmill-labs__shared-utils@1.0.12",
|
||||
"@windmill-labs/shared-utils": "^1.0.12",
|
||||
"diff": "^5.2.0",
|
||||
"esbuild": "0.24.2",
|
||||
"get-port": "7.1.0",
|
||||
@@ -23,6 +18,7 @@
|
||||
"minimatch": "^10.0.0",
|
||||
"open": "^10.0.0",
|
||||
"svelte": "^5.45.2",
|
||||
"tar-stream": "^3.1.7",
|
||||
"windmill-parser-wasm-csharp": "*",
|
||||
"windmill-parser-wasm-go": "*",
|
||||
"windmill-parser-wasm-java": "*",
|
||||
@@ -44,25 +40,11 @@
|
||||
"devDependencies": {
|
||||
"@types/diff": "^5.2.3",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/tar-stream": "^3.1.4",
|
||||
"@types/ws": "^8.5.0",
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ayonli/jsext": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ayonli/jsext/-/jsext-1.9.0.tgz",
|
||||
"integrity": "sha512-hIu6lQhoLr5e26lmt+vzopuZffaAyb623r4+8HlN/rhXgm2ywHslzk7UHiATdfDbfPjBARkB6cfXjVEi3aav6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.3",
|
||||
"sudo-prompt": "^9.2.1",
|
||||
"ws": "^8.17.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@cliffy/ansi": {
|
||||
"name": "@jsr/cliffy__ansi",
|
||||
"version": "1.0.0",
|
||||
@@ -623,15 +605,6 @@
|
||||
"resolved": "https://npm.jsr.io/~/11/@jsr/std__fmt/1.0.9.tgz",
|
||||
"integrity": "sha512-YFJJMozmORj2K91c5J9opWeh0VUwrd+Mwb7Pr0FkVCAKVLu2UhT4LyvJqWiyUT+eF+MdfqQ9F7RtQj4bXn9Smw=="
|
||||
},
|
||||
"node_modules/@jsr/std__fs": {
|
||||
"version": "1.0.23",
|
||||
"resolved": "https://npm.jsr.io/~/11/@jsr/std__fs/1.0.23.tgz",
|
||||
"integrity": "sha512-e8jspB3M44E5YhWiLCTqibBBTwVmxQaHN06WvFa/elAKm5E/LfAe8Hj5XGNC8P7a0MIPASlNJsnF1bgO/g+aqg==",
|
||||
"dependencies": {
|
||||
"@jsr/std__internal": "^1.0.12",
|
||||
"@jsr/std__path": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@jsr/std__internal": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.12.tgz",
|
||||
@@ -671,38 +644,6 @@
|
||||
"@jsr/std__regexp": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@std/encoding": {
|
||||
"name": "@jsr/std__encoding",
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://npm.jsr.io/~/11/@jsr/std__encoding/1.0.10.tgz",
|
||||
"integrity": "sha512-WK2njnDTyKefroRNk2Ooq7GStp6Y0ccAvr4To+Z/zecRAGe7+OSvH9DbiaHpAKwEi2KQbmpWMOYsdNt+TsdmSw=="
|
||||
},
|
||||
"node_modules/@std/log": {
|
||||
"name": "@jsr/std__log",
|
||||
"version": "0.224.14",
|
||||
"resolved": "https://npm.jsr.io/~/11/@jsr/std__log/0.224.14.tgz",
|
||||
"integrity": "sha512-EHT7E0plakyzk/gxMrwqUf3YGCCxN3Is25QrEh7toYA7qwj46R4qY7cIaDEKy8QqI5JHOFHwWXOClcPK6goIoQ==",
|
||||
"dependencies": {
|
||||
"@jsr/std__fmt": "^1.0.5",
|
||||
"@jsr/std__fs": "^1.0.11",
|
||||
"@jsr/std__io": "^0.225.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@std/path": {
|
||||
"name": "@jsr/std__path",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://npm.jsr.io/~/11/@jsr/std__path/1.1.4.tgz",
|
||||
"integrity": "sha512-SK4u9H6NVTfolhPdlvdYXfNFefy1W04AEHWJydryYbk+xqzNiVmr5o7TLJLJFqwHXuwMRhwrn+mcYeUfS0YFaA==",
|
||||
"dependencies": {
|
||||
"@jsr/std__internal": "^1.0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@std/yaml": {
|
||||
"name": "@jsr/std__yaml",
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://npm.jsr.io/~/11/@jsr/std__yaml/1.0.10.tgz",
|
||||
"integrity": "sha512-1WIM023Kvi48pvPE3UO5YcieambLgywUooLhAkkaObIcMB77F/YP2ILdl+vNfik+vElkl9znmuST9AZo8mbCpA=="
|
||||
},
|
||||
"node_modules/@stoplight/ordered-object-literal": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.5.tgz",
|
||||
@@ -784,6 +725,16 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tar-stream": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-3.1.4.tgz",
|
||||
"integrity": "sha512-921gW0+g29mCJX0fRvqeHzBlE/XclDaAG0Ousy1LCghsOhvaKacDeRGEVzQP9IPfKn8Vysy7FEXAIxycpc/CMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
@@ -852,6 +803,20 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/b4a": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
|
||||
"integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"react-native-b4a": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-native-b4a": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz",
|
||||
@@ -861,6 +826,20 @@
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/bare-events": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
|
||||
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"bare-abort-controller": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bare-abort-controller": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
|
||||
@@ -1013,12 +992,27 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/events-universal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
|
||||
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bare-events": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-fifo": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
|
||||
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
@@ -1047,18 +1041,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
@@ -1263,12 +1245,6 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-immediate-shim": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
|
||||
@@ -1278,6 +1254,17 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/streamx": {
|
||||
"version": "2.23.0",
|
||||
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
|
||||
"integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"events-universal": "^1.0.0",
|
||||
"fast-fifo": "^1.3.2",
|
||||
"text-decoder": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
@@ -1287,13 +1274,6 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sudo-prompt": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz",
|
||||
"integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.53.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.2.tgz",
|
||||
@@ -1321,6 +1301,26 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
|
||||
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"b4a": "^1.6.4",
|
||||
"fast-fifo": "^1.2.0",
|
||||
"streamx": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/text-decoder": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
|
||||
"integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"b4a": "^1.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -1484,15 +1484,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { stat, writeFile } from "node:fs/promises";
|
||||
import { stat, writeFile, readdir, readFile } from "node:fs/promises";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import nodePath from "node:path";
|
||||
|
||||
import {
|
||||
GlobalOptions,
|
||||
@@ -27,6 +28,24 @@ export interface ResourceFile {
|
||||
is_oauth?: boolean; // deprecated
|
||||
}
|
||||
|
||||
async function readFilesetDirectory(dirPath: string): Promise<Record<string, string>> {
|
||||
const result: Record<string, string> = {};
|
||||
async function walk(currentPath: string, prefix: string) {
|
||||
const entries = await readdir(currentPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const entryPath = nodePath.join(currentPath, entry.name);
|
||||
const relPath = prefix ? prefix + "/" + entry.name : entry.name;
|
||||
if (entry.isDirectory()) {
|
||||
await walk(entryPath, relPath);
|
||||
} else if (entry.isFile()) {
|
||||
result[relPath] = await readFile(entryPath, "utf-8");
|
||||
}
|
||||
}
|
||||
}
|
||||
await walk(dirPath, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function pushResource(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
@@ -46,7 +65,10 @@ export async function pushResource(
|
||||
|
||||
// Helper function to resolve inline content
|
||||
const resolveInlineContent = async () => {
|
||||
if (localResource.value["content"]?.startsWith("!inline ")) {
|
||||
if (typeof localResource.value === "string" && localResource.value.startsWith("!inline_fileset ")) {
|
||||
const dirPath = localResource.value.split(" ")[1];
|
||||
localResource.value = await readFilesetDirectory(dirPath.replaceAll("/", SEP));
|
||||
} else if (localResource.value["content"]?.startsWith("!inline ")) {
|
||||
const basePath = localResource.value["content"].split(" ")[1];
|
||||
|
||||
// If we're processing a branch-specific metadata file, read from branch-specific resource file
|
||||
|
||||
+132
-10
@@ -38,6 +38,7 @@ import {
|
||||
deepEqual,
|
||||
fetchRemoteVersion,
|
||||
isFileResource,
|
||||
isFilesetResource,
|
||||
isRawAppFile,
|
||||
isWorkspaceDependencies,
|
||||
} from "../../utils/utils.ts";
|
||||
@@ -484,11 +485,53 @@ export function extractInlineScriptsForApps(
|
||||
return [];
|
||||
}
|
||||
|
||||
type FileResourceTypeInfo = { format_extension: string | null; is_fileset: boolean };
|
||||
|
||||
function parseFileResourceTypeMap(
|
||||
raw: Record<string, string | FileResourceTypeInfo>,
|
||||
): { formatExtMap: Record<string, string>; filesetMap: Record<string, boolean> } {
|
||||
const formatExtMap: Record<string, string> = {};
|
||||
const filesetMap: Record<string, boolean> = {};
|
||||
for (const [k, v] of Object.entries(raw)) {
|
||||
if (typeof v === "string") {
|
||||
formatExtMap[k] = v;
|
||||
filesetMap[k] = false;
|
||||
} else {
|
||||
if (v.format_extension) {
|
||||
formatExtMap[k] = v.format_extension;
|
||||
}
|
||||
filesetMap[k] = v.is_fileset ?? false;
|
||||
}
|
||||
}
|
||||
return { formatExtMap, filesetMap };
|
||||
}
|
||||
|
||||
async function findFilesetResourceFile(changePath: string): Promise<string> {
|
||||
// Extract the base path before .fileset/
|
||||
const filesetIdx = changePath.indexOf(".fileset" + SEP);
|
||||
if (filesetIdx === -1) {
|
||||
throw new Error(`Not a fileset resource path: ${changePath}`);
|
||||
}
|
||||
const basePath = changePath.substring(0, filesetIdx);
|
||||
const candidates = [basePath + ".resource.json", basePath + ".resource.yaml"];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const s = await stat(candidate);
|
||||
if (s.isFile()) return candidate;
|
||||
} catch {
|
||||
// not found, try next
|
||||
}
|
||||
}
|
||||
throw new Error(`No resource metadata file found for fileset resource: ${changePath}`);
|
||||
}
|
||||
|
||||
function ZipFSElement(
|
||||
zip: JSZip,
|
||||
useYaml: boolean,
|
||||
defaultTs: "bun" | "deno",
|
||||
resourceTypeToFormatExtension: Record<string, string>,
|
||||
resourceTypeToIsFileset: Record<string, boolean>,
|
||||
ignoreCodebaseChanges: boolean,
|
||||
): DynFSElement {
|
||||
async function _internal_file(
|
||||
@@ -860,10 +903,17 @@ function ZipFSElement(
|
||||
log.error(`Failed to parse resource.yaml at path: ${p}`);
|
||||
throw error;
|
||||
}
|
||||
const resourceType = parsed["resource_type"];
|
||||
const formatExtension =
|
||||
resourceTypeToFormatExtension[parsed["resource_type"]];
|
||||
resourceTypeToFormatExtension[resourceType];
|
||||
const isFileset = resourceTypeToIsFileset[resourceType] ?? false;
|
||||
|
||||
if (formatExtension) {
|
||||
if (isFileset) {
|
||||
parsed["value"] =
|
||||
"!inline_fileset " +
|
||||
removeSuffix(p.replaceAll(SEP, "/"), ".resource.json") +
|
||||
".fileset";
|
||||
} else if (formatExtension) {
|
||||
parsed["value"]["content"] =
|
||||
"!inline " +
|
||||
removeSuffix(p.replaceAll(SEP, "/"), ".resource.json") +
|
||||
@@ -918,10 +968,37 @@ function ZipFSElement(
|
||||
log.error(`Failed to parse resource file content at path: ${p}`);
|
||||
throw error;
|
||||
}
|
||||
const resourceType = parsed["resource_type"];
|
||||
const formatExtension =
|
||||
resourceTypeToFormatExtension[parsed["resource_type"]];
|
||||
resourceTypeToFormatExtension[resourceType];
|
||||
const isFileset = resourceTypeToIsFileset[resourceType] ?? false;
|
||||
|
||||
if (formatExtension) {
|
||||
if (isFileset && typeof parsed["value"] === "object" && parsed["value"] !== null) {
|
||||
const filesetBasePath =
|
||||
removeSuffix(finalPath, ".resource.json") + ".fileset";
|
||||
// Push directory entry for the fileset
|
||||
r.push({
|
||||
isDirectory: true,
|
||||
path: filesetBasePath,
|
||||
async *getChildren() {
|
||||
for (const [relPath, fileContent] of Object.entries(parsed["value"])) {
|
||||
if (typeof fileContent === "string") {
|
||||
yield {
|
||||
isDirectory: false,
|
||||
path: path.join(filesetBasePath, relPath),
|
||||
async *getChildren() {},
|
||||
async getContentText() {
|
||||
return fileContent;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
async getContentText() {
|
||||
throw new Error("Cannot get content of directory");
|
||||
},
|
||||
});
|
||||
} else if (formatExtension) {
|
||||
const fileContent: string = parsed["value"]["content"];
|
||||
if (typeof fileContent === "string") {
|
||||
r.push({
|
||||
@@ -1058,6 +1135,7 @@ export async function elementsToMap(
|
||||
const path = entry.path;
|
||||
if (
|
||||
!isFileResource(path) &&
|
||||
!isFilesetResource(path) &&
|
||||
!isRawAppFile(path) &&
|
||||
!isWorkspaceDependencies(path)
|
||||
) {
|
||||
@@ -1103,7 +1181,7 @@ export async function elementsToMap(
|
||||
}
|
||||
}
|
||||
|
||||
if (skips.skipResources && isFileResource(path)) continue;
|
||||
if (skips.skipResources && (isFileResource(path) || isFilesetResource(path))) continue;
|
||||
|
||||
const ext = json ? ".json" : ".yaml";
|
||||
if (!skips.includeSchedules && path.endsWith(".schedule" + ext)) continue;
|
||||
@@ -1715,10 +1793,14 @@ export async function pull(
|
||||
);
|
||||
|
||||
let resourceTypeToFormatExtension: Record<string, string> = {};
|
||||
let resourceTypeToIsFileset: Record<string, boolean> = {};
|
||||
try {
|
||||
resourceTypeToFormatExtension = (await wmill.fileResourceTypeToFileExtMap({
|
||||
const raw = (await wmill.fileResourceTypeToFileExtMap({
|
||||
workspace: workspace.workspaceId,
|
||||
})) as Record<string, string>;
|
||||
})) as Record<string, string | FileResourceTypeInfo>;
|
||||
const parsed = parseFileResourceTypeMap(raw);
|
||||
resourceTypeToFormatExtension = parsed.formatExtMap;
|
||||
resourceTypeToIsFileset = parsed.filesetMap;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -1745,6 +1827,7 @@ export async function pull(
|
||||
!opts.json,
|
||||
opts.defaultTs ?? "bun",
|
||||
resourceTypeToFormatExtension,
|
||||
resourceTypeToIsFileset,
|
||||
true,
|
||||
);
|
||||
|
||||
@@ -2241,10 +2324,14 @@ export async function push(
|
||||
),
|
||||
);
|
||||
let resourceTypeToFormatExtension: Record<string, string> = {};
|
||||
let resourceTypeToIsFileset: Record<string, boolean> = {};
|
||||
try {
|
||||
resourceTypeToFormatExtension = (await wmill.fileResourceTypeToFileExtMap({
|
||||
const raw = (await wmill.fileResourceTypeToFileExtMap({
|
||||
workspace: workspace.workspaceId,
|
||||
})) as Record<string, string>;
|
||||
})) as Record<string, string | FileResourceTypeInfo>;
|
||||
const parsed = parseFileResourceTypeMap(raw);
|
||||
resourceTypeToFormatExtension = parsed.formatExtMap;
|
||||
resourceTypeToIsFileset = parsed.filesetMap;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -2269,6 +2356,7 @@ export async function push(
|
||||
!opts.json,
|
||||
opts.defaultTs ?? "bun",
|
||||
resourceTypeToFormatExtension,
|
||||
resourceTypeToIsFileset,
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -2587,6 +2675,39 @@ export async function push(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (isFilesetResource(change.path)) {
|
||||
const resourceFilePath = await findFilesetResourceFile(change.path);
|
||||
if (!alreadySynced.includes(resourceFilePath)) {
|
||||
alreadySynced.push(resourceFilePath);
|
||||
|
||||
const newObj = parseFromPath(
|
||||
resourceFilePath,
|
||||
await readFile(resourceFilePath, "utf-8"),
|
||||
);
|
||||
|
||||
let serverPath = resourceFilePath;
|
||||
const currentBranch = cachedBranchForPush;
|
||||
|
||||
if (currentBranch && isBranchSpecificFile(resourceFilePath)) {
|
||||
serverPath = fromBranchSpecificPath(
|
||||
resourceFilePath,
|
||||
currentBranch,
|
||||
);
|
||||
}
|
||||
|
||||
await pushResource(
|
||||
workspace.workspaceId,
|
||||
serverPath,
|
||||
undefined,
|
||||
newObj,
|
||||
resourceFilePath,
|
||||
);
|
||||
if (stateTarget) {
|
||||
await writeFile(stateTarget, change.after, "utf-8");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const oldObj = parseFromPath(change.path, change.before);
|
||||
const newObj = parseFromPath(change.path, change.after);
|
||||
|
||||
@@ -2619,7 +2740,8 @@ export async function push(
|
||||
change.path.endsWith(".script.json") ||
|
||||
change.path.endsWith(".script.yaml") ||
|
||||
change.path.endsWith(".lock") ||
|
||||
isFileResource(change.path)
|
||||
isFileResource(change.path) ||
|
||||
isFilesetResource(change.path)
|
||||
) {
|
||||
continue;
|
||||
} else if (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { minimatch } from "minimatch";
|
||||
import { getCurrentGitBranch, isGitRepository } from "../utils/git.ts";
|
||||
import { isFileResource } from "../utils/utils.ts";
|
||||
import { isFileResource, isFilesetResource } from "../utils/utils.ts";
|
||||
import { SyncOptions } from "./conf.ts";
|
||||
import { TRIGGER_TYPES } from "../types.ts";
|
||||
|
||||
@@ -165,7 +165,7 @@ export function isItemTypeConfigured(path: string, specificItems: SpecificItemsC
|
||||
return specificItems.settings !== undefined;
|
||||
}
|
||||
|
||||
if (isFileResource(path)) {
|
||||
if (isFileResource(path) || isFilesetResource(path)) {
|
||||
return specificItems.resources !== undefined;
|
||||
}
|
||||
|
||||
@@ -219,6 +219,14 @@ export function isSpecificItem(path: string, specificItems: SpecificItemsConfig
|
||||
}
|
||||
}
|
||||
|
||||
if (isFilesetResource(path)) {
|
||||
const basePathMatch = path.match(/^(.+?)\.fileset[/\\]/);
|
||||
if (basePathMatch && specificItems.resources) {
|
||||
const basePath = basePathMatch[1] + '.resource.yaml';
|
||||
return matchesPatterns(basePath, specificItems.resources);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import { pushResourceType } from "./commands/resource-type/resource-type.ts";
|
||||
import { pushVariable } from "./commands/variable/variable.ts";
|
||||
import { yamlOptions } from "./commands/sync/sync.ts";
|
||||
import { showDiffs } from "./core/conf.ts";
|
||||
import { deepEqual, isFileResource, isWorkspaceDependencies } from "./utils/utils.ts";
|
||||
import { deepEqual, isFileResource, isFilesetResource, isWorkspaceDependencies } from "./utils/utils.ts";
|
||||
import { pushSchedule } from "./commands/schedule/schedule.ts";
|
||||
import { pushWorkspaceUser } from "./commands/user/user.ts";
|
||||
import { pushGroup } from "./commands/user/user.ts";
|
||||
@@ -333,7 +333,7 @@ export function getTypeStrFromPath(
|
||||
) {
|
||||
return typeEnding;
|
||||
} else {
|
||||
if (isFileResource(p)) {
|
||||
if (isFileResource(p) || isFilesetResource(p)) {
|
||||
return "resource";
|
||||
}
|
||||
throw new Error("Could not infer type of path " + JSON.stringify(parsed));
|
||||
|
||||
@@ -154,6 +154,10 @@ export function isFileResource(path: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function isFilesetResource(path: string): boolean {
|
||||
return path.includes(".fileset/") || path.includes(".fileset\\");
|
||||
}
|
||||
|
||||
export function isRawAppFile(path: string): boolean {
|
||||
return isRawAppPath(path);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expect, test, describe } from "bun:test";
|
||||
import { deepEqual, isFileResource, toCamel, capitalize } from "../src/utils/utils.ts";
|
||||
import { deepEqual, isFileResource, isFilesetResource, toCamel, capitalize } from "../src/utils/utils.ts";
|
||||
import {
|
||||
getTypeStrFromPath,
|
||||
removeType,
|
||||
@@ -156,6 +156,32 @@ describe("isFileResource", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// isFilesetResource
|
||||
// =============================================================================
|
||||
|
||||
describe("isFilesetResource", () => {
|
||||
test("detects fileset resource paths (unix separator)", () => {
|
||||
expect(isFilesetResource("f/test/my_config.fileset/config.yaml")).toBe(true);
|
||||
expect(isFilesetResource("u/admin/templates.fileset/path/to/file.txt")).toBe(true);
|
||||
});
|
||||
|
||||
test("detects fileset resource paths (windows separator)", () => {
|
||||
expect(isFilesetResource("f\\test\\my_config.fileset\\config.yaml")).toBe(true);
|
||||
});
|
||||
|
||||
test("rejects non-fileset paths", () => {
|
||||
expect(isFilesetResource("f/test/my_resource.resource.yaml")).toBe(false);
|
||||
expect(isFilesetResource("f/test/my_file.resource.file.txt")).toBe(false);
|
||||
expect(isFilesetResource("f/test/my_script.ts")).toBe(false);
|
||||
});
|
||||
|
||||
test("rejects paths ending with .fileset (no child file)", () => {
|
||||
// The directory itself is not a fileset resource file - only children are
|
||||
expect(isFilesetResource("f/test/my_config.fileset")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// removeType
|
||||
// =============================================================================
|
||||
@@ -241,6 +267,11 @@ describe("getTypeStrFromPath", () => {
|
||||
expect(getTypeStrFromPath("devs.group.yaml")).toBe("group");
|
||||
});
|
||||
|
||||
test("detects fileset resource files as resource type", () => {
|
||||
expect(getTypeStrFromPath("f/test/my_config.fileset/config.yaml")).toBe("resource");
|
||||
expect(getTypeStrFromPath("u/admin/templates.fileset/path/to/file.txt")).toBe("resource");
|
||||
});
|
||||
|
||||
test("throws for unknown type", () => {
|
||||
expect(() => getTypeStrFromPath("f/test/unknown.xyz.yaml")).toThrow();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user