@theholocron/cli 2.0.0-alpha.17 → 2.0.0-alpha.19
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 +69 -1
- package/dist/cli.mjs +97 -49
- package/dist/index.d.mts +37 -1
- package/dist/index.mjs +85 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -10,11 +10,79 @@ npm i -g @theholocron/cli@alpha
|
|
|
10
10
|
holocron --help
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Config file
|
|
14
|
+
|
|
15
|
+
Holocron reads `holocron.config.{json,js,ts}` from the project root
|
|
16
|
+
(priority: json → js → ts).
|
|
17
|
+
|
|
18
|
+
**JSON** (simplest):
|
|
19
|
+
```jsonc
|
|
20
|
+
// holocron.config.json
|
|
21
|
+
{
|
|
22
|
+
"project": { "name": "my-app" },
|
|
23
|
+
"providers": {
|
|
24
|
+
"vault": ["1password", { "vault": "my-app" }],
|
|
25
|
+
"source": "github"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**JS/TS** — use `defineConfig` for autocomplete and type-checking:
|
|
31
|
+
```ts
|
|
32
|
+
// holocron.config.ts
|
|
33
|
+
import { defineConfig } from '@theholocron/cli'
|
|
34
|
+
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
project: { name: 'my-app' },
|
|
37
|
+
providers: {
|
|
38
|
+
vault: ['1password', { vault: 'my-app' }],
|
|
39
|
+
source: 'github',
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Shareable configs
|
|
45
|
+
|
|
46
|
+
**Level 1 — per-capability config packages.** Reference a published
|
|
47
|
+
package in any provider slot and Holocron resolves its bundled
|
|
48
|
+
`{ provider, options }` automatically. Per-project options merge on
|
|
49
|
+
top (project wins):
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
providers: {
|
|
53
|
+
vault: '@acme/holocron-vault', // preset only
|
|
54
|
+
source: ['@acme/holocron-github', { repo: 'x' }], // preset + override
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A capability config package exports a `CapabilityConfigPackage` default:
|
|
59
|
+
```ts
|
|
60
|
+
import type { CapabilityConfigPackage } from '@theholocron/cli'
|
|
61
|
+
export default {
|
|
62
|
+
provider: '1password',
|
|
63
|
+
options: { vault: 'acme-app' },
|
|
64
|
+
} satisfies CapabilityConfigPackage
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Level 2 — whole-config presets.** Because the config file can be
|
|
68
|
+
JS/TS, a shared base is just an import:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// holocron.config.ts
|
|
72
|
+
import { acmeConfig } from '@acme/holocron-config'
|
|
73
|
+
export default acmeConfig
|
|
74
|
+
```
|
|
75
|
+
|
|
13
76
|
## What's in here
|
|
14
77
|
|
|
15
78
|
- `src/capabilities/` — the 14 capability interfaces that providers
|
|
16
79
|
implement
|
|
17
|
-
- `src/config.ts` —
|
|
80
|
+
- `src/config.ts` — config schema, `defineConfig`, `resolveConfig`,
|
|
81
|
+
`CapabilityConfigPackage`
|
|
82
|
+
- `src/load-config.ts` — `loadConfig` — reads JSON/JS/TS config files
|
|
83
|
+
- `src/define-config.ts` — `defineConfig` typed pass-through
|
|
84
|
+
- `src/loader.ts` — `PluginLoader` — dynamic-imports plugins, resolves
|
|
85
|
+
capability config packages, builds the capability registry
|
|
18
86
|
- `src/cli.ts` — yargs entry, dispatches subcommands
|
|
19
87
|
- `src/commands/` — `setup`, `doctor`, `deploy`, `secret set`,
|
|
20
88
|
`secrets sync`, `npm publish-initial`
|
package/dist/cli.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import path, { dirname, join } from "node:path";
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { readFile, stat } from "node:fs/promises";
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
10
11
|
//#region src/capabilities/index.ts
|
|
11
12
|
const CARDINALITY = {
|
|
12
13
|
source: "single",
|
|
@@ -446,17 +447,30 @@ var PluginLoader = class {
|
|
|
446
447
|
}
|
|
447
448
|
/** Internal — invoke a plugin's capability factory and return the impl. */
|
|
448
449
|
async loadOne(key, tuple) {
|
|
449
|
-
const
|
|
450
|
+
const mod = await this.importer(tuple.packageName).catch((err) => {
|
|
450
451
|
throw new LoaderError(`failed to import \`${tuple.packageName}\` for capability \`${key}\`: ${err instanceof Error ? err.message : String(err)}`);
|
|
451
452
|
});
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
453
|
+
if (isPluginModule(mod)) {
|
|
454
|
+
const factory = mod.createPlugin({
|
|
455
|
+
...this.projectDefaults(),
|
|
456
|
+
...this.context,
|
|
457
|
+
...tuple.options
|
|
458
|
+
}).capabilities[key];
|
|
459
|
+
if (typeof factory !== "function") throw new LoaderError(`\`${tuple.packageName}\` does not implement the \`${key}\` capability`);
|
|
460
|
+
return factory();
|
|
461
|
+
}
|
|
462
|
+
if (isCapabilityConfigModule(mod)) {
|
|
463
|
+
const cap = mod.default;
|
|
464
|
+
return this.loadOne(key, {
|
|
465
|
+
provider: cap.provider,
|
|
466
|
+
packageName: resolvePluginPackage(cap.provider),
|
|
467
|
+
options: {
|
|
468
|
+
...cap.options,
|
|
469
|
+
...tuple.options
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
throw new LoaderError(`\`${tuple.packageName}\` does not export \`createPlugin(options)\` or a capability config ({ provider, options? })`);
|
|
460
474
|
}
|
|
461
475
|
/**
|
|
462
476
|
* Project-level defaults that get merged into every plugin's options
|
|
@@ -471,8 +485,14 @@ var PluginLoader = class {
|
|
|
471
485
|
};
|
|
472
486
|
/** Default importer — native dynamic import. */
|
|
473
487
|
const defaultImporter = async (pkg) => {
|
|
474
|
-
return
|
|
488
|
+
return import(pkg);
|
|
475
489
|
};
|
|
490
|
+
function isPluginModule(mod) {
|
|
491
|
+
return typeof mod.createPlugin === "function";
|
|
492
|
+
}
|
|
493
|
+
function isCapabilityConfigModule(mod) {
|
|
494
|
+
return typeof mod.default?.provider === "string";
|
|
495
|
+
}
|
|
476
496
|
//#endregion
|
|
477
497
|
//#region src/commands/deploy.ts
|
|
478
498
|
async function runDeploy(input) {
|
|
@@ -745,13 +765,13 @@ on: # yamllint disable-line rule:truthy
|
|
|
745
765
|
workflow_call:
|
|
746
766
|
inputs:
|
|
747
767
|
build-script:
|
|
748
|
-
description: Script to build
|
|
768
|
+
description: Script to build and upload bundle stats to Codecov
|
|
749
769
|
type: string
|
|
750
770
|
required: false
|
|
751
771
|
default: pnpm build
|
|
752
772
|
secrets:
|
|
753
|
-
|
|
754
|
-
required:
|
|
773
|
+
CODECOV_TOKEN:
|
|
774
|
+
required: false
|
|
755
775
|
|
|
756
776
|
jobs:
|
|
757
777
|
bundle-size:
|
|
@@ -764,7 +784,7 @@ jobs:
|
|
|
764
784
|
group: audit-\${{ github.ref }}
|
|
765
785
|
cancel-in-progress: true
|
|
766
786
|
steps:
|
|
767
|
-
- uses: actions/checkout@
|
|
787
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
768
788
|
name: Checkout repository
|
|
769
789
|
with:
|
|
770
790
|
fetch-depth: 0
|
|
@@ -772,11 +792,10 @@ jobs:
|
|
|
772
792
|
- uses: theholocron/.github/.github/actions/setup@main
|
|
773
793
|
name: Setup
|
|
774
794
|
|
|
775
|
-
-
|
|
776
|
-
name:
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
bundlewatch-github-token: \${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}
|
|
795
|
+
- run: \${{ inputs.build-script }}
|
|
796
|
+
name: Build and upload bundle stats
|
|
797
|
+
env:
|
|
798
|
+
CODECOV_TOKEN: \${{ secrets.CODECOV_TOKEN }}
|
|
780
799
|
`,
|
|
781
800
|
"bookkeeping-pr": `\
|
|
782
801
|
name: PR Bookkeeping
|
|
@@ -802,7 +821,7 @@ jobs:
|
|
|
802
821
|
group: bookkeeping-pr-\${{ github.event.pull_request.number }}
|
|
803
822
|
cancel-in-progress: true
|
|
804
823
|
steps:
|
|
805
|
-
- uses: actions/checkout@
|
|
824
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
806
825
|
with:
|
|
807
826
|
sparse-checkout: \${{ inputs.configuration-path || '.github/labeler.yml' }}
|
|
808
827
|
sparse-checkout-cone-mode: false
|
|
@@ -845,18 +864,18 @@ jobs:
|
|
|
845
864
|
group: codeql-\${{ github.ref }}
|
|
846
865
|
cancel-in-progress: false
|
|
847
866
|
steps:
|
|
848
|
-
- uses: actions/checkout@
|
|
867
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
849
868
|
name: Checkout repository
|
|
850
869
|
|
|
851
|
-
- uses: github/codeql-action/init@
|
|
870
|
+
- uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
|
852
871
|
name: Initialize CodeQL
|
|
853
872
|
with:
|
|
854
873
|
languages: \${{ inputs.language }}
|
|
855
874
|
|
|
856
|
-
- uses: github/codeql-action/autobuild@
|
|
875
|
+
- uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
|
857
876
|
name: Autobuild
|
|
858
877
|
|
|
859
|
-
- uses: github/codeql-action/analyze@
|
|
878
|
+
- uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
|
860
879
|
name: Analyze
|
|
861
880
|
with:
|
|
862
881
|
category: /language:\${{ inputs.language }}
|
|
@@ -887,7 +906,7 @@ jobs:
|
|
|
887
906
|
cancel-in-progress: true
|
|
888
907
|
if: github.event.pull_request.user.login == 'dependabot[bot]'
|
|
889
908
|
steps:
|
|
890
|
-
- uses: dependabot/fetch-metadata@
|
|
909
|
+
- uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
|
|
891
910
|
name: Fetch Dependabot metadata
|
|
892
911
|
id: metadata
|
|
893
912
|
|
|
@@ -919,7 +938,7 @@ jobs:
|
|
|
919
938
|
group: greetings-\${{ github.event.issue.number || github.event.pull_request.number }}
|
|
920
939
|
cancel-in-progress: false
|
|
921
940
|
steps:
|
|
922
|
-
- uses: actions/github-script@
|
|
941
|
+
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
923
942
|
name: Greet on first contribution
|
|
924
943
|
with:
|
|
925
944
|
script: |
|
|
@@ -995,7 +1014,7 @@ jobs:
|
|
|
995
1014
|
env:
|
|
996
1015
|
GPG_KEY_SET: \${{ secrets.SUPER_LINTER_GPG_PRIVATE_KEY != '' }}
|
|
997
1016
|
steps:
|
|
998
|
-
- uses: actions/checkout@
|
|
1017
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
999
1018
|
name: Checkout repository
|
|
1000
1019
|
with:
|
|
1001
1020
|
fetch-depth: 0
|
|
@@ -1004,7 +1023,7 @@ jobs:
|
|
|
1004
1023
|
- uses: theholocron/.github/.github/actions/setup@main
|
|
1005
1024
|
name: Setup
|
|
1006
1025
|
|
|
1007
|
-
- uses: super-linter/super-linter/slim@
|
|
1026
|
+
- uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0
|
|
1008
1027
|
name: Run Super Linter
|
|
1009
1028
|
env:
|
|
1010
1029
|
GITHUB_TOKEN: \${{ github.token }}
|
|
@@ -1039,7 +1058,7 @@ jobs:
|
|
|
1039
1058
|
VALIDATE_YAML: true
|
|
1040
1059
|
YAML_CONFIG_FILE: \${{ inputs.yaml-config }}
|
|
1041
1060
|
|
|
1042
|
-
- uses: crazy-max/ghaction-import-gpg@
|
|
1061
|
+
- uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
|
1043
1062
|
name: Import GPG Key
|
|
1044
1063
|
# Conditions mirror auto-commit exactly — no point importing GPG if the
|
|
1045
1064
|
# commit step will be skipped (fork PR, default branch, or secret unset).
|
|
@@ -1055,7 +1074,7 @@ jobs:
|
|
|
1055
1074
|
GPG_PRIVATE_KEY: \${{ secrets.SUPER_LINTER_GPG_PRIVATE_KEY }}
|
|
1056
1075
|
PASSPHRASE: \${{ secrets.SUPER_LINTER_GPG_PASSPHRASE }}
|
|
1057
1076
|
|
|
1058
|
-
- uses: stefanzweifel/git-auto-commit-action@
|
|
1077
|
+
- uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0
|
|
1059
1078
|
name: Commit and push linting fixes
|
|
1060
1079
|
if: >
|
|
1061
1080
|
inputs.enable-auto-commit == true &&
|
|
@@ -1108,7 +1127,7 @@ jobs:
|
|
|
1108
1127
|
group: release-\${{ github.ref }}
|
|
1109
1128
|
cancel-in-progress: false
|
|
1110
1129
|
steps:
|
|
1111
|
-
- uses: actions/checkout@
|
|
1130
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
1112
1131
|
name: Checkout repository
|
|
1113
1132
|
with:
|
|
1114
1133
|
fetch-depth: 0
|
|
@@ -1180,7 +1199,7 @@ jobs:
|
|
|
1180
1199
|
|
|
1181
1200
|
steps:
|
|
1182
1201
|
- name: Checkout repository
|
|
1183
|
-
uses: actions/checkout@
|
|
1202
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
1184
1203
|
|
|
1185
1204
|
- name: Setup
|
|
1186
1205
|
if: \${{ hashFiles('pnpm-lock.yaml') != '' }}
|
|
@@ -1283,7 +1302,7 @@ jobs:
|
|
|
1283
1302
|
|
|
1284
1303
|
- name: dotenv-linter
|
|
1285
1304
|
if: steps.detect.outputs.dotenv == 'true'
|
|
1286
|
-
uses: dotenv-linter/action-dotenv-linter@
|
|
1305
|
+
uses: dotenv-linter/action-dotenv-linter@afde61cfda2ecffe7bea35837b6f20b956c88689 # v3.0.0
|
|
1287
1306
|
with:
|
|
1288
1307
|
reporter: github-code-suggestions
|
|
1289
1308
|
|
|
@@ -1324,7 +1343,7 @@ jobs:
|
|
|
1324
1343
|
runs-on: ubuntu-latest
|
|
1325
1344
|
timeout-minutes: 10
|
|
1326
1345
|
steps:
|
|
1327
|
-
- uses: actions/stale@
|
|
1346
|
+
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
|
1328
1347
|
name: Run Stale
|
|
1329
1348
|
with:
|
|
1330
1349
|
close-issue-message: >
|
|
@@ -1361,7 +1380,7 @@ jobs:
|
|
|
1361
1380
|
runs-on: ubuntu-latest
|
|
1362
1381
|
timeout-minutes: 15
|
|
1363
1382
|
steps:
|
|
1364
|
-
- uses: actions/checkout@
|
|
1383
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
1365
1384
|
name: Checkout repository
|
|
1366
1385
|
with:
|
|
1367
1386
|
fetch-depth: 0
|
|
@@ -1372,8 +1391,14 @@ jobs:
|
|
|
1372
1391
|
- run: pnpm test -- --coverage
|
|
1373
1392
|
name: Run tests with coverage
|
|
1374
1393
|
|
|
1375
|
-
- uses: codecov/codecov-action@
|
|
1376
|
-
name: Upload
|
|
1394
|
+
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
1395
|
+
name: Upload coverage to Codecov
|
|
1396
|
+
with:
|
|
1397
|
+
token: \${{ secrets.CODECOV_TOKEN }}
|
|
1398
|
+
|
|
1399
|
+
- uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1
|
|
1400
|
+
name: Upload test results to Codecov
|
|
1401
|
+
if: \${{ !cancelled() }}
|
|
1377
1402
|
with:
|
|
1378
1403
|
token: \${{ secrets.CODECOV_TOKEN }}
|
|
1379
1404
|
`,
|
|
@@ -1424,7 +1449,7 @@ jobs:
|
|
|
1424
1449
|
permissions:
|
|
1425
1450
|
contents: read
|
|
1426
1451
|
steps:
|
|
1427
|
-
- uses: actions/checkout@
|
|
1452
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
1428
1453
|
name: Checkout repository
|
|
1429
1454
|
|
|
1430
1455
|
- uses: theholocron/.github/.github/actions/setup@main
|
|
@@ -1485,7 +1510,7 @@ jobs:
|
|
|
1485
1510
|
runs-on: ubuntu-latest
|
|
1486
1511
|
timeout-minutes: 10
|
|
1487
1512
|
steps:
|
|
1488
|
-
- uses: actions/checkout@
|
|
1513
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
1489
1514
|
name: Checkout repository
|
|
1490
1515
|
|
|
1491
1516
|
- uses: theholocron/.github/.github/actions/setup@main
|
|
@@ -3581,6 +3606,11 @@ function buildRulesetPayload(requiredChecks = []) {
|
|
|
3581
3606
|
name: RULESET_NAME,
|
|
3582
3607
|
target: "branch",
|
|
3583
3608
|
enforcement: "active",
|
|
3609
|
+
bypass_actors: [{
|
|
3610
|
+
actor_id: 4,
|
|
3611
|
+
actor_type: "RepositoryRole",
|
|
3612
|
+
bypass_mode: "always"
|
|
3613
|
+
}],
|
|
3584
3614
|
conditions: { ref_name: {
|
|
3585
3615
|
include: ["~DEFAULT_BRANCH"],
|
|
3586
3616
|
exclude: []
|
|
@@ -3852,15 +3882,13 @@ function formatStep(step) {
|
|
|
3852
3882
|
/**
|
|
3853
3883
|
* `holocron.config.{json,js,ts}` file loader.
|
|
3854
3884
|
*
|
|
3855
|
-
*
|
|
3856
|
-
*
|
|
3857
|
-
*
|
|
3858
|
-
*
|
|
3859
|
-
* later without a breaking change.
|
|
3885
|
+
* Search order: json → js → ts. JSON is parsed directly; JS is loaded
|
|
3886
|
+
* via native dynamic import; TS is loaded via `tsImport` from tsx (a
|
|
3887
|
+
* runtime dep) so operators can write typed configs with `defineConfig`
|
|
3888
|
+
* without needing a separate build step.
|
|
3860
3889
|
*
|
|
3861
|
-
*
|
|
3862
|
-
*
|
|
3863
|
-
* lookup order, not the interpretation.
|
|
3890
|
+
* All three forms are validated through the same `resolveConfig` path.
|
|
3891
|
+
* Implements the lookup-order contract from issue #75 / #81.
|
|
3864
3892
|
*/
|
|
3865
3893
|
const CANDIDATE_FILENAMES = [
|
|
3866
3894
|
"holocron.config.json",
|
|
@@ -3873,7 +3901,7 @@ var ConfigFileError = class extends Error {
|
|
|
3873
3901
|
/**
|
|
3874
3902
|
* Read + parse + resolve `holocron.config.*` from the given directory.
|
|
3875
3903
|
* Search order: json → js → ts. Throws `ConfigFileError` if nothing
|
|
3876
|
-
* found, or `ConfigError` if the
|
|
3904
|
+
* found, or `ConfigError` if the config is malformed / invalid.
|
|
3877
3905
|
*/
|
|
3878
3906
|
async function loadConfig(cwd) {
|
|
3879
3907
|
for (const filename of CANDIDATE_FILENAMES) {
|
|
@@ -3883,7 +3911,14 @@ async function loadConfig(cwd) {
|
|
|
3883
3911
|
resolved: await loadJson(fullPath),
|
|
3884
3912
|
filepath: fullPath
|
|
3885
3913
|
};
|
|
3886
|
-
|
|
3914
|
+
if (filename.endsWith(".ts")) return {
|
|
3915
|
+
resolved: await loadTs(fullPath),
|
|
3916
|
+
filepath: fullPath
|
|
3917
|
+
};
|
|
3918
|
+
return {
|
|
3919
|
+
resolved: await loadJs(fullPath),
|
|
3920
|
+
filepath: fullPath
|
|
3921
|
+
};
|
|
3887
3922
|
}
|
|
3888
3923
|
}
|
|
3889
3924
|
throw new ConfigFileError(`no holocron.config.{json,js,ts} found in ${cwd}. Create one — see the README for the schema.`);
|
|
@@ -3898,6 +3933,19 @@ async function loadJson(filepath) {
|
|
|
3898
3933
|
}
|
|
3899
3934
|
return resolveConfig(parsed);
|
|
3900
3935
|
}
|
|
3936
|
+
async function loadJs(filepath) {
|
|
3937
|
+
return extractAndResolve(filepath, await import(pathToFileURL(filepath).href));
|
|
3938
|
+
}
|
|
3939
|
+
async function loadTs(filepath) {
|
|
3940
|
+
const { tsImport } = await import("tsx/esm/api");
|
|
3941
|
+
const outer = await tsImport(pathToFileURL(filepath).href, import.meta.url);
|
|
3942
|
+
return extractAndResolve(filepath, outer.default ?? outer);
|
|
3943
|
+
}
|
|
3944
|
+
function extractAndResolve(filepath, mod) {
|
|
3945
|
+
const raw = mod.default;
|
|
3946
|
+
if (raw === void 0 || raw === null) throw new ConfigFileError(`${filepath} must have a default export (use \`export default defineConfig({…})\`)`);
|
|
3947
|
+
return resolveConfig(raw);
|
|
3948
|
+
}
|
|
3901
3949
|
async function fileExists(path) {
|
|
3902
3950
|
try {
|
|
3903
3951
|
return (await stat(path)).isFile();
|
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,23 @@ import { Analytics, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentit
|
|
|
2
2
|
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
type ProviderOptions = Record<string, unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* The shape a capability config package's default export must satisfy.
|
|
7
|
+
* Config packages let teams share a pre-bundled provider + options across
|
|
8
|
+
* repos (Level 1 of the shareable-configs story, issue #75).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // @acme/holocron-vault/index.ts
|
|
12
|
+
* import type { CapabilityConfigPackage } from '@theholocron/cli'
|
|
13
|
+
* export default {
|
|
14
|
+
* provider: '1password',
|
|
15
|
+
* options: { vault: 'acme-app' },
|
|
16
|
+
* } satisfies CapabilityConfigPackage
|
|
17
|
+
*/
|
|
18
|
+
interface CapabilityConfigPackage {
|
|
19
|
+
provider: string;
|
|
20
|
+
options?: ProviderOptions;
|
|
21
|
+
}
|
|
5
22
|
type SingleEntry = string | [provider: string, options: ProviderOptions];
|
|
6
23
|
type MultiEntry = Array<string | [provider: string, options: ProviderOptions]>;
|
|
7
24
|
type RawProviderEntry = SingleEntry | MultiEntry;
|
|
@@ -103,6 +120,9 @@ declare function resolvePluginPackage(provider: string): string;
|
|
|
103
120
|
declare function resolveEntry(key: CapabilityKey, raw: RawProviderEntry): ResolvedProviderEntry;
|
|
104
121
|
declare function resolveConfig(raw: HolocronConfig): ResolvedHolocronConfig;
|
|
105
122
|
//#endregion
|
|
123
|
+
//#region src/define-config.d.ts
|
|
124
|
+
declare function defineConfig(config: HolocronConfig): HolocronConfig;
|
|
125
|
+
//#endregion
|
|
106
126
|
//#region src/keyring.d.ts
|
|
107
127
|
/**
|
|
108
128
|
* Keyring-backed bootstrap credential store.
|
|
@@ -147,4 +167,20 @@ declare function deleteToken(provider: string): boolean;
|
|
|
147
167
|
*/
|
|
148
168
|
declare function listStoredProviders(): string[];
|
|
149
169
|
//#endregion
|
|
150
|
-
|
|
170
|
+
//#region src/load-config.d.ts
|
|
171
|
+
declare class ConfigFileError extends Error {
|
|
172
|
+
name: string;
|
|
173
|
+
}
|
|
174
|
+
interface LoadedConfig {
|
|
175
|
+
resolved: ResolvedHolocronConfig;
|
|
176
|
+
/** Absolute path to the file the config was read from. */
|
|
177
|
+
filepath: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Read + parse + resolve `holocron.config.*` from the given directory.
|
|
181
|
+
* Search order: json → js → ts. Throws `ConfigFileError` if nothing
|
|
182
|
+
* found, or `ConfigError` if the config is malformed / invalid.
|
|
183
|
+
*/
|
|
184
|
+
declare function loadConfig(cwd: string): Promise<LoadedConfig>;
|
|
185
|
+
//#endregion
|
|
186
|
+
export { Analytics, AppConfig, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityConfigPackage, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConfigError, ConfigFileError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DoctorConfig, EnsureResult, Environment, EnvironmentReviewer, Environments, HolocronConfig, Issue, IssueSearchFilter, Issues, LifecycleResult, LifecycleSlot, LoadedConfig, MultiEntry, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, REQUIRED_CAPABILITIES, RawProviderEntry, RawProvidersConfig, RepoPolicyConfig, RepoRef, RepoSettings, ResolvedCapability, ResolvedHolocronConfig, ResolvedProviderEntry, ResolvedProvidersConfig, ResolvedTuple, Ruleset, SecretScope, Secrets, SingleEntry, Source, StatusCategory, Storage, StorageBranch, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { CARDINALITY, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, isMulti } from "./capabilities/index.mjs";
|
|
2
2
|
import { Entry, findCredentials } from "@napi-rs/keyring";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
3
6
|
//#region src/config.ts
|
|
4
7
|
/**
|
|
5
8
|
* `holocron.config.json` schema, parser, and provider resolution.
|
|
@@ -111,6 +114,11 @@ function resolveConfig(raw) {
|
|
|
111
114
|
};
|
|
112
115
|
}
|
|
113
116
|
//#endregion
|
|
117
|
+
//#region src/define-config.ts
|
|
118
|
+
function defineConfig(config) {
|
|
119
|
+
return config;
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
114
122
|
//#region src/keyring.ts
|
|
115
123
|
/**
|
|
116
124
|
* Keyring-backed bootstrap credential store.
|
|
@@ -181,4 +189,80 @@ function listStoredProviders() {
|
|
|
181
189
|
}
|
|
182
190
|
}
|
|
183
191
|
//#endregion
|
|
184
|
-
|
|
192
|
+
//#region src/load-config.ts
|
|
193
|
+
/**
|
|
194
|
+
* `holocron.config.{json,js,ts}` file loader.
|
|
195
|
+
*
|
|
196
|
+
* Search order: json → js → ts. JSON is parsed directly; JS is loaded
|
|
197
|
+
* via native dynamic import; TS is loaded via `tsImport` from tsx (a
|
|
198
|
+
* runtime dep) so operators can write typed configs with `defineConfig`
|
|
199
|
+
* without needing a separate build step.
|
|
200
|
+
*
|
|
201
|
+
* All three forms are validated through the same `resolveConfig` path.
|
|
202
|
+
* Implements the lookup-order contract from issue #75 / #81.
|
|
203
|
+
*/
|
|
204
|
+
const CANDIDATE_FILENAMES = [
|
|
205
|
+
"holocron.config.json",
|
|
206
|
+
"holocron.config.js",
|
|
207
|
+
"holocron.config.ts"
|
|
208
|
+
];
|
|
209
|
+
var ConfigFileError = class extends Error {
|
|
210
|
+
name = "ConfigFileError";
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Read + parse + resolve `holocron.config.*` from the given directory.
|
|
214
|
+
* Search order: json → js → ts. Throws `ConfigFileError` if nothing
|
|
215
|
+
* found, or `ConfigError` if the config is malformed / invalid.
|
|
216
|
+
*/
|
|
217
|
+
async function loadConfig(cwd) {
|
|
218
|
+
for (const filename of CANDIDATE_FILENAMES) {
|
|
219
|
+
const fullPath = join(cwd, filename);
|
|
220
|
+
if (await fileExists(fullPath)) {
|
|
221
|
+
if (filename.endsWith(".json")) return {
|
|
222
|
+
resolved: await loadJson(fullPath),
|
|
223
|
+
filepath: fullPath
|
|
224
|
+
};
|
|
225
|
+
if (filename.endsWith(".ts")) return {
|
|
226
|
+
resolved: await loadTs(fullPath),
|
|
227
|
+
filepath: fullPath
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
resolved: await loadJs(fullPath),
|
|
231
|
+
filepath: fullPath
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
throw new ConfigFileError(`no holocron.config.{json,js,ts} found in ${cwd}. Create one — see the README for the schema.`);
|
|
236
|
+
}
|
|
237
|
+
async function loadJson(filepath) {
|
|
238
|
+
const raw = await readFile(filepath, "utf8");
|
|
239
|
+
let parsed;
|
|
240
|
+
try {
|
|
241
|
+
parsed = JSON.parse(raw);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
throw new ConfigError(`${filepath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
244
|
+
}
|
|
245
|
+
return resolveConfig(parsed);
|
|
246
|
+
}
|
|
247
|
+
async function loadJs(filepath) {
|
|
248
|
+
return extractAndResolve(filepath, await import(pathToFileURL(filepath).href));
|
|
249
|
+
}
|
|
250
|
+
async function loadTs(filepath) {
|
|
251
|
+
const { tsImport } = await import("tsx/esm/api");
|
|
252
|
+
const outer = await tsImport(pathToFileURL(filepath).href, import.meta.url);
|
|
253
|
+
return extractAndResolve(filepath, outer.default ?? outer);
|
|
254
|
+
}
|
|
255
|
+
function extractAndResolve(filepath, mod) {
|
|
256
|
+
const raw = mod.default;
|
|
257
|
+
if (raw === void 0 || raw === null) throw new ConfigFileError(`${filepath} must have a default export (use \`export default defineConfig({…})\`)`);
|
|
258
|
+
return resolveConfig(raw);
|
|
259
|
+
}
|
|
260
|
+
async function fileExists(path) {
|
|
261
|
+
try {
|
|
262
|
+
return (await stat(path)).isFile();
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
268
|
+
export { CARDINALITY, ConfigError, ConfigFileError, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.19",
|
|
4
4
|
"description": "The Holocron CLI — a pluggable, capability-based orchestrator for spinning up and operating software projects.",
|
|
5
5
|
"homepage": "https://github.com/theholocron/holocron/tree/main/packages/cli#readme",
|
|
6
6
|
"bugs": "https://github.com/theholocron/holocron/issues",
|
|
@@ -34,16 +34,19 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@napi-rs/keyring": "^1.3.0",
|
|
37
|
+
"tsx": "^4.22.4",
|
|
37
38
|
"yargs": "^18.0.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@theholocron/eslint-config": "^
|
|
41
|
+
"@theholocron/eslint-config": "^5.1.1",
|
|
41
42
|
"@theholocron/tsconfig": "^4.1.0",
|
|
42
43
|
"@tsconfig/node-lts": "^24.0.0",
|
|
43
44
|
"@types/yargs": "^17.0.35",
|
|
44
45
|
"@vitest/coverage-v8": "^3.2.6",
|
|
45
|
-
"eslint": "^
|
|
46
|
-
"
|
|
46
|
+
"@vitest/eslint-plugin": "^1.6.23",
|
|
47
|
+
"eslint": "^10.7.0",
|
|
48
|
+
"eslint-plugin-n": "^18.2.2",
|
|
49
|
+
"globals": "^17.7.0",
|
|
47
50
|
"tsdown": "^0.22.3",
|
|
48
51
|
"typescript": "^5.9.3",
|
|
49
52
|
"vitest": "^3.2.6",
|