@tscircuit/cli 0.0.125 → 0.0.127
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/bun.lockb +0 -0
- package/dev-server-frontend/bun.lockb +0 -0
- package/dev-server-frontend/package-lock.json +11 -12
- package/dev-server-frontend/package.json +1 -1
- package/dist/cli.js +205 -27
- package/example-project/package-lock.json +8 -9
- package/example-project/package.json +1 -1
- package/lib/cmd-fns/dev/find-available-port.ts +30 -0
- package/lib/cmd-fns/dev/index.ts +35 -1
- package/lib/cmd-fns/index.ts +1 -0
- package/lib/cmd-fns/lint.ts +37 -0
- package/lib/get-program.ts +6 -0
- package/lib/posthog.ts +24 -0
- package/lib/util/lint-project.ts +112 -0
- package/package.json +7 -6
- package/tsup.config.ts +7 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"version": "1.2.26",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@tscircuit/builder": "^1.5.134",
|
|
12
|
-
"@tscircuit/layout": "^0.0.
|
|
12
|
+
"@tscircuit/layout": "^0.0.25",
|
|
13
13
|
"@tscircuit/react-fiber": "^1.1.29"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"integrity": "sha512-iB+oaYyaVK1hQ0cODubnoSDg4gGYL9cp/4ad7G1b9Z0/IqehPztp5qE3KP2mV9Ns0UYmzwvtkEhTCmKUuhorbg=="
|
|
31
31
|
},
|
|
32
32
|
"node_modules/@tscircuit/builder": {
|
|
33
|
-
"version": "1.5.
|
|
34
|
-
"resolved": "https://registry.npmjs.org/@tscircuit/builder/-/builder-1.5.
|
|
35
|
-
"integrity": "sha512
|
|
33
|
+
"version": "1.5.138",
|
|
34
|
+
"resolved": "https://registry.npmjs.org/@tscircuit/builder/-/builder-1.5.138.tgz",
|
|
35
|
+
"integrity": "sha512-zfn08ASPQzKf9YtSytEgNzOGKu9OeVKurCyOyHxBbQMRZkqTSSQElfb1rGWz+uZI9gkhqyZq30989sS9qtOgvQ==",
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@lume/kiwi": "^0.1.0",
|
|
39
39
|
"@tscircuit/footprinter": "^0.0.43",
|
|
40
|
-
"@tscircuit/layout": "0.0.
|
|
40
|
+
"@tscircuit/layout": "^0.0.25",
|
|
41
41
|
"@tscircuit/routing": "1.3.1",
|
|
42
42
|
"@tscircuit/schematic-autolayout": "^0.0.5",
|
|
43
43
|
"convert-units": "^2.3.4",
|
|
@@ -64,15 +64,14 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"node_modules/@tscircuit/layout": {
|
|
67
|
-
"version": "0.0.
|
|
68
|
-
"resolved": "https://registry.npmjs.org/@tscircuit/layout/-/layout-0.0.
|
|
69
|
-
"integrity": "sha512-
|
|
67
|
+
"version": "0.0.25",
|
|
68
|
+
"resolved": "https://registry.npmjs.org/@tscircuit/layout/-/layout-0.0.25.tgz",
|
|
69
|
+
"integrity": "sha512-Mk7LveFUkvV7X5+DjX3aAKClkvPOcIEiPz6knIxay3VZCMJw7sSX1QJVa6VJiUiaV51uFohqvwymLhYOaW/kAg==",
|
|
70
70
|
"license": "ISC",
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"transformation-matrix": "^2.16.1"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"@tscircuit/builder": "*",
|
|
76
75
|
"@tscircuit/manual-edit-events": "*",
|
|
77
76
|
"@tscircuit/schematic-autolayout": "*",
|
|
78
77
|
"@tscircuit/soup": "*",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import net from 'net'
|
|
2
|
+
|
|
3
|
+
const MAX_PORT = 65535 // Maximum valid port number
|
|
4
|
+
|
|
5
|
+
export const findAvailablePort = async (startPort: number): Promise<number> => {
|
|
6
|
+
let port = startPort
|
|
7
|
+
|
|
8
|
+
while (port <= MAX_PORT) {
|
|
9
|
+
if (!(await isPortInUse(port))) {
|
|
10
|
+
return port
|
|
11
|
+
}
|
|
12
|
+
port++
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
throw new Error(`Unable to find an available port in range ${startPort}-${MAX_PORT}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const isPortInUse = (port: number): Promise<boolean> => {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const server = net.createServer()
|
|
21
|
+
server.once('error', () => {
|
|
22
|
+
resolve(true)
|
|
23
|
+
})
|
|
24
|
+
server.once('listening', () => {
|
|
25
|
+
server.close()
|
|
26
|
+
resolve(false)
|
|
27
|
+
})
|
|
28
|
+
server.listen(port)
|
|
29
|
+
})
|
|
30
|
+
}
|
package/lib/cmd-fns/dev/index.ts
CHANGED
|
@@ -16,6 +16,9 @@ import { startEditEventWatcher } from "./start-edit-event-watcher"
|
|
|
16
16
|
import { startExportRequestWatcher } from "./start-export-request-watcher"
|
|
17
17
|
import { startFsWatcher } from "./start-fs-watcher"
|
|
18
18
|
import { uploadExamplesFromDirectory } from "./upload-examples-from-directory"
|
|
19
|
+
import posthog from "lib/posthog"
|
|
20
|
+
import crypto from 'crypto'
|
|
21
|
+
import { findAvailablePort } from "./find-available-port"
|
|
19
22
|
|
|
20
23
|
export const devCmd = async (ctx: AppContext, args: any) => {
|
|
21
24
|
const params = z
|
|
@@ -24,9 +27,22 @@ export const devCmd = async (ctx: AppContext, args: any) => {
|
|
|
24
27
|
})
|
|
25
28
|
.parse(args)
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
let { port } = params
|
|
28
31
|
const { cwd } = ctx
|
|
29
32
|
|
|
33
|
+
// Find an available port
|
|
34
|
+
port = await findAvailablePort(port)
|
|
35
|
+
|
|
36
|
+
const projectHash = crypto.createHash('md5').update(cwd).digest('hex')
|
|
37
|
+
|
|
38
|
+
posthog.capture({
|
|
39
|
+
distinctId: projectHash,
|
|
40
|
+
event: 'tsci_dev_started',
|
|
41
|
+
properties: {
|
|
42
|
+
port: port,
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
30
46
|
// In the future we should automatically run "tsci init" if the directory
|
|
31
47
|
// isn't properly initialized, for now we're just going to do a spot check
|
|
32
48
|
const isInitialized = await checkIfInitialized(ctx)
|
|
@@ -120,14 +136,32 @@ export const devCmd = async (ctx: AppContext, args: any) => {
|
|
|
120
136
|
})
|
|
121
137
|
if (action === "open-in-browser") {
|
|
122
138
|
open(serverUrl)
|
|
139
|
+
posthog.capture({
|
|
140
|
+
distinctId: projectHash,
|
|
141
|
+
event: 'tsci_dev_open_browser'
|
|
142
|
+
})
|
|
123
143
|
} else if (action === "open-in-vs-code") {
|
|
124
144
|
await $`code ${cwd}`
|
|
145
|
+
posthog.capture({
|
|
146
|
+
distinctId: projectHash,
|
|
147
|
+
event: 'tsci_dev_open_vscode'
|
|
148
|
+
})
|
|
125
149
|
} else if (!action || action === "stop") {
|
|
126
150
|
if (server.stop) server.stop()
|
|
127
151
|
if (server.close) server.close()
|
|
128
152
|
fs_watcher.stop()
|
|
129
153
|
er_watcher.stop()
|
|
130
154
|
ee_watcher.stop()
|
|
155
|
+
|
|
156
|
+
posthog.capture({
|
|
157
|
+
distinctId: projectHash,
|
|
158
|
+
event: 'tsci_dev_stopped'
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if (posthog.shutdown) {
|
|
162
|
+
await posthog.shutdown()
|
|
163
|
+
}
|
|
164
|
+
|
|
131
165
|
break
|
|
132
166
|
}
|
|
133
167
|
}
|
package/lib/cmd-fns/index.ts
CHANGED
|
@@ -38,3 +38,4 @@ export { openCmd as open } from "./open"
|
|
|
38
38
|
export { versionCmd as version } from "./version"
|
|
39
39
|
export { exportGerbersCmd as exportGerbers } from "./export-gerbers"
|
|
40
40
|
export { devServerFulfillExportRequests } from "./dev-server-fulfill-export-requests"
|
|
41
|
+
export { lintCmd as lint } from "./lint"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AppContext } from "lib/util/app-context"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import kleur from "kleur"
|
|
4
|
+
import { lintProject } from "../util/lint-project"
|
|
5
|
+
|
|
6
|
+
export const lintCmd = async (ctx: AppContext, args: any) => {
|
|
7
|
+
const params = z
|
|
8
|
+
.object({
|
|
9
|
+
fix: z.boolean().optional().default(false),
|
|
10
|
+
})
|
|
11
|
+
.parse(args)
|
|
12
|
+
|
|
13
|
+
const { cwd } = ctx
|
|
14
|
+
const { fix } = params
|
|
15
|
+
|
|
16
|
+
console.log(kleur.blue("Running tscircuit linter..."))
|
|
17
|
+
|
|
18
|
+
const results = lintProject(cwd, fix);
|
|
19
|
+
|
|
20
|
+
let errorCount = 0;
|
|
21
|
+
for (const result of results) {
|
|
22
|
+
if (result.messages.length > 0) {
|
|
23
|
+
console.log(kleur.yellow(`\nFile: ${result.filePath}`));
|
|
24
|
+
for (const message of result.messages) {
|
|
25
|
+
console.log(` Line ${message.line}, Column ${message.column}: ${message.message}`);
|
|
26
|
+
errorCount++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (errorCount > 0) {
|
|
32
|
+
console.log(kleur.yellow(`\nFound ${errorCount} issue${errorCount === 1 ? '' : 's'} in your tscircuit code.`));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
} else {
|
|
35
|
+
console.log(kleur.green("\nNo tscircuit-specific issues found!"));
|
|
36
|
+
}
|
|
37
|
+
}
|
package/lib/get-program.ts
CHANGED
|
@@ -45,6 +45,12 @@ export const getProgram = (ctx: AppContext) => {
|
|
|
45
45
|
.argument("<packages...>", "Packages to remove")
|
|
46
46
|
.action((packages, flags) => CMDFN.remove(ctx, { packages, flags }))
|
|
47
47
|
|
|
48
|
+
cmd
|
|
49
|
+
.command("lint")
|
|
50
|
+
.description("Lint all TypeScript files in the project")
|
|
51
|
+
.option("--fix", "Automatically fix problems")
|
|
52
|
+
.action((args) => CMDFN.lint(ctx, args))
|
|
53
|
+
|
|
48
54
|
const authCmd = cmd.command("auth").description("Login/logout")
|
|
49
55
|
authCmd
|
|
50
56
|
.command("login")
|
package/lib/posthog.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PostHog } from 'posthog-node'
|
|
2
|
+
|
|
3
|
+
const POSTHOG_API_KEY: string | undefined = process.env.POSTHOG_API_KEY
|
|
4
|
+
|
|
5
|
+
let posthogInstance: PostHog | null = null
|
|
6
|
+
|
|
7
|
+
if (POSTHOG_API_KEY) {
|
|
8
|
+
posthogInstance = new PostHog(
|
|
9
|
+
POSTHOG_API_KEY,
|
|
10
|
+
{ host: 'https://us.i.posthog.com' }
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const posthogProxy = new Proxy<PostHog>({} as PostHog, {
|
|
15
|
+
get(target, prop) {
|
|
16
|
+
if (posthogInstance) {
|
|
17
|
+
return Reflect.get(posthogInstance, prop)
|
|
18
|
+
}
|
|
19
|
+
// Return a no-op function for any method call if PostHog is not initialized
|
|
20
|
+
return () => {}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export default posthogProxy
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
interface LintResult {
|
|
6
|
+
filePath: string;
|
|
7
|
+
messages: LintMessage[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface LintMessage {
|
|
11
|
+
line: number;
|
|
12
|
+
column: number;
|
|
13
|
+
message: string;
|
|
14
|
+
fix?: {
|
|
15
|
+
range: [number, number];
|
|
16
|
+
text: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function lintProject(projectPath: string, fix: boolean = false): LintResult[] {
|
|
21
|
+
const results: LintResult[] = [];
|
|
22
|
+
const files = getTypeScriptFiles(projectPath);
|
|
23
|
+
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const sourceFile = ts.createSourceFile(
|
|
26
|
+
file,
|
|
27
|
+
fs.readFileSync(file, 'utf8'),
|
|
28
|
+
ts.ScriptTarget.Latest,
|
|
29
|
+
true
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const result = lintFile(sourceFile, fix);
|
|
33
|
+
if (result.messages.length > 0) {
|
|
34
|
+
results.push({ filePath: file, messages: result.messages });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (fix && result.messages.some(m => m.fix)) {
|
|
38
|
+
const newContent = applyFixes(sourceFile.text, result.messages);
|
|
39
|
+
fs.writeFileSync(file, newContent);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function lintFile(sourceFile: ts.SourceFile, fix: boolean): { messages: LintMessage[] } {
|
|
47
|
+
const messages: LintMessage[] = [];
|
|
48
|
+
|
|
49
|
+
function visit(node: ts.Node) {
|
|
50
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'capacitor') {
|
|
51
|
+
const valueArg = node.arguments.find(arg => ts.isObjectLiteralExpression(arg));
|
|
52
|
+
if (valueArg && ts.isObjectLiteralExpression(valueArg)) {
|
|
53
|
+
const valueProp = valueArg.properties.find(
|
|
54
|
+
prop => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'value'
|
|
55
|
+
);
|
|
56
|
+
if (valueProp && ts.isPropertyAssignment(valueProp) && ts.isStringLiteral(valueProp.initializer)) {
|
|
57
|
+
const value = valueProp.initializer.text;
|
|
58
|
+
if (!value.match(/[µuμ]F$/)) {
|
|
59
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(valueProp.getStart());
|
|
60
|
+
messages.push({
|
|
61
|
+
line: line + 1,
|
|
62
|
+
column: character + 1,
|
|
63
|
+
message: 'Capacitor value should include units (e.g., "100F")',
|
|
64
|
+
fix: fix ? {
|
|
65
|
+
range: [valueProp.initializer.getStart(), valueProp.initializer.getEnd()],
|
|
66
|
+
text: `"${value}F"`
|
|
67
|
+
} : undefined
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ts.forEachChild(node, visit);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
visit(sourceFile);
|
|
78
|
+
|
|
79
|
+
return { messages };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getTypeScriptFiles(dir: string): string[] {
|
|
83
|
+
const files: string[] = [];
|
|
84
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
const fullPath = path.join(dir, entry.name);
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
if (entry.name !== 'node_modules' && entry.name !== 'dist') {
|
|
90
|
+
files.push(...getTypeScriptFiles(fullPath));
|
|
91
|
+
}
|
|
92
|
+
} else if (entry.isFile() && /\.tsx?$/.test(entry.name)) {
|
|
93
|
+
files.push(fullPath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return files;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function applyFixes(source: string, messages: LintMessage[]): string {
|
|
101
|
+
const fixes = messages
|
|
102
|
+
.filter(m => m.fix)
|
|
103
|
+
.sort((a, b) => (b.fix!.range[0] - a.fix!.range[0]));
|
|
104
|
+
|
|
105
|
+
let result = source;
|
|
106
|
+
for (const message of fixes) {
|
|
107
|
+
const [start, end] = message.fix!.range;
|
|
108
|
+
result = result.slice(0, start) + message.fix!.text + result.slice(end);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.127",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Command line tool for developing, publishing and installing tscircuit circuits",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"edgespec": "^0.0.69",
|
|
48
48
|
"esbuild": "^0.20.2",
|
|
49
49
|
"fast-glob": "^3.3.2",
|
|
50
|
-
"glob": "^10.
|
|
50
|
+
"glob": "^10.4.5",
|
|
51
51
|
"hono": "^4.1.0",
|
|
52
52
|
"ignore": "^5.3.1",
|
|
53
53
|
"json5": "^2.2.3",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"node-persist": "^4.0.1",
|
|
60
60
|
"open": "^10.1.0",
|
|
61
61
|
"perfect-cli": "^1.0.20",
|
|
62
|
+
"posthog-node": "^4.0.1",
|
|
62
63
|
"prompts": "^2.4.2",
|
|
63
64
|
"react": "^18.2.0",
|
|
64
65
|
"semver": "^7.6.0",
|
|
@@ -70,16 +71,16 @@
|
|
|
70
71
|
"peerDependencies": {
|
|
71
72
|
"@tscircuit/builder": "*",
|
|
72
73
|
"@tscircuit/layout": "*",
|
|
74
|
+
"@tscircuit/manual-edit-events": "*",
|
|
73
75
|
"@tscircuit/react-fiber": "*",
|
|
74
|
-
"@tscircuit/soup-util": "*"
|
|
75
|
-
"@tscircuit/manual-edit-events": "*"
|
|
76
|
+
"@tscircuit/soup-util": "*"
|
|
76
77
|
},
|
|
77
78
|
"devDependencies": {
|
|
78
79
|
"@tscircuit/builder": "*",
|
|
79
|
-
"@tscircuit/layout": "^0.0.
|
|
80
|
+
"@tscircuit/layout": "^0.0.25",
|
|
81
|
+
"@tscircuit/manual-edit-events": "^0.0.4",
|
|
80
82
|
"@tscircuit/react-fiber": "*",
|
|
81
83
|
"@tscircuit/soup-util": "^0.0.11",
|
|
82
|
-
"@tscircuit/manual-edit-events": "^0.0.4",
|
|
83
84
|
"@types/archiver": "^6.0.2",
|
|
84
85
|
"@types/bun": "^1.0.8",
|
|
85
86
|
"@types/chokidar": "^2.1.3",
|