@piyushdoorwar/yamlet 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/yamlet.js +412 -203
  2. package/package.json +1 -1
package/dist/yamlet.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // cli/src/index.ts
4
- import { existsSync as existsSync4, readFileSync, statSync as statSync2, writeFileSync } from "node:fs";
4
+ import { existsSync as existsSync4, readFileSync, statSync as statSync3, writeFileSync } from "node:fs";
5
5
  import { basename, resolve } from "node:path";
6
6
 
7
7
  // core/src/requestExecutor.ts
@@ -2090,49 +2090,53 @@ function parseDataFile(text, filename) {
2090
2090
  }
2091
2091
 
2092
2092
  // core/src/workspaceStore.ts
2093
- import { existsSync as existsSync3, promises as fs2, statSync } from "node:fs";
2093
+ import { existsSync as existsSync3, promises as fs2, statSync as statSync2 } from "node:fs";
2094
2094
  import path3 from "node:path";
2095
2095
 
2096
+ // core/src/localValues.ts
2097
+ var environmentScope = (id) => `environment:${id}`;
2098
+ var collectionScope = (id) => `collection:${id}`;
2099
+ var GLOBALS_SCOPE = "globals";
2100
+
2096
2101
  // core/src/pathNaming.ts
2097
- import { existsSync as existsSync2 } from "node:fs";
2102
+ import { existsSync as existsSync2, statSync } from "node:fs";
2098
2103
  import path2 from "node:path";
