@qodalis/cli-files 2.0.0-beta.13 → 2.0.0-beta.15

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/public-api.mjs CHANGED
@@ -1,10 +1,10 @@
1
- import { ICliCompletionProvider_TOKEN, ICliFileTransferService_TOKEN, BrowserFileTransferService, DefaultLibraryAuthor, CliForegroundColor, resolveHeaders, ICliDragDropService_TOKEN } from '@qodalis/cli-core';
1
+ import { ICliCompletionProvider_TOKEN, ICliFileTransferService_TOKEN, CliAccessDeniedError, BrowserFileTransferService, DefaultLibraryAuthor, CliForegroundColor, resolveHeaders, cliCanAccess, ICliUserSessionService_TOKEN, ICliUserEventService_TOKEN, ICliDragDropService_TOKEN, isCliAccessDeniedError, ICliAuthorizationService_TOKEN } from '@qodalis/cli-core';
2
2
 
3
3
  // src/lib/interfaces/i-file-system-service.ts
4
4
  var IFileSystemService_TOKEN = "cli-file-system-service";
5
5
 
6
6
  // src/lib/version.ts
7
- var LIBRARY_VERSION = "2.0.0-beta.13";
7
+ var LIBRARY_VERSION = "2.0.0-beta.15";
8
8
  var API_VERSION = 2;
9
9
 
10
10
  // src/lib/processors/cli-ls-command-processor.ts
@@ -470,6 +470,14 @@ var CliMvCommandProcessor = class {
470
470
  return raw.split(/\s+/).filter((t) => t && !t.startsWith("--") && !t.startsWith("-"));
471
471
  }
472
472
  };
