@cobinar/dalus 0.1.8 → 0.1.11

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.
Files changed (41) hide show
  1. package/README.md +10 -36
  2. package/bin/{dalus.js → dalus.mjs} +5 -14
  3. package/convert_to_mjs.py +59 -0
  4. package/fix_requires.py +57 -0
  5. package/package.json +4 -4
  6. package/publish-dalus.bat +1 -1
  7. package/src/{api-client.js → api-client.mjs} +67 -67
  8. package/src/commands/{forge.js → forge.mjs} +104 -104
  9. package/src/commands/{init.js → init.mjs} +53 -53
  10. package/src/{forgers/login.js → commands/login.mjs} +288 -288
  11. package/src/{config.js → config.mjs} +147 -147
  12. package/src/cors.mjs +19 -0
  13. package/src/forgers/{database.js → database.mjs} +34 -34
  14. package/src/forgers/{pages.js → pages.mjs} +35 -35
  15. package/src/forgers/storage.mjs +39 -0
  16. package/src/forgers/{vault.js → vault.mjs} +27 -27
  17. package/src/forgers/{workers.js → workers.mjs} +85 -85
  18. package/src/{fs-utils.js → fs-utils.mjs} +36 -36
  19. package/src/handlers/login-callback.mjs +143 -0
  20. package/src/handlers/services.mjs +45 -0
  21. package/src/index.mjs +36 -0
  22. package/src/auth.js +0 -78
  23. package/src/commands/auth.js +0 -78
  24. package/src/commands/cors.js +0 -34
  25. package/src/commands/index.js +0 -115
  26. package/src/commands/login.js +0 -288
  27. package/src/commands/storage.js +0 -72
  28. package/src/commands/utils.js +0 -7
  29. package/src/commands/validators.js +0 -101
  30. package/src/cors.js +0 -34
  31. package/src/forgers/auth.js +0 -78
  32. package/src/forgers/cors.js +0 -34
  33. package/src/forgers/index.js +0 -115
  34. package/src/forgers/storage.js +0 -72
  35. package/src/forgers/utils.js +0 -7
  36. package/src/forgers/validators.js +0 -101
  37. package/src/index.js +0 -115
  38. package/src/login.js +0 -288
  39. package/src/storage.js +0 -72
  40. package/src/utils.js +0 -7
  41. package/src/validators.js +0 -101
package/README.md CHANGED
@@ -18,53 +18,27 @@ relying on this day to day.
18
18
 
19
19
  ## Install
20
20
 
21
- ```
22
- npm install -g @cobinar/dalus
23
- ```
24
-
25
- Or from source, for working on dalus itself:
26
-
27
21
  ```
28
22
  cd dalus
29
23
  npm install
30
24
  npm link # makes the `dalus` command available globally
31
25
  ```
32
26
 
27
+ (Not published to npm — this is source you own and can change.)
28
+
33
29
  ## Log in
34
30
 
35
31
  ```
36
- dalus login
32
+ dalus login --api-base https://worker.lobby.cobinar.com --token <your bearer token>
37
33
  ```
38
34
 
39
- Opens your browser to sign in with Cobinar (the same sign-in every
40
- cobinar.com visitor uses dalus doesn't have, and doesn't need, its own
41
- separate login), then stores what it gets back in
42
- `~/.dalus/credentials.json` (mode 0600), never inside a project
43
- directory. You'll also be asked for your dashboard-worker URL the first
44
- time `dalus login --api-base <url>` to skip that prompt.
45
-
46
- Mechanically: dalus starts a one-shot local server on an ephemeral port,
47
- opens `https://cobinar.com/cli-login?port=<port>&ls=<random>`, and waits.
48
- That page runs the exact same auth.cobinar.com round trip as a normal
49
- sign-in; once it succeeds, cobinar.com's own `/auth/callback` page POSTs a
50
- short-lived, single-use code to dalus's local server (proving it's
51
- genuinely that page, not some other local process, via the `ls` value).
52
- dalus then redeems that code itself, directly against
53
- `cobinar-developers-worker`'s `/auth/redeem-code` (override with
54
- `--developers-base` for staging), which is what actually mints the
55
- signed token dashboard-worker checks — cobinar.com's callback never
56
- holds or sees that token, only the one-time code. No separate OAuth
57
- client, no manually copying anything out of a browser session.
58
-
59
- The stored token lasts 2 hours (`cobinar-developers-worker` sets that
60
- expiry) — `dalus login` again once a command starts failing with a
61
- session-expired error.
62
-
63
- `--token <token> [--api-base <url>]` is kept as a manual fallback for a
64
- CI runner or a headless box with no browser to open — get a token some
65
- other way and hand it to dalus directly, the same way `gh auth login
66
- --with-token` works. A real headless flow (a one-time code, email
67
- verification) is planned but not built yet.
35
+ dalus doesn't implement Cobinar's own signup/login flow that lives in
36
+ `cobinar-developers-worker`, a separate system. This command stores a
37
+ token you've already obtained from wherever that flow gives you one, the
38
+ same way `gh auth login --with-token` accepts a token instead of
39
+ reimplementing GitHub's own login. Omit `--api-base`/`--token` to be
40
+ prompted instead. Credentials are stored in `~/.dalus/credentials.json`
41
+ (mode 0600), never inside a project directory.
68
42
 
