Skip to content

Commit

Permalink
feat: save to snippet (#54)
Browse files Browse the repository at this point in the history
* feat: save to snippet

* shared function

* add save to snippet shared

* tests: done

* chore

* promises.resolve better reable
  • Loading branch information
ArnavK-09 authored Feb 7, 2025
1 parent 6930942 commit 8b1d9ee
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 191 deletions.
31 changes: 31 additions & 0 deletions cli/dev/DevServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import fs from "node:fs"
import type { FileUpdatedEvent } from "lib/file-server/FileServerEvent"
import * as chokidar from "chokidar"
import { FilesystemTypesHandler } from "lib/dependency-analysis/FilesystemTypesHandler"
import { pushSnippet } from "lib/shared/push-snippet"

export class DevServer {
port: number
Expand Down Expand Up @@ -68,6 +69,11 @@ export class DevServer {
this.handleFileUpdatedEventFromServer.bind(this),
)

this.eventsWatcher.on(
"REQUEST_TO_SAVE_SNIPPET",
this.saveSnippet.bind(this),
)

this.filesystemWatcher = chokidar.watch(this.projectDir, {
persistent: true,
ignoreInitial: true,
Expand Down Expand Up @@ -159,6 +165,31 @@ circuit.add(<MyCircuit />)
}
}

private async saveSnippet() {
const postEvent = async (
event: "FAILED_TO_SAVE_SNIPPET" | "SNIPPET_SAVED",
) =>
this.fsKy.post("api/events/create", {
json: { event_type: event },
throwHttpErrors: false,
})

await pushSnippet({
filePath: this.componentFilePath,
onExit: (e) => {
console.error("Failed to save snippet", e)
postEvent("FAILED_TO_SAVE_SNIPPET")
},
onError: (e) => {
console.error("Failed to save snippet", e)
postEvent("FAILED_TO_SAVE_SNIPPET")
},
onSuccess: () => {
postEvent("SNIPPET_SAVED")
},
})
}

async stop() {
this.httpServer?.close()
this.eventsWatcher?.stop()
Expand Down
2 changes: 1 addition & 1 deletion cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { registerAuthPrintToken } from "./auth/print-token/register"
import { registerAuthSetToken } from "./auth/set-token/register"
import { registerPush } from "./push/register"

const program = new Command()
export const program = new Command()

program
.name("tsci")
Expand Down
196 changes: 7 additions & 189 deletions cli/push/register.ts
Original file line number Diff line number Diff line change
@@ -1,199 +1,17 @@
import { pushSnippet } from "lib/shared/push-snippet"
import type { Command } from "commander"
import { cliConfig } from "lib/cli-config"
import { getKy } from "lib/registry-api/get-ky"
import * as fs from "node:fs"
import * as path from "node:path"
import semver from "semver"

export const registerPush = (program: Command) => {
program
.command("push")
.description("Save snippet code to Registry API")
.argument("[file]", "Path to the snippet file")
.action(async (filePath?: string) => {
const sessionToken = cliConfig.get("sessionToken")
if (!sessionToken) {
console.error("You need to log in to save snippet.")
process.exit(1)
}

let snippetFilePath: string | null = null
if (filePath) {
snippetFilePath = path.resolve(filePath)
} else {
const defaultEntrypoint = path.resolve("index.tsx")
if (fs.existsSync(defaultEntrypoint)) {
snippetFilePath = defaultEntrypoint
console.log("No file provided. Using 'index.tsx' as the entrypoint.")
} else {
console.error(
"No entrypoint found. Run 'tsci init' to bootstrap a basic project.",
)
process.exit(1)
}
}

const packageJsonPath = path.resolve(
path.join(path.dirname(snippetFilePath), "package.json"),
)
let packageJson: { name?: string; author?: string; version?: string } = {}
if (fs.existsSync(packageJsonPath)) {
try {
packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString())
} catch {
console.error("Invalid package.json provided")
process.exit(1)
}
}

if (!fs.existsSync(snippetFilePath)) {
console.error(`File not found: ${snippetFilePath}`)
process.exit(1)
}

const ky = getKy()
const packageName = (
packageJson.name ?? path.parse(snippetFilePath).name
).replace(/^@/, "")

const packageAuthor =
packageJson.author?.split(" ")[0] ?? cliConfig.get("githubUsername")

const packageIdentifier = `${packageAuthor}/${packageName}`

let packageVersion =
packageJson.version ??
(await ky
.post<{
error?: { error_code: string }
package_releases?: { version: string; is_latest: boolean }[]
}>("package_releases/list", {
json: { package_name: packageIdentifier },
})
.json()
.then(
(response) =>
response.package_releases?.[response.package_releases.length - 1]
?.version,
)
.catch((error) => {
console.error("Failed to retrieve latest package version:", error)
process.exit(1)
}))

if (!packageVersion) {
console.log("Failed to retrieve package version.")
process.exit(1)
}

const updatePackageJsonVersion = (newVersion?: string) => {
if (packageJson.version) {
try {
packageJson.version = newVersion ?? packageVersion
fs.writeFileSync(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
)
} catch (error) {
console.error("Failed to update package.json version:", error)
}
}
}

const doesPackageExist = await ky
.post<{ error?: { error_code: string } }>("packages/get", {
json: { name: packageIdentifier },
throwHttpErrors: false,
})
.json()
.then(
(response) => !(response.error?.error_code === "package_not_found"),
)

if (!doesPackageExist) {
await ky
.post("packages/create", {
json: { name: packageIdentifier },
headers: { Authorization: `Bearer ${sessionToken}` },
})
.catch((error) => {
console.error("Error creating package:", error)
process.exit(1)
})
}

const doesReleaseExist = await ky
.post<{
error?: { error_code: string }
package_release?: { version: string }
}>("package_releases/get", {
json: {
package_name_with_version: `${packageIdentifier}@${packageVersion}`,
},
throwHttpErrors: false,
})
.json()
.then((response) => {
if (response.package_release?.version) {
packageVersion = response.package_release.version
updatePackageJsonVersion(response.package_release.version)
return true
}
return !(response.error?.error_code === "package_release_not_found")
})

if (doesReleaseExist) {
const bumpedVersion = semver.inc(packageVersion, "patch")!
console.log(
`Incrementing Package Version ${packageVersion} -> ${bumpedVersion}`,
)
packageVersion = bumpedVersion
updatePackageJsonVersion(packageVersion)
}

await ky
.post("package_releases/create", {
json: {
package_name_with_version: `${packageIdentifier}@${packageVersion}`,
},
throwHttpErrors: false,
})
.catch((error) => {
console.error("Error creating release:", error)
process.exit(1)
})

console.log("\n")

const directoryFiles = fs.readdirSync(path.dirname(snippetFilePath))
for (const file of directoryFiles) {
const fileExtension = path.extname(file).replace(".", "")
if (!["json", "tsx", "ts"].includes(fileExtension)) continue

const fileContent =
fs
.readFileSync(path.join(path.dirname(snippetFilePath), file))
.toString() ?? ""
await ky
.post("package_files/create", {
json: {
file_path: file,
content_text: fileContent,
package_name_with_version: `${packageIdentifier}@${packageVersion}`,
},
throwHttpErrors: false,
})
.then(() => {
console.log(`Uploaded file ${file} to the registry.`)
})
.catch((error) => {
console.error(`Error uploading file ${file}:`, error)
})
}

console.log(
`\n🎉 Successfully pushed package ${packageIdentifier}@${packageVersion} to the registry!${Bun.color("blue", "ansi")}`,
`https://tscircuit.com/${packageIdentifier} \x1b[0m`,
)
await pushSnippet({
filePath,
onExit: (code) => process.exit(code),
onError: (message) => console.error(message),
onSuccess: (message) => console.log(message),
})
})
}
4 changes: 3 additions & 1 deletion lib/file-server/FileServerRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export interface FileServerRoutes {
import { EventsRoutes } from "lib/server/EventsRoutes"

export interface FileServerRoutes extends EventsRoutes {
"api/files/get": {
GET: {
searchParams: {
Expand Down
32 changes: 32 additions & 0 deletions lib/server/EventsRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export interface EventsRoutes {
"api/events/create": {
POST: {
requestJson: {
event_type: string
}
responseJson: {
event: {
event_id: string
event_type: string
}
}
}
}
"api/events/list": {
GET: {
responseJson: {
event_list: Array<{
event_id: string
event_type:
| "FILE_UPDATED"
| "FAILED_TO_SAVE_SNIPPET"
| "SNIPPET_SAVED"
| "REQUEST_TO_SAVE_SNIPPET"
file_path: string
created_at: string
initiator?: string
}>
}
}
}
}
Loading

0 comments on commit 8b1d9ee

Please sign in to comment.