proca 2.2.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.
@@ -0,0 +1,132 @@
1
+ import { Flags } from "@oclif/core";
2
+ import oPath from "object-path";
3
+ import { updateCounter } from "#src/commands/widget/update/external.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
+ };
17
+
18
+ export default class CounterExternal extends Command {
19
+ static description =
20
+ "Pull external counter and save it into a widget extra Supporter";
21
+
22
+ static examples = [
23
+ "<%= config.bin %> <%= command.id %> --url https://mitmachen.wwf.de/node/506/polling",
24
+ ];
25
+ static args = this.multiid();
26
+
27
+ static flags = {
28
+ ...this.flagify({ multiid: true }),
29
+ url: Flags.string({
30
+ char: "u",
31
+ description: "API endpoint URL to pull from",
32
+ relationships: [{ type: "some", flags: ["path", "dry-run"] }],
33
+ }),
34
+ path: Flags.string({
35
+ helpValue: "object.sub-object.total",
36
+ description:
37
+ "dot notation path to the counter field in the json returned by the url",
38
+ }),
39
+ total: Flags.integer({
40
+ description: "number to add to the total",
41
+ relationships: [
42
+ // define complex relationships between flags
43
+ { type: "none", flags: ["url", "path"] },
44
+ ],
45
+ }),
46
+ timeout: Flags.integer({
47
+ description: "Request timeout in milliseconds",
48
+ default: 10000,
49
+ }),
50
+ "dry-run": Flags.boolean({
51
+ description: "just fetch, don't update",
52
+ }),
53
+ };
54
+
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
+ },
62
+ });
63
+ if (!response.ok) {
64
+ this.error(`API request failed with status ${response.status}`, {
65
+ exit: 1,
66
+ });
67
+ }
68
+
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
+ }
91
+ }
92
+ }
93
+
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;
101
+ }
102
+
103
+ async run() {
104
+ const { flags } = await this.parse(CounterExternal);
105
+ let counter = undefined;
106
+ if (!flags.url && !flags.total) {
107
+ const config = await this.getCounterConfig();
108
+ flags.url = config.url;
109
+ flags.path = config.path;
110
+ }
111
+
112
+ if (flags.url) {
113
+ counter = await this.fetchCounter(flags);
114
+ }
115
+ if (flags.total) {
116
+ counter = flags.total;
117
+ }
118
+ if (flags["dry-run"]) {
119
+ return this.output(
120
+ {
121
+ counter,
122
+ url: flags.url,
123
+ // response: JSON.stringify(data, null, 2),
124
+ },
125
+ { single: true },
126
+ );
127
+ }
128
+
129
+ const updated = await updateCounter(flags.id, counter);
130
+ return this.output(updated, { single: true });
131
+ }
132
+ }
@@ -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,16 +51,29 @@ class ProcaCommand extends Command {
50
51
  return args;
51
52
  }
52
53
 