69
43
  `dalus login --whoami` shows what's currently stored (token
70
44
  redacted). `dalus login --logout` clears it.
@@ -1,25 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { loginCommand } = require('../src/commands/login');
5
- const { forgeCommand } = require('../src/commands/forge');
6
- const { initCommand } = require('../src/commands/init');
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
 
10
10
  Usage:
11
- dalus login [--web-base <url>] [--api-base <url>]
12
- dalus login --token <token> [--api-base <url>]
11
+ dalus login [--api-base <url>] [--token <token>]
13
12
  dalus login --whoami
14
13
  dalus login --logout
15
14
  dalus init [--force]
16
15
  dalus forge [--pages] [--workers] [--storage] [--database] [--vault]
17
16
  [--env <name>] [--name <resourceName>] [--yes]
18
17
 
19
- Bare "dalus login" opens your browser to sign in with Cobinar and stores
20
- what it gets back. --token accepts one you already have some other way
21
- (CI, a headless box with no browser) instead.
22
-
23
18
  Bare "dalus forge" deploys every resource in dalus.jsonc / dalus.toml /
24
19
  dalus.json found in the current directory. Passing one or more of
25
20
  --pages/--workers/--storage/--database/--vault narrows it to just
@@ -60,10 +55,6 @@ async function main() {
60
55
  }
61
56
 
62
57
  main().catch((err) => {
63
- if (err && err.status === 401) {
64
- console.error('Your Cobinar session expired or was rejected. Run "dalus login" again.');
65
- } else {
66
- console.error(err.message || err);
67
- }
58
+ console.error(err.message || err);
68
59
  process.exitCode = 1;
69
60
  });
@@ -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)
@@ -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,18 +1,18 @@
1
1
  {
2
2
  "name": "@cobinar/dalus",
3
- "version": "0.1.8",
4
- "type": "commonjs",
3
+ "version": "0.1.11",
5
4
  "description": "Deploy Cobinar projects (Edge Compute, Static Hosting, Object Storage, Document DB, Vault) from the command line.",
6
5
  "bin": {
7
- "dalus": "./bin/dalus.js"
6
+ "dalus": "./bin/dalus.mjs"
8
7
  },
9
8
  "main": "./src/index.js",
9
+ "type": "commonjs",
10
10
  "engines": {
11
11
  "node": ">=18.0.0"
12
12
  },
13
13
  "dependencies": {
14
14
  "jsonc-parser": "^3.3.1",
15
- "smol-toml": "^1.3.1"
15
+ "smol-toml": "^1.8.0"
16
16
  },
17
17
  "license": "MIT"
18
18
  }
package/publish-dalus.bat CHANGED
@@ -2,7 +2,7 @@
2
2
  echo 🚀 Preparing to publish new @cobinar/dalus version...
3
3
 
4
4
  :: Navigate to your exact project directory
5
- cd /d "D:\Downloads\Cobinar-Lobby + Dashboard + Da;us CLI\dalus"
5
+ cd /d "D:\Downloads\daluss"
6
6
 
7
7
  echo.
8
8
  echo 🔐 Checking npm authentication...
@@ -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
- module.exports = { ApiClient, DalusApiError };
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
- const { loadConfig, resolveEnv, loadCredentials } = require('../config');
3
- const { ApiClient } = require('../api-client');
4
- const { forgeWorker } = require('../forgers/workers');
5
- const { forgePages } = require('../forgers/pages');
6
- const { forgeStorage } = require('../forgers/storage');
7
- const { forgeDatabase } = require('../forgers/database');
8
- const { forgeVault } = require('../forgers/vault');
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
- module.exports = { forgeCommand };
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 };