2099
- function slugify(name, fallback = "untitled") {
2100
- if (!name?.trim()) return fallback;
2101
- let out = "";
2102
- let lastHyphen = false;
2103
- for (const ch of name.trim().toLowerCase()) {
2104
- if (/[a-z0-9]/.test(ch)) {
2105
- out += ch;
2106
- lastHyphen = false;
2107
- } else if (" -_./".includes(ch)) {
2108
- if (!lastHyphen && out.length > 0) {
2109
- out += "-";
2110
- lastHyphen = true;
2111
- }
2112
- }
2113
- }
2114
- out = out.replace(/^-+|-+$/g, "");
2115
- return out || fallback;
2116
- }
2117
- function uniqueFilePath(directory, fileName, except) {
2118
- const ext = path2.extname(fileName);
2119
- const base = fileName.slice(0, fileName.length - ext.length);
2120
- let candidate = path2.join(directory, fileName);
2121
- for (let n = 2; existsSync2(candidate) && candidate !== except; n++) {
2122
- candidate = path2.join(directory, `${base}-${n}${ext}`);
2104
+ var RESERVED = /^(con|prn|aux|nul|com\d|lpt\d)$/i;
2105
+ function fileSafeName(name, fallback = "Untitled") {
2106
+ let out = (name ?? "").replace(/[<>:"/\\|?*\u0000-\u001f\u007f]/g, "-").replace(/\s+/g, " ").trim().replace(/^\.+/, "").replace(/[. ]+$/, "");
2107
+ if (out.length > 120) out = out.slice(0, 120).trimEnd();
2108
+ if (!out) return fallback;
2109
+ return RESERVED.test(out) ? `${out}_` : out;
2110
+ }
2111
+ function uniqueFilePath(directory, base, suffix, except) {
2112
+ let candidate = path2.join(directory, base + suffix);
2113
+ for (let n = 2; existsSync2(candidate) && !samePath(candidate, except); n++) {
2114
+ candidate = path2.join(directory, `${base} ${n}${suffix}`);
2123
2115
  }
2124
2116
  return candidate;
2125
2117
  }
2126
2118
  function uniqueDirectoryPath(parent, name, except) {
2127
2119
  let candidate = path2.join(parent, name);
2128
- for (let n = 2; existsSync2(candidate) && candidate !== except; n++) {
2129
- candidate = path2.join(parent, `${name}-${n}`);
2120
+ for (let n = 2; existsSync2(candidate) && !samePath(candidate, except); n++) {
2121
+ candidate = path2.join(parent, `${name} ${n}`);
2130
2122
  }
2131
2123
  return candidate;
2132
2124
  }
2125
+ function samePath(a, b) {
2126
+ if (!b) return false;
2127
+ if (a === b) return true;
2128
+ if (a.toLowerCase() !== b.toLowerCase()) return false;
2129
+ try {
2130
+ const x = statSync(a);
2131
+ const y = statSync(b);
2132
+ return x.ino === y.ino && x.dev === y.dev;
2133
+ } catch {
2134
+ return false;
2135
+ }
2136
+ }
2133
2137
 
2134
2138
  // core/src/yamlDtos.ts
2135
- import { Document, isAlias, isMap, isScalar, isSeq, parseDocument, stringify } from "yaml";
2139
+ import { Document, isAlias, isMap, isScalar, isSeq, parseDocument, Scalar, visit } from "yaml";
2136
2140
  function loadYaml(text) {
2137
2141
  if (!text?.trim()) return {};
2138
2142
  const doc = parseDocument(text, { uniqueKeys: false });
@@ -2156,26 +2160,57 @@ function loadYaml(text) {
2156
2160
  };
2157
2161
  return toPlain(doc.contents) ?? {};
2158
2162
  }
2159
- var STRINGIFY_OPTS = { lineWidth: 0 };
2160
- function dumpYaml(value) {
2161
- return stringify(value, STRINGIFY_OPTS);
2163
+ var OPTS = { lineWidth: 0 };
2164
+ function styled(doc, style) {
2165
+ if (style?.quoteValues) {
2166
+ visit(doc, {
2167
+ Pair(_, pair) {
2168
+ if (isScalar(pair.key) && pair.key.value === "value" && isScalar(pair.value) && typeof pair.value.value === "string") {
2169
+ pair.value.type = Scalar.QUOTE_SINGLE;
2170
+ }
2171
+ }
2172
+ });
2173
+ }
2174
+ return doc.toString(OPTS);
2175
+ }
2176
+ function dumpYaml(value, style) {
2177
+ return styled(new Document(value), style);
2162
2178
  }
2163
- function dumpYamlPreserving(value, original, known) {
2164
- if (!original?.trim()) return dumpYaml(value);
2179
+ function dumpYamlPreserving(value, original, known, style) {
2180
+ if (!original?.trim()) return dumpYaml(value, style);
2165
2181
  try {
2166
2182
  const orig = parseDocument(original);
2167
- if (orig.errors.length || !isMap(orig.contents)) return dumpYaml(value);
2183
+ if (orig.errors.length || !isMap(orig.contents)) return dumpYaml(value, style);
2168
2184
  const doc = new Document(value);
2169
2185
  const target = doc.contents;
2170
- if (!isMap(target)) return dumpYaml(value);
2186
+ if (!isMap(target)) return dumpYaml(value, style);
2171
2187
  for (const pair of orig.contents.items) {
2172
2188
  const key = isScalar(pair.key) ? String(pair.key.value) : void 0;
2173
2189
  if (!key || known.has(key) || isDropped(key) || doc.has(key)) continue;
2174
2190
  target.items.push(pair);
2175
2191
  }
2176
- return doc.toString(STRINGIFY_OPTS);
2192
+ return styled(doc, style);
2177
2193
  } catch {
2178
- return dumpYaml(value);
2194
+ return dumpYaml(value, style);
2195
+ }
2196
+ }
2197
+ function keepEquivalent(out, original, readers) {
2198
+ if (!original) return;
2199
+ for (const [key, read] of Object.entries(readers)) {
2200
+ if (!(key in out) || !(key in original)) continue;
2201
+ try {
2202
+ if (JSON.stringify(read(original[key])) === JSON.stringify(read(out[key]))) out[key] = original[key];
2203
+ } catch {
2204
+ }
2205
+ }
2206
+ }
2207
+ function parsedObj(text) {
2208
+ if (!text?.trim()) return void 0;
2209
+ try {
2210
+ const o = loadYaml(text);
2211
+ return isObj(o) && o.$kind !== void 0 ? o : void 0;
2212
+ } catch {
2213
+ return void 0;
2179
2214
  }
2180
2215
  }
2181
2216
  var isDropped = (key) => /^_[a-z0-9]+_[a-z_]+$/i.test(key);
@@ -2227,6 +2262,7 @@ function readVariables(raw) {
2227
2262
  return readRows(raw).map((r) => {
2228
2263
  const v = { key: r.key, value: r.value, enabled: r.enabled };
2229
2264
  if (str(r.o.type).toLowerCase() === "secret" || bool(r.o.secret) === true) v.secret = true;
2265
+ if (bool(r.o.local) === true) v.local = true;
2230
2266
  return v;
2231
2267
  });
2232
2268
  }
@@ -2234,20 +2270,47 @@ function writeKeyValues(list) {
2234
2270
  if (!list?.length) return void 0;
2235
2271
  return list.map((kv) => {
2236
2272
  const o = { key: kv.key, value: kv.value ?? "" };
2273
+ if (kv.enabled === false) o.disabled = true;
2237
2274
  if (kv.description) o.description = kv.description;
2238
- if (kv.enabled === false) o.enabled = false;
2239
2275
  return o;
2240
2276
  });
2241
2277
  }
2242
2278
  function writeVariables(list) {
2243
2279
  if (!list?.length) return void 0;
2244
2280
  return list.map((v) => {
2245
- const o = { key: v.key, value: v.value ?? "" };
2281
+ const o = { key: v.key, value: v.local ? "" : v.value ?? "" };
2246
2282
  if (v.secret) o.type = "secret";
2247
2283
  if (v.enabled === false) o.enabled = false;
2284
+ if (v.local) o.local = true;
2248
2285
  return o;
2249
2286
  });
2250
2287
  }
2288
+ function writeVariableMap(list) {
2289
+ const values = {};
2290
+ const settings = {};
2291
+ for (const v of list) {
2292
+ values[v.key] = v.local ? "" : v.value ?? "";
2293
+ const flags = {};
2294
+ if (v.enabled === false) flags.enabled = false;
2295
+ if (v.secret) flags.secret = true;
2296
+ if (v.local) flags.local = true;
2297
+ if (Object.keys(flags).length) settings[v.key] = flags;
2298
+ else delete settings[v.key];
2299
+ }
2300
+ return Object.keys(settings).length ? { values, settings } : { values };
2301
+ }
2302
+ function applyVariableSettings(vars, raw) {
2303
+ if (!isObj(raw)) return vars;
2304
+ return vars.map((v) => {
2305
+ const flags = raw[v.key];
2306
+ if (!isObj(flags)) return v;
2307
+ const out = { ...v };
2308
+ if (bool(flags.enabled) === false) out.enabled = false;
2309
+ if (bool(flags.secret) === true) out.secret = true;
2310
+ if (bool(flags.local) === true) out.local = true;
2311
+ return out;
2312
+ });
2313
+ }
2251
2314
  function authType(raw) {
2252
2315
  switch (str(raw).trim().toLowerCase()) {
2253
2316
  case "bearer":
@@ -2327,15 +2390,15 @@ function readAuth(raw, fallback = "none") {
2327
2390
  if (!isObj(raw)) return defaultAuth(fallback);
2328
2391
  const type = raw.type === void 0 ? fallback : authType(raw.type);
2329
2392
  const a = defaultAuth(type);
2330
- const typed = (name) => raw[name];
2331
- a.token = credential(typed("bearer"), "token") ?? str(raw.token);
2332
- a.username = credential(typed("basic"), "username") ?? str(raw.username);
2333
- a.password = credential(typed("basic"), "password") ?? str(raw.password);
2334
- a.apiKeyName = credential(typed("apikey"), "key") ?? str(raw.key);
2335
- a.apiKeyValue = credential(typed("apikey"), "value") ?? str(raw.value);
2336
- a.apiKeyIn = (credential(typed("apikey"), "in") ?? str(raw.in)).toLowerCase() === "query" ? "query" : "header";
2337
- a.cookie = str(raw.cookie);
2338
- if (isObj(raw.credentials)) a.oauth2 = readOAuth2(raw.credentials);
2393
+ const cred = (block, key) => credential(raw[block], key) ?? credential(raw.credentials, key) ?? str(raw[key]);
2394
+ a.token = cred("bearer", "token");
2395
+ a.username = cred("basic", "username");
2396
+ a.password = cred("basic", "password");
2397
+ a.apiKeyName = credential(raw.apikey, "key") ?? (type === "apikey" ? credential(raw.credentials, "key") : void 0) ?? str(raw.key);
2398
+ a.apiKeyValue = credential(raw.apikey, "value") ?? (type === "apikey" ? credential(raw.credentials, "value") : void 0) ?? str(raw.value);
2399
+ a.apiKeyIn = (credential(raw.apikey, "in") ?? credential(raw.credentials, "in") ?? str(raw.in)).toLowerCase() === "query" ? "query" : "header";
2400
+ a.cookie = credential(raw.credentials, "cookie") ?? str(raw.cookie);
2401
+ if (raw.credentials && (type === "oauth2" || isObj(raw.credentials))) a.oauth2 = readOAuth2(raw.credentials);
2339
2402
  else if (raw.oauth2 !== void 0) a.oauth2 = readOAuth2(raw.oauth2);
2340
2403
  return a;
2341
2404
  }
@@ -2357,24 +2420,83 @@ function writeOAuth2(o) {
2357
2420
  put("accessToken", o.accessToken);
2358
2421
  put("refreshToken", o.refreshToken);
2359
2422
  if (o.headerPrefix && o.headerPrefix !== "Bearer") out.tokenType = o.headerPrefix;
2360
- if (o.addTokenTo === "query") out.addTokenTo = "queryParams";
2361
- if (o.clientAuthentication === "body") out.client_authentication = "body";
2423
+ out.addTokenTo = o.addTokenTo === "query" ? "queryParams" : "header";
2424
+ out.client_authentication = o.clientAuthentication === "body" ? "body" : "header";
2362
2425
  if (o.challengeAlgorithm === "plain") out.challengeAlgorithm = "plain";
2363
2426
  return out;
2364
2427
  }
2428
+ var MODELED_CREDENTIALS = new Set(
2429
+ [
2430
+ "token",
2431
+ "username",
2432
+ "password",
2433
+ "key",
2434
+ "value",
2435
+ "in",
2436
+ "cookie",
2437
+ "grant_type",
2438
+ "grantType",
2439
+ "accessToken",
2440
+ "access_token",
2441
+ "refreshToken",
2442
+ "refresh_token",
2443
+ "headerPrefix",
2444
+ "tokenType",
2445
+ "accessTokenUrl",
2446
+ "tokenUrl",
2447
+ "access_token_url",
2448
+ "authUrl",
2449
+ "authorizationUrl",
2450
+ "auth_url",
2451
+ "clientId",
2452
+ "client_id",
2453
+ "clientSecret",
2454
+ "client_secret",
2455
+ "scope",
2456
+ "redirect_uri",
2457
+ "redirectUri",
2458
+ "redirect_url",
2459
+ "callbackUrl",
2460
+ "addTokenTo",
2461
+ "client_authentication",
2462
+ "clientAuthentication",
2463
+ "challengeAlgorithm"
2464
+ ].map((k) => k.toLowerCase())
2465
+ );
2466
+ var normalizedAuth = (v) => writeAuth(readAuth(v, "none"));
2467
+ function mergeAuth(ours, original) {
2468
+ if (!isObj(original) || str(original.type).toLowerCase() !== str(ours.type).toLowerCase() || !isObj(original.credentials)) return ours;
2469
+ const extra = Object.entries(original.credentials).filter(([k]) => !MODELED_CREDENTIALS.has(k.toLowerCase()));
2470
+ if (!extra.length) return ours;
2471
+ return { ...ours, credentials: { ...isObj(ours.credentials) ? ours.credentials : {}, ...Object.fromEntries(extra) } };
2472
+ }
2365
2473
  function writeAuth(a) {
2366
2474
  const out = { type: a.type === "none" ? "noauth" : a.type };
2475
+ const creds = {};
2367
2476
  const put = (k, v) => {
2368
- if (v) out[k] = v;
2477
+ if (v) creds[k] = v;
2369
2478
  };
2370
- put("token", a.token);
2371
- put("username", a.username);
2372
- put("password", a.password);
2373
- put("key", a.apiKeyName);
2374
- put("value", a.apiKeyValue);
2375
- if (a.type === "apikey") out.in = a.apiKeyIn === "query" ? "query" : "header";
2376
- put("cookie", a.cookie);
2377
- if (a.type === "oauth2") out.credentials = writeOAuth2(a.oauth2);
2479
+ switch (a.type) {
2480
+ case "bearer":
2481
+ put("token", a.token);
2482
+ break;
2483
+ case "basic":
2484
+ put("username", a.username);
2485
+ put("password", a.password);
2486
+ break;
2487
+ case "apikey":
2488
+ put("key", a.apiKeyName);
2489
+ put("value", a.apiKeyValue);
2490
+ creds.in = a.apiKeyIn === "query" ? "query" : "header";
2491
+ break;
2492
+ case "cookie":
2493
+ put("cookie", a.cookie);
2494
+ break;
2495
+ case "oauth2":
2496
+ Object.assign(creds, writeOAuth2(a.oauth2));
2497
+ break;
2498
+ }
2499
+ if (Object.keys(creds).length) out.credentials = creds;
2378
2500
  return out;
2379
2501
  }
2380
2502
  function isPreScriptType(type) {
@@ -2398,10 +2520,11 @@ function readScripts(raw) {
2398
2520
  }
2399
2521
  return { pre: pre.join("\n\n"), post: post.join("\n\n") };
2400
2522
  }
2401
- function writeScripts(pre, post) {
2523
+ var SCRIPT_LANGUAGE = "text/javascript";
2524
+ function writeScripts(pre, post, prefix = "") {
2402
2525
  const list = [];
2403
- if (pre) list.push({ type: "preRequest", code: pre });
2404
- if (post) list.push({ type: "afterResponse", code: post });
2526
+ if (pre) list.push({ type: `${prefix}beforeRequest`, code: pre, language: SCRIPT_LANGUAGE });
2527
+ if (post) list.push({ type: `${prefix}afterResponse`, code: post, language: SCRIPT_LANGUAGE });
2405
2528
  return list.length ? list : void 0;
2406
2529
  }
2407
2530
  function bodyType(raw) {
@@ -2456,47 +2579,58 @@ function readBody(raw) {
2456
2579
  if (fieldsRaw) b.fields = fieldsRaw.filter(isObj).map(readField);
2457
2580
  else if (isObj(content) && (b.type === "form-data" || b.type === "urlencoded"))
2458
2581
  b.fields = Object.entries(content).map(([key, value]) => ({ key, value: str(value), enabled: true }));
2459
- const gql = isObj(raw.graphql) ? raw.graphql : raw;
2582
+ const gql = isObj(raw.graphql) ? raw.graphql : b.type === "graphql" && isObj(content) ? content : raw;
2460
2583
  b.graphqlQuery = str(gql.query);
2461
2584
  b.graphqlVariables = gql.variables === void 0 || gql.variables === null ? "" : typeof gql.variables === "string" ? gql.variables : JSON.stringify(gql.variables, null, 2);
2462
2585
  if (b.type === "graphql" && !b.graphqlQuery && b.raw) b.graphqlQuery = b.raw;
2463
- b.binaryFile = str(raw.file ?? raw.binaryFile ?? (b.type === "binary" && typeof raw.src === "string" ? raw.src : ""));
2586
+ const src = Array.isArray(raw.src) ? raw.src[0] : raw.src;
2587
+ b.binaryFile = str(raw.file ?? raw.binaryFile ?? (b.type === "binary" && typeof src === "string" ? src : ""));
2464
2588
  if (b.type === "binary" && !b.binaryFile && b.raw) b.binaryFile = b.raw;
2465
2589
  return b;
2466
2590
  }
2467
- function writeField(f) {
2468
- const o = { type: f.isFile ? "file" : "text", key: f.key };
2469
- if (f.isFile) o.src = f.value.startsWith("@") ? f.value.slice(1) : f.value;
2591
+ function writeField(f, multipart) {
2592
+ const o = multipart ? { type: f.isFile ? "file" : "text", key: f.key } : { key: f.key };
2593
+ if (multipart && f.isFile) o.src = [f.value.startsWith("@") ? f.value.slice(1) : f.value];
2470
2594
  else o.value = f.value ?? "";
2595
+ if (f.enabled === false) o.disabled = true;
2471
2596
  if (f.description) o.description = f.description;
2472
- if (f.enabled === false) o.enabled = false;
2473
2597
  return o;
2474
2598
  }
2475
2599
  var BODY_TYPE_NAMES = {
2476
2600
  none: "none",
2477
- raw: "raw",
2601
+ raw: "text",
2478
2602
  json: "json",
2479
2603
  xml: "xml",
2480
2604
  text: "text",
2481
2605
  html: "html",
2482
- "form-data": "form-data",
2483
- urlencoded: "x-www-form-urlencoded",
2606
+ "form-data": "formdata",
2607
+ urlencoded: "urlencoded",
2484
2608
  graphql: "graphql",
2485
- binary: "binary"
2609
+ binary: "file"
2486
2610
  };
2487
2611
  function writeBody(b) {
2488
2612
  const out = { type: BODY_TYPE_NAMES[b.type] ?? "none" };
2489
- if (b.raw) out.raw = b.raw;
2490
- const fields = b.fields?.filter((f) => f.key?.trim() || f.value);
2491
- if (fields?.length) out.content = fields.map(writeField);
2492
- if (b.graphqlQuery || b.graphqlVariables) {
2493
- const g = {};
2494
- if (b.graphqlQuery) g.query = b.graphqlQuery;
2495
- if (b.graphqlVariables) g.variables = b.graphqlVariables;
2496
- out.graphql = g;
2497
- }
2498
- if (b.binaryFile) out.file = b.binaryFile;
2499
- if (b.type === "none" && Object.keys(out).length === 1) return void 0;
2613
+ switch (b.type) {
2614
+ case "none":
2615
+ return void 0;
2616
+ case "form-data":
2617
+ case "urlencoded": {
2618
+ const fields = b.fields?.filter((f) => f.key?.trim() || f.value);
2619
+ out.content = (fields ?? []).map((f) => writeField(f, b.type === "form-data"));
2620
+ break;
2621
+ }
2622
+ case "graphql": {
2623
+ const g = { query: b.graphqlQuery ?? "" };
2624
+ if (b.graphqlVariables) g.variables = b.graphqlVariables;
2625
+ out.content = g;
2626
+ break;
2627
+ }
2628
+ case "binary":
2629
+ out.src = b.binaryFile ?? "";
2630
+ break;
2631
+ default:
2632
+ out.content = b.raw ?? "";
2633
+ }
2500
2634
  return out;
2501
2635
  }
2502
2636
  function readSettings(o) {
@@ -2546,6 +2680,7 @@ function writeExamples(list) {
2546
2680
  });
2547
2681
  }
2548
2682
  var REQUEST_KEYS = /* @__PURE__ */ new Set([
2683
+ "$kind",
2549
2684
  "id",
2550
2685
  "name",
2551
2686
  "description",
@@ -2565,6 +2700,8 @@ var REQUEST_KEYS = /* @__PURE__ */ new Set([
2565
2700
  "ssl",
2566
2701
  "protocolProfileBehavior"
2567
2702
  ]);
2703
+ var REQUEST_KIND = "http-request";
2704
+ var COLLECTION_KIND = "collection";
2568
2705
  function deriveNameFromFile(filePath) {
2569
2706
  let name = filePath.split(/[\\/]/).pop() ?? filePath;
2570
2707
  name = name.replace(/\.ya?ml$/i, "");
@@ -2599,12 +2736,12 @@ function requestFromDto(raw, sourceFilePath) {
2599
2736
  if (sourceFilePath) req.sourceFilePath = sourceFilePath;
2600
2737
  return req;
2601
2738
  }
2602
- function requestToDto(r) {
2603
- const o = { id: r.id, name: r.name };
2739
+ function requestToDto(r, filePath) {
2740
+ const o = { $kind: REQUEST_KIND };
2741
+ if (!filePath || deriveNameFromFile(filePath) !== r.name) o.name = r.name;
2604
2742
  if (r.description) o.description = r.description;
2605
- o.order = r.order ?? 0;
2606
- o.method = (r.method || "GET").toUpperCase();
2607
2743
  o.url = r.url ?? "";
2744
+ o.method = (r.method || "GET").toUpperCase();
2608
2745
  const put = (k, v) => {
2609
2746
  if (v !== void 0) o[k] = v;
2610
2747
  };
@@ -2618,23 +2755,41 @@ function requestToDto(r) {
2618
2755
  return x;
2619
2756
  }) : void 0
2620
2757
  );
2621
- put("variables", writeVariables(r.variables));
2622
2758
  if (r.auth && r.auth.type !== "inherit") o.auth = writeAuth(r.auth);
2623
2759
  put("body", r.body ? writeBody(r.body) : void 0);
2624
2760
  put("scripts", writeScripts(r.preRequestScript, r.postResponseScript));
2761
+ o.order = r.order ?? 0;
2762
+ o.id = r.id;
2763
+ put("variables", writeVariables(r.variables));
2625
2764
  put("settings", writeSettings(r.settings));
2626
2765
  put("examples", writeExamples(r.examples));
2627
2766
  return o;
2628
2767
  }
2629
- function requestToYaml(r, original) {
2630
- return dumpYamlPreserving(requestToDto(r), original, REQUEST_KEYS);
2768
+ var sameRows = (v) => readRows(v).map(({ o: _o, ...row }) => row);
2769
+ var REQUEST_READERS = {
2770
+ headers: sameRows,
2771
+ queryParams: sameRows,
2772
+ pathVariables: sameRows,
2773
+ variables: readVariables,
2774
+ auth: normalizedAuth,
2775
+ body: (v) => writeBody(readBody(v)),
2776
+ scripts: readScripts
2777
+ };
2778
+ function requestToYaml(r, original, filePath) {
2779
+ const out = requestToDto(r, filePath);
2780
+ const prev = parsedObj(original);
2781
+ if (isObj(out.auth)) out.auth = mergeAuth(out.auth, prev?.auth);
2782
+ keepEquivalent(out, prev, REQUEST_READERS);
2783
+ return dumpYamlPreserving(out, original, REQUEST_KEYS);
2631
2784
  }
2632
2785
  var COLLECTION_KEYS = /* @__PURE__ */ new Set([
2786
+ "$kind",
2633
2787
  "id",
2634
2788
  "name",
2635
2789
  "description",
2636
2790
  "order",
2637
2791
  "variables",
2792
+ "variableSettings",
2638
2793
  "auth",
2639
2794
  "scripts",
2640
2795
  // Read-only aliases from the collection v2.1 shape; migrated on save.
@@ -2675,9 +2830,12 @@ function applyCollectionMetadata(c, raw) {
2675
2830
  }
2676
2831
  function applyCollectionDefinition(c, raw) {
2677
2832
  if (!isObj(raw)) return;
2833
+ if (str(raw.id).trim()) c.id = str(raw.id);
2678
2834
  if (str(raw.name).trim()) c.name = str(raw.name);
2679
2835
  if (raw.description) c.description = str(raw.description);
2680
- const vars = readVariables(raw.variables);
2836
+ const order = int(raw.order);
2837
+ if (order !== void 0) c.order = order;
2838
+ const vars = applyVariableSettings(readVariables(raw.variables), raw.variableSettings);
2681
2839
  if (vars.length) c.variables = vars;
2682
2840
  const auth = Array.isArray(raw.auth) ? raw.auth.find(isObj) : isObj(raw.auth) ? raw.auth : void 0;
2683
2841
  if (auth) c.auth = readAuth(auth, "none");
@@ -2686,20 +2844,27 @@ function applyCollectionDefinition(c, raw) {
2686
2844
  if (scripts.post) c.postResponseScript = scripts.post;
2687
2845
  }
2688
2846
  function collectionToDto(c) {
2689
- const o = { id: c.id, name: c.name };
2847
+ const o = { $kind: COLLECTION_KIND, name: c.name };
2690
2848
  if (c.description) o.description = c.description;
2691
- o.order = c.order ?? 0;
2692
- const vars = writeVariables(c.variables);
2693
- if (vars) o.variables = vars;
2694
- if (c.auth && c.auth.type !== "none" && c.auth.type !== "inherit") o.auth = writeAuth(c.auth);
2695
- const scripts = writeScripts(c.preRequestScript, c.postResponseScript);
2849
+ const vars = c.variables?.length ? writeVariableMap(c.variables) : void 0;
2850
+ if (vars) o.variables = vars.values;
2851
+ const scripts = writeScripts(c.preRequestScript, c.postResponseScript, "http:");
2696
2852
  if (scripts) o.scripts = scripts;
2853
+ if (c.auth && c.auth.type !== "none" && c.auth.type !== "inherit") o.auth = writeAuth(c.auth);
2854
+ o.order = c.order ?? 0;
2855
+ o.id = c.id;
2856
+ if (vars?.settings) o.variableSettings = vars.settings;
2697
2857
  return o;
2698
2858
  }
2699
2859
  function collectionToYaml(c, original) {
2700
- return dumpYamlPreserving(collectionToDto(c), original, COLLECTION_KEYS);
2701
- }
2702
- var FOLDER_KEYS = /* @__PURE__ */ new Set(["id", "name", "description", "order"]);
2860
+ const out = collectionToDto(c);
2861
+ const prev = parsedObj(original);
2862
+ const prevAuth = Array.isArray(prev?.auth) ? prev.auth.find(isObj) : prev?.auth;
2863
+ if (isObj(out.auth)) out.auth = mergeAuth(out.auth, prevAuth);
2864
+ keepEquivalent(out, prev && { ...prev, auth: prevAuth }, { auth: normalizedAuth, scripts: readScripts });
2865
+ return dumpYamlPreserving(out, original, COLLECTION_KEYS);
2866
+ }
2867
+ var FOLDER_KEYS = /* @__PURE__ */ new Set(["$kind", "id", "name", "description", "order"]);
2703
2868
  function applyFolderMetadata(f, raw) {
2704
2869
  if (!isObj(raw)) return;
2705
2870
  if (str(raw.id).trim()) f.id = str(raw.id);
@@ -2708,14 +2873,16 @@ function applyFolderMetadata(f, raw) {
2708
2873
  const order = int(raw.order);
2709
2874
  if (order !== void 0) f.order = order;
2710
2875
  }
2711
- function folderToDto(f) {
2712
- const o = { id: f.id, name: f.name };
2876
+ function folderToDto(f, directoryName) {
2877
+ const o = { $kind: COLLECTION_KIND };
2878
+ if (directoryName === void 0 || directoryName !== f.name) o.name = f.name;
2713
2879
  if (f.description) o.description = f.description;
2714
2880
  o.order = f.order ?? 0;
2881
+ o.id = f.id;
2715
2882
  return o;
2716
2883
  }
2717
- function folderToYaml(f, original) {
2718
- return dumpYamlPreserving(folderToDto(f), original, FOLDER_KEYS);
2884
+ function folderToYaml(f, original, directoryName) {
2885
+ return dumpYamlPreserving(folderToDto(f, directoryName), original, FOLDER_KEYS);
2719
2886
  }
2720
2887
  var ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["id", "name", "variables", "values"]);
2721
2888
  function deriveEnvironmentName(filePath) {
@@ -2733,23 +2900,20 @@ function environmentFromDto(raw, filePath) {
2733
2900
  return env;
2734
2901
  }
2735
2902
  function environmentToDto(e) {
2736
- const o = { id: e.id, name: e.name };
2737
- const vars = writeVariables(e.variables);
2738
- if (vars) o.variables = vars;
2739
- return o;
2903
+ return { name: e.name, values: writeVariables(e.variables) ?? [], id: e.id };
2740
2904
  }
2741
2905
  function environmentFromYaml(text, filePath) {
2742
2906
  return environmentFromDto(loadYaml(text), filePath);
2743
2907
  }
2744
2908
  function environmentToYaml(e, original) {
2745
- return dumpYamlPreserving(environmentToDto(e), original, ENVIRONMENT_KEYS);
2909
+ return dumpYamlPreserving(environmentToDto(e), original, ENVIRONMENT_KEYS, { quoteValues: true });
2746
2910
  }
2747
2911
  function globalsFromYaml(text) {
2748
2912
  const o = loadYaml(text);
2749
2913
  return isObj(o) ? readVariables(o.variables ?? o.values) : [];
2750
2914
  }
2751
2915
  function globalsToYaml(vars, original) {
2752
- return dumpYamlPreserving({ variables: writeVariables(vars) ?? [] }, original, /* @__PURE__ */ new Set(["variables", "values"]));
2916
+ return dumpYamlPreserving({ name: "Globals", values: writeVariables(vars) ?? [] }, original, /* @__PURE__ */ new Set(["name", "variables", "values"]), { quoteValues: true });
2753
2917
  }
2754
2918
 
2755
2919
  // core/src/workspaceStore.ts
@@ -2757,14 +2921,17 @@ var ROOT_DIR_NAME = "yamlet";
2757
2921
  var COLLECTIONS_DIR = "collections";
2758
2922
  var ENVIRONMENTS_DIR = "environments";
2759
2923
  var GLOBALS_DIR = "globals";
2760
- var GLOBALS_FILE = "globals.yaml";
2761
- var COLLECTION_FILE = "collection.yaml";
2762
- var FOLDER_FILE = "folder.yaml";
2924
+ var GLOBALS_FILE = "workspace.globals.yaml";
2763
2925
  var DEFINITION_FILE = path3.join(".resources", "definition.yaml");
2764
- var REQUEST_EXTENSION = ".yaml";
2926
+ var REQUEST_SUFFIX = ".request.yaml";
2927
+ var ENVIRONMENT_SUFFIX = ".environment.yaml";
2928
+ var LEGACY_COLLECTION_FILE = "collection.yaml";
2929
+ var LEGACY_FOLDER_FILE = "folder.yaml";
2930
+ var LEGACY_GLOBALS_FILE = "globals.yaml";
2931
+ var ORDER_STEP = 1e3;
2765
2932
  var isDir = (p) => {
2766
2933
  try {
2767
- return statSync(p).isDirectory();
2934
+ return statSync2(p).isDirectory();
2768
2935
  } catch {
2769
2936
  return false;
2770
2937
  }
@@ -2780,7 +2947,7 @@ function stableId(prefix, key) {
2780
2947
  }
2781
2948
  var ordinal = (a, b) => a < b ? -1 : a > b ? 1 : 0;
2782
2949
  var byOrder = (list) => list.sort((a, b) => a.order - b.order);
2783
- var nextOrder = (list) => list.reduce((m, x) => Math.max(m, (x.order ?? 0) + 1), 0);
2950
+ var nextOrder = (list) => list.reduce((m, x) => Math.max(m, (x.order ?? 0) + ORDER_STEP), ORDER_STEP);
2784
2951
  async function readIfExists(p) {
2785
2952
  if (!p) return void 0;
2786
2953
  try {
@@ -2828,8 +2995,11 @@ function copyName(name) {
2828
2995
  var WorkspaceStore = class _WorkspaceStore {
2829
2996
  workspace;
2830
2997
  queue = Promise.resolve();
2831
- constructor(workspace) {
2998
+ local;
2999
+ constructor(workspace, options) {
2832
3000
  this.workspace = workspace;
3001
+ this.local = options.localValues;
3002
+ if (this.local) applyLocalValues(workspace, this.local);
2833
3003
  }
2834
3004
  // ---- Opening -------------------------------------------------------------
2835
3005
  /** The picked folder if it already holds collections/ + environments/ (or is named `yamlet`), else `<dir>/yamlet`. */
@@ -2843,22 +3013,22 @@ var WorkspaceStore = class _WorkspaceStore {
2843
3013
  return isDir(path3.join(_WorkspaceStore.resolveRoot(dir), COLLECTIONS_DIR));
2844
3014
  }
2845
3015
  /** Opens an existing workspace. Does not create anything on disk. */
2846
- static async open(dir) {
3016
+ static async open(dir, options = {}) {
2847
3017
  const root = _WorkspaceStore.resolveRoot(dir);
2848
3018
  if (!isDir(root)) throw new Error(`No Yamlet workspace found at ${dir}`);
2849
- return new _WorkspaceStore(await loadWorkspace(root));
3019
+ return new _WorkspaceStore(await loadWorkspace(root), options);
2850
3020
  }
2851
3021
  /** Creates the workspace skeleton (seeding globals) where missing, then opens it. */
2852
- static async create(dir) {
3022
+ static async create(dir, options = {}) {
2853
3023
  const root = _WorkspaceStore.resolveRoot(dir);
2854
3024
  await fs2.mkdir(path3.join(root, COLLECTIONS_DIR), { recursive: true });
2855
3025
  await fs2.mkdir(path3.join(root, ENVIRONMENTS_DIR), { recursive: true });
2856
3026
  await fs2.mkdir(path3.join(root, GLOBALS_DIR), { recursive: true });
2857
3027
  const globals = path3.join(root, GLOBALS_DIR, GLOBALS_FILE);
2858
- if (!existsSync3(globals)) {
3028
+ if (!existsSync3(globals) && !existsSync3(path3.join(root, GLOBALS_DIR, LEGACY_GLOBALS_FILE))) {
2859
3029
  await writeFileAtomic(globals, globalsToYaml([{ key: "appName", value: "Yamlet", enabled: true }]));
2860
3030
  }
2861
- return _WorkspaceStore.open(root);
3031
+ return _WorkspaceStore.open(root, options);
2862
3032
  }
2863
3033
  get rootPath() {
2864
3034
  return this.workspace.rootPath;
@@ -2866,6 +3036,7 @@ var WorkspaceStore = class _WorkspaceStore {
2866
3036
  async reload() {
2867
3037
  return this.enqueue(async () => {
2868
3038
  this.workspace = await loadWorkspace(this.workspace.rootPath);
3039
+ if (this.local) applyLocalValues(this.workspace, this.local);
2869
3040
  return this.workspace;
2870
3041
  });
2871
3042
  }
@@ -2939,12 +3110,13 @@ var WorkspaceStore = class _WorkspaceStore {
2939
3110
  return this.enqueue(async () => {
2940
3111
  const ws = this.workspace;
2941
3112
  await fs2.mkdir(ws.collectionsPath, { recursive: true });
2942
- const dir = uniqueDirectoryPath(ws.collectionsPath, slugify(name, "collection"));
3113
+ const display = name.trim() || "New Collection";
3114
+ const dir = uniqueDirectoryPath(ws.collectionsPath, fileSafeName(display, "New Collection"));
2943
3115
  await fs2.mkdir(dir, { recursive: true });
2944
3116
  const c = newCollection({
2945
- name: name.trim() || "New Collection",
3117
+ name: display,
2946
3118
  directoryPath: dir,
2947
- filePath: path3.join(dir, COLLECTION_FILE),
3119
+ filePath: path3.join(dir, DEFINITION_FILE),
2948
3120
  order: nextOrder(ws.collections)
2949
3121
  });
2950
3122
  await this.writeCollection(c);
@@ -2955,10 +3127,7 @@ var WorkspaceStore = class _WorkspaceStore {
2955
3127
  async updateCollection(id, patch) {
2956
3128
  return this.enqueue(async () => {
2957
3129
  const c = this.requireCollection(id);
2958
- if (patch.name !== void 0 && patch.name.trim() && patch.name !== c.name) {
2959
- c.name = patch.name;
2960
- await this.renameNodeDir(c, this.workspace.collectionsPath, slugify(c.name, "collection"));
2961
- }
3130
+ if (patch.name !== void 0 && patch.name.trim()) c.name = patch.name;
2962
3131
  if (patch.description !== void 0) c.description = patch.description;
2963
3132
  if (patch.variables !== void 0) c.variables = patch.variables;
2964
3133
  if (patch.auth !== void 0) c.auth = patch.auth;
@@ -2972,6 +3141,7 @@ var WorkspaceStore = class _WorkspaceStore {
2972
3141
  return this.enqueue(async () => {
2973
3142
  const c = this.requireCollection(id);
2974
3143
  if (c.directoryPath) await fs2.rm(c.directoryPath, { recursive: true, force: true });
3144
+ await this.local?.set(collectionScope(c.id), void 0);
2975
3145
  this.workspace.collections = this.workspace.collections.filter((x) => x !== c);
2976
3146
  return c;
2977
3147
  });
@@ -2981,13 +3151,13 @@ var WorkspaceStore = class _WorkspaceStore {
2981
3151
  const src = this.requireCollection(id);
2982
3152
  if (!src.directoryPath) throw new Error("Collection has no directory");
2983
3153
  const name = copyName(src.name);
2984
- const dir = uniqueDirectoryPath(this.workspace.collectionsPath, slugify(name, "collection"));
3154
+ const dir = uniqueDirectoryPath(this.workspace.collectionsPath, fileSafeName(name, "Collection"));
2985
3155
  await fs2.cp(src.directoryPath, dir, { recursive: true });
2986
3156
  const c = structuredClone(src);
2987
3157
  repathTree(c, src.directoryPath, dir);
2988
3158
  freshIds(c);
2989
3159
  c.name = name;
2990
- c.filePath = path3.join(dir, COLLECTION_FILE);
3160
+ c.filePath = path3.join(dir, DEFINITION_FILE);
2991
3161
  c.order = nextOrder(this.workspace.collections);
2992
3162
  await this.writeTree(c);
2993
3163
  this.workspace.collections.push(c);
@@ -3001,26 +3171,27 @@ var WorkspaceStore = class _WorkspaceStore {
3001
3171
  const c = structuredClone(imported);
3002
3172
  freshIds(c);
3003
3173
  c.name = c.name?.trim() || "Imported Collection";
3004
- const dir = uniqueDirectoryPath(ws.collectionsPath, slugify(c.name, "collection"));
3174
+ const dir = uniqueDirectoryPath(ws.collectionsPath, fileSafeName(c.name, "Imported Collection"));
3005
3175
  await fs2.mkdir(dir, { recursive: true });
3006
3176
  c.directoryPath = dir;
3007
- c.filePath = path3.join(dir, COLLECTION_FILE);
3177
+ c.filePath = path3.join(dir, DEFINITION_FILE);
3008
3178
  c.order = nextOrder(ws.collections);
3009
3179
  const placeAndWrite = async (node, nodeDir) => {
3010
3180
  for (const [i, r] of node.requests.entries()) {
3011
- r.order = i;
3012
- r.sourceFilePath = uniqueFilePath(nodeDir, slugify(r.name, "request") + REQUEST_EXTENSION);
3013
- await writeFileAtomic(r.sourceFilePath, requestToYaml(r));
3181
+ r.order = (i + 1) * ORDER_STEP;
3182
+ r.sourceFilePath = uniqueFilePath(nodeDir, fileSafeName(r.name, "New Request"), REQUEST_SUFFIX);
3183
+ await writeFileAtomic(r.sourceFilePath, requestToYaml(r, void 0, r.sourceFilePath));
3014
3184
  }
3015
3185
  for (const [i, f] of node.folders.entries()) {
3016
- f.order = i;
3017
- f.directoryPath = uniqueDirectoryPath(nodeDir, slugify(f.name, "folder"));
3186
+ f.order = (i + 1) * ORDER_STEP;
3187
+ f.directoryPath = uniqueDirectoryPath(nodeDir, fileSafeName(f.name, "New Folder"));
3018
3188
  await fs2.mkdir(f.directoryPath, { recursive: true });
3019
- await writeFileAtomic(path3.join(f.directoryPath, FOLDER_FILE), folderToYaml(f));
3189
+ await writeFileAtomic(path3.join(f.directoryPath, DEFINITION_FILE), folderToYaml(f, void 0, path3.basename(f.directoryPath)));
3020
3190
  await placeAndWrite(f, f.directoryPath);
3021
3191
  }
3022
3192
  };
3023
3193
  await placeAndWrite(c, dir);
3194
+ await this.saveLocal(collectionScope(c.id), c.variables);
3024
3195
  await writeFileAtomic(c.filePath, collectionToYaml(c));
3025
3196
  ws.collections.push(c);
3026
3197
  return c;
@@ -3030,9 +3201,10 @@ var WorkspaceStore = class _WorkspaceStore {
3030
3201
  async createFolder(collectionId, parentFolderId, name) {
3031
3202
  return this.enqueue(async () => {
3032
3203
  const { node, dir } = this.container(collectionId, parentFolderId);
3033
- const folderDir = uniqueDirectoryPath(dir, slugify(name, "folder"));
3204
+ const display = name.trim() || "New Folder";
3205
+ const folderDir = uniqueDirectoryPath(dir, fileSafeName(display, "New Folder"));
3034
3206
  await fs2.mkdir(folderDir, { recursive: true });
3035
- const f = newFolder({ name: name.trim() || "New Folder", directoryPath: folderDir, order: nextOrder(node.folders) });
3207
+ const f = newFolder({ name: display, directoryPath: folderDir, order: nextOrder(node.folders) });
3036
3208
  await this.writeFolder(f);
3037
3209
  node.folders.push(f);
3038
3210
  return f;
@@ -3043,11 +3215,7 @@ var WorkspaceStore = class _WorkspaceStore {
3043
3215
  const hit = this.locateFolder(id);
3044
3216
  if (!hit) throw new Error(`Folder not found: ${id}`);
3045
3217
  const f = hit.folder;
3046
- if (patch.name !== void 0 && patch.name.trim() && patch.name !== f.name) {
3047
- f.name = patch.name;
3048
- const parentDir = path3.dirname(f.directoryPath);
3049
- await this.renameNodeDir(f, parentDir, slugify(f.name, "folder"));
3050
- }
3218
+ if (patch.name !== void 0 && patch.name.trim()) f.name = patch.name;
3051
3219
  if (patch.description !== void 0) f.description = patch.description;
3052
3220
  await this.writeFolder(f);
3053
3221
  return f;
@@ -3069,7 +3237,7 @@ var WorkspaceStore = class _WorkspaceStore {
3069
3237
  const src = hit.folder;
3070
3238
  const srcDir = src.directoryPath;
3071
3239
  const name = copyName(src.name);
3072
- const dir = uniqueDirectoryPath(path3.dirname(srcDir), slugify(name, "folder"));
3240
+ const dir = uniqueDirectoryPath(path3.dirname(srcDir), fileSafeName(name, "Folder"));
3073
3241
  await fs2.cp(srcDir, dir, { recursive: true });
3074
3242
  const f = structuredClone(src);
3075
3243
  repathTree(f, srcDir, dir);
@@ -3086,16 +3254,16 @@ var WorkspaceStore = class _WorkspaceStore {
3086
3254
  return this.enqueue(async () => {
3087
3255
  const { node, dir } = this.container(collectionId, parentFolderId);
3088
3256
  const name = init?.name?.trim() ? init.name : "New Request";
3089
- const file = uniqueFilePath(dir, slugify(name, "request") + REQUEST_EXTENSION);
3257
+ const file = uniqueFilePath(dir, fileSafeName(name, "New Request"), REQUEST_SUFFIX);
3090
3258
  const id = init?.id && !this.idInUse(init.id) ? init.id : newId();
3091
3259
  const r = newRequest({ ...init, id, name, sourceFilePath: file, order: nextOrder(node.requests) });
3092
3260
  r.method = (r.method || "GET").toUpperCase();
3093
- await writeFileAtomic(file, requestToYaml(r));
3261
+ await writeFileAtomic(file, requestToYaml(r, void 0, file));
3094
3262
  node.requests.push(r);
3095
3263
  return r;
3096
3264
  });
3097
3265
  }
3098
- /** Replaces a request by id and rewrites its file, renaming the file when the name changed. */
3266
+ /** Replaces a request by id and rewrites its file, renaming the file to match the name. */
3099
3267
  async saveRequest(request3) {
3100
3268
  return this.enqueue(async () => {
3101
3269
  const hit = this.locateRequest(request3.id);
@@ -3107,17 +3275,9 @@ var WorkspaceStore = class _WorkspaceStore {
3107
3275
  order: existing.order,
3108
3276
  sourceFilePath: existing.sourceFilePath
3109
3277
  };
3110
- let file = existing.sourceFilePath;
3111
- if (!file) throw new Error("Request has no file path");
3112
- if (updated.name !== existing.name && updated.name.trim()) {
3113
- const target = uniqueFilePath(path3.dirname(file), slugify(updated.name, "request") + REQUEST_EXTENSION, file);
3114
- if (target !== file) {
3115
- if (existsSync3(file)) await fs2.rename(file, target);
3116
- file = target;
3117
- }
3118
- }
3119
- updated.sourceFilePath = file;
3120
- await writeFileAtomic(file, requestToYaml(updated, await readIfExists(file)));
3278
+ if (!existing.sourceFilePath) throw new Error("Request has no file path");
3279
+ if (!updated.name.trim()) updated.name = existing.name;
3280
+ await this.writeRequest(updated);
3121
3281
  hit.list[hit.list.indexOf(existing)] = updated;
3122
3282
  return updated;
3123
3283
  });
@@ -3140,8 +3300,8 @@ var WorkspaceStore = class _WorkspaceStore {
3140
3300
  r.id = newId();
3141
3301
  for (const e of r.examples ?? []) e.id = newId();
3142
3302
  r.name = copyName(src.name);
3143
- r.sourceFilePath = uniqueFilePath(path3.dirname(src.sourceFilePath), slugify(r.name, "request") + REQUEST_EXTENSION);
3144
- await writeFileAtomic(r.sourceFilePath, requestToYaml(r, await readIfExists(src.sourceFilePath)));
3303
+ r.sourceFilePath = uniqueFilePath(path3.dirname(src.sourceFilePath), fileSafeName(r.name, "New Request"), REQUEST_SUFFIX);
3304
+ await writeFileAtomic(r.sourceFilePath, requestToYaml(r, await readIfExists(src.sourceFilePath), r.sourceFilePath));
3145
3305
  hit.list.splice(hit.list.indexOf(src) + 1, 0, r);
3146
3306
  await this.saveContainerOrder(hit.collection, hit.folders.at(-1) ?? null);
3147
3307
  return r;
@@ -3163,7 +3323,7 @@ var WorkspaceStore = class _WorkspaceStore {
3163
3323
  const r = hit2.request;
3164
3324
  hit2.list.splice(hit2.list.indexOf(r), 1);
3165
3325
  if (r.sourceFilePath && path3.dirname(r.sourceFilePath) !== target.dir) {
3166
- const dest = uniqueFilePath(target.dir, path3.basename(r.sourceFilePath));
3326
+ const dest = uniqueFilePath(target.dir, fileSafeName(r.name, "New Request"), REQUEST_SUFFIX);
3167
3327
  await fs2.rename(r.sourceFilePath, dest);
3168
3328
  r.sourceFilePath = dest;
3169
3329
  }
@@ -3197,13 +3357,15 @@ var WorkspaceStore = class _WorkspaceStore {
3197
3357
  async saveContainerOrder(collection, folder) {
3198
3358
  const node = folder ?? collection;
3199
3359
  for (const [i, r] of node.requests.entries()) {
3200
- if (r.order === i && existsSync3(r.sourceFilePath ?? "")) continue;
3201
- r.order = i;
3202
- if (r.sourceFilePath) await writeFileAtomic(r.sourceFilePath, requestToYaml(r, await readIfExists(r.sourceFilePath)));
3360
+ const order = (i + 1) * ORDER_STEP;
3361
+ if (r.order === order && existsSync3(r.sourceFilePath ?? "")) continue;
3362
+ r.order = order;
3363
+ if (r.sourceFilePath) await this.writeRequest(r);
3203
3364
  }
3204
3365
  for (const [i, f] of node.folders.entries()) {
3205
- if (f.order === i && existsSync3(path3.join(f.directoryPath ?? "", FOLDER_FILE))) continue;
3206
- f.order = i;
3366
+ const order = (i + 1) * ORDER_STEP;
3367
+ if (f.order === order && existsSync3(path3.join(f.directoryPath ?? "", DEFINITION_FILE))) continue;
3368
+ f.order = order;
3207
3369
  await this.writeFolder(f);
3208
3370
  }
3209
3371
  }
@@ -3211,8 +3373,9 @@ var WorkspaceStore = class _WorkspaceStore {
3211
3373
  async createEnvironment(name) {
3212
3374
  return this.enqueue(async () => {
3213
3375
  const ws = this.workspace;
3214
- const file = uniqueFilePath(ws.environmentsPath, slugify(name, "environment") + ".yaml");
3215
- const env = newEnvironment({ name: name.trim() || "New Environment", filePath: file });
3376
+ const display = name.trim() || "New Environment";
3377
+ const file = uniqueFilePath(ws.environmentsPath, fileSafeName(display, "New Environment"), ENVIRONMENT_SUFFIX);
3378
+ const env = newEnvironment({ name: display, filePath: file });
3216
3379
  await writeFileAtomic(file, environmentToYaml(env));
3217
3380
  ws.environments.push(env);
3218
3381
  return env;
@@ -3222,7 +3385,8 @@ var WorkspaceStore = class _WorkspaceStore {
3222
3385
  return this.enqueue(async () => {
3223
3386
  const ws = this.workspace;
3224
3387
  const env = { ...structuredClone(imported), id: newId(), name: imported.name?.trim() || "Imported Environment" };
3225
- env.filePath = uniqueFilePath(ws.environmentsPath, slugify(env.name, "environment") + ".yaml");
3388
+ env.filePath = uniqueFilePath(ws.environmentsPath, fileSafeName(env.name, "Imported Environment"), ENVIRONMENT_SUFFIX);
3389
+ await this.saveLocal(environmentScope(env.id), env.variables);
3226
3390
  await writeFileAtomic(env.filePath, environmentToYaml(env));
3227
3391
  ws.environments.push(env);
3228
3392
  return env;
@@ -3234,16 +3398,14 @@ var WorkspaceStore = class _WorkspaceStore {
3234
3398
  const idx = list.findIndex((e) => e.id === env.id);
3235
3399
  if (idx < 0) throw new Error(`Environment not found: ${env.id}`);
3236
3400
  const existing = list[idx];
3237
- let file = existing.filePath ?? uniqueFilePath(this.workspace.environmentsPath, slugify(env.name, "environment") + ".yaml");
3238
- if (env.name !== existing.name && env.name.trim() && existing.filePath) {
3239
- const target = uniqueFilePath(path3.dirname(file), slugify(env.name, "environment") + ".yaml", file);
3240
- if (target !== file) {
3241
- if (existsSync3(file)) await fs2.rename(file, target);
3242
- file = target;
3243
- }
3244
- }
3245
- const updated = { ...structuredClone(env), filePath: file };
3246
- await writeFileAtomic(file, environmentToYaml(updated, await readIfExists(file)));
3401
+ const name = env.name.trim() ? env.name : existing.name;
3402
+ const dir = existing.filePath ? path3.dirname(existing.filePath) : this.workspace.environmentsPath;
3403
+ const target = uniqueFilePath(dir, fileSafeName(name, "New Environment"), ENVIRONMENT_SUFFIX, existing.filePath);
3404
+ const original = await readIfExists(existing.filePath);
3405
+ if (existing.filePath && target !== existing.filePath && existsSync3(existing.filePath)) await fs2.rename(existing.filePath, target);
3406
+ const updated = { ...structuredClone(env), name, filePath: target };
3407
+ await this.saveLocal(environmentScope(updated.id), updated.variables);
3408
+ await writeFileAtomic(target, environmentToYaml(updated, original));
3247
3409
  list[idx] = updated;
3248
3410
  return updated;
3249
3411
  });
@@ -3254,6 +3416,7 @@ var WorkspaceStore = class _WorkspaceStore {
3254
3416
  const env = list.find((e) => e.id === id);
3255
3417
  if (!env) throw new Error(`Environment not found: ${id}`);
3256
3418
  if (env.filePath) await fs2.rm(env.filePath, { force: true });
3419
+ await this.local?.set(environmentScope(env.id), void 0);
3257
3420
  list.splice(list.indexOf(env), 1);
3258
3421
  return env;
3259
3422
  });
@@ -3264,7 +3427,8 @@ var WorkspaceStore = class _WorkspaceStore {
3264
3427
  const src = list.find((e) => e.id === id);
3265
3428
  if (!src) throw new Error(`Environment not found: ${id}`);
3266
3429
  const env = { ...structuredClone(src), id: newId(), name: copyName(src.name) };
3267
- env.filePath = uniqueFilePath(this.workspace.environmentsPath, slugify(env.name, "environment") + ".yaml");
3430
+ env.filePath = uniqueFilePath(this.workspace.environmentsPath, fileSafeName(env.name, "Environment"), ENVIRONMENT_SUFFIX);
3431
+ await this.saveLocal(environmentScope(env.id), env.variables);
3268
3432
  await writeFileAtomic(env.filePath, environmentToYaml(env, await readIfExists(src.filePath)));
3269
3433
  list.splice(list.indexOf(src) + 1, 0, env);
3270
3434
  return env;
@@ -3273,7 +3437,10 @@ var WorkspaceStore = class _WorkspaceStore {
3273
3437
  async saveGlobals(vars) {
3274
3438
  return this.enqueue(async () => {
3275
3439
  const file = path3.join(this.workspace.globalsPath, GLOBALS_FILE);
3276
- await writeFileAtomic(file, globalsToYaml(vars, await readIfExists(file)));
3440
+ const legacy = path3.join(this.workspace.globalsPath, LEGACY_GLOBALS_FILE);
3441
+ await this.saveLocal(GLOBALS_SCOPE, vars);
3442
+ await writeFileAtomic(file, globalsToYaml(vars, await readIfExists(file) ?? await readIfExists(legacy)));
3443
+ await fs2.rm(legacy, { force: true });
3277
3444
  this.workspace.globals = structuredClone(vars);
3278
3445
  return this.workspace.globals;
3279
3446
  });
@@ -3295,23 +3462,50 @@ var WorkspaceStore = class _WorkspaceStore {
3295
3462
  await fs2.rename(from, to);
3296
3463
  repathTree(node, from, to);
3297
3464
  }
3465
+ /** Stores a scope's local values (the YAML gets blanks for them). */
3466
+ async saveLocal(scope, vars) {
3467
+ if (!this.local) return;
3468
+ const values = {};
3469
+ for (const v of vars) if (v.local && v.key) values[v.key] = v.value ?? "";
3470
+ await this.local.set(scope, values);
3471
+ }
3472
+ /** Writes a request, first renaming its file to `<name>.request.yaml` (older names move over). */
3473
+ async writeRequest(r) {
3474
+ const file = r.sourceFilePath;
3475
+ if (!file) return;
3476
+ const original = await readIfExists(file);
3477
+ const target = uniqueFilePath(path3.dirname(file), fileSafeName(r.name, "New Request"), REQUEST_SUFFIX, file);
3478
+ if (target !== file && existsSync3(file)) await fs2.rename(file, target);
3479
+ r.sourceFilePath = target;
3480
+ await writeFileAtomic(target, requestToYaml(r, original, target));
3481
+ }
3482
+ /** Writes `.resources/definition.yaml` (renaming the directory to match the name) and drops a legacy collection.yaml. */
3298
3483
  async writeCollection(c) {
3299
3484
  if (!c.directoryPath) throw new Error("Collection has no directory");
3300
- c.filePath = path3.join(c.directoryPath, COLLECTION_FILE);
3301
- await writeFileAtomic(c.filePath, collectionToYaml(c, await readIfExists(c.filePath)));
3485
+ await this.renameNodeDir(c, this.workspace.collectionsPath, fileSafeName(c.name, "New Collection"));
3486
+ const file = path3.join(c.directoryPath, DEFINITION_FILE);
3487
+ const legacy = path3.join(c.directoryPath, LEGACY_COLLECTION_FILE);
3488
+ const original = await readIfExists(file) ?? await readIfExists(legacy);
3489
+ c.filePath = file;
3490
+ await this.saveLocal(collectionScope(c.id), c.variables);
3491
+ await writeFileAtomic(file, collectionToYaml(c, original));
3492
+ await fs2.rm(legacy, { force: true });
3302
3493
  }
3303
3494
  async writeFolder(f) {
3304
3495
  if (!f.directoryPath) return;
3305
- const file = path3.join(f.directoryPath, FOLDER_FILE);
3306
- await writeFileAtomic(file, folderToYaml(f, await readIfExists(file)));
3496
+ await this.renameNodeDir(f, path3.dirname(f.directoryPath), fileSafeName(f.name, "New Folder"));
3497
+ const dir = f.directoryPath;
3498
+ const file = path3.join(dir, DEFINITION_FILE);
3499
+ const legacy = path3.join(dir, LEGACY_FOLDER_FILE);
3500
+ const original = await readIfExists(file) ?? await readIfExists(legacy);
3501
+ await writeFileAtomic(file, folderToYaml(f, original, path3.basename(dir)));
3502
+ await fs2.rm(legacy, { force: true });
3307
3503
  }
3308
3504
  /** Rewrites every metadata and request file under a node (used after copying a directory). */
3309
3505
  async writeTree(node) {
3310
3506
  if ("variables" in node) await this.writeCollection(node);
3311
3507
  else await this.writeFolder(node);
3312
- for (const r of node.requests) {
3313
- if (r.sourceFilePath) await writeFileAtomic(r.sourceFilePath, requestToYaml(r, await readIfExists(r.sourceFilePath)));
3314
- }
3508
+ for (const r of node.requests) await this.writeRequest(r);
3315
3509
  for (const f of node.folders) await this.writeTree(f);
3316
3510
  }
3317
3511
  };
@@ -3347,7 +3541,7 @@ async function loadWorkspace(root) {
3347
3541
  } catch {
3348
3542
  }
3349
3543
  }
3350
- const globalsText = await readIfExists(path3.join(ws.globalsPath, GLOBALS_FILE));
3544
+ const globalsText = await readIfExists(path3.join(ws.globalsPath, GLOBALS_FILE)) ?? await readIfExists(path3.join(ws.globalsPath, LEGACY_GLOBALS_FILE));
3351
3545
  if (globalsText) {
3352
3546
  try {
3353
3547
  ws.globals = globalsFromYaml(globalsText);
@@ -3363,11 +3557,11 @@ async function loadCollection(dir, rel) {
3363
3557
  id: stableId("c", rel(dir)),
3364
3558
  name: path3.basename(dir),
3365
3559
  directoryPath: dir,
3366
- filePath: path3.join(dir, COLLECTION_FILE)
3560
+ filePath: path3.join(dir, DEFINITION_FILE)
3367
3561
  });
3368
- const definition = await readIfExists(path3.join(dir, DEFINITION_FILE));
3562
+ const definition = await readIfExists(c.filePath);
3369
3563
  if (definition) applyCollectionDefinition(c, loadYaml(definition));
3370
- const metadata = await readIfExists(c.filePath);
3564
+ const metadata = await readIfExists(path3.join(dir, LEGACY_COLLECTION_FILE));
3371
3565
  if (metadata) applyCollectionMetadata(c, loadYaml(metadata));
3372
3566
  c.requests = await loadRequests(dir, rel);
3373
3567
  c.folders = await loadFolders(dir, rel);
@@ -3379,8 +3573,9 @@ async function loadFolders(parentDir, rel) {
3379
3573
  if (!entry.dir || entry.name.startsWith(".")) continue;
3380
3574
  const dir = path3.join(parentDir, entry.name);
3381
3575
  const f = newFolder({ id: stableId("f", rel(dir)), name: entry.name, directoryPath: dir });
3382
- const meta = await readIfExists(path3.join(dir, FOLDER_FILE));
3383
- if (meta) {
3576
+ for (const file of [DEFINITION_FILE, LEGACY_FOLDER_FILE]) {
3577
+ const meta = await readIfExists(path3.join(dir, file));
3578
+ if (!meta) continue;
3384
3579
  try {
3385
3580
  applyFolderMetadata(f, loadYaml(meta));
3386
3581
  } catch {
@@ -3397,11 +3592,12 @@ async function loadRequests(dir, rel) {
3397
3592
  for (const entry of await listDir(dir)) {
3398
3593
  if (entry.dir || !isYamlFile(entry.name)) continue;
3399
3594
  const lower = entry.name.toLowerCase();
3400
- if (lower === COLLECTION_FILE || lower === FOLDER_FILE) continue;
3595
+ if (lower === LEGACY_COLLECTION_FILE || lower === LEGACY_FOLDER_FILE) continue;
3401
3596
  const file = path3.join(dir, entry.name);
3402
3597
  try {
3403
3598
  const raw = loadYaml(await fs2.readFile(file, "utf8"));
3404
3599
  if (!isObj(raw)) continue;
3600
+ if (raw.$kind !== void 0 && String(raw.$kind) !== REQUEST_KIND) continue;
3405
3601
  const r = requestFromDto(raw, file);
3406
3602
  if (!String(raw.id ?? "").trim()) r.id = stableId("r", rel(file));
3407
3603
  requests.push(r);
@@ -3431,9 +3627,22 @@ function ensureUniqueIds(ws, rel) {
3431
3627
  }
3432
3628
  for (const e of ws.environments) e.id = claim(e.id, "e", rel(e.filePath ?? e.name));
3433
3629
  }
3630
+ function applyLocalValues(ws, local) {
3631
+ const overlay = (vars, stored) => {
3632
+ if (!stored) return;
3633
+ for (const v of vars) {
3634
+ if (!Object.hasOwn(stored, v.key) || !v.local && v.value) continue;
3635
+ v.value = stored[v.key];
3636
+ v.local = true;
3637
+ }
3638
+ };
3639
+ for (const e of ws.environments) overlay(e.variables, local.get(environmentScope(e.id)));
3640
+ for (const c of ws.collections) overlay(c.variables, local.get(collectionScope(c.id)));
3641
+ overlay(ws.globals, local.get(GLOBALS_SCOPE));
3642
+ }
3434
3643
 
3435
3644
  // cli/src/index.ts
3436
- var VERSION = true ? "1.12.0" : "0.0.0-dev";
3645
+ var VERSION = true ? "1.13.0" : "0.0.0-dev";
3437
3646
  setYamletVersion(VERSION);
3438
3647
  var CliError = class extends Error {
3439
3648
  };
@@ -3533,7 +3742,7 @@ function parseArgs(argv) {
3533
3742
  function resolveEnvironment(ws, arg) {
3534
3743
  if (!arg) return void 0;
3535
3744
  const path4 = resolve(arg);
3536
- if (existsSync4(path4) && statSync2(path4).isFile()) return environmentFromYaml(readFileSync(path4, "utf8"), path4);
3745
+ if (existsSync4(path4) && statSync3(path4).isFile()) return environmentFromYaml(readFileSync(path4, "utf8"), path4);
3537
3746
  const stem = basename(arg).replace(/\.(environment\.)?ya?ml$/i, "");
3538
3747
  const match = ws.environments.find((e) => e.name.toLowerCase() === arg.toLowerCase() || e.name.toLowerCase() === stem.toLowerCase());
3539
3748
  if (!match) {
@@ -3631,7 +3840,7 @@ function formatJson(summary) {
3631
3840
  }
3632
3841
  async function run(args) {
3633
3842
  const dir = resolve(args.workspace);
3634
- if (!existsSync4(dir) || !statSync2(dir).isDirectory()) throw new CliError(`Workspace folder not found: ${dir}`);
3843
+ if (!existsSync4(dir) || !statSync3(dir).isDirectory()) throw new CliError(`Workspace folder not found: ${dir}`);
3635
3844
  if (!await WorkspaceStore.isWorkspace(dir)) throw new CliError(`Not a Yamlet workspace (no collections/ and environments/): ${dir}`);
3636
3845
  const store = await WorkspaceStore.open(dir);
3637
3846
  const ws = store.workspace;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piyushdoorwar/yamlet",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Run Yamlet API collections (Git-friendly YAML) from the command line or CI.",
5
5
  "type": "module",
6
6
  "bin": {