@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.
- package/bin/{dalus.js → dalus.mjs} +3 -3
- package/convert_to_mjs.py +59 -0
- package/fix_requires.py +57 -0
- package/package.json +3 -3
- package/src/{api-client.js → api-client.mjs} +67 -67
- package/src/commands/{forge.js → forge.mjs} +104 -104
- package/src/commands/{init.js → init.mjs} +53 -53
- package/src/commands/{login.js → login.mjs} +288 -288
- package/src/{config.js → config.mjs} +147 -147
- package/src/forgers/{database.js → database.mjs} +34 -34
- package/src/forgers/{pages.js → pages.mjs} +35 -35
- package/src/forgers/{storage.js → storage.mjs} +39 -39
- package/src/forgers/{vault.js → vault.mjs} +27 -27
- package/src/forgers/{workers.js → workers.mjs} +85 -85
- package/src/{fs-utils.js → fs-utils.mjs} +36 -36
- package/src/handlers/{login-callback.js → login-callback.mjs} +143 -143
- package/src/handlers/{services.js → services.mjs} +1 -1
- package/src/{index.js → index.mjs} +36 -36
- package/worker/index.js +658 -0
- /package/src/{cors.js → cors.mjs} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { loginCommand } from '../src/commands/login.mjs';
|
|
5
|
+
import { forgeCommand } from '../src/commands/forge.mjs';
|
|
6
|
+
import { initCommand } from '../src/commands/init.mjs';
|
|
7
7
|
|
|
8
8
|
const HELP = `dalus — deploy Cobinar projects from the command line
|
|
9
9
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
TARGET_DIR = r"D:\Downloads\daluss"
|
|
5
|
+
|
|
6
|
+
# Directories to skip to prevent corrupting dependencies or git history
|
|
7
|
+
IGNORE_DIRS = {'node_modules', '.git', 'dist', 'build'}
|
|
8
|
+
|
|
9
|
+
def convert_js_to_mjs(directory):
|
|
10
|
+
if not os.path.exists(directory):
|
|
11
|
+
print(f"Error: Directory '{directory}' does not exist.")
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
js_files = []
|
|
15
|
+
|
|
16
|
+
# 1. Collect all .js file paths
|
|
17
|
+
for root, dirs, files in os.walk(directory):
|
|
18
|
+
dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
|
|
19
|
+
for file in files:
|
|
20
|
+
if file.endswith('.js'):
|
|
21
|
+
js_files.append(os.path.join(root, file))
|
|
22
|
+
|
|
23
|
+
print(f"Found {len(js_files)} .js files in '{directory}'...\n")
|
|
24
|
+
|
|
25
|
+
# Regex matches relative imports/exports/requires pointing to .js files (e.g., './utils.js' -> './utils.mjs')
|
|
26
|
+
import_regex = re.compile(r'(import|export|require)\b([^"\']*["\']\.\.?[^"\']*)\.js(["\'])')
|
|
27
|
+
|
|
28
|
+
# 2. Update relative import paths inside all relevant files
|
|
29
|
+
for root, dirs, files in os.walk(directory):
|
|
30
|
+
dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
|
|
31
|
+
for file in files:
|
|
32
|
+
if file.endswith(('.js', '.mjs', '.ts', '.json')):
|
|
33
|
+
file_path = os.path.join(root, file)
|
|
34
|
+
try:
|
|
35
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
36
|
+
content = f.read()
|
|
37
|
+
|
|
38
|
+
updated_content = import_regex.sub(r'\1\2.mjs\3', content)
|
|
39
|
+
|
|
40
|
+
if updated_content != content:
|
|
41
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
42
|
+
f.write(updated_content)
|
|
43
|
+
print(f"[UPDATED IMPORTS] {file_path}")
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"[SKIPPED FILE READ/WRITE] {file_path}: {e}")
|
|
46
|
+
|
|
47
|
+
# 3. Rename .js files to .mjs
|
|
48
|
+
for old_path in js_files:
|
|
49
|
+
new_path = old_path[:-3] + '.mjs'
|
|
50
|
+
try:
|
|
51
|
+
os.rename(old_path, new_path)
|
|
52
|
+
print(f"[RENAMED] {os.path.basename(old_path)} -> {os.path.basename(new_path)}")
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"[RENAME FAILED] {old_path}: {e}")
|
|
55
|
+
|
|
56
|
+
print("\nConversion complete!")
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
convert_js_to_mjs(TARGET_DIR)
|
package/fix_requires.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
TARGET_DIR = r"D:\Downloads\daluss"
|
|
5
|
+
IGNORE_DIRS = {'node_modules', '.git', 'dist', 'build'}
|
|
6
|
+
|
|
7
|
+
def fix_exports(directory):
|
|
8
|
+
if not os.path.exists(directory):
|
|
9
|
+
print(f"Error: Directory '{directory}' does not exist.")
|
|
10
|
+
return
|
|
11
|
+
|
|
12
|
+
# Matches: module.exports = { something };
|
|
13
|
+
exports_dict_regex = re.compile(r"module\.exports\s*=\s*\{([^}]+)\}\s*;?")
|
|
14
|
+
|
|
15
|
+
# Matches: module.exports.foo = bar; or exports.foo = bar;
|
|
16
|
+
named_exports_regex = re.compile(r"(?:module\.)?exports\.([a-zA-Z0-9_]+)\s*=\s*")
|
|
17
|
+
|
|
18
|
+
# Matches: module.exports = myVariable; (for default exports)
|
|
19
|
+
default_export_regex = re.compile(r"module\.exports\s*=\s*([a-zA-Z0-9_]+)\s*;?")
|
|
20
|
+
|
|
21
|
+
changed_files_count = 0
|
|
22
|
+
|
|
23
|
+
for root, dirs, files in os.walk(directory):
|
|
24
|
+
dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
|
|
25
|
+
|
|
26
|
+
for file in files:
|
|
27
|
+
if file.endswith('.mjs'):
|
|
28
|
+
file_path = os.path.join(root, file)
|
|
29
|
+
try:
|
|
30
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
31
|
+
content = f.read()
|
|
32
|
+
|
|
33
|
+
original_content = content
|
|
34
|
+
|
|
35
|
+
# Fix object exports: module.exports = { foo }; -> export { foo };
|
|
36
|
+
content = exports_dict_regex.sub(r"export { \1 };", content)
|
|
37
|
+
|
|
38
|
+
# Fix named exports: module.exports.foo = ... -> export const foo = ...
|
|
39
|
+
content = named_exports_regex.sub(r"export const \1 = ", content)
|
|
40
|
+
|
|
41
|
+
# Fix default exports: module.exports = foo; -> export default foo;
|
|
42
|
+
content = default_export_regex.sub(r"export default \1;", content)
|
|
43
|
+
|
|
44
|
+
if content != original_content:
|
|
45
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
46
|
+
f.write(content)
|
|
47
|
+
print(f"[FIXED EXPORTS] {file_path}")
|
|
48
|
+
changed_files_count += 1
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print(f"[SKIPPED] {file_path}: {e}")
|
|
52
|
+
|
|
53
|
+
print(f"\n✅ Conversion complete! Fixed {changed_files_count} files.")
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
print("Scanning .mjs files to fix CommonJS exports (module.exports)...\n")
|
|
57
|
+
fix_exports(TARGET_DIR)
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cobinar/dalus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Deploy Cobinar projects (Edge Compute, Static Hosting, Object Storage, Document DB, Vault) from the command line.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"dalus": "./bin/dalus.
|
|
6
|
+
"dalus": "./bin/dalus.mjs"
|
|
7
7
|
},
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"type": "commonjs",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"jsonc-parser": "^3.3.1",
|
|
15
|
-
"smol-toml": "^1.
|
|
15
|
+
"smol-toml": "^1.8.0"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT"
|
|
18
18
|
}
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// A thin client over cobinar-dashboard-worker's real REST API — every
|
|
3
|
-
// call here hits an endpoint that already exists and is already used by
|
|
4
|
-
// the dashboard itself (see that project's README for the full list).
|
|
5
|
-
// dalus doesn't talk to Cloudflare directly and has no separate deploy
|
|
6
|
-
// mechanism of its own; it's a scriptable front door to the same API the
|
|
7
|
-
// web dashboard calls, nothing more.
|
|
8
|
-
|
|
9
|
-
class DalusApiError extends Error {
|
|
10
|
-
constructor(message, status, body) {
|
|
11
|
-
super(message);
|
|
12
|
-
this.status = status;
|
|
13
|
-
this.body = body;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class ApiClient {
|
|
18
|
-
constructor({ apiBase, token }) {
|
|
19
|
-
if (!apiBase) throw new Error('apiBase is required (set it in your dalus config, or run "dalus login").');
|
|
20
|
-
if (!token) throw new Error('Not logged in — run "dalus login" first.');
|
|
21
|
-
this.apiBase = apiBase.replace(/\/$/, '');
|
|
22
|
-
this.token = token;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async request(method, path, { body, headers, rawBody } = {}) {
|
|
26
|
-
const res = await fetch(`${this.apiBase}${path}`, {
|
|
27
|
-
method,
|
|
28
|
-
headers: {
|
|
29
|
-
authorization: `Bearer ${this.token}`,
|
|
30
|
-
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
31
|
-
...headers,
|
|
32
|
-
},
|
|
33
|
-
body: rawBody !== undefined ? rawBody : body !== undefined ? JSON.stringify(body) : undefined,
|
|
34
|
-
});
|
|
35
|
-
const text = await res.text();
|
|
36
|
-
let parsed;
|
|
37
|
-
try { parsed = text ? JSON.parse(text) : null; } catch { parsed = text; }
|
|
38
|
-
if (!res.ok) {
|
|
39
|
-
const message = (parsed && typeof parsed === 'object' && parsed.error) || `${method} ${path} failed with ${res.status}`;
|
|
40
|
-
throw new DalusApiError(message, res.status, parsed);
|
|
41
|
-
}
|
|
42
|
-
return parsed;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
get(path) { return this.request('GET', path); }
|
|
46
|
-
post(path, body) { return this.request('POST', path, { body }); }
|
|
47
|
-
patch(path, body) { return this.request('PATCH', path, { body }); }
|
|
48
|
-
put(path, body) { return this.request('PUT', path, { body }); }
|
|
49
|
-
delete(path) { return this.request('DELETE', path); }
|
|
50
|
-
putRaw(path, rawBody, contentType) {
|
|
51
|
-
return this.request('PUT', path, { rawBody, headers: contentType ? { 'content-type': contentType } : undefined });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ---- find-or-create helpers ----
|
|
55
|
-
// Every resource kind exposes a GET list + POST create, and every
|
|
56
|
-
// create schema 400s on a duplicate name scoped the way that resource
|
|
57
|
-
// scopes names (see each route file) — so "does this already exist"
|
|
58
|
-
// is answered by listing and matching on name, not a dedicated
|
|
59
|
-
// lookup-by-name endpoint (none of these APIs have one).
|
|
60
|
-
|
|
61
|
-
async findByName(listPath, name) {
|
|
62
|
-
const rows = await this.get(listPath);
|
|
63
|
-
return rows.find((r) => r.name === name) || null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
// A thin client over cobinar-dashboard-worker's real REST API — every
|
|
3
|
+
// call here hits an endpoint that already exists and is already used by
|
|
4
|
+
// the dashboard itself (see that project's README for the full list).
|
|
5
|
+
// dalus doesn't talk to Cloudflare directly and has no separate deploy
|
|
6
|
+
// mechanism of its own; it's a scriptable front door to the same API the
|
|
7
|
+
// web dashboard calls, nothing more.
|
|
8
|
+
|
|
9
|
+
class DalusApiError extends Error {
|
|
10
|
+
constructor(message, status, body) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.body = body;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class ApiClient {
|
|
18
|
+
constructor({ apiBase, token }) {
|
|
19
|
+
if (!apiBase) throw new Error('apiBase is required (set it in your dalus config, or run "dalus login").');
|
|
20
|
+
if (!token) throw new Error('Not logged in — run "dalus login" first.');
|
|
21
|
+
this.apiBase = apiBase.replace(/\/$/, '');
|
|
22
|
+
this.token = token;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async request(method, path, { body, headers, rawBody } = {}) {
|
|
26
|
+
const res = await fetch(`${this.apiBase}${path}`, {
|
|
27
|
+
method,
|
|
28
|
+
headers: {
|
|
29
|
+
authorization: `Bearer ${this.token}`,
|
|
30
|
+
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
31
|
+
...headers,
|
|
32
|
+
},
|
|
33
|
+
body: rawBody !== undefined ? rawBody : body !== undefined ? JSON.stringify(body) : undefined,
|
|
34
|
+
});
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
let parsed;
|
|
37
|
+
try { parsed = text ? JSON.parse(text) : null; } catch { parsed = text; }
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const message = (parsed && typeof parsed === 'object' && parsed.error) || `${method} ${path} failed with ${res.status}`;
|
|
40
|
+
throw new DalusApiError(message, res.status, parsed);
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get(path) { return this.request('GET', path); }
|
|
46
|
+
post(path, body) { return this.request('POST', path, { body }); }
|
|
47
|
+
patch(path, body) { return this.request('PATCH', path, { body }); }
|
|
48
|
+
put(path, body) { return this.request('PUT', path, { body }); }
|
|
49
|
+
delete(path) { return this.request('DELETE', path); }
|
|
50
|
+
putRaw(path, rawBody, contentType) {
|
|
51
|
+
return this.request('PUT', path, { rawBody, headers: contentType ? { 'content-type': contentType } : undefined });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---- find-or-create helpers ----
|
|
55
|
+
// Every resource kind exposes a GET list + POST create, and every
|
|
56
|
+
// create schema 400s on a duplicate name scoped the way that resource
|
|
57
|
+
// scopes names (see each route file) — so "does this already exist"
|
|
58
|
+
// is answered by listing and matching on name, not a dedicated
|
|
59
|
+
// lookup-by-name endpoint (none of these APIs have one).
|
|
60
|
+
|
|
61
|
+
async findByName(listPath, name) {
|
|
62
|
+
const rows = await this.get(listPath);
|
|
63
|
+
return rows.find((r) => r.name === name) || null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { ApiClient, DalusApiError };
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const KIND_FLAGS = {
|
|
11
|
-
workers: '--workers',
|
|
12
|
-
pages: '--pages',
|
|
13
|
-
storage: '--storage',
|
|
14
|
-
database: '--database',
|
|
15
|
-
vault: '--vault',
|
|
16
|
-
};
|
|
17
|
-
const FORGERS = {
|
|
18
|
-
workers: (api, log, entry, opts) => forgeWorker(api, log, entry, opts),
|
|
19
|
-
pages: (api, log, entry) => forgePages(api, log, entry),
|
|
20
|
-
storage: (api, log, entry) => forgeStorage(api, log, entry),
|
|
21
|
-
database: (api, log, entry) => forgeDatabase(api, log, entry),
|
|
22
|
-
vault: (api, log, entry) => forgeVault(api, log, entry),
|
|
23
|
-
};
|
|
24
|
-
// Bare `dalus forge` (no kind flags) deploys every kind — in THIS order,
|
|
25
|
-
// not object-key order: storage/database/vault/pages have no
|
|
26
|
-
// dependencies on anything else in this list, while a worker's bindings
|
|
27
|
-
// reference a storage bucket or Document DB collection by name and fail
|
|
28
|
-
// with a clear "doesn't exist yet" error if that resource isn't already
|
|
29
|
-
// forged. Deploying workers last means a bare `dalus forge` on a brand
|
|
30
|
-
// new project just works, instead of depending on running it twice.
|
|
31
|
-
const DEFAULT_KIND_ORDER = ['storage', 'database', 'vault', 'pages', 'workers'];
|
|
32
|
-
|
|
33
|
-
function parseForgeArgs(args) {
|
|
34
|
-
const opts = { kinds: [], env: null, yes: args.includes('--yes'), only: null };
|
|
35
|
-
for (const [kind, flag] of Object.entries(KIND_FLAGS)) {
|
|
36
|
-
if (args.includes(flag)) opts.kinds.push(kind);
|
|
37
|
-
}
|
|
38
|
-
const envIdx = args.indexOf('--env');
|
|
39
|
-
if (envIdx !== -1) opts.env = args[envIdx + 1];
|
|
40
|
-
const onlyIdx = args.indexOf('--name');
|
|
41
|
-
if (onlyIdx !== -1) opts.only = args[onlyIdx + 1]; // deploy just the one resource with this name, across whichever kind(s) it's found under
|
|
42
|
-
return opts;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// `dalus forge` with no --pages/--workers/etc. deploys every resource
|
|
46
|
-
// kind present in the config — bare `dalus forge` is meant to mean "ship
|
|
47
|
-
// this whole project," matching the exact example in the original ask.
|
|
48
|
-
// Naming one or more kind flags narrows it to just those.
|
|
49
|
-
async function forgeCommand(args) {
|
|
50
|
-
const opts = parseForgeArgs(args);
|
|
51
|
-
const creds = loadCredentials();
|
|
52
|
-
if (!creds) {
|
|
53
|
-
console.error('Not logged in. Run "dalus login" first.');
|
|
54
|
-
process.exitCode = 1;
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let config;
|
|
59
|
-
try {
|
|
60
|
-
config = loadConfig();
|
|
61
|
-
if (opts.env) config = resolveEnv(config, opts.env);
|
|
62
|
-
} catch (err) {
|
|
63
|
-
console.error(err.message);
|
|
64
|
-
process.exitCode = 1;
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const api = new ApiClient({ apiBase: config.apiBase || creds.apiBase, token: creds.token });
|
|
69
|
-
const kindsToRun = opts.kinds.length > 0 ? opts.kinds : DEFAULT_KIND_ORDER;
|
|
70
|
-
|
|
71
|
-
const results = [];
|
|
72
|
-
for (const kind of kindsToRun) {
|
|
73
|
-
const entries = (config[kind] || []).filter((e) => !opts.only || e.name === opts.only);
|
|
74
|
-
for (const entry of entries) {
|
|
75
|
-
try {
|
|
76
|
-
await FORGERS[kind](api, (line) => console.log(line), entry, opts);
|
|
77
|
-
results.push({ kind, name: entry.name, ok: true });
|
|
78
|
-
} catch (err) {
|
|
79
|
-
console.error(` FAILED: ${err.message}`);
|
|
80
|
-
results.push({ kind, name: entry.name, ok: false, error: err.message });
|
|
81
|
-
}
|
|
82
|
-
console.log('');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (results.length === 0) {
|
|
87
|
-
console.log(
|
|
88
|
-
opts.kinds.length > 0
|
|
89
|
-
? `No ${opts.kinds.join('/')} entries found in ${config.__configPath}.`
|
|
90
|
-
: `Nothing to deploy — ${config.__configPath} has no workers/pages/storage/database/vault entries.`,
|
|
91
|
-
);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const failed = results.filter((r) => !r.ok);
|
|
96
|
-
console.log(`${results.length - failed.length}/${results.length} deployed successfully.`);
|
|
97
|
-
if (failed.length > 0) {
|
|
98
|
-
console.log('Failed:');
|
|
99
|
-
for (const f of failed) console.log(` - ${f.kind} ${f.name}: ${f.error}`);
|
|
100
|
-
process.exitCode = 1;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
import { loadConfig, resolveEnv, loadCredentials } from '../config.mjs';
|
|
3
|
+
import { ApiClient } from '../api-client.mjs';
|
|
4
|
+
import { forgeWorker } from '../forgers/workers.mjs';
|
|
5
|
+
import { forgePages } from '../forgers/pages.mjs';
|
|
6
|
+
import { forgeStorage } from '../forgers/storage.mjs';
|
|
7
|
+
import { forgeDatabase } from '../forgers/database.mjs';
|
|
8
|
+
import { forgeVault } from '../forgers/vault.mjs';
|
|
9
|
+
|
|
10
|
+
const KIND_FLAGS = {
|
|
11
|
+
workers: '--workers',
|
|
12
|
+
pages: '--pages',
|
|
13
|
+
storage: '--storage',
|
|
14
|
+
database: '--database',
|
|
15
|
+
vault: '--vault',
|
|
16
|
+
};
|
|
17
|
+
const FORGERS = {
|
|
18
|
+
workers: (api, log, entry, opts) => forgeWorker(api, log, entry, opts),
|
|
19
|
+
pages: (api, log, entry) => forgePages(api, log, entry),
|
|
20
|
+
storage: (api, log, entry) => forgeStorage(api, log, entry),
|
|
21
|
+
database: (api, log, entry) => forgeDatabase(api, log, entry),
|
|
22
|
+
vault: (api, log, entry) => forgeVault(api, log, entry),
|
|
23
|
+
};
|
|
24
|
+
// Bare `dalus forge` (no kind flags) deploys every kind — in THIS order,
|
|
25
|
+
// not object-key order: storage/database/vault/pages have no
|
|
26
|
+
// dependencies on anything else in this list, while a worker's bindings
|
|
27
|
+
// reference a storage bucket or Document DB collection by name and fail
|
|
28
|
+
// with a clear "doesn't exist yet" error if that resource isn't already
|
|
29
|
+
// forged. Deploying workers last means a bare `dalus forge` on a brand
|
|
30
|
+
// new project just works, instead of depending on running it twice.
|
|
31
|
+
const DEFAULT_KIND_ORDER = ['storage', 'database', 'vault', 'pages', 'workers'];
|
|
32
|
+
|
|
33
|
+
function parseForgeArgs(args) {
|
|
34
|
+
const opts = { kinds: [], env: null, yes: args.includes('--yes'), only: null };
|
|
35
|
+
for (const [kind, flag] of Object.entries(KIND_FLAGS)) {
|
|
36
|
+
if (args.includes(flag)) opts.kinds.push(kind);
|
|
37
|
+
}
|
|
38
|
+
const envIdx = args.indexOf('--env');
|
|
39
|
+
if (envIdx !== -1) opts.env = args[envIdx + 1];
|
|
40
|
+
const onlyIdx = args.indexOf('--name');
|
|
41
|
+
if (onlyIdx !== -1) opts.only = args[onlyIdx + 1]; // deploy just the one resource with this name, across whichever kind(s) it's found under
|
|
42
|
+
return opts;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// `dalus forge` with no --pages/--workers/etc. deploys every resource
|
|
46
|
+
// kind present in the config — bare `dalus forge` is meant to mean "ship
|
|
47
|
+
// this whole project," matching the exact example in the original ask.
|
|
48
|
+
// Naming one or more kind flags narrows it to just those.
|
|
49
|
+
async function forgeCommand(args) {
|
|
50
|
+
const opts = parseForgeArgs(args);
|
|
51
|
+
const creds = loadCredentials();
|
|
52
|
+
if (!creds) {
|
|
53
|
+
console.error('Not logged in. Run "dalus login" first.');
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let config;
|
|
59
|
+
try {
|
|
60
|
+
config = loadConfig();
|
|
61
|
+
if (opts.env) config = resolveEnv(config, opts.env);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(err.message);
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const api = new ApiClient({ apiBase: config.apiBase || creds.apiBase, token: creds.token });
|
|
69
|
+
const kindsToRun = opts.kinds.length > 0 ? opts.kinds : DEFAULT_KIND_ORDER;
|
|
70
|
+
|
|
71
|
+
const results = [];
|
|
72
|
+
for (const kind of kindsToRun) {
|
|
73
|
+
const entries = (config[kind] || []).filter((e) => !opts.only || e.name === opts.only);
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
try {
|
|
76
|
+
await FORGERS[kind](api, (line) => console.log(line), entry, opts);
|
|
77
|
+
results.push({ kind, name: entry.name, ok: true });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(` FAILED: ${err.message}`);
|
|
80
|
+
results.push({ kind, name: entry.name, ok: false, error: err.message });
|
|
81
|
+
}
|
|
82
|
+
console.log('');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (results.length === 0) {
|
|
87
|
+
console.log(
|
|
88
|
+
opts.kinds.length > 0
|
|
89
|
+
? `No ${opts.kinds.join('/')} entries found in ${config.__configPath}.`
|
|
90
|
+
: `Nothing to deploy — ${config.__configPath} has no workers/pages/storage/database/vault entries.`,
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const failed = results.filter((r) => !r.ok);
|
|
96
|
+
console.log(`${results.length - failed.length}/${results.length} deployed successfully.`);
|
|
97
|
+
if (failed.length > 0) {
|
|
98
|
+
console.log('Failed:');
|
|
99
|
+
for (const f of failed) console.log(` - ${f.kind} ${f.name}: ${f.error}`);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { forgeCommand };
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const TEMPLATE = `{
|
|
6
|
-
// dalus config — see https://github.com/ (your Cobinar docs link here)
|
|
7
|
-
// for the full field reference. Paths below are relative to this file.
|
|
8
|
-
|
|
9
|
-
// Optional — if omitted, dalus uses whatever worker URL you logged in
|
|
10
|
-
// with ("dalus login"). Set this when a project should always deploy
|
|
11
|
-
// to a specific worker regardless of who's running "dalus forge".
|
|
12
|
-
// "apiBase": "https://worker.lobby.cobinar.com",
|
|
13
|
-
|
|
14
|
-
"workers": [
|
|
15
|
-
// { "name": "my-api", "main": "./worker.js",
|
|
16
|
-
// "bindings": [
|
|
17
|
-
// { "name": "MY_BUCKET", "type": "storage", "resource": "my-bucket" }
|
|
18
|
-
// ],
|
|
19
|
-
// "secrets": ["SOME_API_KEY"] }
|
|
20
|
-
],
|
|
21
|
-
"pages": [
|
|
22
|
-
// { "name": "my-site", "dir": "./public" }
|
|
23
|
-
],
|
|
24
|
-
"storage": [
|
|
25
|
-
// { "name": "my-bucket", "dir": "./assets", "public": true }
|
|
26
|
-
],
|
|
27
|
-
"database": [
|
|
28
|
-
// { "name": "my_collection", "seed": "./seed.json" }
|
|
29
|
-
],
|
|
30
|
-
"vault": [
|
|
31
|
-
// { "name": "my-vault", "rules": "./rules.txt" }
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
// Named environments (optional) — "dalus forge --env staging" uses
|
|
35
|
-
// these instead of the top-level lists above, per resource kind:
|
|
36
|
-
// "env": {
|
|
37
|
-
// "staging": { "workers": [ { "name": "my-api-staging", "main": "./worker.js" } ] }
|
|
38
|
-
// }
|
|
39
|
-
}
|
|
40
|
-
`;
|
|
41
|
-
|
|
42
|
-
function initCommand(args) {
|
|
43
|
-
const target = path.join(process.cwd(), 'dalus.jsonc');
|
|
44
|
-
if (fs.existsSync(target) && !args.includes('--force')) {
|
|
45
|
-
console.error(`${target} already exists — pass --force to overwrite.`);
|
|
46
|
-
process.exitCode = 1;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
fs.writeFileSync(target, TEMPLATE);
|
|
50
|
-
console.log(`Created ${target}. Fill in the resources you want, then run "dalus forge".`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const TEMPLATE = `{
|
|
6
|
+
// dalus config — see https://github.com/ (your Cobinar docs link here)
|
|
7
|
+
// for the full field reference. Paths below are relative to this file.
|
|
8
|
+
|
|
9
|
+
// Optional — if omitted, dalus uses whatever worker URL you logged in
|
|
10
|
+
// with ("dalus login"). Set this when a project should always deploy
|
|
11
|
+
// to a specific worker regardless of who's running "dalus forge".
|
|
12
|
+
// "apiBase": "https://worker.lobby.cobinar.com",
|
|
13
|
+
|
|
14
|
+
"workers": [
|
|
15
|
+
// { "name": "my-api", "main": "./worker.js",
|
|
16
|
+
// "bindings": [
|
|
17
|
+
// { "name": "MY_BUCKET", "type": "storage", "resource": "my-bucket" }
|
|
18
|
+
// ],
|
|
19
|
+
// "secrets": ["SOME_API_KEY"] }
|
|
20
|
+
],
|
|
21
|
+
"pages": [
|
|
22
|
+
// { "name": "my-site", "dir": "./public" }
|
|
23
|
+
],
|
|
24
|
+
"storage": [
|
|
25
|
+
// { "name": "my-bucket", "dir": "./assets", "public": true }
|
|
26
|
+
],
|
|
27
|
+
"database": [
|
|
28
|
+
// { "name": "my_collection", "seed": "./seed.json" }
|
|
29
|
+
],
|
|
30
|
+
"vault": [
|
|
31
|
+
// { "name": "my-vault", "rules": "./rules.txt" }
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
// Named environments (optional) — "dalus forge --env staging" uses
|
|
35
|
+
// these instead of the top-level lists above, per resource kind:
|
|
36
|
+
// "env": {
|
|
37
|
+
// "staging": { "workers": [ { "name": "my-api-staging", "main": "./worker.js" } ] }
|
|
38
|
+
// }
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
function initCommand(args) {
|
|
43
|
+
const target = path.join(process.cwd(), 'dalus.jsonc');
|
|
44
|
+
if (fs.existsSync(target) && !args.includes('--force')) {
|
|
45
|
+
console.error(`${target} already exists — pass --force to overwrite.`);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
fs.writeFileSync(target, TEMPLATE);
|
|
50
|
+
console.log(`Created ${target}. Fill in the resources you want, then run "dalus forge".`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { initCommand };
|