@habitat-ai/cli 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generators/init.d.ts +1 -1
- package/dist/generators/init.js +15 -3
- package/dist/nx/initialization.d.ts +13 -2
- package/dist/nx/initialization.js +68 -5
- package/dist/nx/projection.js +9 -5
- package/dist/nx-generators.d.ts +11 -0
- package/dist/nx-generators.js +17 -0
- package/dist/nx-plugin.js +1 -0
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type GeneratorCallback, type Tree } from "@nx/devkit";
|
|
2
2
|
/** Initializes the installed Habitat package inside one Nx consumer. */
|
|
3
|
-
export default function initializeHabitat(tree: Tree): GeneratorCallback
|
|
3
|
+
export default function initializeHabitat(tree: Tree): GeneratorCallback;
|
package/dist/generators/init.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { detectPackageManager, getPackageManagerCommand, installPackagesTask, runTasksInSerial, } from "@nx/devkit";
|
|
2
3
|
import { initializeHabitatConsumer } from "../nx/initialization.js";
|
|
3
4
|
import { habitatConsumerBinding } from "../nx-generators.js";
|
|
4
5
|
/** Initializes the installed Habitat package inside one Nx consumer. */
|
|
5
6
|
export default function initializeHabitat(tree) {
|
|
7
|
+
const packageManager = detectPackageManager(tree.root);
|
|
8
|
+
if (packageManager === "yarn") {
|
|
9
|
+
throw new Error("Habitat Husky initialization supports npm, pnpm, and Bun consumers.");
|
|
10
|
+
}
|
|
6
11
|
const result = initializeHabitatConsumer(tree, habitatConsumerBinding);
|
|
12
|
+
const activateHusky = () => {
|
|
13
|
+
const command = `${getPackageManagerCommand(packageManager).exec} husky`;
|
|
14
|
+
execSync(command, {
|
|
15
|
+
cwd: tree.root,
|
|
16
|
+
stdio: "inherit",
|
|
17
|
+
});
|
|
18
|
+
};
|
|
7
19
|
if (!result.packageChanged)
|
|
8
|
-
return;
|
|
9
|
-
return () => installPackagesTask(tree);
|
|
20
|
+
return activateHusky;
|
|
21
|
+
return runTasksInSerial(() => installPackagesTask(tree), activateHusky);
|
|
10
22
|
}
|
|
@@ -23,18 +23,29 @@ type HookGroup = Static<typeof HookGroupSchema>;
|
|
|
23
23
|
type HabitatOwnedHookGroup = Static<typeof HabitatOwnedHookGroupSchema>;
|
|
24
24
|
/** App-owned identities and exact predecessor states consumed by native Nx initialization. */
|
|
25
25
|
export type HabitatConsumerBinding = Readonly<{
|
|
26
|
+
defaultCheckScript: string;
|
|
27
|
+
gitHook: Readonly<{
|
|
28
|
+
contents: string;
|
|
29
|
+
path: string;
|
|
30
|
+
}>;
|
|
26
31
|
gritPackage: string;
|
|
27
32
|
hook: HabitatOwnedHookGroup;
|
|
33
|
+
husky: Readonly<{
|
|
34
|
+
package: string;
|
|
35
|
+
prepare: string;
|
|
36
|
+
predecessorPrepareScripts: readonly string[];
|
|
37
|
+
version: string;
|
|
38
|
+
}>;
|
|
28
39
|
nxPlugin: string;
|
|
29
40
|
predecessorHooks: readonly HookGroup[];
|
|
30
41
|
predecessorNxPlugins: readonly PluginConfiguration[];
|
|
31
42
|
}>;
|
|
32
|
-
/**
|
|
43
|
+
/** Reports whether dependency installation must precede local Husky activation. */
|
|
33
44
|
export type HabitatInitializationResult = Readonly<{
|
|
34
45
|
packageChanged: boolean;
|
|
35
46
|
}>;
|
|
36
47
|
/**
|
|
37
|
-
* Converges one Nx consumer on
|
|
48
|
+
* Converges one Nx consumer on Habitat's app-owned Nx, Codex, package, and Git-hook state.
|
|
38
49
|
*
|
|
39
50
|
* Every admission and compatibility decision completes before the first Tree
|
|
40
51
|
* write, so an incompatible consumer remains byte-for-byte unchanged.
|
|
@@ -51,6 +51,21 @@ const CodexHooksSchema = Type.Object({
|
|
|
51
51
|
})),
|
|
52
52
|
}, { additionalProperties: true, description: "Consumer-owned Codex hook configuration." });
|
|
53
53
|
const ConsumerPackageSchema = Type.Object({
|
|
54
|
+
dependencies: Type.Optional(Type.Record(Type.String(), Type.String(), {
|
|
55
|
+
description: "Runtime dependencies installed directly in the Nx consumer.",
|
|
56
|
+
})),
|
|
57
|
+
scripts: Type.Optional(Type.Record(Type.String(), Type.String(), {
|
|
58
|
+
description: "Consumer-owned package lifecycle and command scripts.",
|
|
59
|
+
})),
|
|
60
|
+
devDependencies: Type.Optional(Type.Record(Type.String(), Type.String(), {
|
|
61
|
+
description: "Development tools installed directly in the Nx consumer.",
|
|
62
|
+
})),
|
|
63
|
+
optionalDependencies: Type.Optional(Type.Record(Type.String(), Type.String(), {
|
|
64
|
+
description: "Optional runtime dependencies installed in the Nx consumer.",
|
|
65
|
+
})),
|
|
66
|
+
peerDependencies: Type.Optional(Type.Record(Type.String(), Type.String(), {
|
|
67
|
+
description: "Peer dependency contracts declared by the Nx consumer.",
|
|
68
|
+
})),
|
|
54
69
|
trustedDependencies: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
55
70
|
description: "Package lifecycle scripts explicitly trusted by the Bun consumer.",
|
|
56
71
|
})),
|
|
@@ -58,7 +73,7 @@ const ConsumerPackageSchema = Type.Object({
|
|
|
58
73
|
const hooksValidator = new Validator({}, CodexHooksSchema);
|
|
59
74
|
const packageValidator = new Validator({}, ConsumerPackageSchema);
|
|
60
75
|
/**
|
|
61
|
-
* Converges one Nx consumer on
|
|
76
|
+
* Converges one Nx consumer on Habitat's app-owned Nx, Codex, package, and Git-hook state.
|
|
62
77
|
*
|
|
63
78
|
* Every admission and compatibility decision completes before the first Tree
|
|
64
79
|
* write, so an incompatible consumer remains byte-for-byte unchanged.
|
|
@@ -69,13 +84,16 @@ export function initializeHabitatConsumer(tree, binding) {
|
|
|
69
84
|
const packageJson = readPackage(tree);
|
|
70
85
|
const nxPlan = planNxInitialization(nxJson, binding);
|
|
71
86
|
const hookPlan = planHookInitialization(hooks, binding);
|
|
72
|
-
const packagePlan = planPackageInitialization(packageJson, binding
|
|
87
|
+
const packagePlan = planPackageInitialization(packageJson, binding);
|
|
88
|
+
const gitHookPlan = planGitHookInitialization(tree, binding.gitHook);
|
|
73
89
|
if (nxPlan.changed)
|
|
74
90
|
updateNxJson(tree, nxPlan.value);
|
|
75
91
|
if (hookPlan.changed)
|
|
76
92
|
writeJson(tree, CODEX_HOOKS_PATH, hookPlan.value);
|
|
77
93
|
if (packagePlan.changed)
|
|
78
94
|
writeJson(tree, PACKAGE_PATH, packagePlan.value);
|
|
95
|
+
if (gitHookPlan.changed)
|
|
96
|
+
tree.write(binding.gitHook.path, gitHookPlan.value);
|
|
79
97
|
return { packageChanged: packagePlan.changed };
|
|
80
98
|
}
|
|
81
99
|
/** Removes only Habitat's named hook group while preserving installed Nx integration. */
|
|
@@ -167,19 +185,64 @@ function planHookRemoval(hooks, binding) {
|
|
|
167
185
|
},
|
|
168
186
|
};
|
|
169
187
|
}
|
|
170
|
-
function planPackageInitialization(packageJson,
|
|
188
|
+
function planPackageInitialization(packageJson, binding) {
|
|
189
|
+
const { gritPackage, husky } = binding;
|
|
171
190
|
const trusted = packageJson.trustedDependencies ?? [];
|
|
172
191
|
const matches = trusted.filter((dependency) => dependency === gritPackage);
|
|
173
192
|
if (matches.length > 1) {
|
|
174
193
|
throw new Error(`package.json contains duplicate ${gritPackage} trust entries.`);
|
|
175
194
|
}
|
|
176
|
-
|
|
195
|
+
const currentHuskyVersion = packageJson.devDependencies?.[husky.package];
|
|
196
|
+
const invalidHuskyPlacement = ["dependencies", "optionalDependencies", "peerDependencies"].find((bucket) => packageJson[bucket]?.[husky.package] !== undefined);
|
|
197
|
+
if (invalidHuskyPlacement !== undefined) {
|
|
198
|
+
throw new Error(`package.json must declare ${husky.package} only in devDependencies; found ${invalidHuskyPlacement}.`);
|
|
199
|
+
}
|
|
200
|
+
if (currentHuskyVersion !== undefined && currentHuskyVersion !== husky.version) {
|
|
201
|
+
throw new Error(`package.json contains an incompatible ${husky.package} version: ${currentHuskyVersion}.`);
|
|
202
|
+
}
|
|
203
|
+
const currentPrepare = packageJson.scripts?.prepare;
|
|
204
|
+
if (currentPrepare !== undefined &&
|
|
205
|
+
currentPrepare !== husky.prepare &&
|
|
206
|
+
!husky.predecessorPrepareScripts.includes(currentPrepare)) {
|
|
207
|
+
throw new Error("package.json contains an incompatible prepare script.");
|
|
208
|
+
}
|
|
209
|
+
const trustedChanged = matches.length === 0;
|
|
210
|
+
const huskyChanged = currentHuskyVersion === undefined;
|
|
211
|
+
const prepareChanged = currentPrepare !== husky.prepare;
|
|
212
|
+
const currentCheck = packageJson.scripts?.check;
|
|
213
|
+
if (currentCheck !== undefined && currentCheck.trim().length === 0) {
|
|
214
|
+
throw new Error("package.json contains an empty check script.");
|
|
215
|
+
}
|
|
216
|
+
const checkChanged = currentCheck === undefined;
|
|
217
|
+
if (!trustedChanged && !huskyChanged && !prepareChanged && !checkChanged) {
|
|
177
218
|
return { changed: false, value: packageJson };
|
|
219
|
+
}
|
|
178
220
|
return {
|
|
179
221
|
changed: true,
|
|
180
|
-
value: {
|
|
222
|
+
value: {
|
|
223
|
+
...packageJson,
|
|
224
|
+
scripts: {
|
|
225
|
+
...packageJson.scripts,
|
|
226
|
+
check: currentCheck ?? binding.defaultCheckScript,
|
|
227
|
+
prepare: husky.prepare,
|
|
228
|
+
},
|
|
229
|
+
devDependencies: {
|
|
230
|
+
...packageJson.devDependencies,
|
|
231
|
+
[husky.package]: husky.version,
|
|
232
|
+
},
|
|
233
|
+
trustedDependencies: trustedChanged ? [...trusted, gritPackage] : trusted,
|
|
234
|
+
},
|
|
181
235
|
};
|
|
182
236
|
}
|
|
237
|
+
function planGitHookInitialization(tree, hook) {
|
|
238
|
+
if (!tree.exists(hook.path))
|
|
239
|
+
return { changed: true, value: hook.contents };
|
|
240
|
+
const contents = tree.read(hook.path, "utf8");
|
|
241
|
+
if (contents === null || contents.trim().length === 0) {
|
|
242
|
+
throw new Error(`${hook.path} is empty.`);
|
|
243
|
+
}
|
|
244
|
+
return { changed: false, value: contents };
|
|
245
|
+
}
|
|
183
246
|
function oneOwnedHookLocation(hooks, binding) {
|
|
184
247
|
const identity = binding.hook._habitat.identity;
|
|
185
248
|
const locations = Object.entries(hooks.hooks ?? {}).flatMap(([event, groups]) => groups.flatMap((group, index) => {
|
package/dist/nx/projection.js
CHANGED
|
@@ -224,13 +224,13 @@ function ownerInputs(targets, runtimeInputs) {
|
|
|
224
224
|
function applicationInputs(application, runtimeInputs) {
|
|
225
225
|
const files = new Set(HABITAT_CATALOG_PATHS.map(workspaceInput));
|
|
226
226
|
if (application.runner.name === "grit") {
|
|
227
|
-
files
|
|
227
|
+
addLocalAssetInput(files, application.runner.pattern);
|
|
228
228
|
for (const entry of application.runner.acquisition.entries) {
|
|
229
229
|
addSubjectInputs(files, entry.path, entry.kind);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
else {
|
|
233
|
-
files
|
|
233
|
+
addLocalAssetInput(files, application.runner.structure);
|
|
234
234
|
for (const binding of application.runner.rootBindings) {
|
|
235
235
|
if (binding.path !== undefined)
|
|
236
236
|
addSubjectInputs(files, binding.path, binding.kind);
|
|
@@ -242,20 +242,24 @@ function compatibilityInputs(rule, runtimeInputs) {
|
|
|
242
242
|
const files = new Set(HABITAT_CATALOG_PATHS.map(workspaceInput));
|
|
243
243
|
files.add(workspaceInput(".habitat/**"));
|
|
244
244
|
files.add(workspaceInput(rule.manifestPath));
|
|
245
|
-
files
|
|
245
|
+
addLocalAssetInput(files, rule.baseline);
|
|
246
246
|
for (const pattern of rule.coveragePatterns)
|
|
247
247
|
files.add(workspaceInput(pattern));
|
|
248
248
|
if (rule.runner.name === "grit") {
|
|
249
|
-
files
|
|
249
|
+
addLocalAssetInput(files, rule.runner.pattern);
|
|
250
250
|
for (const entry of rule.runner.acquisition.entries) {
|
|
251
251
|
addSubjectInputs(files, entry.path, entry.kind);
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
else {
|
|
255
|
-
files
|
|
255
|
+
addLocalAssetInput(files, rule.runner.structure);
|
|
256
256
|
}
|
|
257
257
|
return [...runtimeInputs, ...[...files].sort(compareText)];
|
|
258
258
|
}
|
|
259
|
+
function addLocalAssetInput(target, asset) {
|
|
260
|
+
if (asset.provenance.kind === "local")
|
|
261
|
+
target.add(workspaceInput(asset.relativePath));
|
|
262
|
+
}
|
|
259
263
|
function addSubjectInputs(target, path, kind) {
|
|
260
264
|
const exact = workspaceInput(path);
|
|
261
265
|
if (kind === "directory" && path === ".") {
|
package/dist/nx-generators.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/** Exact app-owned identities projected by the public Habitat Nx generators. */
|
|
2
2
|
export declare const habitatConsumerBinding: {
|
|
3
|
+
readonly defaultCheckScript: "nx run-many -t check";
|
|
4
|
+
readonly gitHook: {
|
|
5
|
+
readonly path: ".husky/pre-push";
|
|
6
|
+
readonly contents: "# Nested Git work must discover its own repository.\nunset $(git rev-parse --local-env-vars)\nbun run check\n";
|
|
7
|
+
};
|
|
3
8
|
readonly gritPackage: "@getgrit/cli";
|
|
9
|
+
readonly husky: {
|
|
10
|
+
readonly package: "husky";
|
|
11
|
+
readonly version: "9.1.7";
|
|
12
|
+
readonly prepare: "husky";
|
|
13
|
+
readonly predecessorPrepareScripts: readonly ["./scripts/dev/install-repository-hooks.sh", "git config core.hooksPath scripts/githooks"];
|
|
14
|
+
};
|
|
4
15
|
readonly nxPlugin: "@habitat-ai/cli/nx-plugin";
|
|
5
16
|
readonly predecessorNxPlugins: readonly [{
|
|
6
17
|
readonly plugin: "@habitat/cli/nx-plugin";
|
package/dist/nx-generators.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
/** Exact app-owned identities projected by the public Habitat Nx generators. */
|
|
2
2
|
export const habitatConsumerBinding = {
|
|
3
|
+
defaultCheckScript: "nx run-many -t check",
|
|
4
|
+
gitHook: {
|
|
5
|
+
path: ".husky/pre-push",
|
|
6
|
+
contents: `# Nested Git work must discover its own repository.
|
|
7
|
+
unset $(git rev-parse --local-env-vars)
|
|
8
|
+
bun run check
|
|
9
|
+
`,
|
|
10
|
+
},
|
|
3
11
|
gritPackage: "@getgrit/cli",
|
|
12
|
+
husky: {
|
|
13
|
+
package: "husky",
|
|
14
|
+
version: "9.1.7",
|
|
15
|
+
prepare: "husky",
|
|
16
|
+
predecessorPrepareScripts: [
|
|
17
|
+
"./scripts/dev/install-repository-hooks.sh",
|
|
18
|
+
"git config core.hooksPath scripts/githooks",
|
|
19
|
+
],
|
|
20
|
+
},
|
|
4
21
|
nxPlugin: "@habitat-ai/cli/nx-plugin",
|
|
5
22
|
predecessorNxPlugins: [
|
|
6
23
|
{
|
package/dist/nx-plugin.js
CHANGED
|
@@ -4,6 +4,7 @@ const clientForWorkspace = createHabitatClientForWorkspace;
|
|
|
4
4
|
const plugin = createHabitatNxPlugin({
|
|
5
5
|
clientForWorkspace,
|
|
6
6
|
runtimeInputs: [
|
|
7
|
+
{ externalDependencies: ["@habitat-ai/cli", "@habitat-ai/sdk"] },
|
|
7
8
|
"{workspaceRoot}/bun.lock",
|
|
8
9
|
"{workspaceRoot}/package.json",
|
|
9
10
|
{ env: "HABITAT_COMMAND_TIMEOUT_MS" },
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@habitat-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"test": "vitest run --project habitat-cli"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@habitat-ai/sdk": "0.
|
|
48
|
+
"@habitat-ai/sdk": "0.5.0",
|
|
49
49
|
"@nx/devkit": "23.1.0",
|
|
50
50
|
"@oclif/core": "^4.13.2",
|
|
51
51
|
"@oclif/plugin-help": "^6.2.27",
|