diff --git a/cli/metadata.ts b/cli/metadata.ts index e3cecc2352..918ecc877b 100644 --- a/cli/metadata.ts +++ b/cli/metadata.ts @@ -24,8 +24,9 @@ import { SchemaProperty } from "./bootstrap/common.ts"; import { ScriptLanguage } from "./script_common.ts"; import { inferContentTypeFromFilePath } from "./script_common.ts"; import { GlobalDeps } from "./script.ts"; -import { yamlOptions } from "./sync.ts"; +import { findCodebase, yamlOptions } from "./sync.ts"; import { generateHash } from "./utils.ts"; +import { SyncCodebase } from "./codebase.ts"; export async function generateAllMetadata() {} @@ -77,7 +78,8 @@ export async function generateMetadataInternal( }, dryRun: boolean, noStaleMessage: boolean, - globalDeps: GlobalDeps + globalDeps: GlobalDeps, + codebases: SyncCodebase[] ): Promise { const remotePath = scriptPath .substring(0, scriptPath.indexOf(".")) @@ -100,7 +102,8 @@ export async function generateMetadataInternal( const metadataWithType = await parseMetadataFile( remotePath, undefined, - globalDeps + globalDeps, + codebases ); // read script content @@ -138,7 +141,9 @@ export async function generateMetadataInternal( ); } - if (!opts.schemaOnly) { + const c = findCodebase(scriptPath, codebases); + + if (!opts.schemaOnly && !c) { await updateScriptLock( workspace, scriptContent, @@ -163,7 +168,7 @@ export async function generateMetadataInternal( return `${remotePath} (${language})`; } -async function updateScriptSchema( +export async function updateScriptSchema( scriptContent: string, language: ScriptLanguage, metadataContent: Record, @@ -355,8 +360,8 @@ function sortObject(obj: any): any { ); } -function argSigToJsonSchemaType( - typ: +export function argSigToJsonSchemaType( + t: | string | { resource: string | null } | { @@ -371,55 +376,55 @@ function argSigToJsonSchemaType( oldS: SchemaProperty ): void { const newS: SchemaProperty = { type: "" }; - if (typ === "int") { + if (t === "int") { newS.type = "integer"; - } else if (typ === "float") { + } else if (t === "float") { newS.type = "number"; - } else if (typ === "bool") { + } else if (t === "bool") { newS.type = "boolean"; - } else if (typ === "email") { + } else if (t === "email") { newS.type = "string"; newS.format = "email"; - } else if (typ === "sql") { + } else if (t === "sql") { newS.type = "string"; newS.format = "sql"; - } else if (typ === "yaml") { + } else if (t === "yaml") { newS.type = "string"; newS.format = "yaml"; - } else if (typ === "bytes") { + } else if (t === "bytes") { newS.type = "string"; newS.contentEncoding = "base64"; - } else if (typ === "datetime") { + } else if (t === "datetime") { newS.type = "string"; newS.format = "date-time"; - } else if (typeof typ !== "string" && `object` in typ) { + } else if (typeof t !== "string" && `object` in t) { newS.type = "object"; - if (typ.object) { - const properties: Record = {}; - for (const prop of typ.object) { - properties[prop.key] = { type: undefined }; + if (t.object) { + const properties: Record = {}; + for (const prop of t.object) { + properties[prop.key] = {}; argSigToJsonSchemaType(prop.typ, properties[prop.key]); } newS.properties = properties; } - } else if (typeof typ !== "string" && `str` in typ) { + } else if (typeof t !== "string" && `str` in t) { newS.type = "string"; - if (typ.str) { - newS.enum = typ.str; + if (t.str) { + newS.enum = t.str; } - } else if (typeof typ !== "string" && `resource` in typ) { + } else if (typeof t !== "string" && `resource` in t) { newS.type = "object"; - newS.format = `resource-${typ.resource}`; - } else if (typeof typ !== "string" && `list` in typ) { + newS.format = `resource-${t.resource}`; + } else if (typeof t !== "string" && `list` in t) { newS.type = "array"; - if (typ.list === "int" || typ.list === "float") { + if (t.list === "int" || t.list === "float") { newS.items = { type: "number" }; - } else if (typ.list === "bytes") { + } else if (t.list === "bytes") { newS.items = { type: "string", contentEncoding: "base64" }; - } else if (typ.list == "string") { + } else if (t.list == "string") { newS.items = { type: "string" }; - } else if (typ.list && typeof typ.list == "object" && "str" in typ.list) { - newS.items = { type: "string", enum: typ.list.str }; + } else if (t.list && typeof t.list == "object" && "str" in t.list) { + newS.items = { type: "string", enum: t.list.str }; } else { newS.items = { type: "object" }; } @@ -430,17 +435,27 @@ function argSigToJsonSchemaType( if (oldS.type != newS.type) { for (const prop of Object.getOwnPropertyNames(newS)) { if (prop != "description") { - // @ts-ignore: fix delete oldS[prop]; } } - } else if (oldS.format == "date-time" && newS.format != "date-time") { - delete oldS.format; + } else if ( + (oldS.format == "date" || oldS.format === "date-time") && + newS.format == "string" + ) { + newS.format = oldS.format; + } else if (newS.format == "date-time" && oldS.format == "date") { + newS.format = "date"; } else if (oldS.items?.type != newS.items?.type) { delete oldS.items; } Object.assign(oldS, newS); + + // if (sameItems && savedItems != undefined && savedItems.enum != undefined) { + // sendUserToast(JSON.stringify(savedItems)) + // oldS.items = savedItems + // } + if (oldS.format?.startsWith("resource-") && newS.type != "object") { oldS.format = undefined; } @@ -474,7 +489,8 @@ export async function parseMetadataFile( schemaOnly?: boolean; }) | undefined, - globalDeps: GlobalDeps + globalDeps: GlobalDeps, + codebases: SyncCodebase[] ): Promise<{ isJson: boolean; payload: any; path: string }> { let metadataFilePath = scriptPath + ".script.json"; try { @@ -522,7 +538,8 @@ export async function parseMetadataFile( generateMetadataIfMissing, false, false, - globalDeps + globalDeps, + codebases ); scriptInitialMetadata = yamlParse( await Deno.readTextFile(metadataFilePath) diff --git a/cli/script.ts b/cli/script.ts index 5beb79ffdb..e8b29f8a2d 100644 --- a/cli/script.ts +++ b/cli/script.ts @@ -1,5 +1,5 @@ // deno-lint-ignore-file no-explicit-any -import { GlobalOptions } from "./types.ts"; +import { GlobalOptions, showDiff } from "./types.ts"; import { requireLogin, resolveWorkspace, validatePath } from "./context.ts"; import { colors, @@ -21,7 +21,11 @@ import { } from "./bootstrap/script_bootstrap.ts"; import { Workspace } from "./workspace.ts"; -import { generateMetadataInternal, parseMetadataFile } from "./metadata.ts"; +import { + generateMetadataInternal, + parseMetadataFile, + updateScriptSchema, +} from "./metadata.ts"; import { ScriptLanguage, inferContentTypeFromFilePath, @@ -74,7 +78,7 @@ async function push(opts: PushOptions, filePath: string) { await requireLogin(opts); const codebases = await listSyncCodebases(opts as SyncOptions); - const globalDeps = await findGlobalDeps(codebases); + const globalDeps = await findGlobalDeps(); await handleFile( filePath, workspace, @@ -169,7 +173,8 @@ export async function handleFile( schemaOnly: codebase ? true : undefined, } : undefined, - globalDeps + globalDeps, + codebases ) )?.payload; @@ -187,6 +192,22 @@ export async function handleFile( } const content = await Deno.readTextFile(path); + if (codebase) { + const typedBefore = JSON.parse(JSON.stringify(typed.schema)); + await updateScriptSchema(content, language, typed, path); + if (typedBefore != typed.schema) { + log.info(`Updated metadata for bundle ${path}`); + showDiff( + yamlStringify(typedBefore, yamlOptions), + yamlStringify(typed.schema, yamlOptions) + ); + await Deno.writeTextFile( + remotePath + ".script.yaml", + yamlStringify(typed as Record, yamlOptions) + ); + } + } + const requestBodyCommon = { content, description: typed?.description ?? "", @@ -662,12 +683,16 @@ async function bootstrap( yamlOptions ); - Deno.writeTextFile(scriptCodeFileFullPath, scriptInitialCode, { - createNew: true, - }); - Deno.writeTextFile(scriptMetadataFileFullPath, scriptInitialMetadataYaml, { + await Deno.writeTextFile(scriptCodeFileFullPath, scriptInitialCode, { createNew: true, }); + await Deno.writeTextFile( + scriptMetadataFileFullPath, + scriptInitialMetadataYaml, + { + createNew: true, + } + ); } export type GlobalDeps = { @@ -675,13 +700,11 @@ export type GlobalDeps = { reqs: Record; composers: Record; }; -export async function findGlobalDeps( - codebases: SyncCodebase[] -): Promise { +export async function findGlobalDeps(): Promise { const pkgs: { [key: string]: string } = {}; const reqs: { [key: string]: string } = {}; const composers: { [key: string]: string } = {}; - const els = await FSFSElement(Deno.cwd(), codebases); + const els = await FSFSElement(Deno.cwd(), []); for await (const entry of readDirRecursiveWithIgnore((p, isDir) => { p = "/" + p; return ( @@ -725,7 +748,7 @@ async function generateMetadata( opts = await mergeConfigWithConfigFile(opts); const codebases = await listSyncCodebases(opts); - const globalDeps = await findGlobalDeps(codebases); + const globalDeps = await findGlobalDeps(); if (scriptPath) { // read script metadata file await generateMetadataInternal( @@ -734,7 +757,8 @@ async function generateMetadata( opts, false, false, - globalDeps + globalDeps, + codebases ); } else { const ignore = await ignoreF(opts); @@ -760,7 +784,8 @@ async function generateMetadata( opts, true, true, - globalDeps + globalDeps, + codebases ); if (candidate) { hasAny = true; @@ -788,7 +813,8 @@ async function generateMetadata( opts, false, true, - globalDeps + globalDeps, + codebases ); } } diff --git a/cli/sync.ts b/cli/sync.ts index 8ec5c3541a..4bf16ff6eb 100644 --- a/cli/sync.ts +++ b/cli/sync.ts @@ -266,7 +266,7 @@ function ZipFSElement(zip: JSZip, useYaml: boolean): DynFSElement { const r = [{ path: path, content: content }]; m.value.content = "!inline " + path; const lock = m.value.lock; - if (lock) { + if (lock && lock != "") { const lockPath = basePath + "lock"; m.value.lock = "!inline " + lockPath; r.push({ path: lockPath, content: lock }); @@ -312,7 +312,7 @@ function ZipFSElement(zip: JSZip, useYaml: boolean): DynFSElement { content: content, }); } - if (o["lock"]) { + if (o["lock"] && o["lock"] != "") { const lock = o["lock"]; o["lock"] = "!inline " + basePath + "lock"; r.push({ @@ -403,8 +403,14 @@ function ZipFSElement(zip: JSZip, useYaml: boolean): DynFSElement { if (kind == "script") { const parsed = JSON.parse(content); - if (parsed["lock"]) { + if ( + parsed["lock"] && + parsed["lock"] != "" && + parsed["codebase"] == undefined + ) { parsed["lock"] = "!inline " + removeSuffix(p, ".json") + ".lock"; + } else { + parsed["lock"] = undefined; } return useYaml ? yamlStringify(parsed, yamlOptions) @@ -421,7 +427,7 @@ function ZipFSElement(zip: JSZip, useYaml: boolean): DynFSElement { const content = await f.async("text"); const parsed = JSON.parse(content); const lock = parsed["lock"]; - if (lock) { + if (lock && lock != "") { r.push({ isDirectory: false, path: removeSuffix(finalPath, ".json") + ".lock", @@ -867,40 +873,45 @@ async function pull(opts: GlobalOptions & SyncOptions) { const target = path.join(Deno.cwd(), change.path); const stateTarget = path.join(Deno.cwd(), ".wmill", change.path); if (change.name === "edited") { - try { - const currentLocal = await Deno.readTextFile(target); - if (currentLocal !== change.before && currentLocal !== change.after) { - log.info( - colors.red( - `Conflict detected on ${change.path}\nBoth local and remote have been modified.` - ) - ); - if (opts.failConflicts) { - conflicts.push({ - local: currentLocal, - change, - path: change.path, - }); - continue; - } else if (opts.yes) { + if (opts.stateful) { + try { + const currentLocal = await Deno.readTextFile(target); + if ( + currentLocal !== change.before && + currentLocal !== change.after + ) { log.info( colors.red( - `Override local version with remote since --yes was passed and no --fail-conflicts.` + `Conflict detected on ${change.path}\nBoth local and remote have been modified.` ) ); - } else { - showConflict(change.path, currentLocal, change.after); - if ( - await Confirm.prompt( - "Preserve local (push to change remote and avoid seeing this again)?" - ) - ) { + if (opts.failConflicts) { + conflicts.push({ + local: currentLocal, + change, + path: change.path, + }); continue; + } else if (opts.yes) { + log.info( + colors.red( + `Override local version with remote since --yes was passed and no --fail-conflicts.` + ) + ); + } else { + showConflict(change.path, currentLocal, change.after); + if ( + await Confirm.prompt( + "Preserve local (push to change remote and avoid seeing this again)?" + ) + ) { + continue; + } } } + } catch { + // ignore } - } catch { - // ignore } if (!change.path.endsWith(".json") && !change.path.endsWith(".yaml")) { log.info(`Editing script content of ${change.path}`); @@ -1083,7 +1094,7 @@ async function push(opts: GlobalOptions & SyncOptions) { log.info(colors.gray(`Applying changes to files ...`)); const alreadySynced: string[] = []; - const globalDeps = await findGlobalDeps(codebases); + const globalDeps = await findGlobalDeps(); for await (const change of changes) { const stateTarget = path.join(Deno.cwd(), ".wmill", change.path); diff --git a/frontend/src/routes/(root)/(logged)/user/(user)/create_workspace/+page.svelte b/frontend/src/routes/(root)/(logged)/user/(user)/create_workspace/+page.svelte index c453e3a59b..0b936571d2 100644 --- a/frontend/src/routes/(root)/(logged)/user/(user)/create_workspace/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/user/(user)/create_workspace/+page.svelte @@ -58,7 +58,7 @@ if (auto_invite) { await WorkspaceService.editAutoInvite({ workspace: id, - requestBody: { operator: operatorOnly, invite_all: !isCloudHosted(), auto_add: autoAdd } + requestBody: { operator: operatorOnly, invite_all: !isCloudHosted(), auto_add: true } }) } if (openAiKey != '') { @@ -148,7 +148,6 @@ let auto_invite = false let operatorOnly = false - let autoAdd = false @@ -216,15 +215,6 @@
Mode Whether to invite or add users directly to the workspace.
- { - autoAdd = e.detail == 'add' - }} - > - - -
Role Role of the auto-invited users