@sous-io/sous 0.2.6 → 0.2.7
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 +3 -2
- package/bin/run.js +13 -0
- package/docs/markdown/README.md +19 -8
- package/docs/markdown/commands.md +2 -1
- package/package.json +1 -1
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/src/lib/project-install.d.mts +10 -1
- package/src/lib/project-install.mjs +40 -14
- package/src/lib/version-report.ts +90 -0
package/README.md
CHANGED
|
@@ -36,8 +36,9 @@ npm install -D @sous-io/sous # the version this project's templates were wri
|
|
|
36
36
|
A project install pins the sous version a project builds with, and it always does the
|
|
37
37
|
building: a global `sous` run anywhere inside a project that holds `node_modules/@sous-io/sous`
|
|
38
38
|
hands the command to that copy, so `sous`, `npx sous` and a package script all produce the same
|
|
39
|
-
output. When the two versions differ, one line on standard error
|
|
40
|
-
`SOUS_NO_DELEGATE=1` to run the copy you
|
|
39
|
+
output. When the two versions differ, one line on standard error names the version handed off
|
|
40
|
+
to (`--verbose` adds where both installs are). Set `SOUS_NO_DELEGATE=1` to run the copy you
|
|
41
|
+
invoked instead.
|
|
41
42
|
|
|
42
43
|
Or run it from a clone (useful when developing sous itself):
|
|
43
44
|
|
package/bin/run.js
CHANGED
|
@@ -20,6 +20,19 @@ if (!handedOff) {
|
|
|
20
20
|
const { register } = await import("tsx/esm/api");
|
|
21
21
|
register();
|
|
22
22
|
|
|
23
|
+
// `sous --version` is answered here rather than by oclif, whose answer is
|
|
24
|
+
// its user agent string. Plain, it is the version alone; `--verbose` adds
|
|
25
|
+
// the package, where it is installed, the platform and the Node build.
|
|
26
|
+
const argv = process.argv.slice(2);
|
|
27
|
+
const { isVersionRequest, printVersion } = await import("../src/lib/version-report.ts");
|
|
28
|
+
if (isVersionRequest(argv)) {
|
|
29
|
+
printVersion(argv, ownRoot);
|
|
30
|
+
} else {
|
|
31
|
+
await runCommand();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runCommand() {
|
|
23
36
|
const { execute, settings } = await import("@oclif/core");
|
|
24
37
|
|
|
25
38
|
// tsx (above) already makes .ts imports work, so oclif's own auto-transpile
|
package/docs/markdown/README.md
CHANGED
|
@@ -34,18 +34,29 @@ Sous installs three ways, and they work together:
|
|
|
34
34
|
directory the way Node resolves a package, so a copy hoisted to a monorepo root is found from
|
|
35
35
|
any package inside it.
|
|
36
36
|
|
|
37
|
-
When the two versions differ, one
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
When the two versions differ, one line on standard error names the version handed off to;
|
|
38
|
+
standard output is untouched, so a piped command prints exactly what it always did. Add
|
|
39
|
+
`--verbose` to any command and the notice also says where both installs are and how to keep
|
|
40
|
+
the invoked one running; set `SOUS_DEBUG` and the notice prints on every hand-off. Set
|
|
41
|
+
`SOUS_NO_DELEGATE` to anything but `0`, `false`, `no` or `off` to run the copy you invoked
|
|
42
|
+
instead, for debugging a broken project install or for deliberately using the global one:
|
|
42
43
|
|
|
43
44
|
```term
|
|
44
45
|
$ sous --version
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
Handing off to the project-level Sous install: v0.2.4
|
|
47
|
+
v0.2.4
|
|
48
|
+
$ sous --version --verbose
|
|
49
|
+
Handing off to the project-level Sous install: v0.2.4
|
|
50
|
+
Project install: /work/app/node_modules/@sous-io/sous
|
|
51
|
+
Invoked install: v0.3.0 at /usr/lib/node_modules/@sous-io/sous
|
|
52
|
+
Set SOUS_NO_DELEGATE=1 to run the invoked install instead.
|
|
53
|
+
v0.2.4
|
|
54
|
+
Package : @sous-io/sous
|
|
55
|
+
Install : /work/app/node_modules/@sous-io/sous
|
|
56
|
+
Platform: linux-x64
|
|
57
|
+
Node : v22.21.0
|
|
47
58
|
$ SOUS_NO_DELEGATE=1 sous --version
|
|
48
|
-
|
|
59
|
+
v0.3.0
|
|
49
60
|
```
|
|
50
61
|
|
|
51
62
|
## Where to look
|
|
@@ -38,7 +38,8 @@ under [When sous cannot ask](repositories-consuming.md#when-sous-cannot-ask).
|
|
|
38
38
|
|
|
39
39
|
Help has four spellings. `sous --help` prints the root screen; `sous repo add --help`, `sous repo add -h` and
|
|
40
40
|
`sous help repo add` all print that one command's. `sous help` alone lists the topics and commands, and
|
|
41
|
-
`sous --version` prints the version,
|
|
41
|
+
`sous --version` prints the version alone, as `v1.2.3`; `sous --version --verbose` adds the package name, where
|
|
42
|
+
it is installed, the platform and the Node build under it.
|
|
42
43
|
|
|
43
44
|
Every topic answers to both spellings of its name: `repo` and `repos`, `subscription` and `subscriptions`,
|
|
44
45
|
`namespace` and `namespaces`, `recipe` and `recipes`, `lock` and `locks`, `vars` and `var`, `config` and
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
export const PACKAGE_NAME: "@sous-io/sous";
|
|
7
7
|
export const NO_DELEGATE_ENV: "SOUS_NO_DELEGATE";
|
|
8
8
|
export const DEBUG_ENV: "SOUS_DEBUG";
|
|
9
|
+
export const VERBOSE_FLAG: "--verbose";
|
|
9
10
|
|
|
10
11
|
export type ProjectInstall =
|
|
11
12
|
| { same: true; root: string }
|
|
@@ -13,19 +14,27 @@ export type ProjectInstall =
|
|
|
13
14
|
|
|
14
15
|
export type HandoffPlan =
|
|
15
16
|
| { kind: "run-self" }
|
|
16
|
-
| { kind: "hand-off"; install: Extract<ProjectInstall, { same: false }>; notice: string
|
|
17
|
+
| { kind: "hand-off"; install: Extract<ProjectInstall, { same: false }>; notice: string[] };
|
|
17
18
|
|
|
18
19
|
export function isEnvFlagOn(value: string | undefined): boolean;
|
|
19
20
|
export function binEntryOf(pkg: unknown): string | undefined;
|
|
20
21
|
export function findProjectInstall(startDir: string, ownRoot: string): ProjectInstall | undefined;
|
|
22
|
+
export function formatHandoffNotice(input: {
|
|
23
|
+
install: Extract<ProjectInstall, { same: false }>;
|
|
24
|
+
ownVersion: string;
|
|
25
|
+
ownRoot: string;
|
|
26
|
+
verbose: boolean;
|
|
27
|
+
}): string[];
|
|
21
28
|
export function planHandoff(input: {
|
|
22
29
|
cwd: string;
|
|
23
30
|
ownRoot: string;
|
|
24
31
|
env: Record<string, string | undefined>;
|
|
32
|
+
argv?: readonly string[];
|
|
25
33
|
}): HandoffPlan;
|
|
26
34
|
export function handOffToProjectInstall(input: {
|
|
27
35
|
ownRoot: string;
|
|
28
36
|
cwd?: string;
|
|
29
37
|
env?: Record<string, string | undefined>;
|
|
38
|
+
argv?: readonly string[];
|
|
30
39
|
stderr?: { write(chunk: string): unknown };
|
|
31
40
|
}): Promise<boolean>;
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
* A hand-off is a convenience; a refusal to run is not.
|
|
29
29
|
* - `SOUS_NO_DELEGATE` (anything but 0/false/no/off) runs the invoked copy.
|
|
30
30
|
* - The notice goes to stderr, so piped stdout stays clean, and only when
|
|
31
|
-
* the two versions differ; `SOUS_DEBUG` prints it on every hand-off.
|
|
31
|
+
* the two versions differ; `SOUS_DEBUG` prints it on every hand-off. It is
|
|
32
|
+
* one line naming the version handed off to; `--verbose` anywhere on the
|
|
33
|
+
* command line (or `SOUS_DEBUG`) adds where both installs are and how to
|
|
34
|
+
* keep the invoked one running.
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
37
|
import fs from "node:fs";
|
|
@@ -44,6 +47,9 @@ export const NO_DELEGATE_ENV = "SOUS_NO_DELEGATE";
|
|
|
44
47
|
/** The environment variable that makes every hand-off announce itself. */
|
|
45
48
|
export const DEBUG_ENV = "SOUS_DEBUG";
|
|
46
49
|
|
|
50
|
+
/** The flag that makes the notice say where both installs are. */
|
|
51
|
+
export const VERBOSE_FLAG = "--verbose";
|
|
52
|
+
|
|
47
53
|
/**
|
|
48
54
|
* Whether an on/off environment variable is on: set to anything but an empty
|
|
49
55
|
* string, `0`, `false`, `no` or `off` (case-insensitive, whitespace trimmed).
|
|
@@ -129,29 +135,48 @@ function describeInstall(candidate, ownRoot) {
|
|
|
129
135
|
return { same: false, root, version: typeof pkg.version === "string" ? pkg.version : "unknown", bin };
|
|
130
136
|
}
|
|
131
137
|
|
|
138
|
+
/**
|
|
139
|
+
* The lines the notice is made of: one line naming the version handed off to,
|
|
140
|
+
* and, when verbose, where both installs are and how to keep the invoked one
|
|
141
|
+
* running. Plain text, because this prints before tsx exists; the block
|
|
142
|
+
* mirrors the shape `showVariables` gives a key and value list.
|
|
143
|
+
*/
|
|
144
|
+
export function formatHandoffNotice({ install, ownVersion, ownRoot, verbose }) {
|
|
145
|
+
const lines = [`Handing off to the project-level Sous install: v${install.version}`];
|
|
146
|
+
if (verbose) {
|
|
147
|
+
lines.push(
|
|
148
|
+
` Project install: ${install.root}`,
|
|
149
|
+
` Invoked install: v${ownVersion} at ${ownRoot}`,
|
|
150
|
+
`Set ${NO_DELEGATE_ENV}=1 to run the invoked install instead.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return lines;
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
/**
|
|
133
157
|
* Decides what the invoked install should do, without doing it.
|
|
134
158
|
*
|
|
135
159
|
* Returns `{ kind: "run-self" }` when the invoked copy runs the command, or
|
|
136
160
|
* `{ kind: "hand-off", install, notice }` naming the project copy to import
|
|
137
|
-
* and the
|
|
161
|
+
* and the lines to print on stderr first (an empty list when nothing is said).
|
|
138
162
|
*/
|
|
139
|
-
export function planHandoff({ cwd, ownRoot, env }) {
|
|
163
|
+
export function planHandoff({ cwd, ownRoot, env, argv = [] }) {
|
|
140
164
|
if (isEnvFlagOn(env[NO_DELEGATE_ENV])) return { kind: "run-self" };
|
|
141
165
|
const install = findProjectInstall(cwd, ownRoot);
|
|
142
166
|
if (!install || install.same) return { kind: "run-self" };
|
|
143
167
|
|
|
144
168
|
const ownPkg = readPackageJson(ownRoot);
|
|
145
169
|
const ownVersion = typeof ownPkg?.version === "string" ? ownPkg.version : "unknown";
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
const debug = isEnvFlagOn(env[DEBUG_ENV]);
|
|
171
|
+
const announce = ownVersion !== install.version || debug;
|
|
172
|
+
const notice = announce
|
|
173
|
+
? formatHandoffNotice({
|
|
174
|
+
install,
|
|
175
|
+
ownVersion,
|
|
176
|
+
ownRoot: realpathOr(ownRoot),
|
|
177
|
+
verbose: debug || argv.includes(VERBOSE_FLAG),
|
|
178
|
+
})
|
|
179
|
+
: [];
|
|
155
180
|
return { kind: "hand-off", install, notice };
|
|
156
181
|
}
|
|
157
182
|
|
|
@@ -165,11 +190,12 @@ export async function handOffToProjectInstall({
|
|
|
165
190
|
ownRoot,
|
|
166
191
|
cwd = process.cwd(),
|
|
167
192
|
env = process.env,
|
|
193
|
+
argv = process.argv.slice(2),
|
|
168
194
|
stderr = process.stderr,
|
|
169
195
|
}) {
|
|
170
|
-
const plan = planHandoff({ cwd, ownRoot, env });
|
|
196
|
+
const plan = planHandoff({ cwd, ownRoot, env, argv });
|
|
171
197
|
if (plan.kind !== "hand-off") return false;
|
|
172
|
-
|
|
198
|
+
for (const line of plan.notice) stderr.write(`${line}\n`);
|
|
173
199
|
await import(pathToFileURL(plan.install.bin).href);
|
|
174
200
|
return true;
|
|
175
201
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `sous --version` prints.
|
|
3
|
+
*
|
|
4
|
+
* oclif answers `--version` with its user agent string (package, version,
|
|
5
|
+
* platform and Node build on one line). Sous answers it itself, in bin/run.js,
|
|
6
|
+
* before oclif is loaded: the version alone, or with `--verbose` the same facts
|
|
7
|
+
* as a key and value list, so a person asking "which sous is this?" gets one
|
|
8
|
+
* short line and a person asking "where is it?" gets the rest.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import { log, showVariables } from "../utils/formatting.js";
|
|
14
|
+
|
|
15
|
+
/** The one flag oclif treats as a version request, and so does sous. */
|
|
16
|
+
export const VERSION_FLAG = "--version";
|
|
17
|
+
|
|
18
|
+
/** The flag that adds where the install is, the platform and the Node build. */
|
|
19
|
+
export const VERBOSE_FLAG = "--verbose";
|
|
20
|
+
|
|
21
|
+
/** The facts a version report is made of. */
|
|
22
|
+
export type VersionFacts = {
|
|
23
|
+
/** The npm package name. */
|
|
24
|
+
name: string;
|
|
25
|
+
/** The package version. */
|
|
26
|
+
version: string;
|
|
27
|
+
/** The absolute path of the install answering. */
|
|
28
|
+
root: string;
|
|
29
|
+
/** The platform and architecture, the way oclif spells them. */
|
|
30
|
+
platform: string;
|
|
31
|
+
/** The Node build, with its leading `v`. */
|
|
32
|
+
node: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Whether the command line is a version request: `--version` as its first word. */
|
|
36
|
+
export function isVersionRequest(argv: readonly string[]): boolean {
|
|
37
|
+
return argv[0] === VERSION_FLAG;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Reads the facts about the install rooted at `packageRoot`. */
|
|
41
|
+
export function readVersionFacts(packageRoot: string): VersionFacts {
|
|
42
|
+
const pkg = JSON.parse(fs.readFileSync(`${packageRoot}/package.json`, "utf8")) as {
|
|
43
|
+
name?: unknown;
|
|
44
|
+
version?: unknown;
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
name: typeof pkg.name === "string" ? pkg.name : "unknown",
|
|
48
|
+
version: typeof pkg.version === "string" ? pkg.version : "unknown",
|
|
49
|
+
root: packageRoot,
|
|
50
|
+
platform: `${os.platform()}-${os.arch()}`,
|
|
51
|
+
node: process.version,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prints the report: the version on its own line, and with `verbose` the
|
|
57
|
+
* facts under it as a key and value list.
|
|
58
|
+
*
|
|
59
|
+
* @param facts - What to report.
|
|
60
|
+
* @param verbose - Whether to print the facts under the version.
|
|
61
|
+
* @param write - Where each line goes; standard output by default.
|
|
62
|
+
*/
|
|
63
|
+
export function printVersionReport(
|
|
64
|
+
facts: VersionFacts,
|
|
65
|
+
verbose: boolean,
|
|
66
|
+
write: (line: string) => void = log
|
|
67
|
+
): void {
|
|
68
|
+
write(`v${facts.version}`);
|
|
69
|
+
if (!verbose) return;
|
|
70
|
+
showVariables(
|
|
71
|
+
{
|
|
72
|
+
Package: facts.name,
|
|
73
|
+
Install: facts.root,
|
|
74
|
+
Platform: facts.platform,
|
|
75
|
+
Node: facts.node,
|
|
76
|
+
},
|
|
77
|
+
{ write }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Answers a version request from the command line: `sous --version`, or
|
|
83
|
+
* `sous --version --verbose`.
|
|
84
|
+
*
|
|
85
|
+
* @param argv - The command line after the program name.
|
|
86
|
+
* @param packageRoot - The root of the install answering.
|
|
87
|
+
*/
|
|
88
|
+
export function printVersion(argv: readonly string[], packageRoot: string): void {
|
|
89
|
+
printVersionReport(readVersionFacts(packageRoot), argv.includes(VERBOSE_FLAG));
|
|
90
|
+
}
|