@softr/cli 0.1.4 → 0.1.5

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 (3) hide show
  1. package/README.md +24 -9
  2. package/dist/cli.mjs +514 -111
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -222,26 +222,41 @@ schema refresh the cache too; the CLI rewrites it at any time, so never edit it.
222
222
  ```
223
223
  crm/
224
224
  softr-db.jsonc # { "databaseId": "…" }
225
- .softr-db/schema.json # the cache
225
+ .softr-db/schema.json # the cache of the tables and fields
226
+ .softr-db/views/<tableId>.json # the views of a table, once views list read them
226
227
  ```
227
228
 
228
- | Command | What it does |
229
- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
230
- | `db list` | The databases the token reaches, across all workspaces, with their workspace and number of tables. |
231
- | `db get [<database>] [--dir <path>]` | One database with its tables, by id or exact name. Outside a database directory it creates `./<name-slug>/`; inside one it refreshes the cache, and a different database is an error. |
232
- | `db create <name> [--description <text>] [--workspace <id>] [--dir <path>]` | Create an empty database and its directory. The workspace defaults to `SOFTR_WORKSPACE_ID`, else to your only or default workspace among those where the token grants `databases.schema:write`. |
233
- | `db update [<databaseId>] [--name <name>] [--description <text>] [--dir <path>]` | Change the name, the description or both. |
234
- | `db delete [<databaseId>] [--force] [--dir <path>]` | Delete a database. One that still has tables needs `--force`, which also deletes the tables and all their records. The directory keeps its `softr-db.jsonc`; the cache goes. |
229
+ | Command | What it does |
230
+ | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
231
+ | `db list` | The databases the token reaches, across all workspaces, with their workspace and number of tables. |
232
+ | `db get [<database>] [--dir <path>]` | One database with its tables, by id or exact name. Outside a database directory it creates `./<name-slug>/`; inside one it refreshes the cache, and a different database is an error. |
233
+ | `db create <name> [--description <text>] [--workspace <id>] [--dir <path>]` | Create an empty database and its directory. The workspace defaults to `SOFTR_WORKSPACE_ID`, else to your only or default workspace among those where the token grants `databases.schema:write`. |
234
+ | `db update [<databaseId>] [--name <name>] [--description <text>] [--dir <path>]` | Change the name, the description or both. |
235
+ | `db delete [<databaseId>] [--force] [--dir <path>]` | Delete a database. One that still has tables needs `--force`, which also deletes the tables and all their records. The directory keeps its `softr-db.jsonc`; the cache goes. |
236
+ | `db tables list` / `get <table>` | The tables of the database; one table with its fields (ids, types, primary/required/read-only). |
237
+ | `db tables create <name> [--field <name:TYPE>]... [--fields <json>] [--primary-field <name>]` | Create a table with at least one field: `--field` for types without options, `--fields` (a JSON array, `@<file>` or `-`) for any; the first one names a record. |
238
+ | `db tables update <table> [--name] [--description]` / `delete <table> [--force]` | Rename or describe a table; delete it (`--force` when it still has records). |
239
+ | `db fields get <table> <field>` | One field with its options. |
240
+ | `db fields add <table> <name> --type <TYPE> [--options <json>] [--multiple]` | Add a field. The types and their options: [field reference](https://docs.softr.io/softr-api/softr-database-api/table-fields/add-table-field). |
241
+ | `db fields update <table> <field> [--name] [--type] [--options <json>] [--[no-]multiple]` | Change a field; what is not passed stays as it is, `--options` replaces the options as a whole. |
242
+ | `db fields delete <table> <field>` | Delete a field and its values in every record. |
243
+ | `db views list <table>` | The views of a table (the API reads views only). |
244
+
245
+ Tables and fields take an id or an exact name; a name two items share is an error that lists their ids. The table,
246
+ field and view commands take the database from `--database <id>`, else `SOFTR_DATABASE_ID`, else the database
247
+ directory, and keep its cache current; a delete always resolves the name on the server, never from the cache.
235
248
 
236
249
  ## Agent skills
237
250
 
238
- Two skills in the [Agent Skills](https://agentskills.io) format, published in
251
+ Three skills in the [Agent Skills](https://agentskills.io) format, published in
239
252
  [softr-io/softr-io-skills](https://github.com/softr-io/softr-io-skills):
240
253
 
241
254
  - [`softr-cli`](https://github.com/softr-io/softr-io-skills/tree/master/skills/softr-cli): install and update the CLI,
242
255
  log in, the root commands and the environment variables. Every other Softr skill builds on it.
243
256
  - [`softr-workflows`](https://github.com/softr-io/softr-io-skills/tree/master/skills/softr-workflows): the
244
257
  `softr workflows` scope, with the code-file rules in its `references/custom-code.md`.
258
+ - [`softr-db`](https://github.com/softr-io/softr-io-skills/tree/master/skills/softr-db): the `softr db` scope, the
259
+ database directory and the schema commands.
245
260
 
246
261
  Install them as a plugin in Claude Code, which keeps them updated, or with `npx skills add softr-io/softr-io-skills`
247
262
  in every other agent. The exact steps are on [docs.softr.io/softr-cli](https://docs.softr.io/softr-cli).
package/dist/cli.mjs CHANGED
@@ -1745,7 +1745,7 @@ var require_table = __commonJS({
1745
1745
  var debug = require_debug();
1746
1746
  var utils = require_utils();
1747
1747
  var tableLayout = require_layout_manager();
1748
- var Table8 = class extends Array {
1748
+ var Table9 = class extends Array {
1749
1749
  constructor(opts) {
1750
1750
  super();
1751
1751
  const options = utils.mergeOptions(opts);
@@ -1820,7 +1820,7 @@ var require_table = __commonJS({
1820
1820
  return str[0].length;
1821
1821
  }
1822
1822
  };
1823
- Table8.reset = () => debug.reset();
1823
+ Table9.reset = () => debug.reset();
1824
1824
  function doDraw(row, lineNum, result) {
1825
1825
  let line = [];
1826
1826
  row.forEach(function(cell) {
@@ -1829,7 +1829,7 @@ var require_table = __commonJS({
1829
1829
  let str = line.join("");
1830
1830
  if (str.length) result.push(str);
1831
1831
  }
1832
- module.exports = Table8;
1832
+ module.exports = Table9;
1833
1833
  }
1834
1834
  });
1835
1835
 
@@ -2598,14 +2598,14 @@ var Option = class {
2598
2598
  * @param {string} flags
2599
2599
  * @param {string} [description]
2600
2600
  */
2601
- constructor(flags, description) {
2602
- this.flags = flags;
2601
+ constructor(flags2, description) {
2602
+ this.flags = flags2;
2603
2603
  this.description = description || "";
2604
- this.required = flags.includes("<");
2605
- this.optional = flags.includes("[");
2606
- this.variadic = /\w\.\.\.[>\]]$/.test(flags);
2604
+ this.required = flags2.includes("<");
2605
+ this.optional = flags2.includes("[");
2606
+ this.variadic = /\w\.\.\.[>\]]$/.test(flags2);
2607
2607
  this.mandatory = false;
2608
- const optionFlags = splitOptionFlags(flags);
2608
+ const optionFlags = splitOptionFlags(flags2);
2609
2609
  this.short = optionFlags.shortFlag;
2610
2610
  this.long = optionFlags.longFlag;
2611
2611
  this.negate = false;
@@ -2856,12 +2856,12 @@ function camelcase(str) {
2856
2856
  return str2 + word[0].toUpperCase() + word.slice(1);
2857
2857
  });
2858
2858
  }
2859
- function splitOptionFlags(flags) {
2859
+ function splitOptionFlags(flags2) {
2860
2860
  let shortFlag;
2861
2861
  let longFlag;
2862
2862
  const shortFlagExp = /^-[^-]$/;
2863
2863
  const longFlagExp = /^--[^-]/;
2864
- const flagParts = flags.split(/[ |,]+/).concat("guard");
2864
+ const flagParts = flags2.split(/[ |,]+/).concat("guard");
2865
2865
  if (shortFlagExp.test(flagParts[0])) shortFlag = flagParts.shift();
2866
2866
  if (longFlagExp.test(flagParts[0])) longFlag = flagParts.shift();
2867
2867
  if (!shortFlag && shortFlagExp.test(flagParts[0]))
@@ -2872,7 +2872,7 @@ function splitOptionFlags(flags) {
2872
2872
  }
2873
2873
  if (flagParts[0].startsWith("-")) {
2874
2874
  const unsupportedFlag = flagParts[0];
2875
- const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
2875
+ const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags2}'`;
2876
2876
  if (/^-[^-][^-]/.test(unsupportedFlag))
2877
2877
  throw new Error(
2878
2878
  `${baseError}
@@ -2891,7 +2891,7 @@ function splitOptionFlags(flags) {
2891
2891
  }
2892
2892
  if (shortFlag === void 0 && longFlag === void 0)
2893
2893
  throw new Error(
2894
- `option creation failed due to no flags found in '${flags}'.`
2894
+ `option creation failed due to no flags found in '${flags2}'.`
2895
2895
  );
2896
2896
  return { shortFlag, longFlag };
2897
2897
  }
@@ -3454,8 +3454,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
3454
3454
  * @param {string} [description]
3455
3455
  * @return {Option} new option
3456
3456
  */
3457
- createOption(flags, description) {
3458
- return new Option(flags, description);
3457
+ createOption(flags2, description) {
3458
+ return new Option(flags2, description);
3459
3459
  }
3460
3460
  /**
3461
3461
  * Wrap parseArgs to catch 'commander.invalidArgument'.
@@ -3570,13 +3570,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
3570
3570
  * @return {Command} `this` command for chaining
3571
3571
  * @private
3572
3572
  */
3573
- _optionEx(config, flags, description, fn, defaultValue) {
3574
- if (typeof flags === "object" && flags instanceof Option) {
3573
+ _optionEx(config, flags2, description, fn, defaultValue) {
3574
+ if (typeof flags2 === "object" && flags2 instanceof Option) {
3575
3575
  throw new Error(
3576
3576
  "To add an Option object use addOption() instead of option() or requiredOption()"
3577
3577
  );
3578
3578
  }
3579
- const option = this.createOption(flags, description);
3579
+ const option = this.createOption(flags2, description);
3580
3580
  option.makeOptionMandatory(!!config.mandatory);
3581
3581
  if (typeof fn === "function") {
3582
3582
  option.default(defaultValue).argParser(fn);
@@ -3613,8 +3613,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
3613
3613
  * @param {*} [defaultValue]
3614
3614
  * @return {Command} `this` command for chaining
3615
3615
  */
3616
- option(flags, description, parseArg, defaultValue) {
3617
- return this._optionEx({}, flags, description, parseArg, defaultValue);
3616
+ option(flags2, description, parseArg, defaultValue) {
3617
+ return this._optionEx({}, flags2, description, parseArg, defaultValue);
3618
3618
  }
3619
3619
  /**
3620
3620
  * Add a required option which must have a value after parsing. This usually means
@@ -3628,10 +3628,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
3628
3628
  * @param {*} [defaultValue]
3629
3629
  * @return {Command} `this` command for chaining
3630
3630
  */
3631
- requiredOption(flags, description, parseArg, defaultValue) {
3631
+ requiredOption(flags2, description, parseArg, defaultValue) {
3632
3632
  return this._optionEx(
3633
3633
  { mandatory: true },
3634
- flags,
3634
+ flags2,
3635
3635
  description,
3636
3636
  parseArg,
3637
3637
  defaultValue
@@ -3798,18 +3798,18 @@ Expecting one of '${allowedValues.join("', '")}'`);
3798
3798
  *
3799
3799
  * @private
3800
3800
  */
3801
- _prepareUserArgs(argv, parseOptions) {
3801
+ _prepareUserArgs(argv, parseOptions2) {
3802
3802
  if (argv !== void 0 && !Array.isArray(argv)) {
3803
3803
  throw new Error("first parameter to parse must be array or undefined");
3804
3804
  }
3805
- parseOptions = parseOptions || {};
3806
- if (argv === void 0 && parseOptions.from === void 0) {
3805
+ parseOptions2 = parseOptions2 || {};
3806
+ if (argv === void 0 && parseOptions2.from === void 0) {
3807
3807
  if (process2.versions?.electron) {
3808
- parseOptions.from = "electron";
3808
+ parseOptions2.from = "electron";
3809
3809
  }
3810
3810
  const execArgv = process2.execArgv ?? [];
3811
3811
  if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
3812
- parseOptions.from = "eval";
3812
+ parseOptions2.from = "eval";
3813
3813
  }
3814
3814
  }
3815
3815
  if (argv === void 0) {
@@ -3817,7 +3817,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3817
3817
  }
3818
3818
  this.rawArgs = argv.slice();
3819
3819
  let userArgs;
3820
- switch (parseOptions.from) {
3820
+ switch (parseOptions2.from) {
3821
3821
  case void 0:
3822
3822
  case "node":
3823
3823
  this._scriptPath = argv[1];
@@ -3839,7 +3839,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3839
3839
  break;
3840
3840
  default:
3841
3841
  throw new Error(
3842
- `unexpected parse option { from: '${parseOptions.from}' }`
3842
+ `unexpected parse option { from: '${parseOptions2.from}' }`
3843
3843
  );
3844
3844
  }
3845
3845
  if (!this._name && this._scriptPath)
@@ -3869,9 +3869,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3869
3869
  * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
3870
3870
  * @return {Command} `this` command for chaining
3871
3871
  */
3872
- parse(argv, parseOptions) {
3872
+ parse(argv, parseOptions2) {
3873
3873
  this._prepareForParse();
3874
- const userArgs = this._prepareUserArgs(argv, parseOptions);
3874
+ const userArgs = this._prepareUserArgs(argv, parseOptions2);
3875
3875
  this._parseCommand([], userArgs);
3876
3876
  return this;
3877
3877
  }
@@ -3895,9 +3895,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3895
3895
  * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
3896
3896
  * @return {Promise}
3897
3897
  */
3898
- async parseAsync(argv, parseOptions) {
3898
+ async parseAsync(argv, parseOptions2) {
3899
3899
  this._prepareForParse();
3900
- const userArgs = this._prepareUserArgs(argv, parseOptions);
3900
+ const userArgs = this._prepareUserArgs(argv, parseOptions2);
3901
3901
  await this._parseCommand([], userArgs);
3902
3902
  return this;
3903
3903
  }
@@ -4748,12 +4748,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
4748
4748
  * @param {string} [description]
4749
4749
  * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
4750
4750
  */
4751
- version(str, flags, description) {
4751
+ version(str, flags2, description) {
4752
4752
  if (str === void 0) return this._version;
4753
4753
  this._version = str;
4754
- flags = flags || "-V, --version";
4754
+ flags2 = flags2 || "-V, --version";
4755
4755
  description = description || "output the version number";
4756
- const versionOption = this.createOption(flags, description);
4756
+ const versionOption = this.createOption(flags2, description);
4757
4757
  this._versionOptionName = versionOption.attributeName();
4758
4758
  this._registerOption(versionOption);
4759
4759
  this.on("option:" + versionOption.name(), () => {
@@ -5055,9 +5055,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
5055
5055
  * @param {string} [description]
5056
5056
  * @return {Command} `this` command for chaining
5057
5057
  */
5058
- helpOption(flags, description) {
5059
- if (typeof flags === "boolean") {
5060
- if (flags) {
5058
+ helpOption(flags2, description) {
5059
+ if (typeof flags2 === "boolean") {
5060
+ if (flags2) {
5061
5061
  if (this._helpOption === null) this._helpOption = void 0;
5062
5062
  if (this._defaultOptionGroup) {
5063
5063
  this._initOptionGroup(this._getHelpOption());
@@ -5068,10 +5068,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
5068
5068
  return this;
5069
5069
  }
5070
5070
  this._helpOption = this.createOption(
5071
- flags ?? "-h, --help",
5071
+ flags2 ?? "-h, --help",
5072
5072
  description ?? "display help for command"
5073
5073
  );
5074
- if (flags || description) this._initOptionGroup(this._helpOption);
5074
+ if (flags2 || description) this._initOptionGroup(this._helpOption);
5075
5075
  return this;
5076
5076
  }
5077
5077
  /**
@@ -6177,6 +6177,9 @@ function globalOptions(command) {
6177
6177
  function emit(options, human, data2) {
6178
6178
  process.stdout.write(options.json ? JSON.stringify(data2, null, 2) + "\n" : human + "\n");
6179
6179
  }
6180
+ function plainTable(head, colWidths) {
6181
+ return { head, colWidths, wordWrap: true, style: { head: [], border: [], "padding-left": 1, "padding-right": 1 } };
6182
+ }
6180
6183
  function reportError(error) {
6181
6184
  if (error instanceof CliError || error instanceof SoftrApiError) {
6182
6185
  process.stderr.write(`Error: ${error.message}
@@ -7636,8 +7639,8 @@ function parseJsoncFile(file, text) {
7636
7639
  async function listSubdirsWith(cwd, holds) {
7637
7640
  const entries = await readdir(cwd, { withFileTypes: true });
7638
7641
  const dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path2.join(cwd, entry.name));
7639
- const flags = await Promise.all(dirs.map((dir) => holds(dir)));
7640
- return dirs.filter((_, index) => flags[index]);
7642
+ const flags2 = await Promise.all(dirs.map((dir) => holds(dir)));
7643
+ return dirs.filter((_, index) => flags2[index]);
7641
7644
  }
7642
7645
  async function readConfig(dir) {
7643
7646
  const file = configPath(dir);
@@ -8284,12 +8287,12 @@ prototypeAccessors.inAsync.get = function() {
8284
8287
  prototypeAccessors.canAwait.get = function() {
8285
8288
  for (var i2 = this.scopeStack.length - 1; i2 >= 0; i2--) {
8286
8289
  var ref2 = this.scopeStack[i2];
8287
- var flags = ref2.flags;
8288
- if (flags & (SCOPE_CLASS_STATIC_BLOCK | SCOPE_CLASS_FIELD_INIT)) {
8290
+ var flags2 = ref2.flags;
8291
+ if (flags2 & (SCOPE_CLASS_STATIC_BLOCK | SCOPE_CLASS_FIELD_INIT)) {
8289
8292
  return false;
8290
8293
  }
8291
- if (flags & SCOPE_FUNCTION) {
8292
- return (flags & SCOPE_ASYNC) > 0;
8294
+ if (flags2 & SCOPE_FUNCTION) {
8295
+ return (flags2 & SCOPE_ASYNC) > 0;
8293
8296
  }
8294
8297
  }
8295
8298
  return this.inModule && this.options.ecmaVersion >= 13 || this.options.allowAwaitOutsideFunction;
@@ -8305,8 +8308,8 @@ prototypeAccessors.allowReturn.get = function() {
8305
8308
  };
8306
8309
  prototypeAccessors.allowSuper.get = function() {
8307
8310
  var ref2 = this.currentThisScope();
8308
- var flags = ref2.flags;
8309
- return (flags & SCOPE_SUPER) > 0 || this.options.allowSuperOutsideMethod;
8311
+ var flags2 = ref2.flags;
8312
+ return (flags2 & SCOPE_SUPER) > 0 || this.options.allowSuperOutsideMethod;
8310
8313
  };
8311
8314
  prototypeAccessors.allowDirectSuper.get = function() {
8312
8315
  return (this.currentThisScope().flags & SCOPE_DIRECT_SUPER) > 0;
@@ -8317,8 +8320,8 @@ prototypeAccessors.treatFunctionsAsVar.get = function() {
8317
8320
  prototypeAccessors.allowNewDotTarget.get = function() {
8318
8321
  for (var i2 = this.scopeStack.length - 1; i2 >= 0; i2--) {
8319
8322
  var ref2 = this.scopeStack[i2];
8320
- var flags = ref2.flags;
8321
- if (flags & (SCOPE_CLASS_STATIC_BLOCK | SCOPE_CLASS_FIELD_INIT) || flags & SCOPE_FUNCTION && !(flags & SCOPE_ARROW)) {
8323
+ var flags2 = ref2.flags;
8324
+ if (flags2 & (SCOPE_CLASS_STATIC_BLOCK | SCOPE_CLASS_FIELD_INIT) || flags2 & SCOPE_FUNCTION && !(flags2 & SCOPE_ARROW)) {
8322
8325
  return true;
8323
8326
  }
8324
8327
  }
@@ -8326,11 +8329,11 @@ prototypeAccessors.allowNewDotTarget.get = function() {
8326
8329
  };
8327
8330
  prototypeAccessors.allowUsing.get = function() {
8328
8331
  var ref2 = this.currentScope();
8329
- var flags = ref2.flags;
8330
- if (flags & SCOPE_SWITCH) {
8332
+ var flags2 = ref2.flags;
8333
+ if (flags2 & SCOPE_SWITCH) {
8331
8334
  return false;
8332
8335
  }
8333
- if (!this.inModule && flags & SCOPE_TOP) {
8336
+ if (!this.inModule && flags2 & SCOPE_TOP) {
8334
8337
  return false;
8335
8338
  }
8336
8339
  return true;
@@ -11111,14 +11114,14 @@ pp$4.curPosition = function() {
11111
11114
  }
11112
11115
  };
11113
11116
  var pp$3 = Parser.prototype;
11114
- var Scope = function Scope2(flags) {
11115
- this.flags = flags;
11117
+ var Scope = function Scope2(flags2) {
11118
+ this.flags = flags2;
11116
11119
  this.var = [];
11117
11120
  this.lexical = [];
11118
11121
  this.functions = [];
11119
11122
  };
11120
- pp$3.enterScope = function(flags) {
11121
- this.scopeStack.push(new Scope(flags));
11123
+ pp$3.enterScope = function(flags2) {
11124
+ this.scopeStack.push(new Scope(flags2));
11122
11125
  };
11123
11126
  pp$3.exitScope = function() {
11124
11127
  this.scopeStack.pop();
@@ -11334,12 +11337,12 @@ var RegExpValidationState = function RegExpValidationState2(parser) {
11334
11337
  this.backReferenceNames = [];
11335
11338
  this.branchID = null;
11336
11339
  };
11337
- RegExpValidationState.prototype.reset = function reset(start, pattern, flags) {
11338
- var unicodeSets = flags.indexOf("v") !== -1;
11339
- var unicode = flags.indexOf("u") !== -1;
11340
+ RegExpValidationState.prototype.reset = function reset(start, pattern, flags2) {
11341
+ var unicodeSets = flags2.indexOf("v") !== -1;
11342
+ var unicode = flags2.indexOf("u") !== -1;
11340
11343
  this.start = start | 0;
11341
11344
  this.source = pattern + "";
11342
- this.flags = flags;
11345
+ this.flags = flags2;
11343
11346
  if (unicodeSets && this.parser.options.ecmaVersion >= 15) {
11344
11347
  this.switchU = true;
11345
11348
  this.switchV = true;
@@ -11416,15 +11419,15 @@ RegExpValidationState.prototype.eatChars = function eatChars(chs, forceU) {
11416
11419
  };
11417
11420
  pp$1.validateRegExpFlags = function(state) {
11418
11421
  var validFlags = state.validFlags;
11419
- var flags = state.flags;
11422
+ var flags2 = state.flags;
11420
11423
  var u = false;
11421
11424
  var v = false;
11422
- for (var i2 = 0; i2 < flags.length; i2++) {
11423
- var flag = flags.charAt(i2);
11425
+ for (var i2 = 0; i2 < flags2.length; i2++) {
11426
+ var flag = flags2.charAt(i2);
11424
11427
  if (validFlags.indexOf(flag) === -1) {
11425
11428
  this.raise(state.start, "Invalid regular expression flag");
11426
11429
  }
11427
- if (flags.indexOf(flag, i2 + 1) > -1) {
11430
+ if (flags2.indexOf(flag, i2 + 1) > -1) {
11428
11431
  this.raise(state.start, "Duplicate regular expression flag");
11429
11432
  }
11430
11433
  if (flag === "u") {
@@ -13036,20 +13039,20 @@ pp.readRegexp = function() {
13036
13039
  var pattern = this.input.slice(start, this.pos);
13037
13040
  ++this.pos;
13038
13041
  var flagsStart = this.pos;
13039
- var flags = this.readWord1();
13042
+ var flags2 = this.readWord1();
13040
13043
  if (this.containsEsc) {
13041
13044
  this.unexpected(flagsStart);
13042
13045
  }
13043
13046
  var state = this.regexpState || (this.regexpState = new RegExpValidationState(this));
13044
- state.reset(start, pattern, flags);
13047
+ state.reset(start, pattern, flags2);
13045
13048
  this.validateRegExpFlags(state);
13046
13049
  this.validateRegExpPattern(state);
13047
13050
  var value = null;
13048
13051
  try {
13049
- value = new RegExp(pattern, flags);
13052
+ value = new RegExp(pattern, flags2);
13050
13053
  } catch (e) {
13051
13054
  }
13052
- return this.finishToken(types$1.regexp, { pattern, flags, value });
13055
+ return this.finishToken(types$1.regexp, { pattern, flags: flags2, value });
13053
13056
  };
13054
13057
  pp.readInt = function(radix, len, maybeLegacyOctalNumericLiteral) {
13055
13058
  var allowSeparators = this.options.ecmaVersion >= 12 && len === void 0;
@@ -14575,6 +14578,36 @@ async function deleteDatabase(client, databaseId, force) {
14575
14578
  async function listTables(client, databaseId) {
14576
14579
  return (await tablesRequest(client, "GET", `${DATABASES}/${encodeURIComponent(databaseId)}/tables`)).data;
14577
14580
  }
14581
+ function tablePath(databaseId, tableId) {
14582
+ const tables = `${DATABASES}/${encodeURIComponent(databaseId)}/tables`;
14583
+ return tableId ? `${tables}/${encodeURIComponent(tableId)}` : tables;
14584
+ }
14585
+ async function createTable(client, databaseId, table) {
14586
+ return (await tablesRequest(client, "POST", tablePath(databaseId), table)).data;
14587
+ }
14588
+ async function updateTable(client, databaseId, tableId, table) {
14589
+ return (await tablesRequest(client, "PUT", tablePath(databaseId, tableId), table)).data;
14590
+ }
14591
+ async function deleteTable(client, databaseId, tableId, force) {
14592
+ await tablesRequestVoid(client, "DELETE", `${tablePath(databaseId, tableId)}${force ? "?force=true" : ""}`);
14593
+ }
14594
+ async function addField(client, databaseId, tableId, field) {
14595
+ return (await tablesRequest(client, "POST", `${tablePath(databaseId, tableId)}/fields`, field)).data;
14596
+ }
14597
+ async function updateField(client, databaseId, tableId, fieldId, field) {
14598
+ return (await tablesRequest(
14599
+ client,
14600
+ "PUT",
14601
+ `${tablePath(databaseId, tableId)}/fields/${encodeURIComponent(fieldId)}`,
14602
+ field
14603
+ )).data;
14604
+ }
14605
+ async function deleteField(client, databaseId, tableId, fieldId) {
14606
+ await tablesRequestVoid(client, "DELETE", `${tablePath(databaseId, tableId)}/fields/${encodeURIComponent(fieldId)}`);
14607
+ }
14608
+ async function listViews(client, databaseId, tableId) {
14609
+ return (await tablesRequest(client, "GET", `${tablePath(databaseId, tableId)}/views`)).data;
14610
+ }
14578
14611
 
14579
14612
  // src/config/credentials.ts
14580
14613
  import { chmod, mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
@@ -14791,8 +14824,26 @@ async function authenticatedClient(target) {
14791
14824
  }
14792
14825
 
14793
14826
  // src/project/db-dir.ts
14794
- import { mkdir as mkdir5, readFile as readFile6, rm, writeFile as writeFile6 } from "node:fs/promises";
14827
+ import { mkdir as mkdir5, readdir as readdir2, readFile as readFile6, rm, writeFile as writeFile6 } from "node:fs/promises";
14795
14828
  import path8 from "node:path";
14829
+
14830
+ // src/project/lookup.ts
14831
+ function byIdOrName(items, reference, kind, describe2 = (item) => item.id) {
14832
+ const byId = items.find((item) => item.id === reference);
14833
+ if (byId) {
14834
+ return byId;
14835
+ }
14836
+ const byName = items.filter((item) => item.name === reference);
14837
+ if (byName.length > 1) {
14838
+ throw new CliError(
14839
+ `Several ${kind}s are named "${reference}".`,
14840
+ `Pass the id of one: ${byName.map(describe2).join(", ")}`
14841
+ );
14842
+ }
14843
+ return byName[0];
14844
+ }
14845
+
14846
+ // src/project/db-dir.ts
14796
14847
  var DB_CONFIG_FILE_NAME = "softr-db.jsonc";
14797
14848
  var DB_CACHE_DIR_NAME = ".softr-db";
14798
14849
  var DATABASE_ID_ENV_VAR = "SOFTR_DATABASE_ID";
@@ -14841,8 +14892,31 @@ async function writeSchemaCache(dir, database, tables) {
14841
14892
  const cache = { database, tables, refreshedAt: (/* @__PURE__ */ new Date()).toISOString() };
14842
14893
  await mkdir5(path8.dirname(cacheFile(dir)), { recursive: true });
14843
14894
  await writeFile6(cacheFile(dir), JSON.stringify(cache, null, 2) + "\n");
14895
+ await pruneViewsCache(dir, new Set(tables.map((table) => table.id)));
14844
14896
  return cache;
14845
14897
  }
14898
+ function viewsDir(dir) {
14899
+ return path8.join(dir, DB_CACHE_DIR_NAME, "views");
14900
+ }
14901
+ async function writeViewsCache(dir, tableId, views) {
14902
+ const value = { tableId, views, refreshedAt: (/* @__PURE__ */ new Date()).toISOString() };
14903
+ await mkdir5(viewsDir(dir), { recursive: true });
14904
+ await writeFile6(path8.join(viewsDir(dir), `${tableId}.json`), JSON.stringify(value, null, 2) + "\n");
14905
+ }
14906
+ async function pruneViewsCache(dir, tableIds) {
14907
+ let names;
14908
+ try {
14909
+ names = await readdir2(viewsDir(dir));
14910
+ } catch (error) {
14911
+ if (error.code === "ENOENT") {
14912
+ return;
14913
+ }
14914
+ throw error;
14915
+ }
14916
+ await Promise.all(
14917
+ names.filter((name) => name.endsWith(".json") && !tableIds.has(name.slice(0, -".json".length))).map((name) => rm(path8.join(viewsDir(dir), name), { force: true }))
14918
+ );
14919
+ }
14846
14920
  async function refreshSchemaCache(client, dir, databaseId) {
14847
14921
  const [database, tables] = await Promise.all([getDatabase(client, databaseId), listTables(client, databaseId)]);
14848
14922
  return writeSchemaCache(dir, database, tables);
@@ -14903,25 +14977,19 @@ async function databaseDirFor(cwd, explicitDir, databaseId, dirName) {
14903
14977
  return { dir: sibling2, existing: false };
14904
14978
  }
14905
14979
  async function findReachableDatabase(client, reference) {
14906
- const reachable = await listDatabases(client);
14907
- const byId = reachable.find((database) => database.id === reference);
14908
- if (byId) {
14909
- return byId;
14910
- }
14911
- const byName = reachable.filter((database) => database.name === reference);
14912
- if (byName.length > 1) {
14913
- throw new CliError(
14914
- `Several databases are named "${reference}".`,
14915
- `Pass the id of one: ${byName.map((database) => `${database.id} (workspace ${database.workspaceId})`).join(", ")}`
14916
- );
14917
- }
14918
- if (!byName[0]) {
14980
+ const database = byIdOrName(
14981
+ await listDatabases(client),
14982
+ reference,
14983
+ "database",
14984
+ (item) => `${item.id} (workspace ${item.workspaceId})`
14985
+ );
14986
+ if (!database) {
14919
14987
  throw new CliError(
14920
14988
  `The token reaches no database with the id or name "${reference}".`,
14921
14989
  '"softr db list" shows the ones it reaches.'
14922
14990
  );
14923
14991
  }
14924
- return byName[0];
14992
+ return database;
14925
14993
  }
14926
14994
  async function bindDatabaseDir(client, cwd, reference, explicitDir) {
14927
14995
  const here = path8.resolve(cwd, explicitDir ?? ".");
@@ -14944,6 +15012,10 @@ async function bindDatabaseDir(client, cwd, reference, explicitDir) {
14944
15012
  return { databaseId: database.id, dir, wrote: !existing };
14945
15013
  }
14946
15014
 
15015
+ // src/commands/db/help.ts
15016
+ var DATABASE_ID_HELP = `the database id (default: $SOFTR_DATABASE_ID, else the one in ${DB_CONFIG_FILE_NAME})`;
15017
+ var DATABASE_DIR_HELP = `database directory, the one holding ${DB_CONFIG_FILE_NAME} (default: the current directory)`;
15018
+
14947
15019
  // src/api/studio.ts
14948
15020
  async function resolveTokenOwner(client) {
14949
15021
  try {
@@ -15420,18 +15492,11 @@ Next: softr workflows add <TYPE> --dir ${path10.relative(cwd, dir) || "."} (see
15420
15492
  }
15421
15493
 
15422
15494
  // src/commands/db/databases.ts
15423
- var DATABASE_ARGUMENT = `the database id (default: $SOFTR_DATABASE_ID, else the one in ${DB_CONFIG_FILE_NAME})`;
15424
- var DIR_OPTION = `database directory, the one holding ${DB_CONFIG_FILE_NAME} (default: the current directory)`;
15425
15495
  function renderDatabases(databases) {
15426
15496
  if (!databases.length) {
15427
15497
  return "The token reaches no database. Create one with: softr db create <name>";
15428
15498
  }
15429
- const table = new import_cli_table3.default({
15430
- head: ["ID", "Name", "Workspace", "Tables"],
15431
- colWidths: [38, 34, 38, 8],
15432
- wordWrap: true,
15433
- style: { head: [], border: [], "padding-left": 1, "padding-right": 1 }
15434
- });
15499
+ const table = new import_cli_table3.default(plainTable(["ID", "Name", "Workspace", "Tables"], [38, 34, 38, 8]));
15435
15500
  for (const database of databases) {
15436
15501
  table.push([database.id, database.name, database.workspaceId, String(database.tablesCount)]);
15437
15502
  }
@@ -15502,7 +15567,7 @@ Wrote ${path11.join(relativeDir(cwd, dir), DB_CONFIG_FILE_NAME)}.` : "";
15502
15567
  );
15503
15568
  }
15504
15569
  );
15505
- db2.command("update").description("Change a database's name or description").argument("[databaseId]", DATABASE_ARGUMENT).option("--name <name>", "the new name").option("--description <text>", "the new description").option("--dir <path>", DIR_OPTION).action(
15570
+ db2.command("update").description("Change a database's name or description").argument("[databaseId]", DATABASE_ID_HELP).option("--name <name>", "the new name").option("--description <text>", "the new description").option("--dir <path>", DATABASE_DIR_HELP).action(
15506
15571
  async (databaseId, opts, command) => {
15507
15572
  if (!opts.name && !opts.description) {
15508
15573
  throw new CliError("Nothing to change: pass --name, --description or both.");
@@ -15521,7 +15586,7 @@ Wrote ${path11.join(relativeDir(cwd, dir), DB_CONFIG_FILE_NAME)}.` : "";
15521
15586
  emit(options, `Updated database ${database.name} (${database.id}).`, { database });
15522
15587
  }
15523
15588
  );
15524
- db2.command("delete").description("Delete a database; one that still has tables needs --force").argument("[databaseId]", DATABASE_ARGUMENT).option("--force", "also delete its tables and all their records").option("--dir <path>", DIR_OPTION).action(async (databaseId, opts, command) => {
15589
+ db2.command("delete").description("Delete a database; one that still has tables needs --force").argument("[databaseId]", DATABASE_ID_HELP).option("--force", "also delete its tables and all their records").option("--dir <path>", DATABASE_DIR_HELP).action(async (databaseId, opts, command) => {
15525
15590
  const options = globalOptions(command);
15526
15591
  const target = await resolveDatabase(process.cwd(), databaseId, opts.dir);
15527
15592
  const client = await authenticatedClient(resolveTarget());
@@ -15538,8 +15603,343 @@ ${path11.join(relativeDir(process.cwd(), target.dir), DB_CONFIG_FILE_NAME)} stil
15538
15603
  });
15539
15604
  }
15540
15605
 
15541
- // src/commands/integrations.ts
15606
+ // src/commands/db/schema.ts
15542
15607
  var import_cli_table32 = __toESM(require_cli_table3(), 1);
15608
+
15609
+ // src/commands/db/json-input.ts
15610
+ import { readFile as readFile8 } from "node:fs/promises";
15611
+ async function readJsonArgument(value, option, hint) {
15612
+ let text = value;
15613
+ if (value === "-") {
15614
+ const chunks = [];
15615
+ for await (const chunk of process.stdin) {
15616
+ chunks.push(chunk);
15617
+ }
15618
+ text = Buffer.concat(chunks).toString("utf8");
15619
+ } else if (value.startsWith("@")) {
15620
+ text = await readFile8(value.slice(1), "utf8");
15621
+ }
15622
+ try {
15623
+ return JSON.parse(text);
15624
+ } catch {
15625
+ throw new CliError(`${option} is not valid JSON.`, hint ?? "Pass inline JSON, @<file> or - for stdin.");
15626
+ }
15627
+ }
15628
+
15629
+ // src/commands/db/schema.ts
15630
+ var FIELD_REFERENCE_URL = "https://docs.softr.io/softr-api/softr-database-api/table-fields/add-table-field";
15631
+ var TABLE_ARGUMENT = "the table id or exact name";
15632
+ var FIELD_ARGUMENT = "the field id or exact name";
15633
+ function withDatabaseOptions(command) {
15634
+ return command.option("--database <id>", DATABASE_ID_HELP).option("--dir <path>", DATABASE_DIR_HELP);
15635
+ }
15636
+ async function schemaContext(opts) {
15637
+ const target = await resolveDatabase(process.cwd(), opts.database, opts.dir);
15638
+ const client = await authenticatedClient(resolveTarget());
15639
+ const cache = target.dir ? await readSchemaCache(target.dir) : void 0;
15640
+ return { client, databaseId: target.databaseId, dir: target.dir, cache };
15641
+ }
15642
+ async function currentTables(context, fresh) {
15643
+ if (!context.dir) {
15644
+ return listTables(context.client, context.databaseId);
15645
+ }
15646
+ if (!fresh && context.cache) {
15647
+ return context.cache.tables;
15648
+ }
15649
+ context.cache = await refreshSchemaCache(context.client, context.dir, context.databaseId);
15650
+ return context.cache.tables;
15651
+ }
15652
+ function tableIn(context, tables, reference) {
15653
+ const table = byIdOrName(tables, reference, "table");
15654
+ if (!table) {
15655
+ throw new CliError(
15656
+ `Database ${context.databaseId} has no table with the id or name "${reference}".`,
15657
+ '"softr db tables list" shows its tables.'
15658
+ );
15659
+ }
15660
+ return table;
15661
+ }
15662
+ async function findTableOnServer(context, reference) {
15663
+ return tableIn(context, await currentTables(context, true), reference);
15664
+ }
15665
+ async function findTableCached(context, reference) {
15666
+ const cached = context.dir && context.cache ? byIdOrName(context.cache.tables, reference, "table") : void 0;
15667
+ return cached ?? findTableOnServer(context, reference);
15668
+ }
15669
+ async function findField(context, tableReference, reference) {
15670
+ const table = await findTableOnServer(context, tableReference);
15671
+ const field = byIdOrName(table.fields, reference, "field");
15672
+ if (!field) {
15673
+ throw new CliError(
15674
+ `Table ${table.name} has no field with the id or name "${reference}".`,
15675
+ `"softr db tables get ${table.id}" shows its fields.`
15676
+ );
15677
+ }
15678
+ return { table, field };
15679
+ }
15680
+ async function afterSchemaWrite(context) {
15681
+ if (context.dir) {
15682
+ context.cache = await refreshSchemaCache(context.client, context.dir, context.databaseId);
15683
+ }
15684
+ }
15685
+ function parseOptions(json) {
15686
+ if (json === void 0) {
15687
+ return void 0;
15688
+ }
15689
+ try {
15690
+ return JSON.parse(json);
15691
+ } catch {
15692
+ throw new CliError("--options is not valid JSON.", `The options of each field type: ${FIELD_REFERENCE_URL}`);
15693
+ }
15694
+ }
15695
+ function parseFieldSpec(spec) {
15696
+ const colon = spec.lastIndexOf(":");
15697
+ const name = colon > 0 ? spec.slice(0, colon).trim() : "";
15698
+ const type = colon > 0 ? spec.slice(colon + 1).trim().toUpperCase() : "";
15699
+ if (!name || !type) {
15700
+ throw new CliError(`--field "${spec}" must be <name>:<TYPE>, for example "Due date:DATE".`);
15701
+ }
15702
+ return { name, type };
15703
+ }
15704
+ function parseFieldList(value) {
15705
+ if (!Array.isArray(value)) {
15706
+ throw new CliError(
15707
+ "--fields must be a JSON array of fields.",
15708
+ `For example: [{"name": "Stage", "type": "SELECT", "options": {...}}]`
15709
+ );
15710
+ }
15711
+ return value.map((item, index) => {
15712
+ const field = item;
15713
+ if (!field || typeof field !== "object" || typeof field.name !== "string" || !field.name || typeof field.type !== "string" || !field.type) {
15714
+ throw new CliError(`--fields item ${index + 1} needs a "name" and a "type".`);
15715
+ }
15716
+ return { ...field, name: field.name, type: field.type.toUpperCase() };
15717
+ });
15718
+ }
15719
+ function renderTables(tables) {
15720
+ if (!tables.length) {
15721
+ return "The database has no tables. Create one with: softr db tables create <name>";
15722
+ }
15723
+ const table = new import_cli_table32.default(plainTable(["ID", "Name", "Fields", "Primary field"], [18, 34, 8, 30]));
15724
+ for (const item of tables) {
15725
+ const primary = item.fields.find((field) => field.id === item.primaryFieldId);
15726
+ table.push([item.id, item.name, String(item.fields.length), primary?.name ?? item.primaryFieldId]);
15727
+ }
15728
+ return table.toString();
15729
+ }
15730
+ function flags(field, primaryFieldId) {
15731
+ return [
15732
+ field.id === primaryFieldId ? "primary" : "",
15733
+ field.required ? "required" : "",
15734
+ field.readonly ? "read-only" : "",
15735
+ field.allowMultipleEntries ? "multiple" : ""
15736
+ ].filter(Boolean).join(", ");
15737
+ }
15738
+ function renderTable(item) {
15739
+ const fields = new import_cli_table32.default(plainTable(["ID", "Name", "Type", ""], [18, 30, 18, 30]));
15740
+ for (const field of item.fields) {
15741
+ fields.push([field.id, field.name, field.type, flags(field, item.primaryFieldId)]);
15742
+ }
15743
+ return [
15744
+ `${item.name} (${item.id})`,
15745
+ ...item.description ? [` ${item.description}`] : [],
15746
+ ` Default view: ${item.defaultViewId}`,
15747
+ fields.toString()
15748
+ ].join("\n");
15749
+ }
15750
+ function renderField(table, field) {
15751
+ return [
15752
+ `${field.name} (${field.id}) in ${table.name}`,
15753
+ ...field.description ? [` ${field.description}`] : [],
15754
+ ` Type: ${field.type}`,
15755
+ ...flags(field, table.primaryFieldId) ? [` Flags: ${flags(field, table.primaryFieldId)}`] : [],
15756
+ ` Options: ${JSON.stringify(field.options ?? null)}`
15757
+ ].join("\n");
15758
+ }
15759
+ function renderViews(views) {
15760
+ const table = new import_cli_table32.default(plainTable(["ID", "Name"], [18, 50]));
15761
+ for (const view of views) {
15762
+ table.push([view.id, view.name]);
15763
+ }
15764
+ return table.toString();
15765
+ }
15766
+ function registerTables(db2) {
15767
+ const tables = db2.command("tables").description("The tables of a database, with their fields");
15768
+ withDatabaseOptions(tables.command("list").description("List the tables of the database")).action(
15769
+ async (opts, command) => {
15770
+ const options = globalOptions(command);
15771
+ const context = await schemaContext(opts);
15772
+ const items = await currentTables(context, true);
15773
+ emit(options, renderTables(items), { databaseId: context.databaseId, tables: items });
15774
+ }
15775
+ );
15776
+ withDatabaseOptions(
15777
+ tables.command("get").description("Show a table with its fields").argument("<table>", TABLE_ARGUMENT)
15778
+ ).action(async (reference, opts, command) => {
15779
+ const options = globalOptions(command);
15780
+ const context = await schemaContext(opts);
15781
+ const table = await findTableOnServer(context, reference);
15782
+ emit(options, renderTable(table), { table });
15783
+ });
15784
+ withDatabaseOptions(
15785
+ tables.command("create").description(
15786
+ "Create a table with at least one field; the first one names a record unless --primary-field says otherwise"
15787
+ ).argument("<name>", "the table name").option("--description <text>", "what the table holds").option(
15788
+ "--field <name:TYPE>",
15789
+ "a field of a type without options, repeatable",
15790
+ (value, previous) => [...previous, value],
15791
+ []
15792
+ ).option(
15793
+ "--fields <json>",
15794
+ `fields as a JSON array of {name, type, options?, description?, allowMultipleEntries?}, @<file> or - for stdin; they follow the --field ones (types and options: ${FIELD_REFERENCE_URL})`
15795
+ ).option("--primary-field <name>", "the field that names a record (default: the first field)")
15796
+ ).action(
15797
+ async (name, opts, command) => {
15798
+ const options = globalOptions(command);
15799
+ const fields = [
15800
+ ...opts.field.map(parseFieldSpec),
15801
+ ...opts.fields === void 0 ? [] : parseFieldList(await readJsonArgument(opts.fields, "--fields"))
15802
+ ];
15803
+ if (!fields.length) {
15804
+ throw new CliError(
15805
+ 'A table needs at least one field: pass --field <name>:<TYPE>, for example --field "Name:SINGLE_LINE_TEXT", or --fields with a JSON array.',
15806
+ "The first field names a record unless --primary-field names another."
15807
+ );
15808
+ }
15809
+ if (opts.primaryField && !fields.some((field) => field.name === opts.primaryField)) {
15810
+ throw new CliError(`--primary-field "${opts.primaryField}" is not one of the fields' names.`);
15811
+ }
15812
+ const context = await schemaContext(opts);
15813
+ const table = await createTable(context.client, context.databaseId, {
15814
+ name,
15815
+ description: opts.description,
15816
+ primaryFieldName: opts.primaryField ?? fields[0].name,
15817
+ fields
15818
+ });
15819
+ await afterSchemaWrite(context);
15820
+ emit(options, `Created table ${table.name} (${table.id}) with ${table.fields.length} fields.`, { table });
15821
+ }
15822
+ );
15823
+ withDatabaseOptions(
15824
+ tables.command("update").description("Change a table's name or description").argument("<table>", TABLE_ARGUMENT).option("--name <name>", "the new name").option("--description <text>", "the new description")
15825
+ ).action(
15826
+ async (reference, opts, command) => {
15827
+ if (!opts.name && opts.description === void 0) {
15828
+ throw new CliError("Nothing to change: pass --name, --description or both.");
15829
+ }
15830
+ const options = globalOptions(command);
15831
+ const context = await schemaContext(opts);
15832
+ const current2 = await findTableOnServer(context, reference);
15833
+ const table = await updateTable(context.client, context.databaseId, current2.id, {
15834
+ name: opts.name ?? current2.name,
15835
+ description: opts.description ?? current2.description
15836
+ });
15837
+ await afterSchemaWrite(context);
15838
+ emit(options, `Updated table ${table.name} (${table.id}).`, { table });
15839
+ }
15840
+ );
15841
+ withDatabaseOptions(
15842
+ tables.command("delete").description("Delete a table; one that still has records needs --force").argument("<table>", TABLE_ARGUMENT).option("--force", "also delete all its records")
15843
+ ).action(async (reference, opts, command) => {
15844
+ const options = globalOptions(command);
15845
+ const context = await schemaContext(opts);
15846
+ const table = await findTableOnServer(context, reference);
15847
+ await deleteTable(context.client, context.databaseId, table.id, opts.force === true);
15848
+ await afterSchemaWrite(context);
15849
+ emit(options, `Deleted table ${table.name} (${table.id}).`, { deleted: table.id });
15850
+ });
15851
+ }
15852
+ function registerFields(db2) {
15853
+ const fields = db2.command("fields").description("The fields of a table");
15854
+ withDatabaseOptions(
15855
+ fields.command("get").description("Show a field with its options").argument("<table>", TABLE_ARGUMENT).argument("<field>", FIELD_ARGUMENT)
15856
+ ).action(async (tableReference, reference, opts, command) => {
15857
+ const options = globalOptions(command);
15858
+ const context = await schemaContext(opts);
15859
+ const { table, field } = await findField(context, tableReference, reference);
15860
+ emit(options, renderField(table, field), { tableId: table.id, field });
15861
+ });
15862
+ withDatabaseOptions(
15863
+ fields.command("add").description("Add a field to a table").argument("<table>", TABLE_ARGUMENT).argument("<name>", "the field name").requiredOption("--type <TYPE>", "the field type, for example SINGLE_LINE_TEXT, NUMBER, SELECT, LINKED_RECORD").option("--options <json>", `the type's options as JSON (${FIELD_REFERENCE_URL})`).option("--description <text>", "what the field holds").option("--multiple", "allow several values (SELECT, LINKED_RECORD, USER, ATTACHMENT)")
15864
+ ).action(
15865
+ async (tableReference, name, opts, command) => {
15866
+ const options = globalOptions(command);
15867
+ const fieldOptions = parseOptions(opts.options);
15868
+ const context = await schemaContext(opts);
15869
+ const table = await findTableCached(context, tableReference);
15870
+ const field = await addField(context.client, context.databaseId, table.id, {
15871
+ name,
15872
+ type: opts.type.toUpperCase(),
15873
+ description: opts.description,
15874
+ ...fieldOptions === void 0 ? {} : { options: fieldOptions },
15875
+ allowMultipleEntries: opts.multiple === true
15876
+ });
15877
+ await afterSchemaWrite(context);
15878
+ emit(options, `Added field ${field.name} (${field.id}, ${field.type}) to ${table.name}.`, {
15879
+ tableId: table.id,
15880
+ field
15881
+ });
15882
+ }
15883
+ );
15884
+ withDatabaseOptions(
15885
+ fields.command("update").description("Change a field: what is not passed stays as it is").argument("<table>", TABLE_ARGUMENT).argument("<field>", FIELD_ARGUMENT).option("--name <name>", "the new name").option("--type <TYPE>", "the new type").option("--options <json>", `the new options as JSON, replacing the current ones (${FIELD_REFERENCE_URL})`).option("--description <text>", "the new description").option("--multiple", "allow several values").option("--no-multiple", "allow one value only")
15886
+ ).action(
15887
+ async (tableReference, reference, opts, command) => {
15888
+ const fieldOptions = parseOptions(opts.options);
15889
+ if (!opts.name && !opts.type && fieldOptions === void 0 && opts.description === void 0 && opts.multiple === void 0) {
15890
+ throw new CliError("Nothing to change: pass --name, --type, --options, --description or --[no-]multiple.");
15891
+ }
15892
+ const options = globalOptions(command);
15893
+ const context = await schemaContext(opts);
15894
+ const { table, field: current2 } = await findField(context, tableReference, reference);
15895
+ const field = await updateField(context.client, context.databaseId, table.id, current2.id, {
15896
+ name: opts.name ?? current2.name,
15897
+ type: opts.type?.toUpperCase() ?? current2.type,
15898
+ description: opts.description ?? current2.description,
15899
+ options: fieldOptions ?? current2.options,
15900
+ aiOptions: current2.aiOptions ?? null,
15901
+ defaultValue: current2.defaultValue ?? null,
15902
+ allowMultipleEntries: opts.multiple ?? current2.allowMultipleEntries
15903
+ });
15904
+ await afterSchemaWrite(context);
15905
+ emit(options, `Updated field ${field.name} (${field.id}, ${field.type}) in ${table.name}.`, {
15906
+ tableId: table.id,
15907
+ field
15908
+ });
15909
+ }
15910
+ );
15911
+ withDatabaseOptions(
15912
+ fields.command("delete").description("Delete a field and its values in every record").argument("<table>", TABLE_ARGUMENT).argument("<field>", FIELD_ARGUMENT)
15913
+ ).action(async (tableReference, reference, opts, command) => {
15914
+ const options = globalOptions(command);
15915
+ const context = await schemaContext(opts);
15916
+ const { table, field } = await findField(context, tableReference, reference);
15917
+ await deleteField(context.client, context.databaseId, table.id, field.id);
15918
+ await afterSchemaWrite(context);
15919
+ emit(options, `Deleted field ${field.name} (${field.id}) from ${table.name}.`, {
15920
+ tableId: table.id,
15921
+ deleted: field.id
15922
+ });
15923
+ });
15924
+ }
15925
+ function registerViews(db2) {
15926
+ const views = db2.command("views").description("The views of a table");
15927
+ withDatabaseOptions(
15928
+ views.command("list").description("List the views of a table").argument("<table>", TABLE_ARGUMENT)
15929
+ ).action(async (reference, opts, command) => {
15930
+ const options = globalOptions(command);
15931
+ const context = await schemaContext(opts);
15932
+ const table = await findTableOnServer(context, reference);
15933
+ const items = await listViews(context.client, context.databaseId, table.id);
15934
+ if (context.dir) {
15935
+ await writeViewsCache(context.dir, table.id, items);
15936
+ }
15937
+ emit(options, renderViews(items), { tableId: table.id, views: items });
15938
+ });
15939
+ }
15940
+
15941
+ // src/commands/integrations.ts
15942
+ var import_cli_table33 = __toESM(require_cli_table3(), 1);
15543
15943
  import path12 from "node:path";
15544
15944
  function toRow(integration) {
15545
15945
  return {
@@ -15558,7 +15958,7 @@ function renderIntegrations(rows) {
15558
15958
  if (!rows.length) {
15559
15959
  return "No integrations connected in this workspace. Connect one in the studio (Workspace settings > Integrations).";
15560
15960
  }
15561
- const table = new import_cli_table32.default({
15961
+ const table = new import_cli_table33.default({
15562
15962
  head: ["ID", "Name", "Type", "Account", "Auth"],
15563
15963
  colWidths: [38, 34, 22, 30, 11],
15564
15964
  wordWrap: true,
@@ -15610,7 +16010,7 @@ ${renderIntegrations(rows)}`, {
15610
16010
  }
15611
16011
 
15612
16012
  // src/commands/list.ts
15613
- var import_cli_table33 = __toESM(require_cli_table3(), 1);
16013
+ var import_cli_table34 = __toESM(require_cli_table3(), 1);
15614
16014
  function toRow2(workflow, workspace) {
15615
16015
  return {
15616
16016
  id: workflow.id,
@@ -15652,7 +16052,7 @@ async function collectWorkflows(client, { workspaceId, applicationId } = {}) {
15652
16052
  }
15653
16053
  var TITLE_WIDTH = 34;
15654
16054
  function workflowTable(rows) {
15655
- const table = new import_cli_table33.default({
16055
+ const table = new import_cli_table34.default({
15656
16056
  head: ["ID", "Title", "Triggers", "Status", "Version", "Draft"],
15657
16057
  // Only the free-text title wraps; the other columns size to their content, so trigger types are never cut off.
15658
16058
  colWidths: [null, TITLE_WIDTH, null, null, null, null],
@@ -15973,7 +16373,7 @@ ${identity.statements.map((s) => ` ${s.actions.join(", ")} on ${s.resources.joi
15973
16373
  }
15974
16374
 
15975
16375
  // src/commands/outputs.ts
15976
- var import_cli_table35 = __toESM(require_cli_table3(), 1);
16376
+ var import_cli_table36 = __toESM(require_cli_table3(), 1);
15977
16377
 
15978
16378
  // src/project/test-state.ts
15979
16379
  function outdatedReferences(row) {
@@ -16022,7 +16422,7 @@ function collectVariables(nodeId, sample, metadata) {
16022
16422
  }
16023
16423
 
16024
16424
  // src/commands/test.ts
16025
- var import_cli_table34 = __toESM(require_cli_table3(), 1);
16425
+ var import_cli_table35 = __toESM(require_cli_table3(), 1);
16026
16426
  var OUTPUTS_FILE = "outputs.json";
16027
16427
  function fieldCount(row) {
16028
16428
  const output = row.sampleOutput?.result?.output;
@@ -16123,7 +16523,7 @@ var LABEL = {
16123
16523
  error: "ERROR"
16124
16524
  };
16125
16525
  function renderResults(results) {
16126
- const table = new import_cli_table34.default({
16526
+ const table = new import_cli_table35.default({
16127
16527
  head: ["Result", "Node", "Type", "Detail"],
16128
16528
  colWidths: [9, 30, 24, 60],
16129
16529
  wordWrap: true,
@@ -16239,7 +16639,7 @@ function sampleState(row) {
16239
16639
  }
16240
16640
  }
16241
16641
  function renderVariables(nodeId, entries, unverified) {
16242
- const table = new import_cli_table35.default({
16642
+ const table = new import_cli_table36.default({
16243
16643
  head: ["Variable", "Type", "Example"],
16244
16644
  colWidths: [64, 32, 44],
16245
16645
  wordWrap: true,
@@ -16307,7 +16707,7 @@ function registerOutputs(program3) {
16307
16707
  outdated: Object.keys(outdatedReferences(row))
16308
16708
  };
16309
16709
  });
16310
- const table = new import_cli_table35.default({
16710
+ const table = new import_cli_table36.default({
16311
16711
  head: ["Node", "Type", "Sample", "Fields"],
16312
16712
  colWidths: [30, 24, 60, 8],
16313
16713
  wordWrap: true,
@@ -16876,10 +17276,10 @@ ${unbacked.map(
16876
17276
  }
16877
17277
 
16878
17278
  // src/commands/spec.ts
16879
- var import_cli_table36 = __toESM(require_cli_table3(), 1);
17279
+ var import_cli_table37 = __toESM(require_cli_table3(), 1);
16880
17280
  var TABLE_STYLE2 = { head: [], border: [], "padding-left": 1, "padding-right": 1 };
16881
17281
  function renderList() {
16882
- const table = new import_cli_table36.default({
17282
+ const table = new import_cli_table37.default({
16883
17283
  head: ["Type", "Kind", "Label", "Summary"],
16884
17284
  colWidths: [40, 9, 24, 70],
16885
17285
  wordWrap: true,
@@ -16907,7 +17307,7 @@ function renderEntry(entry) {
16907
17307
  const blocks = [`${entry.type} (${entry.kind}) \u2014 ${entry.label}`, entry.summary, `Docs: ${entry.docs}`];
16908
17308
  const inputs = Object.entries(entry.inputs);
16909
17309
  if (inputs.length) {
16910
- const table = new import_cli_table36.default({
17310
+ const table = new import_cli_table37.default({
16911
17311
  head: ["Input", "Type", "Required", "Description"],
16912
17312
  colWidths: [20, 9, 10, 90],
16913
17313
  wordWrap: true,
@@ -17157,14 +17557,14 @@ ${item.lines.map((line) => ` ${line}`).join("\n")}`
17157
17557
  }
17158
17558
 
17159
17559
  // src/commands/workspaces.ts
17160
- var import_cli_table37 = __toESM(require_cli_table3(), 1);
17560
+ var import_cli_table38 = __toESM(require_cli_table3(), 1);
17161
17561
  function registerWorkspaces(program3) {
17162
17562
  program3.command("workspaces").description("The workspaces the stored token reaches, with your role and the scopes the token grants in each").action(async (_opts, command) => {
17163
17563
  const options = globalOptions(command);
17164
17564
  const target = resolveTarget();
17165
17565
  const client = await authenticatedClient(target);
17166
17566
  const workspaces = await listWorkspaces(client);
17167
- const table = new import_cli_table37.default({
17567
+ const table = new import_cli_table38.default({
17168
17568
  head: ["ID", "Name", "Role", "Default", "Scopes"],
17169
17569
  colWidths: [38, 26, 10, 9, 34],
17170
17570
  wordWrap: true,
@@ -17191,7 +17591,7 @@ Pass --workspace <id> to a command, or set SOFTR_WORKSPACE_ID, to work in one of
17191
17591
  // package.json
17192
17592
  var package_default = {
17193
17593
  name: "@softr/cli",
17194
- version: "0.1.4",
17594
+ version: "0.1.5",
17195
17595
  description: "The Softr command line: build, test and publish Softr workflows from local files, manage Softr Databases; more Softr APIs to come",
17196
17596
  keywords: [
17197
17597
  "softr",
@@ -17219,7 +17619,7 @@ var package_default = {
17219
17619
  format: "prettier --write .",
17220
17620
  "format:check": "prettier --check .",
17221
17621
  check: "npm run typecheck && npm run format:check && npm test && npm run build",
17222
- "check:skill": "npx -y skills-ref@0.1.5 validate skills/softr-cli && npx -y skills-ref@0.1.5 validate skills/softr-workflows"
17622
+ "check:skill": "npx -y skills-ref@0.1.5 validate skills/softr-cli && npx -y skills-ref@0.1.5 validate skills/softr-workflows && npx -y skills-ref@0.1.5 validate skills/softr-db"
17223
17623
  },
17224
17624
  publishConfig: {
17225
17625
  access: "public"
@@ -17264,4 +17664,7 @@ registerSpec(workflows);
17264
17664
  registerSchema(workflows);
17265
17665
  var db = program2.command("db").description("Manage Softr Databases: databases, their tables, fields and records");
17266
17666
  registerDatabases(db);
17667
+ registerTables(db);
17668
+ registerFields(db);
17669
+ registerViews(db);
17267
17670
  program2.parseAsync(process.argv).catch(reportError);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softr/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "The Softr command line: build, test and publish Softr workflows from local files, manage Softr Databases; more Softr APIs to come",
5
5
  "keywords": [
6
6
  "softr",
@@ -28,7 +28,7 @@
28
28
  "format": "prettier --write .",
29
29
  "format:check": "prettier --check .",
30
30
  "check": "npm run typecheck && npm run format:check && npm test && npm run build",
31
- "check:skill": "npx -y skills-ref@0.1.5 validate skills/softr-cli && npx -y skills-ref@0.1.5 validate skills/softr-workflows"
31
+ "check:skill": "npx -y skills-ref@0.1.5 validate skills/softr-cli && npx -y skills-ref@0.1.5 validate skills/softr-workflows && npx -y skills-ref@0.1.5 validate skills/softr-db"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"