@stacksjs/defaults 0.75.61 → 0.75.63

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.
@@ -1,163 +0,0 @@
1
- /**
2
- * The extension entry VS Code loads (`main` in package.json, bundled to
3
- * dist/extension.js). It wires the real machine into the controllers and
4
- * starts each part; the behavior lives in the modules it imports:
5
- *
6
- * - ./stacks.ts preview, dev server and buddy commands
7
- * - ./pickier.ts lint diagnostics, formatting and fix-all from the project's pickier
8
- * - ./env.ts decrypted env values on hover, missing keys, `buddy env:*` commands
9
- * - stx the stx extension's language support, built in from `@stacksjs/stx-vscode`,
10
- * and its TypeScript server plugin, which type-checks `.stx` files
11
- *
12
- * Nothing here depends on another extension being installed.
13
- */
14
- import type { PickierProcess } from './pickier'
15
- import type { Host } from './stacks'
16
- import { execFile, spawn } from 'node:child_process'
17
- import { existsSync, readFileSync } from 'node:fs'
18
- import { connect } from 'node:net'
19
- import { join } from 'node:path'
20
- import process from 'node:process'
21
- import { createInterface } from 'node:readline'
22
- import { activateStxLanguage, configureTypeScriptPlugin, deactivateStxLanguage, STX_EXTENSION_ID } from '@stacksjs/stx-vscode'
23
- import * as vscode from 'vscode'
24
- import { createEnvSupport, projectDecryptor } from './env'
25
- import { createPickierSupport } from './pickier'
26
- import { createStacksExtension } from './stacks'
27
-
28
- const PROBE_TIMEOUT_MS = 500
29
-
30
- function read(path: string): string | undefined {
31
- try {
32
- return readFileSync(path, 'utf8')
33
- }
34
- catch {
35
- return undefined
36
- }
37
- }
38
-
39
- function exec(file: string, args: string[], cwd: string): Promise<string> {
40
- return new Promise((resolve, reject) => {
41
- execFile(file, args, {
42
- cwd,
43
- timeout: 60_000,
44
- maxBuffer: 32 * 1024 * 1024,
45
- env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
46
- }, (error, stdout) => error ? reject(error) : resolve(String(stdout)))
47
- })
48
- }
49
-
50
- const envHost = { exists: existsSync, read, exec, environment: process.env }
51
-
52
- const host: Host = {
53
- exists: path => existsSync(path),
54
- read,
55
- exec,
56
-
57
- probe(hostname, port) {
58
- return new Promise((resolve) => {
59
- const socket = connect({ host: hostname, port })
60
- const finish = (open: boolean) => {
61
- socket.destroy()
62
- resolve(open)
63
- }
64
-
65
- socket.setTimeout(PROBE_TIMEOUT_MS)
66
- socket.once('connect', () => finish(true))
67
- socket.once('timeout', () => finish(false))
68
- socket.once('error', () => finish(false))
69
- })
70
- },
71
-
72
- sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
73
-
74
- decryptEnv: root => projectDecryptor(envHost, root),
75
- }
76
-
77
- /**
78
- * `bun --config=<empty> dist/pickier-worker.js` in the project root. The
79
- * explicit config keeps the project's bunfig.toml out: its `preload` boots the
80
- * framework (env decryption, auto-imports), which pickier does not need.
81
- */
82
- function spawnBun(bun: string, script: string, cwd: string): PickierProcess {
83
- const child = spawn(bun, [`--config=${join(script, '..', 'pickier-worker.bunfig.toml')}`, script], {
84
- cwd,
85
- stdio: ['pipe', 'pipe', 'pipe'],
86
- env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
87
- })
88
- const lines = createInterface({ input: child.stdout })
89
- let exited = false
90
- const exitListeners: Array<() => void> = []
91
- const exit = () => {
92
- if (exited)
93
- return
94
- exited = true
95
- for (const listener of exitListeners)
96
- listener()
97
- }
98
- child.once('exit', exit)
99
- child.once('error', exit)
100
- child.stderr.resume()
101
-
102
- return {
103
- write: line => child.stdin.write(line),
104
- onLine: listener => lines.on('line', listener),
105
- onExit: listener => exitListeners.push(listener),
106
- kill: () => child.kill(),
107
- }
108
- }
109
-
110
- const stacks = createStacksExtension(vscode, host)
111
- const env = createEnvSupport(vscode, envHost)
112
- let pickier: ReturnType<typeof createPickierSupport> | undefined
113
-
114
- /**
115
- * What `activate` resolves to. The stx extension (Stacks.vscode-stx) reads
116
- * `stx.active` and stands down when it is true, so the two never register
117
- * stx support twice in one window.
118
- */
119
- export interface StacksExtensionApi {
120
- stx: { active: boolean }
121
- }
122
-
123
- async function startStx(context: vscode.ExtensionContext): Promise<boolean> {
124
- try {
125
- await activateStxLanguage(context, {
126
- assetsPath: join(context.extensionPath, 'dist', 'stx'),
127
- // The stx extension contributes the snippets in its manifest; offering
128
- // them here as well would show each one twice.
129
- snippets: !vscode.extensions.getExtension(STX_EXTENSION_ID),
130
- })
131
- return true
132
- }
133
- catch (error) {
134
- console.error('Stacks: stx support failed to start', error)
135
- return false
136
- }
137
- }
138
-
139
- export async function activate(context: vscode.ExtensionContext): Promise<StacksExtensionApi> {
140
- // The manifest contributes the stx TypeScript server plugin. VS Code's
141
- // TypeScript extension does not activate on `stx` by itself, so this starts
142
- // it, and forwards `stxTypescriptPlugin.enabled` to the plugin. The stx
143
- // extension, when installed, does the same; the plugin decorates a project
144
- // once however many extensions contribute it.
145
- configureTypeScriptPlugin(vscode, context.subscriptions).catch((error) => {
146
- console.error('Stacks: could not configure the stx TypeScript plugin', error)
147
- })
148
-
149
- stacks.activate(context)
150
- env.activate(context)
151
- pickier = createPickierSupport(vscode, { exists: existsSync, spawnBun }, {
152
- workerPath: join(context.extensionPath, 'dist', 'pickier-worker.js'),
153
- })
154
- pickier.activate(context)
155
-
156
- return { stx: { active: await startStx(context) } }
157
- }
158
-
159
- export function deactivate(): void {
160
- stacks.deactivate()
161
- pickier?.deactivate()
162
- deactivateStxLanguage()
163
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * The line protocol between the extension and dist/pickier-worker.js.
3
- *
4
- * Pickier runs on Bun (its config is a TypeScript file, and it uses Bun's glob
5
- * and YAML APIs), and VS Code runs extensions on Node, so the extension keeps
6
- * one Bun process per project and talks to it over stdin/stdout: one JSON
7
- * object per line each way, answers matched to requests by `id`.
8
- */
9
-
10
- export type PickierOperation = 'lint' | 'fix' | 'format'
11
-
12
- export interface PickierRequest {
13
- id: number
14
- op: PickierOperation
15
- /** Absolute path, for config ignores and the per-extension rules. */
16
- file: string
17
- text: string
18
- }
19
-
20
- /** One issue, as pickier's `lintText` reports it: 1-based line and column. */
21
- export interface PickierIssue {
22
- line: number
23
- column: number
24
- ruleId: string
25
- message: string
26
- severity: 'error' | 'warning'
27
- help?: string
28
- }
29
-
30
- export type PickierResult =
31
- | { op: 'lint', issues: PickierIssue[] }
32
- /** `text` is null when the project's pickier predates `fixText`, or the file is ignored. */
33
- | { op: 'fix' | 'format', text: string | null }
34
-
35
- export type PickierResponse =
36
- | { id: number, ok: true, result: PickierResult }
37
- | { id: number, ok: false, error: string, missing?: boolean }
38
-
39
- /** The first line the worker writes, once it has loaded the project's pickier and config. */
40
- export interface PickierReady {
41
- ready: true
42
- version: string
43
- fixText: boolean
44
- }
45
-
46
- /** What the worker writes instead when the project has no pickier it can load. */
47
- export interface PickierUnavailable {
48
- ready: false
49
- error: string
50
- missing: boolean
51
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * The Bun side of the extension's pickier support (bundled to
3
- * dist/pickier-worker.js, started with the project root as its working
4
- * directory). See ./pickier-protocol.ts.
5
- *
6
- * It loads the PROJECT's pickier, not a copy bundled into the extension, and
7
- * lets pickier find the project's config itself, the way `./buddy lint` does
8
- * (core/actions/src/lint/lint.ts). Editor and command therefore agree on the
9
- * rules, the version and the ignores.
10
- */
11
- import type { PickierReady, PickierRequest, PickierResponse, PickierResult, PickierUnavailable } from './pickier-protocol'
12
- import { relative } from 'node:path'
13
- import process from 'node:process'
14
-
15
- interface PickierModule {
16
- lintText: (text: string, config: unknown, filePath?: string) => Promise<Array<{ line: number, column: number, ruleId: string, message: string, severity: 'error' | 'warning', help?: string }>>
17
- formatCode: (text: string, config: unknown, filePath: string) => string
18
- fixText?: (text: string, config: unknown, filePath?: string) => string
19
- loadConfigFromPath: (path: string | undefined) => Promise<{ ignores?: string[] }>
20
- shouldIgnorePath?: (absPath: string, ignoreGlobs: string[]) => boolean
21
- }
22
-
23
- function send(message: PickierResponse | PickierReady | PickierUnavailable): void {
24
- process.stdout.write(`${JSON.stringify(message)}\n`)
25
- }
26
-
27
- const root = process.cwd()
28
- let pickier: PickierModule
29
- let config: { ignores?: string[] }
30
- let version = 'unknown'
31
-
32
- try {
33
- const entry = Bun.resolveSync('pickier', root)
34
- pickier = await import(entry) as PickierModule
35
- try {
36
- version = (await import(Bun.resolveSync('pickier/package.json', root))).version ?? version
37
- }
38
- catch {}
39
- config = await pickier.loadConfigFromPath(undefined)
40
- }
41
- catch (error) {
42
- // Not a pickier project (or not installed yet): say so and stop, so the
43
- // extension stops asking instead of retrying on every keystroke.
44
- send({ ready: false, error: String(error), missing: true })
45
- process.exit(0)
46
- }
47
-
48
- send({ ready: true, version, fixText: typeof pickier.fixText === 'function' })
49
-
50
- function ignored(file: string): boolean {
51
- const rel = relative(root, file)
52
- if (rel.startsWith('..'))
53
- return true
54
- return pickier.shouldIgnorePath?.(file, config.ignores ?? []) ?? false
55
- }
56
-
57
- async function handle(request: PickierRequest): Promise<PickierResult> {
58
- if (request.op === 'lint') {
59
- if (ignored(request.file))
60
- return { op: 'lint', issues: [] }
61
- const issues = await pickier.lintText(request.text, config, request.file)
62
- return { op: 'lint', issues: issues.map(({ line, column, ruleId, message, severity, help }) => ({ line, column, ruleId, message, severity, help })) }
63
- }
64
-
65
- if (ignored(request.file))
66
- return { op: request.op, text: null }
67
-
68
- if (request.op === 'fix')
69
- return { op: 'fix', text: pickier.fixText ? pickier.fixText(request.text, config, request.file) : null }
70
-
71
- return { op: 'format', text: pickier.formatCode(request.text, config, request.file) }
72
- }
73
-
74
- for await (const line of console) {
75
- if (!line.trim())
76
- continue
77
-
78
- let request: PickierRequest
79
- try {
80
- request = JSON.parse(line)
81
- }
82
- catch {
83
- continue
84
- }
85
-
86
- try {
87
- send({ id: request.id, ok: true, result: await handle(request) })
88
- }
89
- catch (error) {
90
- send({ id: request.id, ok: false, error: error instanceof Error ? error.message : String(error) })
91
- }
92
- }