@cobinar/dalus 0.1.10 → 0.1.12

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.
@@ -1,147 +1,147 @@
1
- 'use strict';
2
- // Config discovery and credential storage for dalus.
3
- //
4
- // Project config: dalus.jsonc, dalus.toml, or dalus.json, checked in the
5
- // current directory in that order (first one found wins — this is the
6
- // exact order named in the original ask: "look for file named
7
- // dalus.jsonc, dalus.toml, or dalus."). Login credentials are kept
8
- // entirely separate, in ~/.dalus/credentials.json, same reasoning
9
- // Wrangler keeps its own auth out of the project directory: a project
10
- // config is something you'd commit; a bearer token is not.
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const os = require('os');
15
- const toml = require('smol-toml');
16
- const jsonc = require('jsonc-parser');
17
-
18
- const CONFIG_FILENAMES = ['dalus.jsonc', 'dalus.toml', 'dalus.json'];
19
- const CREDENTIALS_DIR = path.join(os.homedir(), '.dalus');
20
- const CREDENTIALS_PATH = path.join(CREDENTIALS_DIR, 'credentials.json');
21
-
22
- class DalusConfigError extends Error {}
23
-
24
- /** Searches the given directory (default: cwd) for a dalus config file,
25
- * in the fixed priority order above. Returns { path, format } or null if
26
- * none exist — forge.js decides what "none found" means for the command
27
- * being run, this module just reports the fact. */
28
- function findConfigFile(dir = process.cwd()) {
29
- for (const name of CONFIG_FILENAMES) {
30
- const p = path.join(dir, name);
31
- if (fs.existsSync(p)) {
32
- return { path: p, format: path.extname(name).slice(1) };
33
- }
34
- }
35
- return null;
36
- }
37
-
38
- /** Loads and parses whichever config file is found, resolving `dir`/
39
- * `main`/`rules`/`seed` fields in every resource entry to absolute paths
40
- * relative to the config file's own directory — so a project's resource
41
- * definitions keep working regardless of what directory `dalus forge`
42
- * happens to be invoked from. */
43
- function loadConfig(dir = process.cwd()) {
44
- const found = findConfigFile(dir);
45
- if (!found) {
46
- throw new DalusConfigError(
47
- `No dalus.jsonc, dalus.toml, or dalus.json found in ${dir}.\nRun "dalus init" to create one, or pass --pages/--workers/etc. with the other required flags directly.`,
48
- );
49
- }
50
- const raw = fs.readFileSync(found.path, 'utf8');
51
- let parsed;
52
- try {
53
- if (found.format === 'toml') {
54
- parsed = toml.parse(raw);
55
- } else {
56
- const errors = [];
57
- parsed = jsonc.parse(raw, errors, { allowTrailingComma: true });
58
- if (errors.length > 0) {
59
- throw new DalusConfigError(`Could not parse ${found.path}: ${jsonc.printParseErrorCode(errors[0].error)} near offset ${errors[0].offset}`);
60
- }
61
- }
62
- } catch (err) {
63
- if (err instanceof DalusConfigError) throw err;
64
- throw new DalusConfigError(`Could not parse ${found.path}: ${err.message}`);
65
- }
66
-
67
- const baseDir = path.dirname(found.path);
68
- const resolvePathFields = (entry, fields) => {
69
- const out = { ...entry };
70
- for (const f of fields) {
71
- if (typeof out[f] === 'string') out[f] = path.resolve(baseDir, out[f]);
72
- }
73
- return out;
74
- };
75
-
76
- const resourceKinds = {
77
- workers: ['main'],
78
- pages: ['dir'],
79
- storage: ['dir'],
80
- database: ['seed'],
81
- vault: ['rules'],
82
- };
83
- const normalized = { apiBase: parsed.apiBase, env: {} };
84
- for (const kind of Object.keys(resourceKinds)) {
85
- normalized[kind] = (parsed[kind] || []).map((e) => resolvePathFields(e, resourceKinds[kind]));
86
- }
87
- if (parsed.env && typeof parsed.env === 'object') {
88
- for (const envName of Object.keys(parsed.env)) {
89
- const envBlock = parsed.env[envName] || {};
90
- normalized.env[envName] = {};
91
- for (const kind of Object.keys(resourceKinds)) {
92
- normalized.env[envName][kind] = (envBlock[kind] || []).map((e) => resolvePathFields(e, resourceKinds[kind]));
93
- }
94
- }
95
- }
96
- normalized.__configPath = found.path;
97
- return normalized;
98
- }
99
-
100
- /** Applies `--env NAME`, if given: for each resource kind, an env block
101
- * that defines that kind REPLACES the top-level list for it; a resource
102
- * kind the env block doesn't mention falls back to the top-level list.
103
- * This is a deliberately simpler model than Wrangler's deep-merge — easy
104
- * to explain, easy to predict what a given --env will actually deploy. */
105
- function resolveEnv(config, envName) {
106
- if (!envName) return config;
107
- const envBlock = config.env && config.env[envName];
108
- if (!envBlock) {
109
- throw new DalusConfigError(`No [env.${envName}] block in ${config.__configPath}.`);
110
- }
111
- const merged = { ...config };
112
- for (const kind of ['workers', 'pages', 'storage', 'database', 'vault']) {
113
- if (envBlock[kind] && envBlock[kind].length > 0) merged[kind] = envBlock[kind];
114
- }
115
- return merged;
116
- }
117
-
118
- function loadCredentials() {
119
- if (!fs.existsSync(CREDENTIALS_PATH)) return null;
120
- try {
121
- return JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
122
- } catch {
123
- return null;
124
- }
125
- }
126
-
127
- function saveCredentials(creds) {
128
- fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
129
- // Same-user-only permissions — this file holds a real bearer token.
130
- fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });
131
- }
132
-
133
- function clearCredentials() {
134
- if (fs.existsSync(CREDENTIALS_PATH)) fs.unlinkSync(CREDENTIALS_PATH);
135
- }
136
-
137
- module.exports = {
138
- DalusConfigError,
139
- CONFIG_FILENAMES,
140
- CREDENTIALS_PATH,
141
- findConfigFile,
142
- loadConfig,
143
- resolveEnv,
144
- loadCredentials,
145
- saveCredentials,
146
- clearCredentials,
147
- };
1
+ 'use strict';
2
+ // Config discovery and credential storage for dalus.
3
+ //
4
+ // Project config: dalus.jsonc, dalus.toml, or dalus.json, checked in the
5
+ // current directory in that order (first one found wins — this is the
6
+ // exact order named in the original ask: "look for file named
7
+ // dalus.jsonc, dalus.toml, or dalus."). Login credentials are kept
8
+ // entirely separate, in ~/.dalus/credentials.json, same reasoning
9
+ // Wrangler keeps its own auth out of the project directory: a project
10
+ // config is something you'd commit; a bearer token is not.
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import os from 'os';
15
+ import toml from 'smol-toml';
16
+ import jsonc from 'jsonc-parser';
17
+
18
+ const CONFIG_FILENAMES = ['dalus.jsonc', 'dalus.toml', 'dalus.json'];
19
+ const CREDENTIALS_DIR = path.join(os.homedir(), '.dalus');
20
+ const CREDENTIALS_PATH = path.join(CREDENTIALS_DIR, 'credentials.json');
21
+
22
+ class DalusConfigError extends Error {}
23
+
24
+ /** Searches the given directory (default: cwd) for a dalus config file,
25
+ * in the fixed priority order above. Returns { path, format } or null if
26
+ * none exist — forge.js decides what "none found" means for the command
27
+ * being run, this module just reports the fact. */
28
+ function findConfigFile(dir = process.cwd()) {
29
+ for (const name of CONFIG_FILENAMES) {
30
+ const p = path.join(dir, name);
31
+ if (fs.existsSync(p)) {
32
+ return { path: p, format: path.extname(name).slice(1) };
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /** Loads and parses whichever config file is found, resolving `dir`/
39
+ * `main`/`rules`/`seed` fields in every resource entry to absolute paths
40
+ * relative to the config file's own directory — so a project's resource
41
+ * definitions keep working regardless of what directory `dalus forge`
42
+ * happens to be invoked from. */
43
+ function loadConfig(dir = process.cwd()) {
44
+ const found = findConfigFile(dir);
45
+ if (!found) {
46
+ throw new DalusConfigError(
47
+ `No dalus.jsonc, dalus.toml, or dalus.json found in ${dir}.\nRun "dalus init" to create one, or pass --pages/--workers/etc. with the other required flags directly.`,
48
+ );
49
+ }
50
+ const raw = fs.readFileSync(found.path, 'utf8');
51
+ let parsed;
52
+ try {
53
+ if (found.format === 'toml') {
54
+ parsed = toml.parse(raw);
55
+ } else {
56
+ const errors = [];
57
+ parsed = jsonc.parse(raw, errors, { allowTrailingComma: true });
58
+ if (errors.length > 0) {
59
+ throw new DalusConfigError(`Could not parse ${found.path}: ${jsonc.printParseErrorCode(errors[0].error)} near offset ${errors[0].offset}`);
60
+ }
61
+ }
62
+ } catch (err) {
63
+ if (err instanceof DalusConfigError) throw err;
64
+ throw new DalusConfigError(`Could not parse ${found.path}: ${err.message}`);
65
+ }
66
+
67
+ const baseDir = path.dirname(found.path);
68
+ const resolvePathFields = (entry, fields) => {
69
+ const out = { ...entry };
70
+ for (const f of fields) {
71
+ if (typeof out[f] === 'string') out[f] = path.resolve(baseDir, out[f]);
72
+ }
73
+ return out;
74
+ };
75
+
76
+ const resourceKinds = {
77
+ workers: ['main'],
78
+ pages: ['dir'],
79
+ storage: ['dir'],
80
+ database: ['seed'],
81
+ vault: ['rules'],
82
+ };
83
+ const normalized = { apiBase: parsed.apiBase, env: {} };
84
+ for (const kind of Object.keys(resourceKinds)) {
85
+ normalized[kind] = (parsed[kind] || []).map((e) => resolvePathFields(e, resourceKinds[kind]));
86
+ }
87
+ if (parsed.env && typeof parsed.env === 'object') {
88
+ for (const envName of Object.keys(parsed.env)) {
89
+ const envBlock = parsed.env[envName] || {};
90
+ normalized.env[envName] = {};
91
+ for (const kind of Object.keys(resourceKinds)) {
92
+ normalized.env[envName][kind] = (envBlock[kind] || []).map((e) => resolvePathFields(e, resourceKinds[kind]));
93
+ }
94
+ }
95
+ }
96
+ normalized.__configPath = found.path;
97
+ return normalized;
98
+ }
99
+
100
+ /** Applies `--env NAME`, if given: for each resource kind, an env block
101
+ * that defines that kind REPLACES the top-level list for it; a resource
102
+ * kind the env block doesn't mention falls back to the top-level list.
103
+ * This is a deliberately simpler model than Wrangler's deep-merge — easy
104
+ * to explain, easy to predict what a given --env will actually deploy. */
105
+ function resolveEnv(config, envName) {
106
+ if (!envName) return config;
107
+ const envBlock = config.env && config.env[envName];
108
+ if (!envBlock) {
109
+ throw new DalusConfigError(`No [env.${envName}] block in ${config.__configPath}.`);
110
+ }
111
+ const merged = { ...config };
112
+ for (const kind of ['workers', 'pages', 'storage', 'database', 'vault']) {
113
+ if (envBlock[kind] && envBlock[kind].length > 0) merged[kind] = envBlock[kind];
114
+ }
115
+ return merged;
116
+ }
117
+
118
+ function loadCredentials() {
119
+ if (!fs.existsSync(CREDENTIALS_PATH)) return null;
120
+ try {
121
+ return JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ function saveCredentials(creds) {
128
+ fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
129
+ // Same-user-only permissions — this file holds a real bearer token.
130
+ fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });
131
+ }
132
+
133
+ function clearCredentials() {
134
+ if (fs.existsSync(CREDENTIALS_PATH)) fs.unlinkSync(CREDENTIALS_PATH);
135
+ }
136
+
137
+ export {
138
+ DalusConfigError,
139
+ CONFIG_FILENAMES,
140
+ CREDENTIALS_PATH,
141
+ findConfigFile,
142
+ loadConfig,
143
+ resolveEnv,
144
+ loadCredentials,
145
+ saveCredentials,
146
+ clearCredentials,
147
+ };
@@ -1,34 +1,34 @@
1
- 'use strict';
2
- const fs = require('fs');
3
-
4
- /** Deploys one Document DB collection: find-or-create by name, then
5
- * optionally seeds it from a local JSON file (a single object -> one
6
- * document, an array -> one document per entry). Seeding only ever
7
- * ADDS documents — it doesn't diff against or replace what's already in
8
- * the collection, so re-running against a collection that already has
9
- * the seed data creates duplicates. Fine for "seed a fresh collection
10
- * once"; a real sync mode (matching on some key field) would need this
11
- * project's documents to have a stable id convention to match against,
12
- * which isn't assumed here. */
13
- async function forgeDatabase(api, log, entry) {
14
- log(`Document DB: ${entry.name}`);
15
-
16
- let collection = await api.findByName('/database/collections', entry.name);
17
- if (!collection) {
18
- log(` creating collection...`);
19
- collection = await api.post('/database/collections', { name: entry.name });
20
- }
21
-
22
- if (entry.seed) {
23
- const raw = JSON.parse(fs.readFileSync(entry.seed, 'utf8'));
24
- const docs = Array.isArray(raw) ? raw : [raw];
25
- for (const doc of docs) {
26
- await api.post(`/database/collections/${collection.id}/documents`, doc);
27
- }
28
- log(` seeded ${docs.length} document${docs.length === 1 ? '' : 's'} from ${entry.seed}`);
29
- }
30
-
31
- log(` done`);
32
- }
33
-
34
- module.exports = { forgeDatabase };
1
+ 'use strict';
2
+ import fs from 'fs';
3
+
4
+ /** Deploys one Document DB collection: find-or-create by name, then
5
+ * optionally seeds it from a local JSON file (a single object -> one
6
+ * document, an array -> one document per entry). Seeding only ever
7
+ * ADDS documents — it doesn't diff against or replace what's already in
8
+ * the collection, so re-running against a collection that already has
9
+ * the seed data creates duplicates. Fine for "seed a fresh collection
10
+ * once"; a real sync mode (matching on some key field) would need this
11
+ * project's documents to have a stable id convention to match against,
12
+ * which isn't assumed here. */
13
+ async function forgeDatabase(api, log, entry) {
14
+ log(`Document DB: ${entry.name}`);
15
+
16
+ let collection = await api.findByName('/database/collections', entry.name);
17
+ if (!collection) {
18
+ log(` creating collection...`);
19
+ collection = await api.post('/database/collections', { name: entry.name });
20
+ }
21
+
22
+ if (entry.seed) {
23
+ const raw = JSON.parse(fs.readFileSync(entry.seed, 'utf8'));
24
+ const docs = Array.isArray(raw) ? raw : [raw];
25
+ for (const doc of docs) {
26
+ await api.post(`/database/collections/${collection.id}/documents`, doc);
27
+ }
28
+ log(` seeded ${docs.length} document${docs.length === 1 ? '' : 's'} from ${entry.seed}`);
29
+ }
30
+
31
+ log(` done`);
32
+ }
33
+
34
+ export { forgeDatabase };
@@ -1,35 +1,35 @@
1
- 'use strict';
2
- const fs = require('fs');
3
- const path = require('path');
4
- const { walkFiles, guessContentType } = require('../fs-utils');
5
-
6
- /** Deploys one Static Hosting project: find-or-create by name, then PUT
7
- * every file under `dir`. Uploads are NOT diffed against what's already
8
- * there — every file in `dir` is re-uploaded every run, matching how
9
- * `wrangler pages deploy` treats a deploy as a fresh snapshot rather
10
- * than an incremental sync. Stale files left over from a previous
11
- * deploy (renamed/removed locally) are not currently cleaned up — worth
12
- * a `--clean` flag later if that turns out to matter in practice. */
13
- async function forgePages(api, log, entry) {
14
- log(`Static Hosting: ${entry.name}`);
15
- const files = walkFiles(entry.dir);
16
- if (files.length === 0) throw new Error(`No files found in ${entry.dir}`);
17
-
18
- let project = await api.findByName('/pages/projects', entry.name);
19
- if (!project) {
20
- log(` creating project...`);
21
- project = await api.post('/pages/projects', { name: entry.name });
22
- }
23
-
24
- for (const relPath of files) {
25
- const fullPath = path.join(entry.dir, relPath);
26
- const contentType = guessContentType(relPath);
27
- const body = fs.readFileSync(fullPath);
28
- await api.putRaw(`/pages/projects/${project.id}/files/${relPath}`, body, contentType);
29
- log(` uploaded ${relPath} (${contentType})`);
30
- }
31
-
32
- log(` done -> ${project.liveUrl || project.fallbackUrl || '(live link not returned by the API)'}`);
33
- }
34
-
35
- module.exports = { forgePages };
1
+ 'use strict';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { walkFiles, guessContentType } from '../fs-utils.mjs';
5
+
6
+ /** Deploys one Static Hosting project: find-or-create by name, then PUT
7
+ * every file under `dir`. Uploads are NOT diffed against what's already
8
+ * there — every file in `dir` is re-uploaded every run, matching how
9
+ * `wrangler pages deploy` treats a deploy as a fresh snapshot rather
10
+ * than an incremental sync. Stale files left over from a previous
11
+ * deploy (renamed/removed locally) are not currently cleaned up — worth
12
+ * a `--clean` flag later if that turns out to matter in practice. */
13
+ async function forgePages(api, log, entry) {
14
+ log(`Static Hosting: ${entry.name}`);
15
+ const files = walkFiles(entry.dir);
16
+ if (files.length === 0) throw new Error(`No files found in ${entry.dir}`);
17
+
18
+ let project = await api.findByName('/pages/projects', entry.name);
19
+ if (!project) {
20
+ log(` creating project...`);
21
+ project = await api.post('/pages/projects', { name: entry.name });
22
+ }
23
+
24
+ for (const relPath of files) {
25
+ const fullPath = path.join(entry.dir, relPath);
26
+ const contentType = guessContentType(relPath);
27
+ const body = fs.readFileSync(fullPath);
28
+ await api.putRaw(`/pages/projects/${project.id}/files/${relPath}`, body, contentType);
29
+ log(` uploaded ${relPath} (${contentType})`);
30
+ }
31
+
32
+ log(` done -> ${project.liveUrl || project.fallbackUrl || '(live link not returned by the API)'}`);
33
+ }
34
+
35
+ export { forgePages };
@@ -1,39 +1,39 @@
1
- 'use strict';
2
- const fs = require('fs');
3
- const path = require('path');
4
- const { walkFiles, guessContentType } = require('../fs-utils');
5
-
6
- /** Deploys one Object Storage bucket: find-or-create by name, flips
7
- * public access if configured, then PUTs every file under `dir` as an
8
- * object keyed by its relative path — same "re-upload everything every
9
- * run" behavior as the Pages forger, for the same reason (matching a
10
- * deploy tool's usual "this directory IS the desired state" model). */
11
- async function forgeStorage(api, log, entry) {
12
- log(`Object Storage: ${entry.name}`);
13
-
14
- let bucket = await api.findByName('/storage/buckets', entry.name);
15
- if (!bucket) {
16
- log(` creating bucket...`);
17
- bucket = await api.post('/storage/buckets', { name: entry.name });
18
- }
19
-
20
- if (typeof entry.public === 'boolean' && entry.public !== bucket.publicEnabled) {
21
- bucket = await api.patch(`/storage/buckets/${bucket.id}/public`, { enabled: entry.public });
22
- log(` public access -> ${entry.public}`);
23
- }
24
-
25
- if (entry.dir) {
26
- const files = walkFiles(entry.dir);
27
- for (const relPath of files) {
28
- const fullPath = path.join(entry.dir, relPath);
29
- const contentType = guessContentType(relPath);
30
- const body = fs.readFileSync(fullPath);
31
- const result = await api.putRaw(`/storage/buckets/${bucket.id}/objects/${relPath}`, body, contentType);
32
- log(` uploaded ${relPath}${result && result.url ? ` -> ${result.url}` : ''}`);
33
- }
34
- }
35
-
36
- log(` done`);
37
- }
38
-
39
- module.exports = { forgeStorage };
1
+ 'use strict';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { walkFiles, guessContentType } from '../fs-utils.mjs';
5
+
6
+ /** Deploys one Object Storage bucket: find-or-create by name, flips
7
+ * public access if configured, then PUTs every file under `dir` as an
8
+ * object keyed by its relative path — same "re-upload everything every
9
+ * run" behavior as the Pages forger, for the same reason (matching a
10
+ * deploy tool's usual "this directory IS the desired state" model). */
11
+ async function forgeStorage(api, log, entry) {
12
+ log(`Object Storage: ${entry.name}`);
13
+
14
+ let bucket = await api.findByName('/storage/buckets', entry.name);
15
+ if (!bucket) {
16
+ log(` creating bucket...`);
17
+ bucket = await api.post('/storage/buckets', { name: entry.name });
18
+ }
19
+
20
+ if (typeof entry.public === 'boolean' && entry.public !== bucket.publicEnabled) {
21
+ bucket = await api.patch(`/storage/buckets/${bucket.id}/public`, { enabled: entry.public });
22
+ log(` public access -> ${entry.public}`);
23
+ }
24
+
25
+ if (entry.dir) {
26
+ const files = walkFiles(entry.dir);
27
+ for (const relPath of files) {
28
+ const fullPath = path.join(entry.dir, relPath);
29
+ const contentType = guessContentType(relPath);
30
+ const body = fs.readFileSync(fullPath);
31
+ const result = await api.putRaw(`/storage/buckets/${bucket.id}/objects/${relPath}`, body, contentType);
32
+ log(` uploaded ${relPath}${result && result.url ? ` -> ${result.url}` : ''}`);
33
+ }
34
+ }
35
+
36
+ log(` done`);
37
+ }
38
+
39
+ export { forgeStorage };
@@ -1,27 +1,27 @@
1
- 'use strict';
2
- const fs = require('fs');
3
-
4
- /** Deploys one Vault: find-or-create by name, then uploads its rules
5
- * text if configured. Rules are validated server-side (a real parse —
6
- * see lib/rules.ts in the worker project) before being saved, so a
7
- * syntax error in the local rules file surfaces here as a clear error,
8
- * not a silent deploy of broken rules. */
9
- async function forgeVault(api, log, entry) {
10
- log(`Vault: ${entry.name}`);
11
-
12
- let vault = await api.findByName('/vaults', entry.name);
13
- if (!vault) {
14
- log(` creating vault...`);
15
- vault = await api.post('/vaults', { name: entry.name });
16
- }
17
-
18
- if (entry.rules) {
19
- const rules = fs.readFileSync(entry.rules, 'utf8');
20
- await api.put(`/vaults/${vault.id}/rules`, { rules });
21
- log(` rules uploaded from ${entry.rules}`);
22
- }
23
-
24
- log(` done -> data endpoint at ${vault.dataUrl || `${api.apiBase}/vault/${entry.name}`}`);
25
- }
26
-
27
- module.exports = { forgeVault };
1
+ 'use strict';
2
+ import fs from 'fs';
3
+
4
+ /** Deploys one Vault: find-or-create by name, then uploads its rules
5
+ * text if configured. Rules are validated server-side (a real parse —
6
+ * see lib/rules.ts in the worker project) before being saved, so a
7
+ * syntax error in the local rules file surfaces here as a clear error,
8
+ * not a silent deploy of broken rules. */
9
+ async function forgeVault(api, log, entry) {
10
+ log(`Vault: ${entry.name}`);
11
+
12
+ let vault = await api.findByName('/vaults', entry.name);
13
+ if (!vault) {
14
+ log(` creating vault...`);
15
+ vault = await api.post('/vaults', { name: entry.name });
16
+ }
17
+
18
+ if (entry.rules) {
19
+ const rules = fs.readFileSync(entry.rules, 'utf8');
20
+ await api.put(`/vaults/${vault.id}/rules`, { rules });
21
+ log(` rules uploaded from ${entry.rules}`);
22
+ }
23
+
24
+ log(` done -> data endpoint at ${vault.dataUrl || `${api.apiBase}/vault/${entry.name}`}`);
25
+ }
26
+
27
+ export { forgeVault };