473
+ function readableChildren(fs, path) {
474
+ try {
475
+ return fs.listDirectory(path);
476
+ } catch (e) {
477
+ if (isCliAccessDeniedError(e)) return null;
478
+ throw e;
479
+ }
480
+ }
473
481
  var CliTreeCommandProcessor = class {
474
482
  constructor() {
475
483
  this.command = "tree";
@@ -516,7 +524,8 @@ var CliTreeCommandProcessor = class {
516
524
  )
517
525
  );
518
526
  const counts = { dirs: 0, files: 0 };
519
- this.printTree(node, "", true, 0, maxDepth, context, counts);
527
+ this.fs = fs;
528
+ this.printTree(node, "", true, 0, maxDepth, context, counts, resolvedPath);
520
529
  context.writer.writeln();
521
530
  context.writer.writeln(
522
531
  `${counts.dirs} directories, ${counts.files} files`
@@ -525,11 +534,15 @@ var CliTreeCommandProcessor = class {
525
534
  context.writer.writeError(e.message);
526
535
  }
527
536
  }
528
- printTree(node, prefix, isRoot, depth, maxDepth, context, counts) {
529
- if (!node.children || depth >= maxDepth) {
537
+ printTree(node, prefix, isRoot, depth, maxDepth, context, counts, path = "/") {
538
+ if (depth >= maxDepth) {
530
539
  return;
531
540
  }
532
- const children = [...node.children].sort((a, b) => {
541
+ const visible = this.fs ? readableChildren(this.fs, path) : node.children;
542
+ if (!visible) {
543
+ return;
544
+ }
545
+ const children = [...visible].sort((a, b) => {
533
546
  if (a.type !== b.type) {
534
547
  return a.type === "directory" ? -1 : 1;
535
548
  }
@@ -554,7 +567,8 @@ var CliTreeCommandProcessor = class {
554
567
  depth + 1,
555
568
  maxDepth,
556
569
  context,
557
- counts
570
+ counts,
571
+ path === "/" ? `/${child.name}` : `${path}/${child.name}`
558
572
  );
559
573
  } else {
560
574
  counts.files++;
@@ -844,6 +858,7 @@ var CliFindCommandProcessor = class {
844
858
  const resolvedBase = fs.resolvePath(searchPath);
845
859
  const nameRegex = namePattern ? this.globToRegex(namePattern) : null;
846
860
  const results = [];
861
+ this.fs = fs;
847
862
  this.walk(
848
863
  node,
849
864
  resolvedBase,
@@ -871,8 +886,9 @@ var CliFindCommandProcessor = class {
871
886
  return new RegExp(`^${regexStr}$`, "i");
872
887
  }
873
888
  walk(node, currentPath, depth, maxDepth, nameRegex, typeFilter, results) {
874
- if (!node.children) return;
875
- for (const child of node.children) {
889
+ const children = this.fs ? readableChildren(this.fs, currentPath) : node.children;
890
+ if (!children) return;
891
+ for (const child of children) {
876
892
  const childPath = currentPath === "/" ? `/${child.name}` : `${currentPath}/${child.name}`;
877
893
  let matches = true;
878
894
  if (nameRegex && !nameRegex.test(child.name)) {
@@ -1103,8 +1119,9 @@ var CliGrepCommandProcessor = class {
1103
1119
  };
1104
1120
  }
1105
1121
  collectFiles(fs, basePath, node, result) {
1106
- if (!node.children) return;
1107
- for (const child of node.children) {
1122
+ const children = readableChildren(fs, basePath);
1123
+ if (!children) return;
1124
+ for (const child of children) {
1108
1125
  const childPath = basePath === "/" ? `/${child.name}` : `${basePath}/${child.name}`;
1109
1126
  if (child.type === "file") {
1110
1127
  result.push(childPath);
@@ -2063,6 +2080,10 @@ var CliStatCommandProcessor = class {
2063
2080
  }
2064
2081
  for (const path of paths) {
2065
2082
  const resolved = fs.resolvePath(path);
2083
+ if (fs.canTraverse && !fs.canTraverse(resolved)) {
2084
+ context.writer.writeError(`stat: cannot stat '${path}': Permission denied`);
2085
+ continue;
2086
+ }
2066
2087
  const node = fs.getNode(resolved);
2067
2088
  if (!node) {
2068
2089
  context.writer.writeError(
@@ -2331,6 +2352,10 @@ var CliDuCommandProcessor = class {
2331
2352
  }
2332
2353
  for (const path of paths) {
2333
2354
  const resolved = fs.resolvePath(path);
2355
+ if (fs.canTraverse && !fs.canTraverse(resolved)) {
2356
+ context.writer.writeError(`du: cannot access '${path}': Permission denied`);
2357
+ continue;
2358
+ }
2334
2359
  const node = fs.getNode(resolved);
2335
2360
  if (!node) {
2336
2361
  context.writer.writeError(
@@ -2344,6 +2369,7 @@ var CliDuCommandProcessor = class {
2344
2369
  );
2345
2370
  } else {
2346
2371
  const entries = [];
2372
+ this.fs = fs;
2347
2373
  this.collectSizes(node, resolved, 0, maxDepth, summaryOnly, entries);
2348
2374
  for (const entry of entries) {
2349
2375
  context.writer.writeln(
@@ -2366,8 +2392,9 @@ var CliDuCommandProcessor = class {
2366
2392
  return node.size;
2367
2393
  }
2368
2394
  let total = 0;
2369
- if (node.children) {
2370
- for (const child of node.children) {
2395
+ const children = this.fs ? readableChildren(this.fs, path) : node.children;
2396
+ if (children) {
2397
+ for (const child of children) {
2371
2398
  const childPath = path === "/" ? `/${child.name}` : `${path}/${child.name}`;
2372
2399
  total += this.collectSizes(
2373
2400
  child,
@@ -3830,14 +3857,14 @@ function substituteVariables(input, variables) {
3830
3857
  );
3831
3858
  return result;
3832
3859
  }
3833
-
3834
- // src/lib/services/indexed-db-file-system.service.ts
3835
3860
  var DB_NAME = "qodalis-cli-filesystem";
3836
3861
  var STORE_NAME = "filesystem";
3837
3862
  var ROOT_KEY = "root";
3838
3863
  var CWD_KEY = "cwd";
3839
3864
  var HOME_KEY = "home";
3840
3865
  var DEFAULT_HOME = "/";
3866
+ var SYSTEM_OWNERSHIP = { uid: "root", gid: "admin" };
3867
+ var ANONYMOUS_OWNERSHIP = { uid: "nobody", gid: "nogroup" };
3841
3868
  function createSeedFileSystem() {
3842
3869
  const now = Date.now();
3843
3870
  return {
@@ -3885,6 +3912,88 @@ var IndexedDbFileSystemService = class {
3885
3912
  this.homePath = DEFAULT_HOME;
3886
3913
  this.currentUid = null;
3887
3914
  this.currentGroups = [];
3915
+ this.systemDepth = 0;
3916
+ }
3917
+ // --- Access control ---
3918
+ setAccessPolicy(policy) {
3919
+ this.accessPolicy = policy;
3920
+ }
3921
+ runAsSystem(fn) {
3922
+ this.systemDepth++;
3923
+ try {
3924
+ return fn();
3925
+ } finally {
3926
+ this.systemDepth--;
3927
+ }
3928
+ }
3929
+ reassignOwner(fromUid, toUid) {
3930
+ const from = fromUid.toLowerCase();
3931
+ if (!from || !toUid) return 0;
3932
+ let changed = 0;
3933
+ const visit = (node) => {
3934
+ if (node.ownership && node.ownership.uid.toLowerCase() === from) {
3935
+ node.ownership = { ...node.ownership, uid: toUid };
3936
+ changed++;
3937
+ }
3938
+ node.children?.forEach(visit);
3939
+ };
3940
+ visit(this.root);
3941
+ return changed;
3942
+ }
3943
+ canTraverse(path) {
3944
+ try {
3945
+ this.assertTraverse(this.resolvePath(path), "stat", path);
3946
+ return true;
3947
+ } catch {
3948
+ return false;
3949
+ }
3950
+ }
3951
+ get enforcing() {
3952
+ return !!this.accessPolicy && this.systemDepth === 0;
3953
+ }
3954
+ deny(op, path, reason = "Permission denied") {
3955
+ throw new CliAccessDeniedError(`${op}: ${path}: ${reason}`);
3956
+ }
3957
+ /** `x` on every existing directory on the way to `resolved` (excluding the target). */
3958
+ assertTraverse(resolved, op, display) {
3959
+ if (!this.enforcing) return;
3960
+ const parts = resolved.split("/").filter(Boolean);
3961
+ let current = this.root;
3962
+ for (let i = 0; i < parts.length; i++) {
3963
+ if (!current || current.type !== "directory") return;
3964
+ if (!this.accessPolicy.canAccess(current, "execute")) this.deny(op, display);
3965
+ current = current.children?.find((c) => c.name === parts[i]);
3966
+ }
3967
+ }
3968
+ assertAccess(node, action, op, display) {
3969
+ if (this.enforcing && !this.accessPolicy.canAccess(node, action)) this.deny(op, display);
3970
+ }
3971
+ /** Recursive delete: every directory inside must allow removing its entries (`w` + `x`, readable). */
3972
+ assertRemovableTree(dir, dirPath, display) {
3973
+ if (!this.enforcing) return;
3974
+ this.assertAccess(dir, "read", "rm", display);
3975
+ this.assertAccess(dir, "execute", "rm", display);
3976
+ if ((dir.children?.length ?? 0) > 0) this.assertAccess(dir, "write", "rm", display);
3977
+ for (const child of dir.children ?? []) {
3978
+ if (child.type === "directory") this.assertRemovableTree(child, `${dirPath}/${child.name}`, display);
3979
+ }
3980
+ }
3981
+ /** Copying reads everything it copies: `r` on every file, `r` + `x` on every directory. */
3982
+ assertReadableTree(node, display) {
3983
+ if (!this.enforcing) return;
3984
+ this.assertAccess(node, "read", "cp", display);
3985
+ if (node.type !== "directory") return;
3986
+ this.assertAccess(node, "execute", "cp", display);
3987
+ for (const child of node.children ?? []) {
3988
+ this.assertReadableTree(child, `${display.replace(/\/+$/, "")}/${child.name}`);
3989
+ }
3990
+ }
3991
+ /** Creating/removing entries needs `w` and `x` on the containing directory. */
3992
+ assertModifyDir(dir, dirPath, op, display) {
3993
+ if (!this.enforcing) return;
3994
+ this.assertTraverse(dirPath, op, display);
3995
+ this.assertAccess(dir, "execute", op, display);
3996
+ this.assertAccess(dir, "write", op, display);
3888
3997
  }
3889
3998
  // --- User context ---
3890
3999
  setCurrentUser(uid, groups) {
@@ -3892,7 +4001,9 @@ var IndexedDbFileSystemService = class {
3892
4001
  this.currentGroups = groups;
3893
4002
  }
3894
4003
  getDefaultOwnership() {
3895
- if (!this.currentUid) return void 0;
4004
+ if (!this.currentUid) {
4005
+ return this.accessPolicy ? { ...ANONYMOUS_OWNERSHIP } : void 0;
4006
+ }
3896
4007
  return {
3897
4008
  uid: this.currentUid,
3898
4009
  gid: this.currentGroups[0] || "users"
@@ -3911,6 +4022,8 @@ var IndexedDbFileSystemService = class {
3911
4022
  if (node.type !== "directory") {
3912
4023
  throw new Error(`cd: ${path}: Not a directory`);
3913
4024
  }
4025
+ this.assertTraverse(resolved, "cd", path);
4026
+ this.assertAccess(node, "execute", "cd", path);
3914
4027
  this.cwd = resolved;
3915
4028
  }
3916
4029
  getHomePath() {
@@ -3967,6 +4080,8 @@ var IndexedDbFileSystemService = class {
3967
4080
  if (node.type !== "directory") {
3968
4081
  throw new Error(`ls: ${path}: Not a directory`);
3969
4082
  }
4083
+ this.assertTraverse(this.resolvePath(path), "ls", path);
4084
+ this.assertAccess(node, "read", "ls", path);
3970
4085
  return node.children || [];
3971
4086
  }
3972
4087
  readFile(path) {
@@ -3977,6 +4092,8 @@ var IndexedDbFileSystemService = class {
3977
4092
  if (node.type === "directory") {
3978
4093
  throw new Error(`cat: ${path}: Is a directory`);
3979
4094
  }
4095
+ this.assertTraverse(this.resolvePath(path), "cat", path);
4096
+ this.assertAccess(node, "read", "cat", path);
3980
4097
  return node.content ?? "";
3981
4098
  }
3982
4099
  exists(path) {
@@ -4001,6 +4118,10 @@ var IndexedDbFileSystemService = class {
4001
4118
  }
4002
4119
  let child = current.children.find((c) => c.name === part);
4003
4120
  if (!child) {
4121
+ if (this.enforcing) {
4122
+ this.assertAccess(current, "execute", "mkdir", path);
4123
+ this.assertAccess(current, "write", "mkdir", path);
4124
+ }
4004
4125
  const now = Date.now();
4005
4126
  child = {
4006
4127
  name: part,
@@ -4016,6 +4137,8 @@ var IndexedDbFileSystemService = class {
4016
4137
  current.modifiedAt = now;
4017
4138
  } else if (child.type !== "directory") {
4018
4139
  throw new Error(`mkdir: ${part}: Not a directory`);
4140
+ } else {
4141
+ this.assertAccess(child, "execute", "mkdir", path);
4019
4142
  }
4020
4143
  current = child;
4021
4144
  }
@@ -4034,6 +4157,7 @@ var IndexedDbFileSystemService = class {
4034
4157
  if (parent.children?.find((c) => c.name === dirName)) {
4035
4158
  throw new Error(`mkdir: ${path}: File exists`);
4036
4159
  }
4160
+ this.assertModifyDir(parent, parentPath, "mkdir", path);
4037
4161
  const now = Date.now();
4038
4162
  if (!parent.children) {
4039
4163
  parent.children = [];
@@ -4068,6 +4192,12 @@ var IndexedDbFileSystemService = class {
4068
4192
  }
4069
4193
  const existing = parent.children?.find((c) => c.name === fileName);
4070
4194
  const now = Date.now();
4195
+ if (existing) {
4196
+ this.assertTraverse(resolved, "touch", path);
4197
+ this.assertAccess(existing, "write", "touch", path);
4198
+ } else {
4199
+ this.assertModifyDir(parent, parentPath, "touch", path);
4200
+ }
4071
4201
  if (existing) {
4072
4202
  existing.modifiedAt = now;
4073
4203
  } else {
@@ -4094,6 +4224,8 @@ var IndexedDbFileSystemService = class {
4094
4224
  if (node.type === "directory") {
4095
4225
  throw new Error(`write: ${path}: Is a directory`);
4096
4226
  }
4227
+ this.assertTraverse(resolved, "write", path);
4228
+ this.assertAccess(node, "write", "write", path);
4097
4229
  const now = Date.now();
4098
4230
  node.content = append ? (node.content ?? "") + content : content;
4099
4231
  node.size = new Blob([node.content]).size;
@@ -4122,6 +4254,8 @@ var IndexedDbFileSystemService = class {
4122
4254
  if (target.type === "directory" && !recursive) {
4123
4255
  throw new Error(`rm: ${path}: Is a directory`);
4124
4256
  }
4257
+ this.assertModifyDir(parent, parentPath, "rm", path);
4258
+ if (target.type === "directory") this.assertRemovableTree(target, resolved, path);
4125
4259
  parent.children.splice(index, 1);
4126
4260
  parent.modifiedAt = Date.now();
4127
4261
  }
@@ -4136,10 +4270,13 @@ var IndexedDbFileSystemService = class {
4136
4270
  `cp: -r not specified; omitting directory '${src}'`
4137
4271
  );
4138
4272
  }
4273
+ this.assertTraverse(srcResolved, "cp", src);
4274
+ this.assertReadableTree(srcNode, src);
4139
4275
  const destResolved = this.resolvePath(dest);
4140
4276
  const destNode = this.getNode(destResolved);
4141
4277
  if (destNode && destNode.type === "directory") {
4142
- const clone = this.cloneNode(srcNode);
4278
+ this.assertModifyDir(destNode, destResolved, "cp", dest);
4279
+ const clone = this.ownedByCurrentUser(this.cloneNode(srcNode));
4143
4280
  if (!destNode.children) {
4144
4281
  destNode.children = [];
4145
4282
  }
@@ -4159,7 +4296,8 @@ var IndexedDbFileSystemService = class {
4159
4296
  if (!parent || parent.type !== "directory") {
4160
4297
  throw new Error(`cp: ${parentPath}: No such file or directory`);
4161
4298
  }
4162
- const clone = this.cloneNode(srcNode);
4299
+ this.assertModifyDir(parent, parentPath, "cp", dest);
4300
+ const clone = this.ownedByCurrentUser(this.cloneNode(srcNode));
4163
4301
  clone.name = newName;
4164
4302
  if (!parent.children) {
4165
4303
  parent.children = [];
@@ -4191,10 +4329,12 @@ var IndexedDbFileSystemService = class {
4191
4329
  if (!srcParent || !srcParent.children) {
4192
4330
  throw new Error(`mv: ${src}: No such file or directory`);
4193
4331
  }
4332
+ this.assertModifyDir(srcParent, srcParentPath, "mv", src);
4194
4333
  const srcIndex = srcParent.children.findIndex(
4195
4334
  (c) => c.name === srcNode.name
4196
4335
  );
4197
4336
  if (destNode && destNode.type === "directory") {
4337
+ this.assertModifyDir(destNode, destResolved, "mv", dest);
4198
4338
  if (!destNode.children) {
4199
4339
  destNode.children = [];
4200
4340
  }
@@ -4217,6 +4357,7 @@ var IndexedDbFileSystemService = class {
4217
4357
  `mv: ${destParentPath}: No such file or directory`
4218
4358
  );
4219
4359
  }
4360
+ this.assertModifyDir(destParent, destParentPath, "mv", dest);
4220
4361
  srcParent.children.splice(srcIndex, 1);
4221
4362
  srcNode.name = newName;
4222
4363
  if (!destParent.children) {
@@ -4240,6 +4381,9 @@ var IndexedDbFileSystemService = class {
4240
4381
  if (!node) {
4241
4382
  throw new Error(`chmod: ${path}: No such file or directory`);
4242
4383
  }
4384
+ if (this.enforcing && !this.accessPolicy.canChangeOwnership(node)) {
4385
+ this.deny("chmod", path, "Operation not permitted");
4386
+ }
4243
4387
  node.permissions = permissions;
4244
4388
  }
4245
4389
  chown(path, ownership) {
@@ -4248,6 +4392,9 @@ var IndexedDbFileSystemService = class {
4248
4392
  if (!node) {
4249
4393
  throw new Error(`chown: ${path}: No such file or directory`);
4250
4394
  }
4395
+ if (this.enforcing && !this.accessPolicy.canChangeOwnership(node, ownership)) {
4396
+ this.deny("chown", path, "Operation not permitted");
4397
+ }
4251
4398
  node.ownership = ownership;
4252
4399
  }
4253
4400
  // --- Persistence ---
@@ -4289,6 +4436,18 @@ var IndexedDbFileSystemService = class {
4289
4436
  }
4290
4437
  }
4291
4438
  // --- Private helpers ---
4439
+ /** A copy belongs to whoever made it (like `cp` without `-p`) */
4440
+ ownedByCurrentUser(node) {
4441
+ const owner = this.getDefaultOwnership();
4442
+ if (owner) {
4443
+ const apply = (n) => {
4444
+ n.ownership = { ...owner };
4445
+ n.children?.forEach(apply);
4446
+ };
4447
+ apply(node);
4448
+ }
4449
+ return node;
4450
+ }
4292
4451
  cloneNode(node) {
4293
4452
  const now = Date.now();
4294
4453
  const clone = {
@@ -4963,6 +5122,115 @@ var VirtualFsFileTransferService = class {
4963
5122
  return this._browserService.uploadFromBrowser(accept);
4964
5123
  }
4965
5124
  };
5125
+ var CliFilesPermissions = {
5126
+ /** Bypass all file permission checks */
5127
+ Admin: "files:admin",
5128
+ /** chmod/chown files you do not own */
5129
+ Chown: "files:chown"
5130
+ };
5131
+ var HOME_DIR_PERMISSIONS = "rwxr-x---";
5132
+ function authorization(context) {
5133
+ try {
5134
+ return context.services.get(ICliAuthorizationService_TOKEN);
5135
+ } catch {
5136
+ return void 0;
5137
+ }
5138
+ }
5139
+ function createFileAccessPolicy(context) {
5140
+ return {
5141
+ // Nodes without an owner (system seed, legacy data) belong to root:admin
5142
+ canAccess: (node, action) => cliCanAccess(
5143
+ context,
5144
+ node.ownership ? node : { ...node, ownership: SYSTEM_OWNERSHIP },
5145
+ action,
5146
+ [CliFilesPermissions.Admin]
5147
+ ),
5148
+ canChangeOwnership: (node, newOwnership) => {
5149
+ const auth = authorization(context);
5150
+ if (!auth) return true;
5151
+ if (auth.can(CliFilesPermissions.Admin) || auth.can(CliFilesPermissions.Chown)) return true;
5152
+ const principal = auth.getPrincipal();
5153
+ if (!principal) return false;
5154
+ const owns = !!node.ownership && (node.ownership.uid === principal.name || node.ownership.uid === principal.id);
5155
+ const keepsOwner = !newOwnership || newOwnership.uid === node.ownership?.uid || newOwnership.uid === principal.name;
5156
+ return owns && keepsOwner;
5157
+ }
5158
+ };
5159
+ }
5160
+ function followUserSession(context, fs) {
5161
+ let sessions;
5162
+ try {
5163
+ sessions = context.services.get(ICliUserSessionService_TOKEN);
5164
+ } catch {
5165
+ sessions = void 0;
5166
+ }
5167
+ if (!sessions) return void 0;
5168
+ let previousUser = null;
5169
+ return sessions.getUserSession().subscribe((session) => {
5170
+ if (!session?.user) {
5171
+ fs.setCurrentUser("", []);
5172
+ if (previousUser !== null || !canEnter(fs, fs.getCurrentDirectory())) {
5173
+ try {
5174
+ fs.setCurrentDirectory("/");
5175
+ void fs.persist();
5176
+ } catch {
5177
+ }
5178
+ }
5179
+ previousUser = null;
5180
+ return;
5181
+ }
5182
+ const user = session.user;
5183
+ fs.setCurrentUser(user.name, user.groups ?? []);
5184
+ if (user.homeDir) {
5185
+ const home = user.homeDir;
5186
+ const system = (fn) => fs.runAsSystem ? fs.runAsSystem(fn) : fn();
5187
+ if (!fs.exists(home)) {
5188
+ system(() => {
5189
+ fs.createDirectory(home, true);
5190
+ if (home !== "/") {
5191
+ fs.chown(home, { uid: user.name, gid: user.groups?.[0] ?? "users" });
5192
+ fs.chmod(home, HOME_DIR_PERMISSIONS);
5193
+ }
5194
+ });
5195
+ void fs.persist();
5196
+ }
5197
+ fs.setHomePath(home);
5198
+ if (previousUser !== user.id) {
5199
+ try {
5200
+ fs.setCurrentDirectory(home);
5201
+ } catch {
5202
+ }
5203
+ }
5204
+ }
5205
+ previousUser = user.id;
5206
+ });
5207
+ }
5208
+ function canEnter(fs, path) {
5209
+ try {
5210
+ fs.setCurrentDirectory(path);
5211
+ return true;
5212
+ } catch {
5213
+ return false;
5214
+ }
5215
+ }
5216
+ function followUserAccounts(context, fs) {
5217
+ let events;
5218
+ try {
5219
+ events = context.services.get(ICliUserEventService_TOKEN);
5220
+ } catch {
5221
+ events = void 0;
5222
+ }
5223
+ if (!events || !fs.reassignOwner) return void 0;
5224
+ return events.events$.subscribe((event) => {
5225
+ let changed = 0;
5226
+ if (event.type === "user_updated" && event.previousName && event.previousName !== event.user?.name) {
5227
+ changed = fs.reassignOwner(event.previousName, event.user.name);
5228
+ } else if (event.type === "user_deleted" && event.user?.name) {
5229
+ changed = fs.reassignOwner(event.user.name, SYSTEM_OWNERSHIP.uid);
5230
+ }
5231
+ if (changed > 0) void fs.persist().catch(() => void 0);
5232
+ });
5233
+ }
4966
5234
 
4967
5235
  // src/lib/completion/file-path-completion-provider.ts
4968
5236
  var FILE_COMMANDS = /* @__PURE__ */ new Set([
@@ -5157,6 +5425,10 @@ var filesModule = {
5157
5425
  new CliShCommandProcessor(),
5158
5426
  new CliUploadCommandProcessor()
5159
5427
  ],
5428
+ permissions: [
5429
+ { name: CliFilesPermissions.Admin, description: "Bypass file permission checks" },
5430
+ { name: CliFilesPermissions.Chown, description: "chmod/chown files owned by others" }
5431
+ ],
5160
5432
  services: [
5161
5433
  {
5162
5434
  provide: IFileSystemService_TOKEN,
@@ -5228,13 +5500,25 @@ var filesModule = {
5228
5500
  });
5229
5501
  }
5230
5502
  },
5503
+ async onAfterBoot(context) {
5504
+ const fs = context.services.getRequired(IFileSystemService_TOKEN);
5505
+ fs.setAccessPolicy?.(createFileAccessPolicy(context));
5506
+ this._sessionSubscription?.unsubscribe();
5507
+ this._sessionSubscription = followUserSession(context, fs);
5508
+ this._accountsSubscription?.unsubscribe();
5509
+ this._accountsSubscription = followUserAccounts(context, fs);
5510
+ },
5231
5511
  async onDestroy(_context) {
5232
5512
  const sub = this._dragDropSubscription;
5233
5513
  sub?.unsubscribe();
5234
5514
  this._dragDropSubscription = void 0;
5515
+ this._sessionSubscription?.unsubscribe();
5516
+ this._sessionSubscription = void 0;
5517
+ this._accountsSubscription?.unsubscribe();
5518
+ this._accountsSubscription = void 0;
5235
5519
  }
5236
5520
  };
5237
5521
 
5238
- export { CliAwkCommandProcessor, CliBasenameCommandProcessor, CliCatCommandProcessor, CliCdCommandProcessor, CliChmodCommandProcessor, CliChownCommandProcessor, CliCpCommandProcessor, CliCutCommandProcessor, CliDiffCommandProcessor, CliDirnameCommandProcessor, CliDuCommandProcessor, CliEchoCommandProcessor, CliFindCommandProcessor, CliGrepCommandProcessor, CliHeadCommandProcessor, CliLnCommandProcessor, CliLsCommandProcessor, CliMkdirCommandProcessor, CliMvCommandProcessor, CliPasteCommandProcessor, CliPwdCommandProcessor, CliRmCommandProcessor, CliRmdirCommandProcessor, CliSedCommandProcessor, CliShCommandProcessor, CliSortCommandProcessor, CliStatCommandProcessor, CliTacCommandProcessor, CliTailCommandProcessor, CliTeeCommandProcessor, CliTouchCommandProcessor, CliTrCommandProcessor, CliTreeCommandProcessor, CliUniqCommandProcessor, CliWcCommandProcessor, CliXargsCommandProcessor, FilePathCompletionProvider, IFileSystemService_TOKEN, IndexedDbFileSystemService, ServerFileSystemService, VirtualFsFileTransferService, executeScript, filesModule };
5522
+ export { ANONYMOUS_OWNERSHIP, CliAwkCommandProcessor, CliBasenameCommandProcessor, CliCatCommandProcessor, CliCdCommandProcessor, CliChmodCommandProcessor, CliChownCommandProcessor, CliCpCommandProcessor, CliCutCommandProcessor, CliDiffCommandProcessor, CliDirnameCommandProcessor, CliDuCommandProcessor, CliEchoCommandProcessor, CliFilesPermissions, CliFindCommandProcessor, CliGrepCommandProcessor, CliHeadCommandProcessor, CliLnCommandProcessor, CliLsCommandProcessor, CliMkdirCommandProcessor, CliMvCommandProcessor, CliPasteCommandProcessor, CliPwdCommandProcessor, CliRmCommandProcessor, CliRmdirCommandProcessor, CliSedCommandProcessor, CliShCommandProcessor, CliSortCommandProcessor, CliStatCommandProcessor, CliTacCommandProcessor, CliTailCommandProcessor, CliTeeCommandProcessor, CliTouchCommandProcessor, CliTrCommandProcessor, CliTreeCommandProcessor, CliUniqCommandProcessor, CliWcCommandProcessor, CliXargsCommandProcessor, FilePathCompletionProvider, HOME_DIR_PERMISSIONS, IFileSystemService_TOKEN, IndexedDbFileSystemService, SYSTEM_OWNERSHIP, ServerFileSystemService, VirtualFsFileTransferService, createFileAccessPolicy, executeScript, filesModule, followUserAccounts, followUserSession };
5239
5523
  //# sourceMappingURL=public-api.mjs.map
5240
5524
  //# sourceMappingURL=public-api.mjs.map