fastkv 0.2.0 → 2.0.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 +10 -6
- package/dist/index.d.mts +27 -35
- package/dist/index.d.ts +27 -35
- package/dist/index.js +151 -1
- package/dist/index.mjs +116 -1
- package/package.json +26 -6
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
FastKV is a key-value store which offers the following features:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- Supports temporary in-memory storage 🕒
|
|
10
|
+
- Supports persistent storage with JSON files 📁
|
|
11
|
+
- Lightweight with no dependencies ⚡
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
@@ -19,6 +20,8 @@ npm install fastkv
|
|
|
19
20
|
yarn add fastkv
|
|
20
21
|
# or
|
|
21
22
|
pnpm add fastkv
|
|
23
|
+
# or
|
|
24
|
+
bun add fastkv
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
## Example
|
|
@@ -28,19 +31,20 @@ import { KV } from 'fastkv';
|
|
|
28
31
|
|
|
29
32
|
// For TypeScript users, the KV supports generics:
|
|
30
33
|
// const example = new KV<string>();
|
|
34
|
+
// Or: example.get<string>('my-key-name');
|
|
31
35
|
|
|
32
36
|
const kv = new KV('./db.json'); // Save the data. Path resolves with process.cwd()
|
|
33
37
|
const cache = new KV('::memory::'); // Keep the data in the system's memory.
|
|
34
38
|
|
|
35
39
|
// Set data
|
|
36
|
-
|
|
40
|
+
kv.set('userSettings', { theme: 'dark' });
|
|
37
41
|
|
|
38
42
|
// Retreive data by a key
|
|
39
|
-
|
|
43
|
+
kv.get('userSettings'); // -> { theme: 'dark' }
|
|
40
44
|
|
|
41
45
|
// Retreive all data
|
|
42
|
-
|
|
46
|
+
kv.all(); // -> [{ key: 'userSettings', value: { theme: 'dark' } }]
|
|
43
47
|
|
|
44
48
|
// Clear the store
|
|
45
|
-
|
|
49
|
+
kv.clear();
|
|
46
50
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,39 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
type Entry<T = any> = {
|
|
2
|
+
__id: string;
|
|
3
|
+
key: string;
|
|
4
|
+
value: T;
|
|
5
|
+
};
|
|
6
|
+
type DeepPartial<T> = {
|
|
7
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
8
|
+
};
|
|
5
9
|
declare class KV<T = any> {
|
|
6
10
|
#private;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
get(key: string): T | null;
|
|
26
|
-
/**
|
|
27
|
-
* Gets all data in the store.
|
|
28
|
-
* @returns An array of data.
|
|
29
|
-
*/
|
|
30
|
-
all(): T[];
|
|
31
|
-
/**
|
|
32
|
-
* Clears all data in the store.
|
|
33
|
-
* @returns The KV instance.
|
|
34
|
-
*/
|
|
35
|
-
clear(): this;
|
|
11
|
+
path?: string;
|
|
12
|
+
constructor(options?: {
|
|
13
|
+
path?: string;
|
|
14
|
+
prettify?: boolean;
|
|
15
|
+
});
|
|
16
|
+
set(key: string, value: T): Promise<Entry<T>>;
|
|
17
|
+
upsert(options: {
|
|
18
|
+
where: DeepPartial<T> | string;
|
|
19
|
+
create?: DeepPartial<T>;
|
|
20
|
+
update?: DeepPartial<T>;
|
|
21
|
+
}): Promise<Entry<T>>;
|
|
22
|
+
find(options: {
|
|
23
|
+
in?: "key" | "value";
|
|
24
|
+
where: T | string | number | boolean;
|
|
25
|
+
}): Promise<Entry<T>[]>;
|
|
26
|
+
get(query: string): Promise<Entry<T> | T | null>;
|
|
27
|
+
all(): Promise<Entry<T>[]>;
|
|
28
|
+
clear(): Promise<void>;
|
|
36
29
|
}
|
|
37
|
-
declare const _default: KV<any>;
|
|
38
30
|
|
|
39
|
-
export { KV,
|
|
31
|
+
export { KV, KV as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,39 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
type Entry<T = any> = {
|
|
2
|
+
__id: string;
|
|
3
|
+
key: string;
|
|
4
|
+
value: T;
|
|
5
|
+
};
|
|
6
|
+
type DeepPartial<T> = {
|
|
7
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
8
|
+
};
|
|
5
9
|
declare class KV<T = any> {
|
|
6
10
|
#private;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
get(key: string): T | null;
|
|
26
|
-
/**
|
|
27
|
-
* Gets all data in the store.
|
|
28
|
-
* @returns An array of data.
|
|
29
|
-
*/
|
|
30
|
-
all(): T[];
|
|
31
|
-
/**
|
|
32
|
-
* Clears all data in the store.
|
|
33
|
-
* @returns The KV instance.
|
|
34
|
-
*/
|
|
35
|
-
clear(): this;
|
|
11
|
+
path?: string;
|
|
12
|
+
constructor(options?: {
|
|
13
|
+
path?: string;
|
|
14
|
+
prettify?: boolean;
|
|
15
|
+
});
|
|
16
|
+
set(key: string, value: T): Promise<Entry<T>>;
|
|
17
|
+
upsert(options: {
|
|
18
|
+
where: DeepPartial<T> | string;
|
|
19
|
+
create?: DeepPartial<T>;
|
|
20
|
+
update?: DeepPartial<T>;
|
|
21
|
+
}): Promise<Entry<T>>;
|
|
22
|
+
find(options: {
|
|
23
|
+
in?: "key" | "value";
|
|
24
|
+
where: T | string | number | boolean;
|
|
25
|
+
}): Promise<Entry<T>[]>;
|
|
26
|
+
get(query: string): Promise<Entry<T> | T | null>;
|
|
27
|
+
all(): Promise<Entry<T>[]>;
|
|
28
|
+
clear(): Promise<void>;
|
|
36
29
|
}
|
|
37
|
-
declare const _default: KV<any>;
|
|
38
30
|
|
|
39
|
-
export { KV,
|
|
31
|
+
export { KV, KV as default };
|
package/dist/index.js
CHANGED
|
@@ -1 +1,151 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
KV: () => KV,
|
|
34
|
+
default: () => index_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_node_crypto = require("crypto");
|
|
38
|
+
var import_node_path = __toESM(require("path"));
|
|
39
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
40
|
+
var KV = class {
|
|
41
|
+
path;
|
|
42
|
+
#data = [];
|
|
43
|
+
#ready;
|
|
44
|
+
#prettify;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
if (options?.prettify) {
|
|
47
|
+
this.#prettify = options?.prettify;
|
|
48
|
+
} else {
|
|
49
|
+
this.#prettify = false;
|
|
50
|
+
}
|
|
51
|
+
if (options?.path) {
|
|
52
|
+
this.path = import_node_path.default.resolve(process.cwd(), options.path);
|
|
53
|
+
this.#ready = this.#loadFromFile();
|
|
54
|
+
} else {
|
|
55
|
+
this.#ready = Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async #loadFromFile() {
|
|
59
|
+
try {
|
|
60
|
+
const content = await import_promises.default.readFile(this.path, "utf8");
|
|
61
|
+
this.#data = JSON.parse(content);
|
|
62
|
+
} catch {
|
|
63
|
+
this.#data = [];
|
|
64
|
+
await this.#saveToFile();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async #saveToFile() {
|
|
68
|
+
if (!this.path) return;
|
|
69
|
+
await import_promises.default.writeFile(
|
|
70
|
+
this.path,
|
|
71
|
+
JSON.stringify(this.#data, null, this.#prettify ? 2 : 0),
|
|
72
|
+
"utf8"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
#resolvePath(object, path2) {
|
|
76
|
+
return path2.split(".").reduce((acc, part) => acc ? acc[part] : void 0, object);
|
|
77
|
+
}
|
|
78
|
+
#matches(value, where) {
|
|
79
|
+
if (typeof value !== typeof where) return false;
|
|
80
|
+
if (typeof value === "object" && value !== null && where !== null) {
|
|
81
|
+
return Object.entries(where).every(([k, v]) => this.#matches(value[k], v));
|
|
82
|
+
}
|
|
83
|
+
return value === where;
|
|
84
|
+
}
|
|
85
|
+
async set(key, value) {
|
|
86
|
+
await this.#ready;
|
|
87
|
+
const entry = this.#data.find((entry2) => entry2.key === key);
|
|
88
|
+
if (entry) {
|
|
89
|
+
entry.value = value;
|
|
90
|
+
await this.#saveToFile();
|
|
91
|
+
return entry;
|
|
92
|
+
}
|
|
93
|
+
const newEntry = { __id: (0, import_node_crypto.randomUUID)(), key, value };
|
|
94
|
+
this.#data.push(newEntry);
|
|
95
|
+
await this.#saveToFile();
|
|
96
|
+
return newEntry;
|
|
97
|
+
}
|
|
98
|
+
async upsert(options) {
|
|
99
|
+
await this.#ready;
|
|
100
|
+
let entry;
|
|
101
|
+
if (typeof options.where === "string") {
|
|
102
|
+
entry = this.#data.find((entry2) => entry2.key === options.where);
|
|
103
|
+
} else {
|
|
104
|
+
entry = this.#data.find((entry2) => this.#matches(entry2.value, options.where));
|
|
105
|
+
}
|
|
106
|
+
if (entry) {
|
|
107
|
+
if (options.update) {
|
|
108
|
+
entry.value = { ...entry.value, ...options.update };
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const key = typeof options.where === "string" ? options.where : (0, import_node_crypto.randomUUID)();
|
|
112
|
+
entry = { __id: (0, import_node_crypto.randomUUID)(), key, value: { ...options.create ?? {} } };
|
|
113
|
+
this.#data.push(entry);
|
|
114
|
+
}
|
|
115
|
+
await this.#saveToFile();
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
async find(options) {
|
|
119
|
+
await this.#ready;
|
|
120
|
+
const field = options.in ?? "value";
|
|
121
|
+
const where = options.where;
|
|
122
|
+
return this.#data.filter((entry) => {
|
|
123
|
+
if (field === "key") {
|
|
124
|
+
return typeof where === "string" && entry.key.includes(where);
|
|
125
|
+
}
|
|
126
|
+
return this.#matches(entry.value, where);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async get(query) {
|
|
130
|
+
await this.#ready;
|
|
131
|
+
const [baseKey, ...rest] = query.split(".");
|
|
132
|
+
const entry = this.#data.find((entry2) => entry2.key === baseKey);
|
|
133
|
+
if (!entry) return null;
|
|
134
|
+
if (rest.length === 0) return entry;
|
|
135
|
+
return this.#resolvePath({ value: entry.value }, rest.join(".")) ?? null;
|
|
136
|
+
}
|
|
137
|
+
async all() {
|
|
138
|
+
await this.#ready;
|
|
139
|
+
return [...this.#data];
|
|
140
|
+
}
|
|
141
|
+
async clear() {
|
|
142
|
+
await this.#ready;
|
|
143
|
+
this.#data = [];
|
|
144
|
+
await this.#saveToFile();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var index_default = KV;
|
|
148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
149
|
+
0 && (module.exports = {
|
|
150
|
+
KV
|
|
151
|
+
});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1,116 @@
|
|
|
1
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
var KV = class {
|
|
6
|
+
path;
|
|
7
|
+
#data = [];
|
|
8
|
+
#ready;
|
|
9
|
+
#prettify;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
if (options?.prettify) {
|
|
12
|
+
this.#prettify = options?.prettify;
|
|
13
|
+
} else {
|
|
14
|
+
this.#prettify = false;
|
|
15
|
+
}
|
|
16
|
+
if (options?.path) {
|
|
17
|
+
this.path = path.resolve(process.cwd(), options.path);
|
|
18
|
+
this.#ready = this.#loadFromFile();
|
|
19
|
+
} else {
|
|
20
|
+
this.#ready = Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async #loadFromFile() {
|
|
24
|
+
try {
|
|
25
|
+
const content = await fs.readFile(this.path, "utf8");
|
|
26
|
+
this.#data = JSON.parse(content);
|
|
27
|
+
} catch {
|
|
28
|
+
this.#data = [];
|
|
29
|
+
await this.#saveToFile();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async #saveToFile() {
|
|
33
|
+
if (!this.path) return;
|
|
34
|
+
await fs.writeFile(
|
|
35
|
+
this.path,
|
|
36
|
+
JSON.stringify(this.#data, null, this.#prettify ? 2 : 0),
|
|
37
|
+
"utf8"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
#resolvePath(object, path2) {
|
|
41
|
+
return path2.split(".").reduce((acc, part) => acc ? acc[part] : void 0, object);
|
|
42
|
+
}
|
|
43
|
+
#matches(value, where) {
|
|
44
|
+
if (typeof value !== typeof where) return false;
|
|
45
|
+
if (typeof value === "object" && value !== null && where !== null) {
|
|
46
|
+
return Object.entries(where).every(([k, v]) => this.#matches(value[k], v));
|
|
47
|
+
}
|
|
48
|
+
return value === where;
|
|
49
|
+
}
|
|
50
|
+
async set(key, value) {
|
|
51
|
+
await this.#ready;
|
|
52
|
+
const entry = this.#data.find((entry2) => entry2.key === key);
|
|
53
|
+
if (entry) {
|
|
54
|
+
entry.value = value;
|
|
55
|
+
await this.#saveToFile();
|
|
56
|
+
return entry;
|
|
57
|
+
}
|
|
58
|
+
const newEntry = { __id: randomUUID(), key, value };
|
|
59
|
+
this.#data.push(newEntry);
|
|
60
|
+
await this.#saveToFile();
|
|
61
|
+
return newEntry;
|
|
62
|
+
}
|
|
63
|
+
async upsert(options) {
|
|
64
|
+
await this.#ready;
|
|
65
|
+
let entry;
|
|
66
|
+
if (typeof options.where === "string") {
|
|
67
|
+
entry = this.#data.find((entry2) => entry2.key === options.where);
|
|
68
|
+
} else {
|
|
69
|
+
entry = this.#data.find((entry2) => this.#matches(entry2.value, options.where));
|
|
70
|
+
}
|
|
71
|
+
if (entry) {
|
|
72
|
+
if (options.update) {
|
|
73
|
+
entry.value = { ...entry.value, ...options.update };
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
const key = typeof options.where === "string" ? options.where : randomUUID();
|
|
77
|
+
entry = { __id: randomUUID(), key, value: { ...options.create ?? {} } };
|
|
78
|
+
this.#data.push(entry);
|
|
79
|
+
}
|
|
80
|
+
await this.#saveToFile();
|
|
81
|
+
return entry;
|
|
82
|
+
}
|
|
83
|
+
async find(options) {
|
|
84
|
+
await this.#ready;
|
|
85
|
+
const field = options.in ?? "value";
|
|
86
|
+
const where = options.where;
|
|
87
|
+
return this.#data.filter((entry) => {
|
|
88
|
+
if (field === "key") {
|
|
89
|
+
return typeof where === "string" && entry.key.includes(where);
|
|
90
|
+
}
|
|
91
|
+
return this.#matches(entry.value, where);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async get(query) {
|
|
95
|
+
await this.#ready;
|
|
96
|
+
const [baseKey, ...rest] = query.split(".");
|
|
97
|
+
const entry = this.#data.find((entry2) => entry2.key === baseKey);
|
|
98
|
+
if (!entry) return null;
|
|
99
|
+
if (rest.length === 0) return entry;
|
|
100
|
+
return this.#resolvePath({ value: entry.value }, rest.join(".")) ?? null;
|
|
101
|
+
}
|
|
102
|
+
async all() {
|
|
103
|
+
await this.#ready;
|
|
104
|
+
return [...this.#data];
|
|
105
|
+
}
|
|
106
|
+
async clear() {
|
|
107
|
+
await this.#ready;
|
|
108
|
+
this.#data = [];
|
|
109
|
+
await this.#saveToFile();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var index_default = KV;
|
|
113
|
+
export {
|
|
114
|
+
KV,
|
|
115
|
+
index_default as default
|
|
116
|
+
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastkv",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"package.json",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
7
12
|
"description": "A key-value store, helpful for caches in apps.",
|
|
8
|
-
"main": "dist/index.js",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
9
16
|
"repository": {
|
|
10
17
|
"url": "https://github.com/m1-dev/fastkv"
|
|
11
18
|
},
|
|
@@ -14,16 +21,29 @@
|
|
|
14
21
|
"database",
|
|
15
22
|
"json"
|
|
16
23
|
],
|
|
24
|
+
"prettier": {
|
|
25
|
+
"tabWidth": 4,
|
|
26
|
+
"printWidth": 95,
|
|
27
|
+
"arrowParens": "always",
|
|
28
|
+
"trailingComma": "es5",
|
|
29
|
+
"singleQuote": false,
|
|
30
|
+
"jsxSingleQuote": false,
|
|
31
|
+
"useTabs": false,
|
|
32
|
+
"semi": true
|
|
33
|
+
},
|
|
17
34
|
"license": "MIT",
|
|
18
35
|
"devDependencies": {
|
|
19
|
-
"@types/node": "^20.
|
|
20
|
-
"
|
|
36
|
+
"@types/node": "^20.11.16",
|
|
37
|
+
"prettier": "^3.2.5",
|
|
21
38
|
"tsup": "^8.0.1",
|
|
22
39
|
"typescript": "^5.3.3"
|
|
23
40
|
},
|
|
24
41
|
"scripts": {
|
|
25
42
|
"build": "tsup",
|
|
26
|
-
"lint": "tsc --noEmit
|
|
27
|
-
"prepublish": "pnpm run build"
|
|
43
|
+
"lint": "tsc --noEmit; prettier . --check --ignore-path=.prettierignore",
|
|
44
|
+
"prepublish": "pnpm run build",
|
|
45
|
+
"format": "prettier . --write --ignore-path=.prettierignore",
|
|
46
|
+
"deploy:fastkv:beta": "pnpm publish --no-git-checks --access public --tag beta",
|
|
47
|
+
"deploy:fastkv:stable": "pnpm publish --no-git-checks --access public"
|
|
28
48
|
}
|
|
29
49
|
}
|