53
- static flagify({ multiid = false, name = false } = {}) {
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
+
65
+ static flagify({ multiid = false, name = false, char } = {}) {
54
66
  const flags = Object.assign({}, ProcaCommand.baseFlags);
55
67
  if (name || multiid) {
56
68
  flags.name = Flags.string({
57
69
  char: "n",
70
+ charAliases: char ? ["n", char] : undefined,
58
71
  description: "name (technical short name, also called slug)",
59
72
  helpValue: typeof name === "string" ? `<${name}>` : "<the_short_name>",
60
73
  parse: (input) => ProcaCommand.safeName(input),
61
74
  });
62
75
  }
76
+
63
77
  if (multiid) {
64
78
  flags.id = Flags.string({
65
79
  char: "i",
@@ -84,9 +98,14 @@ class ProcaCommand extends Command {
84
98
 
85
99
  async parse() {
86
100
  const parsed = await super.parse();
101
+
102
+ if (parsed.args.name)
103
+ parsed.flags.name = ProcaCommand.safeName(parsed.args.name);
104
+
87
105
  if (this.ctor.args.id_name_dxid === undefined) {
88
106
  return parsed;
89
107
  }
108
+
90
109
  const maybe = parsed.args.id_name_dxid;
91
110
  if (maybe) {
92
111
  const identified = [
@@ -104,9 +123,11 @@ class ProcaCommand extends Command {
104
123
  if (d) parsed.flags.id = d;
105
124
  else parsed.flags.name = ProcaCommand.safeName(maybe);
106
125
  }
126
+
107
127
  if (parsed.flags.dxid) {
108
128
  parsed.flags.id = dxid(parsed.flags.dxid);
109
129
  }
130
+
110
131
  const identified = [
111
132
  parsed.flags.name,
112
133
  parsed.flags.id,
@@ -118,7 +139,7 @@ class ProcaCommand extends Command {
118
139
  code: 1,
119
140
  });
120
141
  }
121
-
142
+ this._flags = parsed.flags;
122
143
  return parsed;
123
144
  }
124
145
 
@@ -130,16 +151,16 @@ class ProcaCommand extends Command {
130
151
  };
131
152
  async init() {
132
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 });
133
158
  const { flags } = await this.parse();
134
159
  this.flags = flags;
135
160
  if (flags.json) this.format = "json";
136
161
  if (flags.csv) this.format = "csv";
137
162
  if (flags.markdown) this.format = "markdown";
138
163
 
139
- this.debug = debug("proca");
140
- initHook({ config: this.config });
141
- this.procaConfig = this.config.procaConfig; // set up from the hooks/init
142
- // await this.config.runHook('init', { config: this.config });
143
164
  createClient(this.procaConfig);
144
165
  }
145
166
 
@@ -157,6 +178,10 @@ class ProcaCommand extends Command {
157
178
  }
158
179
 
159
180
  if (err.networkError) {
181
+ if (err.response.status === 500) {
182
+ this.error("500 Internal Server Error", { exit: err.code || 1 });
183
+ return;
184
+ }
160
185
  this.info("Looks like there’s a problem with your internet connection");
161
186
  this.error(err.networkError.cause, { exit: err.code || 1 });
162
187
  }
@@ -186,7 +211,7 @@ class ProcaCommand extends Command {
186
211
  for (const [key, value] of Object.entries(d)) {
187
212
  if (key === "__typename") continue;
188
213
  if (key === "config" && typeof value === "string") continue; // it's just a giant mess if not processed, let's skipt
189
- if (value === null) continue;
214
+ if (value === null || value === "") continue;
190
215
  if (typeof value === "string" || typeof value === "number") {
191
216
  r[key] = value;
192
217
  continue;
@@ -286,7 +311,7 @@ class ProcaCommand extends Command {
286
311
  table(data, transformRow, print = (table) => table.toString()) {
287
312
  if (!transformRow) {
288
313
  if (this.flags.simplify !== false) {
289
- transformRow = (d, cell, idx) => {
314
+ transformRow = (d, cell) => {
290
315
  const r = this.simplify(d);
291
316
  if (r === null) return null;
292
317
  for (const [key, value] of Object.entries(r)) {
@@ -295,7 +320,7 @@ class ProcaCommand extends Command {
295
320
  return true;
296
321
  };
297
322
  } else {
298
- transformRow = (d, cell, idx) => {
323
+ transformRow = (d, cell) => {
299
324
  for (const [key, value] of Object.entries(this.flatten(d))) {
300
325
  cell(key, value);
301
326
  }
@@ -316,15 +341,18 @@ class ProcaCommand extends Command {
316
341
  }, this);
317
342
  return this.newRow();
318
343
  };
319
-
320
344
  this.log(Table.print(data, transformRow, print));
321
345
  }
322
346
 
323
347
  single = (r) => {
324
- this.table(r, null, null);
348
+ return this.table(r[0], null, null);
325
349
  };
326
350
 
327
351
  async output(data, { single = false } = {}) {
352
+ if (!Array.isArray(data)) {
353
+ data = [data];
354
+ single = true;
355
+ }
328
356
  if (this.format === "json") {
329
357
  if (this.flags.simplify)
330
358
  return data?.map(this.simplify) || this.simplify(data);
@@ -1,6 +1,6 @@
1
1
  import { gql } from "#src/urql.mjs";
2
2
 
3
- export const FragmentSummary = gql`fragment Summary on Campaign {id name title externalId status}`;
3
+ export const FragmentSummary = gql`fragment Summary on Campaign {id name title externalId status start end}`;
4
4
 
5
5
  export const FragmentMtt = gql`
6
6
  fragment Mtt on PrivateCampaign {
package/src/urql.mjs CHANGED
@@ -51,7 +51,6 @@ export const query = async (query, payload) => {
51
51
  export const mutation = async (mutation, payload) => {
52
52
  const result = await client.mutation(mutation, payload).toPromise();
53
53
  if (result.error) {
54
- console.log("error", result.error);
55
54
  throw result.error;
56
55
  }
57
56
  return result.data;