proca 2.3.1 → 2.5.1

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/README.md CHANGED
@@ -111,6 +111,7 @@ you should also use the local proca-api in your [widget generator](https://githu
111
111
  * [`proca widget add`](#proca-widget-add)
112
112
  * [`proca widget delete`](#proca-widget-delete)
113
113
  * [`proca widget external`](#proca-widget-external)
114
+ * [`proca widget external cron`](#proca-widget-external-cron)
114
115
  * [`proca widget get`](#proca-widget-get)
115
116
  * [`proca widget list`](#proca-widget-list)
116
117
  * [`proca widget rebuild`](#proca-widget-rebuild)
@@ -124,8 +125,8 @@ you should also use the local proca-api in your [widget generator](https://githu
124
125
  USAGE
125
126
  $ proca action add [ID_NAME_DXID...] -i <value> --firstname <value> --email <value>
126
127
  [--json | --csv | --markdown] [--env <value>] [--simplify] [-n <the_short_name>] [-x <value>] [--testing] [--optin]
127
- [--action_type <value>] [--lastname <value>] [--street <value>] [--locality <value>] [--region <value>] [--country
128
- <value>] [--utm <value>] [--target <value>] [--subject <value>] [--body <value>]
128
+ [--action_type <value>] [--lastname <value>] [--street <value>] [--locality <value>] [--region <value>] [--postcode
129
+ <value>] [--country <value>] [--utm <value>] [--target <value>] [--subject <value>] [--body <value>]
129
130
 
130
131
  FLAGS
131
132
  -i, --id=<value> (required) widget's id
@@ -141,6 +142,7 @@ FLAGS
141
142
  --locality=<value>
142
143
  --[no-]optin Whether the user opts in (default: false). Use --optin to enable or --no-optin to
143
144
  explicitly disable.
145
+ --postcode=<value>
144
146
  --region=<value>
145
147
  --street=<value>
146
148
  --subject=<value> [mtt] subject of the email
@@ -470,8 +472,8 @@ list all the campaigns
470
472
 
471
473
  ```
472
474
  USAGE
473
- $ proca campaign list [--json | --csv | --markdown] [--env <value>] [--simplify] [-n
474
- <name of the organisation>] [-t <campaign title>...] [--stats]
475
+ $ proca campaign list [NAME] [--json | --csv | --markdown] [--env <value>] [--simplify]
476
+ [-n <name of the organisation>] [-t <campaign title>...] [--stats]
475
477
 
476
478
  FLAGS
477
479
  -n, --name=<name of the organisation> name (technical short name, also called slug)
@@ -1160,6 +1162,15 @@ OUTPUT FLAGS
1160
1162
 
1161
1163
  DESCRIPTION
1162
1164
  Set email service and supporter confirmation for an org
1165
+
1166
+ EXAMPLES
1167
+ $ proca org email myorg --mailer=ses --from=hello@example.com
1168
+
1169
+ $ proca org email myorg --mailer=mailjet
1170
+
1171
+ $ proca org email myorg --supporter-confirm --supporter-confirm-template=confirm_v2
1172
+
1173
+ $ proca org email myorg --no-supporter-confirm
1163
1174
  ```
1164
1175
 
1165
1176
  ## `proca org get`
@@ -1222,7 +1233,7 @@ EXAMPLES
1222
1233
  $ proca org logo <name of the ngo>
1223
1234
  ```
1224
1235
 
1225
- _See code: [src/commands/org/logo.ts](https://github.com/fixthestatusquo/proca-cli/blob/v2.3.1/src/commands/org/logo.ts)_
1236
+ _See code: [src/commands/org/logo.ts](https://github.com/fixthestatusquo/proca-cli/blob/v2.5.1/src/commands/org/logo.ts)_
1226
1237
 
1227
1238
  ## `proca org user get`
1228
1239
 
@@ -1545,7 +1556,7 @@ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/
1545
1556
 
1546
1557
  ## `proca service add`
1547
1558
 
1548
- Set service, usually email backend for an org, the specific meaning of each param is dependant on the service
1559
+ Set service, usually email backend for an org. the specific meaning of each param is dependant on the service.
1549
1560
 
1550
1561
  ```
1551
1562
  USAGE
@@ -1559,7 +1570,7 @@ FLAGS
1559
1570
  --host=<value> server of the service
1560
1571
  --password=<value> credential of the account on the service
1561
1572
  --path=<value> path on the service
1562
- --type=<option> (required) [default: system] type of the service
1573
+ --type=<option> (required) type of the service
1563
1574
  <options: mailjet|ses|stripe|test_stripe|preview|webhook|supabase|smtp>
1564
1575
  --user=<value> credential of the account on the service
1565
1576
 
@@ -1570,7 +1581,15 @@ OUTPUT FLAGS
1570
1581
  --[no-]simplify flatten and filter to output only the most important attributes, mostly relevant for json
1571
1582
 
1572
1583
  DESCRIPTION
1573
- Set service, usually email backend for an org, the specific meaning of each param is dependant on the service
1584
+ Set service, usually email backend for an org. the specific meaning of each param is dependant on the service.
1585
+ If a service from that type exists, it will replace it
1586
+
1587
+ EXAMPLES
1588
+ $ proca service add -o example_org --type system
1589
+
1590
+ $ proca service add -o example_org --host=tls://mail.example.org:587 --user=login --password "secret" --type smtp
1591
+
1592
+ $ proca service add -o example_org --host=ssl://mail.example.org:465 --user=login --password "secret" --type smtp
1574
1593
  ```
1575
1594
 
1576
1595
  ## `proca service list`
@@ -1728,14 +1747,14 @@ let a user join an organisation with a role
1728
1747
  ```
1729
1748
  USAGE
1730
1749
  $ proca user join [ID_NAME_DXID] [--json | --csv | --markdown] [--env <value>]
1731
- [--simplify] [-n <org>] [--role owner|campaigner|coordinator|translator] [-u <user email>]
1750
+ [--simplify] [-n <org>] [-r owner|campaigner|coordinator|translator] [-u <user email>]
1732
1751
 
1733
1752
  FLAGS
1734
1753
  -n, --name=<org> name (technical short name, also called slug)
1754
+ -r, --role=<option> [default: campaigner] permission level in that org
1755
+ <options: owner|campaigner|coordinator|translator>
1735
1756
  -u, --user=<user email> email, default current user
1736
1757
  --env=<value> [default: default] allow to switch between configurations (server or users)
1737
- --role=<option> [default: campaigner] permission level in that org
1738
- <options: owner|campaigner|coordinator|translator>
1739
1758
 
1740
1759
  OUTPUT FLAGS
1741
1760
  --csv Format output as csv
@@ -1970,6 +1989,34 @@ EXAMPLES
1970
1989
  $ proca widget external --url https://mitmachen.wwf.de/node/506/polling
1971
1990
  ```
1972
1991
 
1992
+ ## `proca widget external cron`
1993
+
1994
+ Pull all external counters and save it into a widget extra Supporter. symlink the widget json into config/counter
1995
+
1996
+ ```
1997
+ USAGE
1998
+ $ proca widget external cron [--json | --csv | --markdown] [--env <value>] [--simplify]
1999
+ [--dry-run]
2000
+
2001
+ FLAGS
2002
+ --dry-run just fetch, don't update
2003
+ --env=<value> [default: default] allow to switch between configurations (server or users)
2004
+
2005
+ OUTPUT FLAGS
2006
+ --csv Format output as csv
2007
+ --json Format output as json
2008
+ --markdown Format output as markdown table
2009
+ --[no-]simplify flatten and filter to output only the most important attributes, mostly relevant for json
2010
+
2011
+ DESCRIPTION
2012
+ Pull all external counters and save it into a widget extra Supporter. symlink the widget json into config/counter
2013
+
2014
+ EXAMPLES
2015
+ $ proca widget external cron
2016
+ ```
2017
+
2018
+ _See code: [src/commands/widget/external/cron.ts](https://github.com/fixthestatusquo/proca-cli/blob/v2.5.1/src/commands/widget/external/cron.ts)_
2019
+
1973
2020
  ## `proca widget get`
1974
2021
 
1975
2022
  view a widget
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "proca",
3
3
  "description": "Access the proca api",
4
- "version": "2.3.1",
4
+ "version": "2.5.1",
5
5
  "author": "Xavier",
6
6
  "bin": {
7
7
  "proca": "proca-cli"
@@ -21,6 +21,7 @@
21
21
  "merge-anything": "^6.0.6",
22
22
  "object-path": "^0.11.8",
23
23
  "prompts": "^2.4.2",
24
+ "simple-git": "^3.33.0",
24
25
  "typescript": "^5.7.3",
25
26
  "urql": "^4.1.0"
26
27
  },
@@ -1,8 +1,16 @@
1
- import { Args, Flags } from "@oclif/core";
2
- import { error, stdout, ux } from "@oclif/core/ux";
1
+ import { Flags } from "@oclif/core";
3
2
  import Command from "#src/procaCommand.mjs";
4
3
  import { gql, mutation } from "#src/urql.mjs";
5
- import { getTwitter } from "#src/util/twitter.mjs";
4
+
5
+ export const addAction = async (params) => {
6
+ const d = new ActionAdd([]);
7
+ if (params.utm) {
8
+ const [campaign, source, medium] = params.utm.split(".");
9
+ params.tracking = { source, medium, campaign };
10
+ } else params.tracking = {};
11
+ const data = await d.create(params);
12
+ return data;
13
+ };
6
14
 
7
15
  export default class ActionAdd extends Command {
8
16
  static examples = [
@@ -44,6 +52,7 @@ export default class ActionAdd extends Command {
44
52
  street: Flags.string(),
45
53
  locality: Flags.string(),
46
54
  region: Flags.string(),
55
+ postcode: Flags.string(),
47
56
  country: Flags.string({
48
57
  description: "2-letter country iso code",
49
58
  parse: async (input) => {
@@ -116,7 +125,6 @@ export default class ActionAdd extends Command {
116
125
  };
117
126
  values.action.actionType = "mail2target";
118
127
  }
119
- console.log(values.action.mtt);
120
128
 
121
129
  const query = gql`
122
130
  mutation (
@@ -139,11 +147,9 @@ export default class ActionAdd extends Command {
139
147
  firstName
140
148
  }
141
149
  }`;
142
-
143
150
  const result = await mutation(query, values);
144
151
 
145
- console.log("result", result);
146
- return result;
152
+ return result.addActionContact;
147
153
  };
148
154
 
149
155
  parseUnknownFlags = (argv) => {
@@ -151,20 +157,6 @@ export default class ActionAdd extends Command {
151
157
  const chars = def.char ? [def.char] : [];
152
158
  return [key, ...chars];
153
159
  });
154
- /* doesn't work static=false has no effect const unknownFlags = Object.fromEntries(
155
- argv
156
- .filter(arg =>
157
- (/^--?\w+=/.test(arg)) // --key=val or -x=val
158
- )
159
- .map(arg => {
160
- const keyval = arg.replace(/^-+/, '').split('=')
161
- return [keyval[0], keyval[1]]
162
- })
163
- .filter(([key]) => !knownFlags.includes(key))
164
- )
165
- */
166
-
167
- // Extract key=val style positional args (e.g. foo=bar)
168
160
  const kvArgs = Object.fromEntries(
169
161
  argv
170
162
  .filter((arg) => !arg.startsWith("-") && arg.includes("="))
@@ -175,6 +167,7 @@ export default class ActionAdd extends Command {
175
167
 
176
168
  return kvArgs;
177
169
  };
170
+
178
171
  async run() {
179
172
  const { args, flags } = await this.parse(ActionAdd, {
180
173
  context: { strict: false /* this does not work*/ },
@@ -62,6 +62,7 @@ export default class CampaignGet extends Command {
62
62
  name: name,
63
63
  withStats: this.flags.stats,
64
64
  });
65
+ result.campaign.config = JSON.parse(result.campaign.config);
65
66
  return result.campaign;
66
67
  };
67
68
 
@@ -108,7 +109,6 @@ export default class CampaignGet extends Command {
108
109
  };
109
110
 
110
111
  table = (r) => {
111
- r.config = JSON.parse(r.config);
112
112
  super.table(r, null, null);
113
113
  if (this.flags.locale) {
114
114
  this.prettyJson(r.config?.locales[this.flags.locale]);
@@ -18,11 +18,11 @@ export default class CampaignList extends Command {
18
18
 
19
19
  static description = "list all the campaigns";
20
20
 
21
- // static args = this.multiid();
21
+ static args = this.namearg();
22
22
 
23
23
  static flags = {
24
24
  // flag with no value (-f, --force)
25
- ...this.flagify({ name: "name of the organisation" }),
25
+ ...this.flagify({ name: "name of the organisation", char: "o" }),
26
26
  title: Flags.string({
27
27
  char: "t",
28
28
  description: "name of the campaign",
@@ -111,7 +111,7 @@ export default class CampaignList extends Command {
111
111
 
112
112
  if (!flags.title && !flags.name) {
113
113
  throw new Error(
114
- `${this.id} -t [title of the campaign] or -o [organisation]`,
114
+ `${this.id} -t [title of the campaign] or -n [organisation]`,
115
115
  );
116
116
  }
117
117
 
@@ -18,6 +18,13 @@ export default class OrgEmail extends Command {
18
18
  static description =
19
19
  "Set email service and supporter confirmation for an org";
20
20
 
21
+ static examples = [
22
+ "<%= config.bin %> <%= command.id %> myorg --mailer=ses --from=hello@example.com",
23
+ "<%= config.bin %> <%= command.id %> myorg --mailer=mailjet",
24
+ "<%= config.bin %> <%= command.id %> myorg --supporter-confirm --supporter-confirm-template=confirm_v2",
25
+ "<%= config.bin %> <%= command.id %> myorg --no-supporter-confirm",
26
+ ];
27
+
21
28
  static args = this.multiid();
22
29
 
23
30
  static flags = {
@@ -1,6 +1,6 @@
1
1
  import { Flags } from "@oclif/core";
2
2
  import Command from "#src/procaCommand.mjs";
3
- import { gql, mutation, query } from "#src/urql.mjs";
3
+ import { gql, mutation } from "#src/urql.mjs";
4
4
 
5
5
  const SERVICE_NAMES = [
6
6
  "MAILJET",
@@ -14,9 +14,19 @@ const SERVICE_NAMES = [
14
14
  "SMTP",
15
15
  ].map((d) => d.toLowerCase());
16
16
 
17
- export default class OrgEmail extends Command {
17
+ export default class ServiceAdd extends Command {
18
18
  static description =
19
- "Set service, usually email backend for an org, the specific meaning of each param is dependant on the service";
19
+ "Set service, usually email backend for an org. the specific meaning of each param is dependant on the service. \nIf a service from that type exists, it will replace it";
20
+
21
+ // examples to add to help
22
+ // <%= config.bin %> resolves to the executable name
23
+ // <%= command.id %> resolves to the command name
24
+ static examples = [
25
+ // Examples can be simple strings
26
+ "<%= config.bin %> <%= command.id %> -o example_org --type system",
27
+ '<%= config.bin %> <%= command.id %> -o example_org --host=tls://mail.example.org:587 --user=login --password "secret" --type smtp',
28
+ '<%= config.bin %> <%= command.id %> -o example_org --host=ssl://mail.example.org:465 --user=login --password "secret" --type smtp',
29
+ ];
20
30
 
21
31
  static flags = {
22
32
  ...super.globalFlags,
@@ -29,7 +39,6 @@ export default class OrgEmail extends Command {
29
39
  description: "type of the service",
30
40
  options: SERVICE_NAMES,
31
41
  required: true,
32
- default: "system",
33
42
  }),
34
43
  user: Flags.string({
35
44
  description: "credential of the account on the service",
@@ -37,7 +46,7 @@ export default class OrgEmail extends Command {
37
46
  password: Flags.string({
38
47
  description: "credential of the account on the service",
39
48
  }),
40
- host: Flags.string({
49
+ host: Flags.url({
41
50
  description: "server of the service",
42
51
  }),
43
52
  path: Flags.string({
@@ -14,6 +14,7 @@ export default class UserJoinOrg extends Command {
14
14
  // flag with no value (-f, --force)
15
15
  ...this.flagify({ multiid: false, name: "org", char: "o" }),
16
16
  role: Flags.string({
17
+ char: "r",
17
18
  description: "permission level in that org",
18
19
  default: "campaigner",
19
20
  options: ["owner", "campaigner", "coordinator", "translator"],
@@ -0,0 +1,63 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { Flags } from "@oclif/core";
4
+ import { update } from "#src/commands/widget/external/index.mjs";
5
+ import Command from "#src/procaCommand.mjs";
6
+
7
+ export default class CounterExternal extends Command {
8
+ static description =
9
+ "Pull all external counters and save it into a widget extra Supporter. symlink the widget json into config/counter";
10
+
11
+ static examples = ["<%= config.bin %> <%= command.id %>"];
12
+
13
+ static flags = {
14
+ "dry-run": Flags.boolean({
15
+ description: "just fetch, don't update",
16
+ }),
17
+ };
18
+
19
+ async monitored() {
20
+ const dir = path.join(this.config.procaConfig.folder, "/counter");
21
+ try {
22
+ await fs.access(dir);
23
+ } catch {
24
+ this.error(`create the folder ${dir}`);
25
+ }
26
+ const entries = await fs.readdir(dir);
27
+ const results = await Promise.all(
28
+ entries.map(async (entry) => {
29
+ const file = entry.split(".");
30
+ if (file.length !== 2) {
31
+ this.warn(`invalid file ${entry}`);
32
+ return;
33
+ }
34
+ if (file[1] !== "json") {
35
+ this.warn(`should be a json file ${entry}`);
36
+ return;
37
+ }
38
+ const content = JSON.parse(
39
+ await fs.readFile(path.join(dir, entry), "utf-8"),
40
+ );
41
+ return {
42
+ id: Number.parseInt(file[0]),
43
+ name: content.filename,
44
+ url: content.component.counter?.url,
45
+ path: content.component.counter?.path,
46
+ };
47
+ }),
48
+ );
49
+ return results.flat();
50
+ }
51
+
52
+ async run() {
53
+ const { flags } = await this.parse(CounterExternal);
54
+ const widgets = await this.monitored();
55
+ if (flags["dry-run"]) return this.output(widgets);
56
+ const updated = await Promise.all(
57
+ widgets.map(async (widget) => {
58
+ return await update(widget);
59
+ }),
60
+ );
61
+ return this.output(updated);
62
+ }
63
+ }
@@ -1,8 +1,19 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { Flags } from "@oclif/core";
3
2
  import oPath from "object-path";
4
3
  import { updateCounter } from "#src/commands/widget/update/external.mjs";
5
- import Command from "#src/procaCommand.mjs";
4
+ import Command from "#src/gitCommand.mjs";
5
+
6
+ export const update = async (config) => {
7
+ const d = new CounterExternal([]);
8
+ // const config = await d.getCounterConfig({id});
9
+ if (!config) {
10
+ console.warn("missing config");
11
+ return undefined;
12
+ }
13
+ const counter = await d.fetchCounter(config);
14
+ await updateCounter(config.id, counter);
15
+ return { name: config.name, counter, id: config.id };
16
+ };
6
17
 
7
18
  export default class CounterExternal extends Command {
8
19
  static description =
@@ -41,47 +52,59 @@ export default class CounterExternal extends Command {
41
52
  }),
42
53
  };
43
54
 
44
- async fetchCounter({ url, path, timeout, "dry-run": verbose }) {
45
- const response = await fetch(url, {
46
- timeout,
47
- headers: {
48
- "User-Agent": "proca",
49
- },
50
- });
51
-
52
- if (!response.ok) {
53
- this.error(`API request failed with status ${response.status}`, {
54
- exit: 1,
55
+ async fetchCounter({ url, path, timeout = 10000, "dry-run": verbose }) {
56
+ try {
57
+ const response = await fetch(url, {
58
+ signal: AbortSignal.timeout(timeout),
59
+ headers: {
60
+ "User-Agent": "proca/451.42",
61
+ },
55
62
  });
56
- }
63
+ if (!response.ok) {
64
+ this.error(`API request failed with status ${response.status}`, {
65
+ exit: 1,
66
+ });
67
+ }
57
68
 
58
- const data = await response.json();
59
- if (verbose) {
60
- this.log(JSON.stringify(data, null, 2));
69
+ const data = await response.json();
70
+ if (verbose) {
71
+ this.log(JSON.stringify(data, null, 2));
72
+ }
73
+ const counter = oPath.get(data, path);
74
+ if (
75
+ Number.isNaN(Number.parseFloat(counter)) ||
76
+ !Number.isFinite(counter)
77
+ ) {
78
+ this.error(`Could not extract value from ${counter} at ${path}`, {
79
+ exit: 1,
80
+ });
81
+ }
82
+ return Number.parseFloat(counter);
83
+ } catch (err) {
84
+ if (err.name === "TimeoutError") {
85
+ console.error("Request timed out after", timeout, "ms");
86
+ } else if (err.cause?.code === "ETIMEDOUT") {
87
+ console.error("Network timeout — server unreachable:", url);
88
+ } else {
89
+ throw err;
90
+ }
61
91
  }
62
- const counter = oPath.get(data, path);
63
- if (Number.isNaN(Number.parseFloat(counter)) || !Number.isFinite(counter)) {
64
- this.error(`Could not extract value from ${counter} at ${path}`, {
65
- exit: 1,
66
- });
67
- }
68
- return Number.parseFloat(counter);
69
92
  }
70
93
 
71
- async getCounterConfig(id) {
72
- const folder = this.procaConfig.folder;
73
- const filePath = `${folder}/${id}.json`;
74
-
75
- const data = await readFile(filePath, "utf8");
76
- const jsonData = JSON.parse(data);
77
- return jsonData.component.counter;
94
+ async getCounterConfig() {
95
+ const data = await this.read();
96
+ if (!data.component.counter)
97
+ this.error(
98
+ "missing config.component.counter {url, path} in ${this.getFile()}",
99
+ );
100
+ return data.component.counter;
78
101
  }
79
102
 
80
103
  async run() {
81
104
  const { flags } = await this.parse(CounterExternal);
82
105
  let counter = undefined;
83
- if (!flags.url) {
84
- const config = await this.getCounterConfig(flags.id);
106
+ if (!flags.url && !flags.total) {
107
+ const config = await this.getCounterConfig();
85
108
  flags.url = config.url;
86
109
  flags.path = config.path;
87
110
  }
@@ -0,0 +1,173 @@
1
+ import fs from "node:fs";
2
+ //import { load as loadConfig } from "proca/src/config.mjs";
3
+ import path from "node:path";
4
+ import chalk from "chalk";
5
+ import simpleGit from "simple-git";
6
+ import ProcaCommand from "#src/procaCommand.mjs";
7
+ export { Args, Flags } from "#src/index.mjs";
8
+
9
+ export class ProcaGitCommand extends ProcaCommand {
10
+ fileName = undefined;
11
+ newFile = undefined;
12
+
13
+ static extension = "json";
14
+
15
+ initGit = () => {
16
+ if (!fs.existsSync(path.join(this.config.procaConfig.folder, "/.git"))) {
17
+ this.warn("config not on git");
18
+ }
19
+ return simpleGit({ baseDir: this.config.procaConfig.folder });
20
+ };
21
+
22
+ commit = async () => this.git.commit(this.gitMessage(), [this.getFile()]);
23
+
24
+ checkFile = (fileName) => {
25
+ if (fileName.toString().includes("..")) {
26
+ this.error("invalid filename ", fileName);
27
+ }
28
+ return fileName;
29
+ };
30
+ // mkdir -p
31
+ mkdirp = () => {
32
+ fs.mkdirSync(this.getFile(), { recursive: true });
33
+ };
34
+
35
+ getFolder = () => {
36
+ const mapping = { widget: "./", campaign: "campaign/", org: "org/" };
37
+ const type = this.id.split(":")[0];
38
+ const folder = mapping[type];
39
+ if (!folder) this.error(`no folder defined for ${type}`);
40
+ return folder;
41
+ };
42
+
43
+ setFile = (name, ext = "json") => {
44
+ if (!name) {
45
+ const folder = this.getFolder();
46
+ if (folder === "./") {
47
+ //we are dealing with widgets
48
+ name = `${folder}${this._flags.id}`;
49
+ } else {
50
+ name = `${folder}${this._flags.name}`;
51
+ }
52
+ }
53
+ this.fileName = path.join(
54
+ this.config.procaConfig.folder,
55
+ `${this.checkFile(name)}.${ext}`,
56
+ );
57
+ return this.fileName;
58
+ };
59
+
60
+ getFile = () => {
61
+ if (!this.fileName) this.error("you need to setFile first");
62
+ return this.fileName;
63
+ };
64
+
65
+ gitMessage = (_obj) => `proca cli ${this.id}`;
66
+
67
+ fileExists = () => {
68
+ return fs.existsSync(this.getFile());
69
+ };
70
+
71
+ uncommited = async ({ file, commit = false, exit = true }) => {
72
+ if (!this.git) return false;
73
+ if (!file) file = this.getFile();
74
+ const status = await this.git.status();
75
+ const relative = path.relative(this.config.procaConfig.folder, file);
76
+
77
+ const hasChanges = status.modified.includes(relative);
78
+ if (!hasChanges) return false;
79
+ if (commit) {
80
+ this.git.commit(this.gitMessage(data, file));
81
+ }
82
+ if (exit) {
83
+ this.log(await this.diff(file));
84
+ this.error(`Your local changes to ${file} would be overwritten`, {
85
+ code: "git error",
86
+ exit: 1,
87
+ suggestions: ["run with --auto to commit the changes before pulling"],
88
+ });
89
+ }
90
+ return true;
91
+ };
92
+
93
+ read = async ({ file = this.getFile(), commit } = {}) => {
94
+ try {
95
+ let data;
96
+ if (!file) file = getFile();
97
+ const ext = this.constructor.extension;
98
+ console.log("read file", file, ext);
99
+ if (ext === "json") {
100
+ data = JSON.parse(fs.readFileSync(file, "utf8"));
101
+ } else {
102
+ data = fs.readFileSync(file, "utf8");
103
+ }
104
+ if (commit && this.git) {
105
+ const status = await this.git.status();
106
+ const hasChanges = status.modified.includes(file);
107
+ if (hasChanges) {
108
+ this.git.commit(this.gitMessage(data, file));
109
+ }
110
+ }
111
+ return data;
112
+ } catch (e) {
113
+ console.error(`no local copy of ${file}: ${e.message}`);
114
+ return null;
115
+ }
116
+ };
117
+
118
+ stringify = (obj) =>
119
+ `${JSON.stringify(
120
+ obj,
121
+ (_key, value) => (value === null ? undefined : value),
122
+ 2,
123
+ )}\n`;
124
+
125
+ diff = async (file) => {
126
+ const diff = await this.git.diff([file || this.getFile()]);
127
+ const colorDiff = diff
128
+ .split("\n")
129
+ .map((line) => {
130
+ if (line.startsWith("+")) return chalk.green(line);
131
+ if (line.startsWith("-")) return chalk.red(line);
132
+ if (line.startsWith("@@")) return chalk.cyan(line);
133
+ return line;
134
+ })
135
+ .join("\n");
136
+ return colorDiff;
137
+ };
138
+
139
+ save = async (json) => {
140
+ let needToAdd = false;
141
+ const file = this.getFile();
142
+ if (this.git && !this.fileExists(file)) {
143
+ needToAdd = true;
144
+ }
145
+ this.uncommited({ file }); //exit if unsaved, better to always stash?
146
+ fs.writeFileSync(file, this.stringify(json));
147
+ if (this.git) {
148
+ if (needToAdd) {
149
+ await this.git.add(file);
150
+ }
151
+ const diff = await this.diff(file);
152
+ const r = await this.git.commit(this.gitMessage(json), file);
153
+ return { ...r, ...r.summary, diff };
154
+ }
155
+ return { file, git: false };
156
+ };
157
+
158
+ parse = async () => {
159
+ const r = await super.parse();
160
+ if (this._flags.git === false) {
161
+ this.git = undefined;
162
+ }
163
+ this._flags && this.setFile(null, this.constructor.extension);
164
+ return r;
165
+ };
166
+
167
+ async init() {
168
+ await super.init();
169
+ this.git = this.initGit();
170
+ }
171
+ }
172
+
173
+ export default ProcaGitCommand;
@@ -0,0 +1 @@
1
+ export { run, Args, Flags } from "@oclif/core";
@@ -10,7 +10,8 @@ class ProcaCommand extends Command {
10
10
  static enableJsonFlag = true;
11
11
  procaConfig = { url: "https://api.proca.app/api" };
12
12
  format = "human"; // the default formatting
13
- flags = {};
13
+ flags = {}; // the definition
14
+ _flags = undefined; // the cli flags
14
15
 
15
16
  static baseFlags = {
16
17
  env: Flags.string({
@@ -50,6 +51,17 @@ class ProcaCommand extends Command {
50
51
  return args;
51
52
  }
52
53
 
54
+ static namearg() {
55
+ const args = {
56
+ name: Args.string({
57
+ ignoreStdin: true,
58
+ hidden: true,
59
+ description: "convenience, but try to use -n <name> instead",
60
+ }),
61
+ };
62
+ return args;
63
+ }
64
+
53
65
  static flagify({ multiid = false, name = false, char } = {}) {
54
66
  const flags = Object.assign({}, ProcaCommand.baseFlags);
55
67
  if (name || multiid) {
@@ -86,9 +98,14 @@ class ProcaCommand extends Command {
86
98
 
87
99
  async parse() {
88
100
  const parsed = await super.parse();
101
+
102
+ if (parsed.args.name)
103
+ parsed.flags.name = ProcaCommand.safeName(parsed.args.name);
104
+
89
105
  if (this.ctor.args.id_name_dxid === undefined) {
90
106
  return parsed;
91
107
  }
108
+
92
109
  const maybe = parsed.args.id_name_dxid;
93
110
  if (maybe) {
94
111
  const identified = [
@@ -106,9 +123,11 @@ class ProcaCommand extends Command {
106
123
  if (d) parsed.flags.id = d;
107
124
  else parsed.flags.name = ProcaCommand.safeName(maybe);
108
125
  }
126
+
109
127
  if (parsed.flags.dxid) {
110
128
  parsed.flags.id = dxid(parsed.flags.dxid);
111
129
  }
130
+
112
131
  const identified = [
113
132
  parsed.flags.name,
114
133
  parsed.flags.id,
@@ -120,7 +139,7 @@ class ProcaCommand extends Command {
120
139
  code: 1,
121
140
  });
122
141
  }
123
-
142
+ this._flags = parsed.flags;
124
143
  return parsed;
125
144
  }
126
145
 
@@ -132,16 +151,16 @@ class ProcaCommand extends Command {
132
151
  };
133
152
  async init() {
134
153
  await super.init();
154
+ this.debug = debug("proca");
155
+ initHook({ config: this.config });
156
+ this.procaConfig = this.config.procaConfig; // set up from the hooks/init
157
+ // await this.config.runHook('init', { config: this.config });
135
158
  const { flags } = await this.parse();
136
159
  this.flags = flags;
137
160
  if (flags.json) this.format = "json";
138
161
  if (flags.csv) this.format = "csv";
139
162
  if (flags.markdown) this.format = "markdown";
140
163
 
141
- this.debug = debug("proca");
142
- initHook({ config: this.config });
143
- this.procaConfig = this.config.procaConfig; // set up from the hooks/init
144
- // await this.config.runHook('init', { config: this.config });
145
164
  createClient(this.procaConfig);
146
165
  }
147
166
 
@@ -192,7 +211,7 @@ class ProcaCommand extends Command {
192
211
  for (const [key, value] of Object.entries(d)) {
193
212
  if (key === "__typename") continue;
194
213
  if (key === "config" && typeof value === "string") continue; // it's just a giant mess if not processed, let's skipt
195
- if (value === null) continue;
214
+ if (value === null || value === "") continue;
196
215
  if (typeof value === "string" || typeof value === "number") {
197
216
  r[key] = value;
198
217
  continue;