@enrichlayer/el-linear 1.4.0 → 1.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/README.md +54 -0
- package/dist/auth/oauth-callback.d.ts +40 -0
- package/dist/auth/oauth-callback.js +142 -0
- package/dist/auth/oauth-client.d.ts +55 -0
- package/dist/auth/oauth-client.js +134 -0
- package/dist/auth/oauth-fs.d.ts +1 -0
- package/dist/auth/oauth-fs.js +29 -0
- package/dist/auth/oauth-headless.d.ts +38 -0
- package/dist/auth/oauth-headless.js +50 -0
- package/dist/auth/oauth-storage.d.ts +51 -0
- package/dist/auth/oauth-storage.js +87 -0
- package/dist/auth/oauth-token.d.ts +70 -0
- package/dist/auth/oauth-token.js +141 -0
- package/dist/auth/token-resolver.d.ts +48 -0
- package/dist/auth/token-resolver.js +95 -0
- package/dist/commands/init/index.js +22 -0
- package/dist/commands/init/oauth.d.ts +85 -0
- package/dist/commands/init/oauth.js +308 -0
- package/dist/commands/profile/migrate-legacy.d.ts +96 -0
- package/dist/commands/profile/migrate-legacy.js +272 -0
- package/dist/commands/profile.js +6 -0
- package/dist/main.js +1 -1
- package/dist/utils/auth.js +8 -0
- package/dist/utils/graphql-service.d.ts +16 -1
- package/dist/utils/graphql-service.js +19 -7
- package/dist/utils/legacy-config-detection.d.ts +47 -0
- package/dist/utils/legacy-config-detection.js +90 -0
- package/dist/utils/migration-hint.d.ts +46 -0
- package/dist/utils/migration-hint.js +90 -0
- package/package.json +1 -1
package/dist/commands/profile.js
CHANGED
|
@@ -26,6 +26,7 @@ import { confirm } from "@inquirer/prompts";
|
|
|
26
26
|
import { ACTIVE_PROFILE_FILE, CONFIG_DIR, CONFIG_PATH, PROFILES_DIR, profilePaths, resolveActiveProfile, setActiveProfileForSession, TOKEN_PATH, } from "../config/paths.js";
|
|
27
27
|
import { outputSuccess, outputWarning } from "../utils/output.js";
|
|
28
28
|
import { runFullWizard } from "./init/index.js";
|
|
29
|
+
import { registerMigrateLegacy } from "./profile/migrate-legacy.js";
|
|
29
30
|
export function setupProfileCommands(program) {
|
|
30
31
|
const profile = program
|
|
31
32
|
.command("profile")
|
|
@@ -72,6 +73,11 @@ export function setupProfileCommands(program) {
|
|
|
72
73
|
.action(async (name, opts) => {
|
|
73
74
|
await runProfileRemove(name, opts.force === true);
|
|
74
75
|
});
|
|
76
|
+
// `el-linear profile migrate-legacy` — registered alongside add/list/etc.
|
|
77
|
+
// Lives in its own module so the multi-step migration logic stays
|
|
78
|
+
// self-contained and unit-testable without dragging in the full
|
|
79
|
+
// profile-management surface.
|
|
80
|
+
registerMigrateLegacy(profile);
|
|
75
81
|
}
|
|
76
82
|
export async function runProfileList() {
|
|
77
83
|
const active = resolveActiveProfile();
|
package/dist/main.js
CHANGED
|
@@ -30,7 +30,7 @@ import { splitList } from "./utils/validators.js";
|
|
|
30
30
|
program
|
|
31
31
|
.name("el-linear")
|
|
32
32
|
.description("A pragmatic CLI for Linear.app — deterministic resolution, structured validation, GraphQL escape hatch.")
|
|
33
|
-
.version("1.
|
|
33
|
+
.version("1.5.0")
|
|
34
34
|
.option("--api-token <token>", "Linear API token")
|
|
35
35
|
.option("--profile <name>", "named profile (under ~/.config/el-linear/profiles/<name>/) for this invocation. Overrides EL_LINEAR_PROFILE env + the on-disk active-profile marker.")
|
|
36
36
|
.option("--json", "output as JSON (default, accepted for compatibility)")
|
package/dist/utils/auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { LEGACY_LINCTL_TOKEN_PATH, LEGACY_TOKEN_PATH, resolveActiveProfile, TOKEN_PATH, } from "../config/paths.js";
|
|
3
|
+
import { maybeEmitMigrationHint } from "./migration-hint.js";
|
|
3
4
|
export function getApiToken(options) {
|
|
4
5
|
if (options.apiToken) {
|
|
5
6
|
return options.apiToken;
|
|
@@ -30,6 +31,13 @@ export function getApiToken(options) {
|
|
|
30
31
|
if (fs.existsSync(LEGACY_TOKEN_PATH)) {
|
|
31
32
|
return fs.readFileSync(LEGACY_TOKEN_PATH, "utf8").trim();
|
|
32
33
|
}
|
|
34
|
+
// Before falling through to the auth error, check for legacy-config
|
|
35
|
+
// drift (legacy `config.json` present but no token, or active-profile
|
|
36
|
+
// pointer broken). If detected, emit a one-shot stderr hint pointing
|
|
37
|
+
// the user at `el-linear profile migrate-legacy`. The hint is purely
|
|
38
|
+
// informational — we always still throw below, so scripted callers
|
|
39
|
+
// continue to see a non-zero exit and a parseable JSON error on stdout.
|
|
40
|
+
maybeEmitMigrationHint();
|
|
33
41
|
const profileNote = active.name
|
|
34
42
|
? ` (active profile: \`${active.name}\` — expected token at ${active.tokenPath})`
|
|
35
43
|
: "";
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import type { GraphQLResponseData, GraphQLVariables } from "../types/linear.js";
|
|
2
2
|
import { type AuthOptions } from "./auth.js";
|
|
3
|
+
/**
|
|
4
|
+
* Constructor arg shapes for `GraphQLService`. Three variants:
|
|
5
|
+
* - `string` → personal API token (legacy; sent without `Bearer` prefix).
|
|
6
|
+
* - `{apiKey: string}` → personal API token (explicit).
|
|
7
|
+
* - `{oauthToken: string}` → OAuth access token (sent as
|
|
8
|
+
* `Authorization: Bearer <token>` via the SDK's accessToken option).
|
|
9
|
+
*
|
|
10
|
+
* The string variant exists because hundreds of call sites and tests pass
|
|
11
|
+
* a plain string. We continue to support it indefinitely.
|
|
12
|
+
*/
|
|
13
|
+
export type GraphQLServiceAuth = string | {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
} | {
|
|
16
|
+
oauthToken: string;
|
|
17
|
+
};
|
|
3
18
|
export declare class GraphQLService {
|
|
4
19
|
private readonly graphQLClient;
|
|
5
|
-
constructor(
|
|
20
|
+
constructor(auth: GraphQLServiceAuth);
|
|
6
21
|
rawRequest<T = GraphQLResponseData>(query: string, variables?: GraphQLVariables): Promise<T>;
|
|
7
22
|
}
|
|
8
23
|
export declare function createGraphQLService(options: AuthOptions): GraphQLService;
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { LinearClient } from "@linear/sdk";
|
|
2
2
|
import { getApiToken } from "./auth.js";
|
|
3
|
+
function buildLinearClient(auth) {
|
|
4
|
+
const baseHeaders = { "public-file-urls-expire-in": "3600" };
|
|
5
|
+
if (typeof auth === "string") {
|
|
6
|
+
return new LinearClient({ apiKey: auth, headers: baseHeaders });
|
|
7
|
+
}
|
|
8
|
+
if ("oauthToken" in auth) {
|
|
9
|
+
// Linear's SDK natively supports OAuth via the `accessToken` option,
|
|
10
|
+
// which causes the underlying graphql-request client to send
|
|
11
|
+
// `Authorization: Bearer <token>` instead of the personal-token
|
|
12
|
+
// shape (`Authorization: <token>`).
|
|
13
|
+
return new LinearClient({
|
|
14
|
+
accessToken: auth.oauthToken,
|
|
15
|
+
headers: baseHeaders,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return new LinearClient({ apiKey: auth.apiKey, headers: baseHeaders });
|
|
19
|
+
}
|
|
3
20
|
export class GraphQLService {
|
|
4
21
|
graphQLClient;
|
|
5
|
-
constructor(
|
|
6
|
-
const client =
|
|
7
|
-
apiKey: apiToken,
|
|
8
|
-
headers: {
|
|
9
|
-
"public-file-urls-expire-in": "3600",
|
|
10
|
-
},
|
|
11
|
-
});
|
|
22
|
+
constructor(auth) {
|
|
23
|
+
const client = buildLinearClient(auth);
|
|
12
24
|
// LinearClient stores a private graphql-request client — access via escape hatch
|
|
13
25
|
this.graphQLClient = client.client;
|
|
14
26
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects "legacy drift" — the on-disk state where a user upgraded el-linear
|
|
3
|
+
* to >=1.4.0 (named profiles) while their old `~/.config/el-linear/config.json`
|
|
4
|
+
* is still present but the legacy single-file `token` (and any per-profile
|
|
5
|
+
* token) is missing or unreadable. In that state every command fails with
|
|
6
|
+
* "No API token found" and the user has no clear migration path.
|
|
7
|
+
*
|
|
8
|
+
* This module is **pure detection** — it returns a discriminated state and
|
|
9
|
+
* nothing else. The hint emission lives in `migration-hint.ts` and is wired
|
|
10
|
+
* into the auth-failure path (`auth.ts`) so the user gets a single clear
|
|
11
|
+
* stderr line *before* the regular auth error fires.
|
|
12
|
+
*
|
|
13
|
+
* The state shape is intentionally a discriminated union so callers can match
|
|
14
|
+
* exhaustively without re-checking individual booleans:
|
|
15
|
+
*
|
|
16
|
+
* { kind: 'no-drift' }
|
|
17
|
+
* — healthy: legacy single-file layout *or* a working active profile.
|
|
18
|
+
*
|
|
19
|
+
* { kind: 'legacy-no-token' }
|
|
20
|
+
* — `config.json` exists but no token (legacy or per-profile) does. This
|
|
21
|
+
* is the post-upgrade case: 1.4.0 expects per-profile tokens; legacy
|
|
22
|
+
* config was never migrated.
|
|
23
|
+
*
|
|
24
|
+
* { kind: 'broken-active-profile' }
|
|
25
|
+
* — `active-profile` points at a name whose directory doesn't exist.
|
|
26
|
+
* Typically caused by an interrupted `profile remove` or a hand-edit.
|
|
27
|
+
*/
|
|
28
|
+
export type LegacyDriftState = {
|
|
29
|
+
kind: "no-drift";
|
|
30
|
+
} | {
|
|
31
|
+
kind: "legacy-no-token";
|
|
32
|
+
legacyConfigPath: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "broken-active-profile";
|
|
35
|
+
pointedAt: string;
|
|
36
|
+
};
|
|
37
|
+
export interface DetectionFsOps {
|
|
38
|
+
existsSync: (p: string) => boolean;
|
|
39
|
+
readFileSync: (p: string) => string;
|
|
40
|
+
readdirSync: (p: string) => string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Detect drift between the legacy single-file layout and the >=1.4 named-
|
|
44
|
+
* profiles layout. Pure — `fsImpl` is overridable so tests can drive every
|
|
45
|
+
* branch without touching the filesystem.
|
|
46
|
+
*/
|
|
47
|
+
export declare function detectLegacyDrift(fsImpl?: DetectionFsOps): LegacyDriftState;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects "legacy drift" — the on-disk state where a user upgraded el-linear
|
|
3
|
+
* to >=1.4.0 (named profiles) while their old `~/.config/el-linear/config.json`
|
|
4
|
+
* is still present but the legacy single-file `token` (and any per-profile
|
|
5
|
+
* token) is missing or unreadable. In that state every command fails with
|
|
6
|
+
* "No API token found" and the user has no clear migration path.
|
|
7
|
+
*
|
|
8
|
+
* This module is **pure detection** — it returns a discriminated state and
|
|
9
|
+
* nothing else. The hint emission lives in `migration-hint.ts` and is wired
|
|
10
|
+
* into the auth-failure path (`auth.ts`) so the user gets a single clear
|
|
11
|
+
* stderr line *before* the regular auth error fires.
|
|
12
|
+
*
|
|
13
|
+
* The state shape is intentionally a discriminated union so callers can match
|
|
14
|
+
* exhaustively without re-checking individual booleans:
|
|
15
|
+
*
|
|
16
|
+
* { kind: 'no-drift' }
|
|
17
|
+
* — healthy: legacy single-file layout *or* a working active profile.
|
|
18
|
+
*
|
|
19
|
+
* { kind: 'legacy-no-token' }
|
|
20
|
+
* — `config.json` exists but no token (legacy or per-profile) does. This
|
|
21
|
+
* is the post-upgrade case: 1.4.0 expects per-profile tokens; legacy
|
|
22
|
+
* config was never migrated.
|
|
23
|
+
*
|
|
24
|
+
* { kind: 'broken-active-profile' }
|
|
25
|
+
* — `active-profile` points at a name whose directory doesn't exist.
|
|
26
|
+
* Typically caused by an interrupted `profile remove` or a hand-edit.
|
|
27
|
+
*/
|
|
28
|
+
import fs from "node:fs";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { ACTIVE_PROFILE_FILE, CONFIG_PATH, PROFILES_DIR, TOKEN_PATH, } from "../config/paths.js";
|
|
31
|
+
const DEFAULT_FS_OPS = {
|
|
32
|
+
existsSync: (p) => fs.existsSync(p),
|
|
33
|
+
readFileSync: (p) => fs.readFileSync(p, "utf8"),
|
|
34
|
+
readdirSync: (p) => fs.readdirSync(p),
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Detect drift between the legacy single-file layout and the >=1.4 named-
|
|
38
|
+
* profiles layout. Pure — `fsImpl` is overridable so tests can drive every
|
|
39
|
+
* branch without touching the filesystem.
|
|
40
|
+
*/
|
|
41
|
+
export function detectLegacyDrift(fsImpl = DEFAULT_FS_OPS) {
|
|
42
|
+
// Branch 1: broken active-profile pointer.
|
|
43
|
+
// The `active-profile` marker names a profile that doesn't exist on disk —
|
|
44
|
+
// classic post-`profile remove` orphan, or a hand-edit typo. We classify
|
|
45
|
+
// this *before* the legacy-no-token branch because the user explicitly
|
|
46
|
+
// asked for that profile; the right fix is `profile use <good-name>`, not
|
|
47
|
+
// migration.
|
|
48
|
+
if (fsImpl.existsSync(ACTIVE_PROFILE_FILE)) {
|
|
49
|
+
let pointedAt = "";
|
|
50
|
+
try {
|
|
51
|
+
pointedAt = fsImpl.readFileSync(ACTIVE_PROFILE_FILE).trim();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
pointedAt = "";
|
|
55
|
+
}
|
|
56
|
+
if (pointedAt.length > 0) {
|
|
57
|
+
const profileDir = path.join(PROFILES_DIR, pointedAt);
|
|
58
|
+
if (!fsImpl.existsSync(profileDir)) {
|
|
59
|
+
return { kind: "broken-active-profile", pointedAt };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Branch 2: legacy drift.
|
|
64
|
+
// Legacy `config.json` exists; legacy `token` doesn't; no profiles configured.
|
|
65
|
+
// "No profiles configured" = profiles dir missing OR exists-but-empty.
|
|
66
|
+
if (!fsImpl.existsSync(CONFIG_PATH)) {
|
|
67
|
+
return { kind: "no-drift" };
|
|
68
|
+
}
|
|
69
|
+
if (fsImpl.existsSync(TOKEN_PATH)) {
|
|
70
|
+
return { kind: "no-drift" };
|
|
71
|
+
}
|
|
72
|
+
const profilesEmpty = isProfilesDirEmpty(fsImpl);
|
|
73
|
+
if (!profilesEmpty) {
|
|
74
|
+
return { kind: "no-drift" };
|
|
75
|
+
}
|
|
76
|
+
return { kind: "legacy-no-token", legacyConfigPath: CONFIG_PATH };
|
|
77
|
+
}
|
|
78
|
+
function isProfilesDirEmpty(fsImpl) {
|
|
79
|
+
if (!fsImpl.existsSync(PROFILES_DIR))
|
|
80
|
+
return true;
|
|
81
|
+
try {
|
|
82
|
+
const entries = fsImpl.readdirSync(PROFILES_DIR);
|
|
83
|
+
return entries.length === 0;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Unreadable dir — treat as empty for the purposes of drift detection
|
|
87
|
+
// so we don't suppress the hint on a permissions edge case.
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Once-per-process stderr hint for legacy-config drift.
|
|
3
|
+
*
|
|
4
|
+
* Wired into the auth-failure path in `auth.ts`. When the legacy single-file
|
|
5
|
+
* config exists but no token can be found anywhere (the >=1.4 post-upgrade
|
|
6
|
+
* scenario), we emit one stderr line pointing the user at
|
|
7
|
+
* `el-linear profile migrate-legacy` *before* the regular "No API token
|
|
8
|
+
* found" error fires.
|
|
9
|
+
*
|
|
10
|
+
* Constraints:
|
|
11
|
+
*
|
|
12
|
+
* - **stderr only** — stdout is reserved for the JSON error payload that
|
|
13
|
+
* machine callers parse, and the hint must not corrupt that stream.
|
|
14
|
+
* - **Once per process** — even if a script invokes el-linear in a tight
|
|
15
|
+
* loop within a single Node process (uncommon but possible), only the
|
|
16
|
+
* first failure prints. Anything more is noise.
|
|
17
|
+
* - **Suppressible** — `EL_LINEAR_SKIP_MIGRATION_HINT=1` silences the hint
|
|
18
|
+
* for users who've decided to stay on the legacy layout intentionally.
|
|
19
|
+
* The env var is read at *emission time*, not module load, so toggling
|
|
20
|
+
* it in tests works without re-importing.
|
|
21
|
+
* - **Non-blocking** — never throws, never delays the underlying auth
|
|
22
|
+
* error. The hint is purely informational.
|
|
23
|
+
*/
|
|
24
|
+
import { type DetectionFsOps, type LegacyDriftState } from "./legacy-config-detection.js";
|
|
25
|
+
/**
|
|
26
|
+
* Test seam — resets the once-per-process latch so each test starts fresh.
|
|
27
|
+
* Production code never calls this.
|
|
28
|
+
*/
|
|
29
|
+
export declare function _resetMigrationHintForTests(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Emit the migration hint to stderr if (and only if) legacy drift is
|
|
32
|
+
* detected, the latch hasn't fired this process, and the suppress env var
|
|
33
|
+
* isn't set. Returns the detection state so callers can branch (or log).
|
|
34
|
+
*
|
|
35
|
+
* Optional `fsImpl` lets tests drive detection without touching disk.
|
|
36
|
+
* Optional `stderr` lets tests capture the output without spying on the
|
|
37
|
+
* global stream.
|
|
38
|
+
*/
|
|
39
|
+
export declare function maybeEmitMigrationHint(fsImpl?: DetectionFsOps, stderr?: {
|
|
40
|
+
write: (chunk: string) => void;
|
|
41
|
+
}): LegacyDriftState;
|
|
42
|
+
/**
|
|
43
|
+
* Render the hint string for a given state. Returns null when no hint
|
|
44
|
+
* should be emitted (no-drift). Exported for unit testing.
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatHint(state: LegacyDriftState): string | null;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Once-per-process stderr hint for legacy-config drift.
|
|
3
|
+
*
|
|
4
|
+
* Wired into the auth-failure path in `auth.ts`. When the legacy single-file
|
|
5
|
+
* config exists but no token can be found anywhere (the >=1.4 post-upgrade
|
|
6
|
+
* scenario), we emit one stderr line pointing the user at
|
|
7
|
+
* `el-linear profile migrate-legacy` *before* the regular "No API token
|
|
8
|
+
* found" error fires.
|
|
9
|
+
*
|
|
10
|
+
* Constraints:
|
|
11
|
+
*
|
|
12
|
+
* - **stderr only** — stdout is reserved for the JSON error payload that
|
|
13
|
+
* machine callers parse, and the hint must not corrupt that stream.
|
|
14
|
+
* - **Once per process** — even if a script invokes el-linear in a tight
|
|
15
|
+
* loop within a single Node process (uncommon but possible), only the
|
|
16
|
+
* first failure prints. Anything more is noise.
|
|
17
|
+
* - **Suppressible** — `EL_LINEAR_SKIP_MIGRATION_HINT=1` silences the hint
|
|
18
|
+
* for users who've decided to stay on the legacy layout intentionally.
|
|
19
|
+
* The env var is read at *emission time*, not module load, so toggling
|
|
20
|
+
* it in tests works without re-importing.
|
|
21
|
+
* - **Non-blocking** — never throws, never delays the underlying auth
|
|
22
|
+
* error. The hint is purely informational.
|
|
23
|
+
*/
|
|
24
|
+
import { detectLegacyDrift, } from "./legacy-config-detection.js";
|
|
25
|
+
let hintAlreadyEmitted = false;
|
|
26
|
+
/**
|
|
27
|
+
* Test seam — resets the once-per-process latch so each test starts fresh.
|
|
28
|
+
* Production code never calls this.
|
|
29
|
+
*/
|
|
30
|
+
export function _resetMigrationHintForTests() {
|
|
31
|
+
hintAlreadyEmitted = false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Emit the migration hint to stderr if (and only if) legacy drift is
|
|
35
|
+
* detected, the latch hasn't fired this process, and the suppress env var
|
|
36
|
+
* isn't set. Returns the detection state so callers can branch (or log).
|
|
37
|
+
*
|
|
38
|
+
* Optional `fsImpl` lets tests drive detection without touching disk.
|
|
39
|
+
* Optional `stderr` lets tests capture the output without spying on the
|
|
40
|
+
* global stream.
|
|
41
|
+
*/
|
|
42
|
+
export function maybeEmitMigrationHint(fsImpl, stderr = process.stderr) {
|
|
43
|
+
const state = detectLegacyDrift(fsImpl);
|
|
44
|
+
// Read the env var at call time — the suppress flag may be flipped between
|
|
45
|
+
// commands in long-lived test processes.
|
|
46
|
+
if (process.env.EL_LINEAR_SKIP_MIGRATION_HINT === "1") {
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
if (hintAlreadyEmitted) {
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
const message = formatHint(state);
|
|
53
|
+
if (message === null) {
|
|
54
|
+
return state;
|
|
55
|
+
}
|
|
56
|
+
hintAlreadyEmitted = true;
|
|
57
|
+
stderr.write(`${message}\n`);
|
|
58
|
+
return state;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Render the hint string for a given state. Returns null when no hint
|
|
62
|
+
* should be emitted (no-drift). Exported for unit testing.
|
|
63
|
+
*/
|
|
64
|
+
export function formatHint(state) {
|
|
65
|
+
if (state.kind === "no-drift")
|
|
66
|
+
return null;
|
|
67
|
+
if (state.kind === "broken-active-profile") {
|
|
68
|
+
return [
|
|
69
|
+
`el-linear: active profile "${state.pointedAt}" doesn't exist.`,
|
|
70
|
+
"Switch with:",
|
|
71
|
+
"",
|
|
72
|
+
" el-linear profile use <name>",
|
|
73
|
+
"",
|
|
74
|
+
"Or list available profiles:",
|
|
75
|
+
"",
|
|
76
|
+
" el-linear profile list",
|
|
77
|
+
"",
|
|
78
|
+
"Or suppress this hint with EL_LINEAR_SKIP_MIGRATION_HINT=1.",
|
|
79
|
+
].join("\n");
|
|
80
|
+
}
|
|
81
|
+
// legacy-no-token
|
|
82
|
+
return [
|
|
83
|
+
`el-linear: legacy config detected at ${state.legacyConfigPath}`,
|
|
84
|
+
"but no token. Migrate with:",
|
|
85
|
+
"",
|
|
86
|
+
" el-linear profile migrate-legacy [--name <profile>]",
|
|
87
|
+
"",
|
|
88
|
+
"Or suppress this hint with EL_LINEAR_SKIP_MIGRATION_HINT=1.",
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enrichlayer/el-linear",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "A pragmatic CLI for Linear.app — deterministic team/label/member resolution, structured issue validation, configurable term enforcement, and a GraphQL escape hatch.",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"types": "dist/main.d.ts",
|