proschi 0.13.0 → 0.14.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.
package/dist/server.cjs CHANGED
@@ -25478,6 +25478,19 @@ var BracketCounter = class {
25478
25478
  return this.depth;
25479
25479
  }
25480
25480
  };
25481
+ function qualify(tokens) {
25482
+ let out;
25483
+ for (let i = 0; i < tokens.length; i++) {
25484
+ const [t, next] = [tokens[i], tokens[i + 1]];
25485
+ const joined = t.kind === "ident" && next?.kind === "tag" && next.col === t.col + t.length;
25486
+ if (joined && (tokens[i + 2]?.kind === "arrow" || tokens[i - 1]?.kind === "arrow")) {
25487
+ out ??= tokens.slice(0, i);
25488
+ out.push({ ...t, value: `${t.value}.${next.value}`, length: t.length + next.length });
25489
+ i++;
25490
+ } else out?.push(t);
25491
+ }
25492
+ return out ?? tokens;
25493
+ }
25481
25494
 
25482
25495
  // ../frontend/src/dsl/format.ts
25483
25496
  function format(source2) {
@@ -25514,7 +25527,8 @@ function classify(lines) {
25514
25527
  entries2.push({ kind: "blank", depth, text: "" });
25515
25528
  continue;
25516
25529
  }
25517
- const { tokens, diagnostics } = tokenizeLine(raw, i + 1);
25530
+ const { tokens: lexed, diagnostics } = tokenizeLine(raw, i + 1);
25531
+ const tokens = qualify(lexed);
25518
25532
  if (diagnostics.some((d) => d.severity === "error") || tokens.length === 0) {
25519
25533
  entries2.push({ kind: "line", depth, text: original });
25520
25534
  continue;
@@ -25669,7 +25683,8 @@ function join(raw, tokens) {
25669
25683
  return out;
25670
25684
  }
25671
25685
  function sameTokens(text, tokens) {
25672
- const { tokens: again, diagnostics } = tokenizeLine(text, 1);
25686
+ const { tokens: lexed, diagnostics } = tokenizeLine(text, 1);
25687
+ const again = qualify(lexed);
25673
25688
  return diagnostics.length === 0 && again.length === tokens.length && again.every((t, i) => t.kind === tokens[i].kind && t.value === tokens[i].value);
25674
25689
  }
25675
25690
  function reindent(line, delta) {
@@ -28013,19 +28028,6 @@ function expand(items) {
28013
28028
  }
28014
28029
  return paths;
28015
28030
  }
28016
- function qualify(tokens) {
28017
- let out;
28018
- for (let i = 0; i < tokens.length; i++) {
28019
- const [t, next] = [tokens[i], tokens[i + 1]];
28020
- const joined = t.kind === "ident" && next?.kind === "tag" && next.col === t.col + t.length;
28021
- if (joined && (tokens[i + 2]?.kind === "arrow" || tokens[i - 1]?.kind === "arrow")) {
28022
- out ??= tokens.slice(0, i);
28023
- out.push({ ...t, value: `${t.value}.${next.value}`, length: t.length + next.length });
28024
- i++;
28025
- } else out?.push(t);
28026
- }
28027
- return out ?? tokens;
28028
- }
28029
28031
  function findUseCaseIn(diagram, name) {
28030
28032
  const lower = name.toLowerCase();
28031
28033
  return diagram.useCases.find((u) => u.name === name) ?? diagram.useCases.find((u) => u.name.toLowerCase() === lower) ?? diagram.useCases.find((u) => u.id === name);
@@ -30155,6 +30157,10 @@ var import_node_path5 = require("node:path");
30155
30157
  var ImportError = class extends Error {
30156
30158
  name = "ImportError";
30157
30159
  };
30160
+ var LATEST_TAG = "latest";
30161
+ var VERSION_TAG = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
30162
+ var CLOUD_NAME_SEGMENT = /^[A-Za-z0-9_][A-Za-z0-9._+-]*$/;
30163
+ var MAX_CLOUD_NAME = 200;
30158
30164
  var MAX_SPEC_LENGTH = 1024;
30159
30165
  var SEGMENT = /^[A-Za-z0-9_][A-Za-z0-9._ +-]*$/;
30160
30166
  var HOST = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*(?::\d{1,5})?$/;
@@ -30202,8 +30208,9 @@ function parseRemoteSpec(spec) {
30202
30208
  checkRef(ref, spec);
30203
30209
  return { kind: "git", repo, path, ref };
30204
30210
  }
30211
+ if (scheme === "cloud") return parseCloudSpec(spec);
30205
30212
  if (scheme === "http") throw new ImportError(`'${spec}' uses http://; only https:// imports are allowed`);
30206
- if (scheme !== "https") throw new ImportError(`'${spec}' is not a supported import source; use a relative path, git: or https://`);
30213
+ if (scheme !== "https") throw new ImportError(`'${spec}' is not a supported import source; use a relative path, git:, https:// or cloud:`);
30207
30214
  let url;
30208
30215
  try {
30209
30216
  url = new URL(spec);
@@ -30215,15 +30222,48 @@ function parseRemoteSpec(spec) {
30215
30222
  if (!HOST.test(url.host)) throw new ImportError(`'${spec}' has an invalid host`);
30216
30223
  return { kind: "https", url: url.href };
30217
30224
  }
30225
+ function parseCloudSpec(spec) {
30226
+ const body = spec.slice("cloud:".length);
30227
+ const split2 = body.indexOf("//");
30228
+ const head = split2 === -1 ? body : body.slice(0, split2);
30229
+ const parts = head.split(":");
30230
+ if (parts.length > 2) throw new ImportError(`'${spec}' is not a cloud source; write cloud:<name> or cloud:<name>:<tag>`);
30231
+ const name = parts[0].endsWith(".proschi") ? parts[0].slice(0, -".proschi".length) : parts[0];
30232
+ if (!name || name.length > MAX_CLOUD_NAME || name.split("/").some((p) => !CLOUD_NAME_SEGMENT.test(p) || p === "." || p === "..")) {
30233
+ throw new ImportError(
30234
+ `'${spec}' names an invalid diagram '${parts[0]}'; use the diagram's file name (letters, digits, '.', '_', '+', '-' and '/', no spaces), with or without .proschi`
30235
+ );
30236
+ }
30237
+ const tag = parts.length === 2 ? parts[1] : LATEST_TAG;
30238
+ if (!VERSION_TAG.test(tag)) {
30239
+ throw new ImportError(`'${spec}' has an invalid version tag '${tag}'; use up to 64 letters, digits, '.', '_' and '-', starting with a letter or digit, e.g. 2026-11-01`);
30240
+ }
30241
+ if (split2 === -1) return { kind: "cloud", name, tag };
30242
+ return { kind: "cloud", name, tag, path: repoPathSegments(body.slice(split2 + 2), spec).join("/") };
30243
+ }
30218
30244
  function formatRemoteSpec(spec) {
30245
+ if (spec.kind === "cloud") return `cloud:${spec.name}:${spec.tag}${spec.path ? `//${spec.path}` : ""}`;
30219
30246
  return spec.kind === "git" ? `git:${spec.repo}//${spec.path}@${spec.ref}` : spec.url;
30220
30247
  }
30221
30248
  function resolveImportPath(fromPath, importPath) {
30222
30249
  if (importPath.includes("\0")) throw new ImportError(`'${importPath.replace(/\0/g, "")}' contains a NUL character`);
30223
30250
  const remote = parseRemoteSpec(importPath);
30251
+ if (remote?.kind === "cloud" && fromPath && isRemoteSpec(fromPath) && parseRemoteSpec(fromPath)?.kind !== "cloud") {
30252
+ throw new ImportError(`'${importPath}' is a cloud import inside ${fromPath}; cloud: sources only work from your own diagrams`);
30253
+ }
30224
30254
  if (remote) return formatRemoteSpec(remote);
30225
30255
  if (isAbsolutePath(importPath)) throw new ImportError(`'${importPath}' is an absolute path; imports must be relative to the importing file`);
30226
30256
  const from = fromPath && isRemoteSpec(fromPath) ? parseRemoteSpec(fromPath) : void 0;
30257
+ if (from?.kind === "cloud") {
30258
+ const parts2 = (from.path ?? "").split("/").slice(0, -1);
30259
+ for (const part of importPath.split("/")) {
30260
+ if (part === "" || part === ".") continue;
30261
+ if (part !== "..") parts2.push(part);
30262
+ else if (parts2.length > 0) parts2.pop();
30263
+ else throw new ImportError(`'${importPath}' leaves the files saved with cloud:${from.name}:${from.tag}`);
30264
+ }
30265
+ return formatRemoteSpec({ ...from, path: repoPathSegments(parts2.join("/"), importPath).join("/") });
30266
+ }
30227
30267
  if (from?.kind === "git") {
30228
30268
  const parts2 = from.path.split("/").slice(0, -1);
30229
30269
  for (const part of importPath.split("/")) {
@@ -30260,11 +30300,14 @@ var LOCK_FILE = "proschi.lock";
30260
30300
  var CACHE_DIR = ".proschi/cache";
30261
30301
  var HASH = /^[0-9a-f]{64}$/;
30262
30302
  var COMMIT = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
30263
- var emptyLock = () => ({ version: 1, git: {}, https: {} });
30303
+ var emptyLock = () => ({ version: 1, git: {}, https: {}, cloud: {} });
30264
30304
  var gitKey = (repo, ref) => `${repo}@${ref}`;
30305
+ var cloudKey = (name, tag) => `${name}:${tag}`;
30265
30306
  var remoteError = {
30266
30307
  noLock: (spec) => new ImportError(`'${spec}' is a remote import and there is no ${LOCK_FILE}; run proschi imports sync`),
30267
- notLocked: (spec) => new ImportError(`'${spec}' is not in ${LOCK_FILE}; run proschi imports sync`),
30308
+ notLocked: (spec) => spec.startsWith("cloud:") ? remoteError.cloud(spec) : new ImportError(`'${spec}' is not in ${LOCK_FILE}; run proschi imports sync`),
30309
+ /** A cloud version that is not pinned yet (docs/design/versions-and-cloud.md). */
30310
+ cloud: (spec) => new ImportError(`'${spec}' is a version in your Proschi account; run proschi imports sync to pin it (cloud sources need proschi login)`),
30268
30311
  notCached: (spec) => new ImportError(`'${spec}' is not in the import cache (${CACHE_DIR}); run proschi imports sync`),
30269
30312
  altered: (spec) => new ImportError(`The cached copy of '${spec}' does not match its hash in ${LOCK_FILE}; run proschi imports sync`)
30270
30313
  };
@@ -30281,7 +30324,8 @@ function parseLock(text) {
30281
30324
  const lock = emptyLock();
30282
30325
  const git = json.git ?? {};
30283
30326
  const https = json.https ?? {};
30284
- if (!isRecord(git) || !isRecord(https)) throw invalid('"git" and "https" must be objects');
30327
+ const cloud = json.cloud ?? {};
30328
+ if (!isRecord(git) || !isRecord(https) || !isRecord(cloud)) throw invalid('"git", "https" and "cloud" must be objects');
30285
30329
  for (const [key, entry] of Object.entries(git)) {
30286
30330
  const at = key.lastIndexOf("@");
30287
30331
  const valid = at > 0 && isRecord(entry) && typeof entry.commit === "string" && COMMIT.test(entry.commit) && isRecord(entry.files) && Object.entries(entry.files).every(([path, hash]) => typeof hash === "string" && HASH.test(hash) && isGitSpec(`git:${key.slice(0, at)}//${path}@${key.slice(at + 1)}`));
@@ -30292,8 +30336,28 @@ function parseLock(text) {
30292
30336
  if (typeof hash !== "string" || !HASH.test(hash) || parseRemoteSpec(url)?.kind !== "https") throw invalid(`bad https entry '${url}'`);
30293
30337
  lock.https[url] = hash;
30294
30338
  }
30339
+ for (const [key, entry] of Object.entries(cloud)) {
30340
+ const hash = (v) => typeof v === "string" && HASH.test(v);
30341
+ const valid = isCanonicalSpec(`cloud:${key}`, "cloud") && isRecord(entry) && typeof entry.tag === "string" && VERSION_TAG.test(entry.tag) && entry.tag !== LATEST_TAG && Number.isSafeInteger(entry.seq) && entry.seq > 0 && hash(entry.sha256) && (entry.source === void 0 || hash(entry.source)) && isRecord(entry.files) && Object.entries(entry.files).every(([path, h]) => hash(h) && isCanonicalSpec(`cloud:${key}//${path}`, "cloud"));
30342
+ if (!valid) throw invalid(`bad cloud entry '${key}'`);
30343
+ lock.cloud[key] = {
30344
+ tag: entry.tag,
30345
+ seq: entry.seq,
30346
+ sha256: entry.sha256,
30347
+ ...entry.source === void 0 ? {} : { source: entry.source },
30348
+ files: { ...entry.files }
30349
+ };
30350
+ }
30295
30351
  return lock;
30296
30352
  }
30353
+ function isCanonicalSpec(spec, kind) {
30354
+ try {
30355
+ const remote = parseRemoteSpec(spec);
30356
+ return remote?.kind === kind && formatRemoteSpec(remote) === spec;
30357
+ } catch {
30358
+ return false;
30359
+ }
30360
+ }
30297
30361
  function isGitSpec(spec) {
30298
30362
  try {
30299
30363
  return parseRemoteSpec(spec)?.kind === "git";
@@ -30305,6 +30369,10 @@ function lockedHash(lock, spec) {
30305
30369
  const remote = parseRemoteSpec(spec);
30306
30370
  if (remote?.kind === "git") return lock.git[gitKey(remote.repo, remote.ref)]?.files[remote.path];
30307
30371
  if (remote?.kind === "https") return lock.https[remote.url];
30372
+ if (remote?.kind === "cloud") {
30373
+ const entry = lock.cloud[cloudKey(remote.name, remote.tag)];
30374
+ return remote.path === void 0 ? entry?.source : entry?.files[remote.path];
30375
+ }
30308
30376
  return void 0;
30309
30377
  }
30310
30378
 
@@ -30757,7 +30825,7 @@ function positionAt(text, offset) {
30757
30825
 
30758
30826
  // src/server.ts
30759
30827
  if (process.argv.includes("--version")) {
30760
- console.log(true ? "0.13.0" : "dev");
30828
+ console.log(true ? "0.14.0" : "dev");
30761
30829
  process.exit(0);
30762
30830
  }
30763
30831
  var connection = (0, import_node.createConnection)(import_node.ProposedFeatures.all);
@@ -30803,7 +30871,7 @@ connection.onInitialize(() => ({
30803
30871
  codeActionProvider: { codeActionKinds: [import_node.CodeActionKind.QuickFix] },
30804
30872
  semanticTokensProvider: { legend: { tokenTypes: TOKEN_TYPES, tokenModifiers: [] }, full: true }
30805
30873
  },
30806
- serverInfo: { name: "proschi-language-server", version: true ? "0.13.0" : "dev" }
30874
+ serverInfo: { name: "proschi-language-server", version: true ? "0.14.0" : "dev" }
30807
30875
  }));
30808
30876
  function validate(document) {
30809
30877
  const analysis = analysisOf(document);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proschi",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Command-line checker and language server for Proschi architecture diagrams",
5
5
  "license": "MIT",
6
6
  "repository": {