proca 2.3.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -6
- package/package.json +2 -1
- package/src/commands/action/add/mtt.mjs +192 -0
- package/src/commands/campaign/get.mjs +1 -1
- package/src/commands/campaign/list.mjs +3 -3
- package/src/commands/service/add.mjs +14 -5
- package/src/commands/widget/external/cron.js +63 -0
- package/src/commands/widget/external/index.mjs +56 -33
- package/src/gitCommand.mjs +173 -0
- package/src/index.d.mts +1 -0
- package/src/procaCommand.mjs +26 -7
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ you should also use the local proca-api in your [widget generator](https://githu
|
|
|
48
48
|
|
|
49
49
|
<!-- commands -->
|
|
50
50
|
* [`proca action add`](#proca-action-add)
|
|
51
|
+
* [`proca action add mtt`](#proca-action-add-mtt)
|
|
51
52
|
* [`proca action confirm`](#proca-action-confirm)
|
|
52
53
|
* [`proca action count`](#proca-action-count)
|
|
53
54
|
* [`proca action list [TITLE]`](#proca-action-list-title)
|
|
@@ -111,6 +112,7 @@ you should also use the local proca-api in your [widget generator](https://githu
|
|
|
111
112
|
* [`proca widget add`](#proca-widget-add)
|
|
112
113
|
* [`proca widget delete`](#proca-widget-delete)
|
|
113
114
|
* [`proca widget external`](#proca-widget-external)
|
|
115
|
+
* [`proca widget external cron`](#proca-widget-external-cron)
|
|
114
116
|
* [`proca widget get`](#proca-widget-get)
|
|
115
117
|
* [`proca widget list`](#proca-widget-list)
|
|
116
118
|
* [`proca widget rebuild`](#proca-widget-rebuild)
|
|
@@ -162,6 +164,50 @@ EXAMPLES
|
|
|
162
164
|
$ proca action add -i <widget_id> --firstname=John --email=john@example.org target=715a9580-cfe6-4005-9e23-61a62ddecfea --subject='MTT subject' --body='message MTT'
|
|
163
165
|
```
|
|
164
166
|
|
|
167
|
+
## `proca action add mtt`
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
USAGE
|
|
171
|
+
$ proca action add mtt [ID_NAME_DXID...] -i <value> --firstname <value> --email <value>
|
|
172
|
+
[--json | --csv | --markdown] [--env <value>] [--simplify] [-n <the_short_name>] [-x <value>] [--testing] [--optin]
|
|
173
|
+
[--action_type <value>] [--lastname <value>] [--street <value>] [--locality <value>] [--region <value>] [--country
|
|
174
|
+
<value>] [--utm <value>] [--target <value>] [--subject <value>] [--body <value>]
|
|
175
|
+
|
|
176
|
+
FLAGS
|
|
177
|
+
-i, --id=<value> (required) widget's id
|
|
178
|
+
-n, --name=<the_short_name> name (technical short name, also called slug)
|
|
179
|
+
-x, --dxid=<value> dxid
|
|
180
|
+
--action_type=<value> [default: email]
|
|
181
|
+
--body=<value> [mtt] body of the email
|
|
182
|
+
--country=<value> 2-letter country iso code
|
|
183
|
+
--email=<value> (required) email
|
|
184
|
+
--env=<value> [default: default] allow to switch between configurations (server or users)
|
|
185
|
+
--firstname=<value> (required) supporter's firstname
|
|
186
|
+
--lastname=<value>
|
|
187
|
+
--locality=<value>
|
|
188
|
+
--[no-]optin Whether the user opts in (default: false). Use --optin to enable or --no-optin to
|
|
189
|
+
explicitly disable.
|
|
190
|
+
--region=<value>
|
|
191
|
+
--street=<value>
|
|
192
|
+
--subject=<value> [mtt] subject of the email
|
|
193
|
+
--target=<value> [mtt] uid of the target
|
|
194
|
+
--[no-]testing Run action in testing mode (default: true). Use --no-testing to disable.
|
|
195
|
+
--utm=<value> utm=campaign.source.medium
|
|
196
|
+
|
|
197
|
+
OUTPUT FLAGS
|
|
198
|
+
--csv Format output as csv
|
|
199
|
+
--json Format output as json
|
|
200
|
+
--markdown Format output as markdown table
|
|
201
|
+
--[no-]simplify flatten and filter to output only the most important attributes, mostly relevant for json
|
|
202
|
+
|
|
203
|
+
EXAMPLES
|
|
204
|
+
$ proca action add mtt -i <widget_id> --firstname=John --email=john@example.org
|
|
205
|
+
|
|
206
|
+
$ proca action add mtt -i <widget_id> --firstname=John --email=john@example.org --country=FR custom1=A custom2=B
|
|
207
|
+
|
|
208
|
+
$ proca action add mtt -i <widget_id> --firstname=John --email=john@example.org target=715a9580-cfe6-4005-9e23-61a62ddecfea --subject='MTT subject' --body='message MTT'
|
|
209
|
+
```
|
|
210
|
+
|
|
165
211
|
## `proca action confirm`
|
|
166
212
|
|
|
167
213
|
Should the supporter confirm the action? it can be set either for all the widgets or an organisation or all the widgets of a campaign
|
|
@@ -470,8 +516,8 @@ list all the campaigns
|
|
|
470
516
|
|
|
471
517
|
```
|
|
472
518
|
USAGE
|
|
473
|
-
$ proca campaign list [--json | --csv | --markdown] [--env <value>] [--simplify]
|
|
474
|
-
<name of the organisation>] [-t <campaign title>...] [--stats]
|
|
519
|
+
$ proca campaign list [NAME] [--json | --csv | --markdown] [--env <value>] [--simplify]
|
|
520
|
+
[-n <name of the organisation>] [-t <campaign title>...] [--stats]
|
|
475
521
|
|
|
476
522
|
FLAGS
|
|
477
523
|
-n, --name=<name of the organisation> name (technical short name, also called slug)
|
|
@@ -1222,7 +1268,7 @@ EXAMPLES
|
|
|
1222
1268
|
$ proca org logo <name of the ngo>
|
|
1223
1269
|
```
|
|
1224
1270
|
|
|
1225
|
-
_See code: [src/commands/org/logo.ts](https://github.com/fixthestatusquo/proca-cli/blob/v2.
|
|
1271
|
+
_See code: [src/commands/org/logo.ts](https://github.com/fixthestatusquo/proca-cli/blob/v2.5.0/src/commands/org/logo.ts)_
|
|
1226
1272
|
|
|
1227
1273
|
## `proca org user get`
|
|
1228
1274
|
|
|
@@ -1545,7 +1591,7 @@ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/
|
|
|
1545
1591
|
|
|
1546
1592
|
## `proca service add`
|
|
1547
1593
|
|
|
1548
|
-
Set service, usually email backend for an org
|
|
1594
|
+
Set service, usually email backend for an org. the specific meaning of each param is dependant on the service.
|
|
1549
1595
|
|
|
1550
1596
|
```
|
|
1551
1597
|
USAGE
|
|
@@ -1559,7 +1605,7 @@ FLAGS
|
|
|
1559
1605
|
--host=<value> server of the service
|
|
1560
1606
|
--password=<value> credential of the account on the service
|
|
1561
1607
|
--path=<value> path on the service
|
|
1562
|
-
--type=<option> (required)
|
|
1608
|
+
--type=<option> (required) type of the service
|
|
1563
1609
|
<options: mailjet|ses|stripe|test_stripe|preview|webhook|supabase|smtp>
|
|
1564
1610
|
--user=<value> credential of the account on the service
|
|
1565
1611
|
|
|
@@ -1570,7 +1616,15 @@ OUTPUT FLAGS
|
|
|
1570
1616
|
--[no-]simplify flatten and filter to output only the most important attributes, mostly relevant for json
|
|
1571
1617
|
|
|
1572
1618
|
DESCRIPTION
|
|
1573
|
-
Set service, usually email backend for an org
|
|
1619
|
+
Set service, usually email backend for an org. the specific meaning of each param is dependant on the service.
|
|
1620
|
+
If a service from that type exists, it will replace it
|
|
1621
|
+
|
|
1622
|
+
EXAMPLES
|
|
1623
|
+
$ proca service add -o example_org --type system
|
|
1624
|
+
|
|
1625
|
+
$ proca service add -o example_org --host=tls://mail.example.org:587 --user=login --password "secret" --type smtp
|
|
1626
|
+
|
|
1627
|
+
$ proca service add -o example_org --host=ssl://mail.example.org:465 --user=login --password "secret" --type smtp
|
|
1574
1628
|
```
|
|
1575
1629
|
|
|
1576
1630
|
## `proca service list`
|
|
@@ -1970,6 +2024,34 @@ EXAMPLES
|
|
|
1970
2024
|
$ proca widget external --url https://mitmachen.wwf.de/node/506/polling
|
|
1971
2025
|
```
|
|
1972
2026
|
|
|
2027
|
+
## `proca widget external cron`
|
|
2028
|
+
|
|
2029
|
+
Pull all external counters and save it into a widget extra Supporter. symlink the widget json into config/counter
|
|
2030
|
+
|
|
2031
|
+
```
|
|
2032
|
+
USAGE
|
|
2033
|
+
$ proca widget external cron [--json | --csv | --markdown] [--env <value>] [--simplify]
|
|
2034
|
+
[--dry-run]
|
|
2035
|
+
|
|
2036
|
+
FLAGS
|
|
2037
|
+
--dry-run just fetch, don't update
|
|
2038
|
+
--env=<value> [default: default] allow to switch between configurations (server or users)
|
|
2039
|
+
|
|
2040
|
+
OUTPUT FLAGS
|
|
2041
|
+
--csv Format output as csv
|
|
2042
|
+
--json Format output as json
|
|
2043
|
+
--markdown Format output as markdown table
|
|
2044
|
+
--[no-]simplify flatten and filter to output only the most important attributes, mostly relevant for json
|
|
2045
|
+
|
|
2046
|
+
DESCRIPTION
|
|
2047
|
+
Pull all external counters and save it into a widget extra Supporter. symlink the widget json into config/counter
|
|
2048
|
+
|
|
2049
|
+
EXAMPLES
|
|
2050
|
+
$ proca widget external cron
|
|
2051
|
+
```
|
|
2052
|
+
|
|
2053
|
+
_See code: [src/commands/widget/external/cron.ts](https://github.com/fixthestatusquo/proca-cli/blob/v2.5.0/src/commands/widget/external/cron.ts)_
|
|
2054
|
+
|
|
1973
2055
|
## `proca widget get`
|
|
1974
2056
|
|
|
1975
2057
|
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.
|
|
4
|
+
"version": "2.5.0",
|
|
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
|
},
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import { error, stdout, ux } from "@oclif/core/ux";
|
|
3
|
+
import Command from "#src/procaCommand.mjs";
|
|
4
|
+
import { gql, mutation } from "#src/urql.mjs";
|
|
5
|
+
import { getTwitter } from "#src/util/twitter.mjs";
|
|
6
|
+
|
|
7
|
+
export default class ActionAdd extends Command {
|
|
8
|
+
static examples = [
|
|
9
|
+
"<%= config.bin %> <%= command.id %> -i <widget_id> --firstname=John --email=john@example.org",
|
|
10
|
+
"<%= config.bin %> <%= command.id %> -i <widget_id> --firstname=John --email=john@example.org --country=FR custom1=A custom2=B",
|
|
11
|
+
"<%= config.bin %> <%= command.id %> -i <widget_id> --firstname=John --email=john@example.org target=715a9580-cfe6-4005-9e23-61a62ddecfea --subject='MTT subject' --body='message MTT'",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
static args = this.multiid();
|
|
15
|
+
static strict = false; //THIS DOES NOT WORK
|
|
16
|
+
|
|
17
|
+
static flags = {
|
|
18
|
+
...this.flagify({ multiid: true }),
|
|
19
|
+
id: Flags.integer({
|
|
20
|
+
char: "i",
|
|
21
|
+
description: "widget's id",
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
testing: Flags.boolean({
|
|
25
|
+
default: true,
|
|
26
|
+
allowNo: true, // ✅ enables --no-testing
|
|
27
|
+
description:
|
|
28
|
+
"Run action in testing mode (default: true). Use --no-testing to disable.",
|
|
29
|
+
}),
|
|
30
|
+
optin: Flags.boolean({
|
|
31
|
+
default: false,
|
|
32
|
+
allowNo: true, // ✅ enables --no-optin
|
|
33
|
+
description:
|
|
34
|
+
"Whether the user opts in (default: false). Use --optin to enable or --no-optin to explicitly disable.",
|
|
35
|
+
}),
|
|
36
|
+
action_type: Flags.string({
|
|
37
|
+
default: "email",
|
|
38
|
+
}),
|
|
39
|
+
firstname: Flags.string({
|
|
40
|
+
description: "supporter's firstname",
|
|
41
|
+
required: true,
|
|
42
|
+
}),
|
|
43
|
+
lastname: Flags.string(),
|
|
44
|
+
street: Flags.string(),
|
|
45
|
+
locality: Flags.string(),
|
|
46
|
+
region: Flags.string(),
|
|
47
|
+
country: Flags.string({
|
|
48
|
+
description: "2-letter country iso code",
|
|
49
|
+
parse: async (input) => {
|
|
50
|
+
if (input && !/^[A-Za-z]{2}$/.test(input)) {
|
|
51
|
+
throw new Error("Country code must be exactly 2 letters");
|
|
52
|
+
}
|
|
53
|
+
return input?.toUpperCase(); // optional: normalize to uppercase
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
utm: Flags.string({
|
|
57
|
+
description: "utm=campaign.source.medium",
|
|
58
|
+
}),
|
|
59
|
+
email: Flags.string({
|
|
60
|
+
description: "email",
|
|
61
|
+
required: true,
|
|
62
|
+
}),
|
|
63
|
+
target: Flags.string({ description: "[mtt] uid of the target" }),
|
|
64
|
+
subject: Flags.string({ description: "[mtt] subject of the email" }),
|
|
65
|
+
body: Flags.string({ description: "[mtt] body of the email" }),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
create = async (flags) => {
|
|
69
|
+
const values = {
|
|
70
|
+
action: {
|
|
71
|
+
actionType: flags.action_type,
|
|
72
|
+
customFields: flags.customFields,
|
|
73
|
+
/* "mtt": {
|
|
74
|
+
"body": "body",
|
|
75
|
+
"files": [
|
|
76
|
+
"files"
|
|
77
|
+
],
|
|
78
|
+
"subject": "subject",
|
|
79
|
+
"targets": [
|
|
80
|
+
"targets"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
*/
|
|
84
|
+
testing: flags.testing,
|
|
85
|
+
},
|
|
86
|
+
actionPageId: flags.id,
|
|
87
|
+
contact: {
|
|
88
|
+
address: {
|
|
89
|
+
country: flags.country,
|
|
90
|
+
locality: flags.locality,
|
|
91
|
+
postcode: flags.postcode,
|
|
92
|
+
region: flags.region,
|
|
93
|
+
street: flags.street,
|
|
94
|
+
// "streetNumber": "streetNumber"
|
|
95
|
+
},
|
|
96
|
+
email: flags.email,
|
|
97
|
+
firstName: flags.firstname,
|
|
98
|
+
lastName: flags.lastname,
|
|
99
|
+
phone: flags.phone,
|
|
100
|
+
},
|
|
101
|
+
privacy: {
|
|
102
|
+
// "leadOptIn": true,
|
|
103
|
+
optIn: flags.optin,
|
|
104
|
+
},
|
|
105
|
+
tracking: {
|
|
106
|
+
...flags.tracking,
|
|
107
|
+
location: "proca-cli/action/add",
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (flags.target) {
|
|
112
|
+
values.action.mtt = {
|
|
113
|
+
targets: [flags.target],
|
|
114
|
+
subject: flags.subject || "Test MTT",
|
|
115
|
+
body: flags.body || "Please ignore, this is a test",
|
|
116
|
+
};
|
|
117
|
+
values.action.actionType = "mail2target";
|
|
118
|
+
}
|
|
119
|
+
console.log(values.action.mtt);
|
|
120
|
+
|
|
121
|
+
const query = gql`
|
|
122
|
+
mutation (
|
|
123
|
+
$action: ActionInput!
|
|
124
|
+
$actionPageId: Int!
|
|
125
|
+
$contact: ContactInput!
|
|
126
|
+
$contactRef: ID
|
|
127
|
+
$privacy: ConsentInput!
|
|
128
|
+
$tracking: TrackingInput
|
|
129
|
+
) {
|
|
130
|
+
addActionContact(
|
|
131
|
+
action: $action
|
|
132
|
+
actionPageId: $actionPageId
|
|
133
|
+
contact: $contact
|
|
134
|
+
contactRef: $contactRef
|
|
135
|
+
privacy: $privacy
|
|
136
|
+
tracking: $tracking
|
|
137
|
+
) {
|
|
138
|
+
contactRef
|
|
139
|
+
firstName
|
|
140
|
+
}
|
|
141
|
+
}`;
|
|
142
|
+
|
|
143
|
+
const result = await mutation(query, values);
|
|
144
|
+
|
|
145
|
+
console.log("result", result);
|
|
146
|
+
return result;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
parseUnknownFlags = (argv) => {
|
|
150
|
+
const knownFlags = Object.entries(ActionAdd.flags).flatMap(([key, def]) => {
|
|
151
|
+
const chars = def.char ? [def.char] : [];
|
|
152
|
+
return [key, ...chars];
|
|
153
|
+
});
|
|
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
|
+
const kvArgs = Object.fromEntries(
|
|
169
|
+
argv
|
|
170
|
+
.filter((arg) => !arg.startsWith("-") && arg.includes("="))
|
|
171
|
+
.map((arg) => arg.split("=")),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!Object.keys(kvArgs).length) return undefined;
|
|
175
|
+
|
|
176
|
+
return kvArgs;
|
|
177
|
+
};
|
|
178
|
+
async run() {
|
|
179
|
+
const { args, flags } = await this.parse(ActionAdd, {
|
|
180
|
+
context: { strict: false /* this does not work*/ },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const customFields = this.parseUnknownFlags(this.argv);
|
|
184
|
+
if (customFields) flags.customFields = JSON.stringify(customFields);
|
|
185
|
+
if (flags.utm) {
|
|
186
|
+
const [campaign, source, medium] = flags.utm.split(".");
|
|
187
|
+
flags.tracking = { source, medium, campaign };
|
|
188
|
+
} else flags.tracking = {};
|
|
189
|
+
const data = await this.create(flags);
|
|
190
|
+
return this.output(data);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -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
|
-
|
|
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 -
|
|
114
|
+
`${this.id} -t [title of the campaign] or -n [organisation]`,
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
2
|
import Command from "#src/procaCommand.mjs";
|
|
3
|
-
import { gql, mutation
|
|
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
|
|
17
|
+
export default class ServiceAdd extends Command {
|
|
18
18
|
static description =
|
|
19
|
-
"Set service, usually email backend for an org
|
|
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.
|
|
49
|
+
host: Flags.url({
|
|
41
50
|
description: "server of the service",
|
|
42
51
|
}),
|
|
43
52
|
path: Flags.string({
|
|
@@ -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/
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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(
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return
|
|
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(
|
|
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;
|
package/src/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run, Args, Flags } from "@oclif/core";
|
package/src/procaCommand.mjs
CHANGED
|
@@ -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;
|