├── .npmrc ├── src ├── core │ ├── deactivate.ts │ └── activate.ts ├── extension.ts ├── commands │ ├── applications │ │ ├── actions │ │ │ ├── index.ts │ │ │ ├── stop.ts │ │ │ ├── start.ts │ │ │ └── restart.ts │ │ ├── tools │ │ │ ├── index.ts │ │ │ ├── snapshot.ts │ │ │ ├── logs.ts │ │ │ ├── delete.ts │ │ │ └── commit.ts │ │ ├── index.ts │ │ ├── favorite.ts │ │ ├── unfavorite.ts │ │ ├── open.ts │ │ ├── refresh.ts │ │ └── copy-id.ts │ ├── index.ts │ ├── refresh-cache.ts │ ├── copy-text.ts │ └── set-api-key.ts ├── lib │ ├── utils │ │ ├── capitalize.ts │ │ ├── sets.ts │ │ ├── icons.ts │ │ ├── diagnostic.ts │ │ └── format.ts │ ├── constants.ts │ ├── api-key │ │ ├── index.ts │ │ └── store.ts │ └── store.ts ├── types │ ├── helpers.ts │ └── config-file.ts ├── structures │ ├── logger.ts │ ├── constant.ts │ ├── command.ts │ └── application │ │ ├── command.ts │ │ └── status.ts ├── managers │ ├── config.ts │ ├── commands.ts │ ├── treeviews.ts │ ├── extension.ts │ ├── config-file.ts │ └── api.ts ├── treeviews │ ├── items │ │ ├── custom.ts │ │ └── generic.ts │ ├── base.ts │ ├── applications │ │ ├── item.ts │ │ └── provider.ts │ └── user │ │ └── provider.ts ├── config-file │ ├── parameters.ts │ └── parameters │ │ ├── start.ts │ │ ├── description.ts │ │ ├── display-name.ts │ │ ├── version.ts │ │ ├── autorestart.ts │ │ ├── subdomain.ts │ │ ├── main.ts │ │ └── memory.ts └── providers │ ├── config-file-action.ts │ ├── config-file-completion.ts │ └── config-file-validation.ts ├── resources ├── squarecloud.ignore ├── squarecloud.png ├── dark │ ├── email.svg │ ├── logs.svg │ ├── username.svg │ ├── uptime.svg │ ├── refresh.svg │ ├── network.svg │ ├── copy.svg │ ├── plan.svg │ ├── star-fill.svg │ ├── cpu.svg │ ├── id.svg │ ├── ram.svg │ ├── star.svg │ ├── storage.svg │ ├── online.svg │ ├── squarecloud.svg │ ├── squareignore.svg │ ├── offline.svg │ ├── error.svg │ ├── loading.svg │ └── warning.svg ├── light │ ├── email.svg │ ├── logs.svg │ ├── username.svg │ ├── uptime.svg │ ├── refresh.svg │ ├── network.svg │ ├── copy.svg │ ├── plan.svg │ ├── star-fill.svg │ ├── cpu.svg │ ├── id.svg │ ├── ram.svg │ ├── star.svg │ ├── storage.svg │ ├── online.svg │ ├── squarecloud.svg │ ├── squareignore.svg │ ├── offline.svg │ ├── error.svg │ ├── loading.svg │ └── warning.svg └── squarecloud.svg ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .gitignore ├── .vscodeignore ├── pnpm-workspace.yaml ├── TODO.md ├── tsconfig.json ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── syntaxes └── config.tmLanguage.json ├── patches └── adm-zip.patch ├── LICENSE ├── contributes.disabled.json ├── esbuild.js ├── README.md ├── biome.json ├── CHANGELOG.md ├── package.nls.json ├── package.nls.pt-br.json ├── package.nls.es.json └── package.json /.npmrc: -------------------------------------------------------------------------------- 1 | enable-pre-post-scripts = true -------------------------------------------------------------------------------- /src/core/deactivate.ts: -------------------------------------------------------------------------------- 1 | export function deactivate(): void {} 2 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | export * from "./core/activate"; 2 | export * from "./core/deactivate"; 3 | -------------------------------------------------------------------------------- /resources/squarecloud.ignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome", "connor4312.esbuild-problem-matchers"] 3 | } 4 | -------------------------------------------------------------------------------- /resources/squarecloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squarecloudofc/vscode-extension/HEAD/resources/squarecloud.png -------------------------------------------------------------------------------- /src/commands/applications/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./restart"; 2 | export * from "./start"; 3 | export * from "./stop"; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build 2 | dist 3 | 4 | # dependencies 5 | node_modules 6 | yarn.lock 7 | 8 | # vscode 9 | .vscode-test/ 10 | *.vsix -------------------------------------------------------------------------------- /src/lib/utils/capitalize.ts: -------------------------------------------------------------------------------- 1 | export function capitalize(str: string) { 2 | return str.charAt(0).toUpperCase() + str.slice(1); 3 | } 4 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | out 4 | src 5 | .github 6 | tsconfig.json 7 | esbuild.js 8 | contributes.disabled.json 9 | pnpm-lock.yaml -------------------------------------------------------------------------------- /src/commands/applications/tools/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./commit"; 2 | export * from "./delete"; 3 | export * from "./logs"; 4 | export * from "./snapshot"; 5 | -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./applications"; 2 | export * from "./copy-text"; 3 | export * from "./refresh-cache"; 4 | export * from "./set-api-key"; 5 | -------------------------------------------------------------------------------- /src/lib/utils/sets.ts: -------------------------------------------------------------------------------- 1 | export function compareSets(a: Set, b: Set): boolean { 2 | return a.size === b.size && [...a].every((item) => b.has(item)); 3 | } 4 | -------------------------------------------------------------------------------- /src/types/helpers.ts: -------------------------------------------------------------------------------- 1 | export type If< 2 | Condition extends boolean, 3 | True, 4 | False = True | undefined, 5 | > = Condition extends true ? True : False; 6 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | onlyBuiltDependencies: 2 | - '@biomejs/biome' 3 | - '@vscode/vsce-sign' 4 | - esbuild 5 | - keytar 6 | 7 | patchedDependencies: 8 | adm-zip: patches/adm-zip.patch 9 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - [ ] Workspace commands: upload and commit (compatible with CLI) 4 | - [ ] Status bar item 5 | - [ ] Improved cache invalidation 6 | - [ ] Create configuration file command 7 | -------------------------------------------------------------------------------- /src/structures/logger.ts: -------------------------------------------------------------------------------- 1 | export class Logger { 2 | constructor(public readonly name: string) {} 3 | 4 | log(message: string) { 5 | console.log(`[${this.name}] ${message}`); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/commands/refresh-cache.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "@/structures/command"; 2 | 3 | export const refreshCache = new Command("refreshCache", async (extension) => { 4 | await extension.api.refresh(); 5 | }); 6 | -------------------------------------------------------------------------------- /src/commands/applications/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./actions"; 2 | export * from "./copy-id"; 3 | export * from "./favorite"; 4 | export * from "./open"; 5 | export * from "./refresh"; 6 | export * from "./tools"; 7 | export * from "./unfavorite"; 8 | -------------------------------------------------------------------------------- /src/core/activate.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext } from "vscode"; 2 | 3 | import { SquareCloudExtension } from "@/managers/extension"; 4 | 5 | export async function activate(context: ExtensionContext): Promise { 6 | new SquareCloudExtension(context); 7 | } 8 | -------------------------------------------------------------------------------- /src/commands/applications/favorite.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationCommand } from "@/structures/application/command"; 2 | 3 | export const favoriteEntry = new ApplicationCommand( 4 | "favoriteEntry", 5 | (extension, { application }) => { 6 | extension.store.actions.toggleFavorite(application.id, true); 7 | }, 8 | ); 9 | -------------------------------------------------------------------------------- /resources/dark/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/commands/applications/unfavorite.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationCommand } from "@/structures/application/command"; 2 | 3 | export const unfavoriteEntry = new ApplicationCommand( 4 | "unfavoriteEntry", 5 | (extension, { application }) => { 6 | extension.store.actions.toggleFavorite(application.id, false); 7 | }, 8 | ); 9 | -------------------------------------------------------------------------------- /src/commands/applications/open.ts: -------------------------------------------------------------------------------- 1 | import { env, Uri } from "vscode"; 2 | 3 | import { ApplicationCommand } from "@/structures/application/command"; 4 | 5 | export const openEntry = new ApplicationCommand( 6 | "openEntry", 7 | (_extension, { application }) => { 8 | env.openExternal(Uri.parse(application.url)); 9 | }, 10 | ); 11 | -------------------------------------------------------------------------------- /resources/dark/logs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/logs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/username.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/username.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/commands/applications/refresh.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationCommand } from "@/structures/application/command"; 2 | 3 | export const refreshEntry = new ApplicationCommand( 4 | "refreshEntry", 5 | (extension, { application }) => { 6 | if (extension.api.paused) { 7 | return; 8 | } 9 | 10 | extension.api.refreshStatus(application.id); 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /resources/dark/uptime.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/uptime.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "dist", 6 | "lib": ["ESNext", "DOM"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "paths": { 13 | "@/*": ["./src/*"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "docker" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" -------------------------------------------------------------------------------- /resources/dark/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/network.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/network.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/utils/icons.ts: -------------------------------------------------------------------------------- 1 | import { join } from "node:path"; 2 | import { Uri } from "vscode"; 3 | 4 | export function getIcons(iconName: string) { 5 | const pathByTheme = (theme: "dark" | "light") => { 6 | return Uri.file(join(__dirname, "..", "resources", theme, iconName)); 7 | }; 8 | 9 | return { 10 | light: pathByTheme("light"), 11 | dark: pathByTheme("dark"), 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 9 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 10 | "preLaunchTask": "${defaultBuildTask}", 11 | "sourceMaps": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/managers/config.ts: -------------------------------------------------------------------------------- 1 | import { type SecretStorage, workspace } from "vscode"; 2 | 3 | import { ApiKey } from "@/lib/api-key"; 4 | import { ExtensionID } from "@/lib/constants"; 5 | 6 | export class ConfigManager { 7 | apiKey = new ApiKey(this.secrets); 8 | 9 | constructor(private readonly secrets: SecretStorage) {} 10 | 11 | get root() { 12 | return workspace.getConfiguration(ExtensionID); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /resources/dark/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/plan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/plan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/commands/applications/copy-id.ts: -------------------------------------------------------------------------------- 1 | import { env, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import { ApplicationCommand } from "@/structures/application/command"; 5 | 6 | export const copyIdEntry = new ApplicationCommand( 7 | "copyIdEntry", 8 | async (_extension, { application }) => { 9 | await env.clipboard.writeText(application.id); 10 | window.showInformationMessage(t("copy.copiedId")); 11 | }, 12 | ); 13 | -------------------------------------------------------------------------------- /resources/dark/star-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/star-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/utils/diagnostic.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | export function createDiagnostic( 4 | document: vscode.TextDocument, 5 | line: number, 6 | message: string, 7 | severity = vscode.DiagnosticSeverity.Error, 8 | ): vscode.Diagnostic { 9 | const range = new vscode.Range( 10 | line, 11 | 0, 12 | line, 13 | document.lineAt(line).text.length, 14 | ); 15 | return new vscode.Diagnostic(range, message, severity); 16 | } 17 | -------------------------------------------------------------------------------- /src/treeviews/items/custom.ts: -------------------------------------------------------------------------------- 1 | import { type Command, TreeItem } from "vscode"; 2 | 3 | import { getIcons } from "@/lib/utils/icons"; 4 | 5 | export class CustomTreeItem extends TreeItem { 6 | iconPath = this.iconName ? getIcons(`${this.iconName}.svg`) : undefined; 7 | 8 | constructor( 9 | public readonly label: string, 10 | public readonly command: Command, 11 | public readonly iconName?: string, 12 | ) { 13 | super(label); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /resources/dark/cpu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/cpu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/id.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/id.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /syntaxes/config.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.squarecloud", 3 | "patterns": [ 4 | { 5 | "comment": "Key-Value Pair", 6 | "match": "^\\s*([A-Z_]+)\\s*(=)\\s*(.*)$", 7 | "captures": { 8 | "1": { 9 | "name": "variable.language.key.squarecloud" 10 | }, 11 | "2": { 12 | "name": "keyword.operator.assignment.squarecloud" 13 | }, 14 | "3": { 15 | "name": "string.value.squarecloud" 16 | } 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/structures/constant.ts: -------------------------------------------------------------------------------- 1 | export class Constant { 2 | public readonly value: `${U}.${T}`; 3 | 4 | /** 5 | * Constructs a new instance of the Constant class. 6 | * 7 | * @param name - The name of the constant. 8 | * @param id - The ID of the constant. 9 | */ 10 | constructor( 11 | public readonly name: T, 12 | public readonly id: U, 13 | ) { 14 | this.value = `${id}.${name}`; 15 | } 16 | 17 | toString() { 18 | return this.value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const ExtensionID = "squarecloud"; 2 | 3 | export const Config = { 4 | APIKey: "api-key", 5 | FavoritedApps: "favApps", 6 | }; 7 | 8 | export const AllowedExtensions = [ 9 | "js", 10 | "jsx", 11 | "mjsx", 12 | "cjsx", 13 | "mjs", 14 | "cjs", 15 | "ts", 16 | "tsx", 17 | "mtsx", 18 | "ctsx", 19 | "mts", 20 | "cts", 21 | "py", 22 | "cs", 23 | "csproj", 24 | "sln", 25 | "dll", 26 | "ex", 27 | "exs", 28 | "jar", 29 | "rs", 30 | "php", 31 | "go", 32 | "html", 33 | ]; 34 | -------------------------------------------------------------------------------- /src/treeviews/items/generic.ts: -------------------------------------------------------------------------------- 1 | import { TreeItem } from "vscode"; 2 | 3 | import { getIcons } from "@/lib/utils/icons"; 4 | 5 | export class GenericTreeItem extends TreeItem { 6 | iconPath = this.iconName ? getIcons(`${this.iconName}.svg`) : undefined; 7 | 8 | constructor( 9 | public readonly label: string, 10 | public readonly iconName?: string, 11 | public readonly description?: string, 12 | public readonly contextValue: string = "generic", 13 | ) { 14 | super(label); 15 | this.description = description; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /resources/dark/ram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/ram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/storage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/storage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/commands/copy-text.ts: -------------------------------------------------------------------------------- 1 | import { env, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import type { GenericTreeItem } from "@/treeviews/items/generic"; 5 | import { Command } from "@/structures/command"; 6 | 7 | export const copyText = new Command( 8 | "copyText", 9 | (_extension, treeItem: GenericTreeItem) => { 10 | if (!treeItem.description) { 11 | return; 12 | } 13 | 14 | env.clipboard.writeText(treeItem.description); 15 | window.showInformationMessage( 16 | t("copy.copiedText", { TYPE: treeItem.label }), 17 | ); 18 | }, 19 | ); 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | ci: 13 | name: CI 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v5 17 | - uses: pnpm/action-setup@v4 18 | with: 19 | run_install: false 20 | - uses: actions/setup-node@v5 21 | 22 | - name: Install Dependencies 23 | run: pnpm install 24 | 25 | - name: Lint 26 | run: pnpm lint 27 | 28 | - name: Build 29 | run: pnpm build -------------------------------------------------------------------------------- /src/config-file/parameters.ts: -------------------------------------------------------------------------------- 1 | import { AUTORESTART } from "./parameters/autorestart"; 2 | import { DESCRIPTION } from "./parameters/description"; 3 | import { DISPLAY_NAME } from "./parameters/display-name"; 4 | import { MAIN } from "./parameters/main"; 5 | import { MEMORY } from "./parameters/memory"; 6 | import { START } from "./parameters/start"; 7 | import { SUBDOMAIN } from "./parameters/subdomain"; 8 | import { VERSION } from "./parameters/version"; 9 | 10 | export const ConfigFileParameters = { 11 | MAIN, 12 | VERSION, 13 | AUTORESTART, 14 | SUBDOMAIN, 15 | DISPLAY_NAME, 16 | DESCRIPTION, 17 | START, 18 | MEMORY, 19 | }; 20 | -------------------------------------------------------------------------------- /src/structures/command.ts: -------------------------------------------------------------------------------- 1 | import type { SquareCloudExtension } from "@/managers/extension"; 2 | import { ExtensionID } from "@/lib/constants"; 3 | 4 | export type CommandExecute = ( 5 | extension: SquareCloudExtension, 6 | ...args: any[] 7 | ) => void; 8 | 9 | export class Command { 10 | /** 11 | * Constructs a new instance of the class. 12 | * 13 | * @param name - The name of the command. 14 | * @param execute - The function to execute when the command is triggered. 15 | */ 16 | constructor( 17 | public name: string, 18 | public execute: CommandExecute, 19 | ) { 20 | this.name = `${ExtensionID}.${this.name}`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /patches/adm-zip.patch: -------------------------------------------------------------------------------- 1 | diff --git a/adm-zip.js b/adm-zip.js 2 | index 31dddf2b39c3a0956102a25c984e02a1c3e3b83b..82ad4cd945c870ce92d306928078985a3b0d08a3 100644 3 | --- a/adm-zip.js 4 | +++ b/adm-zip.js 5 | @@ -485,7 +485,7 @@ module.exports = function (/**String*/ input, /** object */ options) { 6 | addLocalFolderAsync2: function (options, callback) { 7 | const self = this; 8 | options = typeof options === "object" ? options : { localPath: options }; 9 | - localPath = pth.resolve(fixPath(options.localPath)); 10 | + const localPath = pth.resolve(fixPath(options.localPath)); 11 | let { zipPath, filter, namefix } = options; 12 | 13 | if (filter instanceof RegExp) { 14 | -------------------------------------------------------------------------------- /resources/dark/online.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/online.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/types/config-file.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from "vscode"; 2 | 3 | import type { ConfigFileParameters } from "@/config-file/parameters"; 4 | import type { SquareCloudExtension } from "@/managers/extension"; 5 | 6 | export type ConfigFileKeys = Map; 7 | export type ConfigFileAllowedParams = keyof typeof ConfigFileParameters; 8 | export type ConfigFileParameter = { 9 | required: boolean; 10 | validation?: ( 11 | keys: ConfigFileKeys, 12 | value: string, 13 | line: number, 14 | diagnostics: vscode.Diagnostic[], 15 | document: vscode.TextDocument, 16 | extension: SquareCloudExtension, 17 | ) => any; 18 | autocomplete?: ( 19 | document: vscode.TextDocument, 20 | position: vscode.Position, 21 | ) => any; 22 | }; 23 | -------------------------------------------------------------------------------- /src/structures/application/command.ts: -------------------------------------------------------------------------------- 1 | import type { SquareCloudExtension } from "@/managers/extension"; 2 | import type { ApplicationTreeItem } from "@/treeviews/applications/item"; 3 | import { ExtensionID } from "@/lib/constants"; 4 | 5 | export type CommandExecute = ( 6 | extension: SquareCloudExtension, 7 | treeItem: ApplicationTreeItem, 8 | ...args: any[] 9 | ) => void; 10 | 11 | export class ApplicationCommand { 12 | /** 13 | * Constructs a new instance of the class. 14 | * 15 | * @param name - The name of the command. 16 | * @param execute - The function to execute when the command is triggered. 17 | */ 18 | constructor( 19 | public name: string, 20 | public execute: CommandExecute, 21 | ) { 22 | this.name = `${ExtensionID}.${this.name}`; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/config-file/parameters/start.ts: -------------------------------------------------------------------------------- 1 | import { t } from "vscode-ext-localisation"; 2 | 3 | import type { ConfigFileParameter } from "@/types/config-file"; 4 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 5 | 6 | export const START = { 7 | required: false, 8 | validation(_keys, value, line, diagnostics, document) { 9 | // Validate if exists some value on START 10 | if (!value) { 11 | diagnostics.push( 12 | createDiagnostic(document, line, t("configFile.error.missing.start")), 13 | ); 14 | } 15 | 16 | // Validate if the START exceeds 128 characters 17 | if (value.length > 128) { 18 | diagnostics.push( 19 | createDiagnostic(document, line, t("configFile.error.long.start")), 20 | ); 21 | } 22 | }, 23 | } satisfies ConfigFileParameter; 24 | -------------------------------------------------------------------------------- /resources/dark/squarecloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/dark/squareignore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/light/squarecloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/light/squareignore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | publish: 12 | name: Publish 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | registry: ["vsce", "ovsx"] 17 | steps: 18 | - uses: actions/checkout@v6 19 | 20 | - uses: pnpm/action-setup@v4 21 | with: 22 | run_install: false 23 | 24 | - uses: actions/setup-node@v6 25 | with: 26 | cache: "pnpm" 27 | 28 | - name: Install Dependencies 29 | run: pnpm install --frozen-lockfile 30 | 31 | - name: Publish 32 | env: 33 | VSCE_PAT: ${{ secrets.VSCODE_PERSONAL_TOKEN }} 34 | OVSX_PAT: ${{ secrets.ECLIPSE_PERSONAL_TOKEN }} 35 | run: pnpm run publish:${{ matrix.registry }} 36 | -------------------------------------------------------------------------------- /resources/dark/offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/offline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/commands/applications/actions/stop.ts: -------------------------------------------------------------------------------- 1 | import { ProgressLocation, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import { ApplicationCommand } from "@/structures/application/command"; 5 | 6 | export const stopEntry = new ApplicationCommand( 7 | "stopEntry", 8 | (extension, { application }) => { 9 | if (extension.api.paused) { 10 | return; 11 | } 12 | 13 | window.withProgress( 14 | { 15 | location: ProgressLocation.Notification, 16 | title: t("stop.loading"), 17 | }, 18 | async (progress) => { 19 | const app = await application.fetch(); 20 | 21 | await extension.api.pauseUntil(() => app.stop()); 22 | setTimeout(() => extension.api.refreshStatus(app.id), 7000); 23 | 24 | window.showInformationMessage(t("stop.loaded")); 25 | progress.report({ increment: 100 }); 26 | }, 27 | ); 28 | }, 29 | ); 30 | -------------------------------------------------------------------------------- /src/commands/applications/actions/start.ts: -------------------------------------------------------------------------------- 1 | import { ProgressLocation, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import { ApplicationCommand } from "@/structures/application/command"; 5 | 6 | export const startEntry = new ApplicationCommand( 7 | "startEntry", 8 | (extension, { application }) => { 9 | if (extension.api.paused) { 10 | return; 11 | } 12 | 13 | window.withProgress( 14 | { 15 | location: ProgressLocation.Notification, 16 | title: t("start.loading"), 17 | }, 18 | async (progress) => { 19 | const app = await application.fetch(); 20 | 21 | await extension.api.pauseUntil(() => app.start()); 22 | setTimeout(() => extension.api.refreshStatus(app.id), 7000); 23 | 24 | window.showInformationMessage(t("start.loaded")); 25 | progress.report({ increment: 100 }); 26 | }, 27 | ); 28 | }, 29 | ); 30 | -------------------------------------------------------------------------------- /src/commands/applications/actions/restart.ts: -------------------------------------------------------------------------------- 1 | import { ProgressLocation, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import { ApplicationCommand } from "@/structures/application/command"; 5 | 6 | export const restartEntry = new ApplicationCommand( 7 | "restartEntry", 8 | (extension, { application }) => { 9 | if (extension.api.paused) { 10 | return; 11 | } 12 | 13 | window.withProgress( 14 | { 15 | location: ProgressLocation.Notification, 16 | title: t("restart.loading"), 17 | }, 18 | async (progress) => { 19 | const app = await application.fetch(); 20 | 21 | await extension.api.pauseUntil(() => app.restart()); 22 | setTimeout(() => extension.api.refreshStatus(app.id), 7000); 23 | 24 | window.showInformationMessage(t("restart.loaded")); 25 | progress.report({ increment: 100 }); 26 | }, 27 | ); 28 | }, 29 | ); 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome", 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.biome": "explicit", 5 | "source.organizeImports.biome": "explicit", 6 | "source.organizeImports": "never" 7 | }, 8 | "editor.formatOnSave": true, 9 | 10 | "[css]": { 11 | "editor.defaultFormatter": "biomejs.biome" 12 | }, 13 | 14 | "[typescript]": { 15 | "editor.defaultFormatter": "biomejs.biome" 16 | }, 17 | 18 | "[typescriptreact]": { 19 | "editor.defaultFormatter": "biomejs.biome" 20 | }, 21 | 22 | "[json]": { 23 | "editor.defaultFormatter": "biomejs.biome" 24 | }, 25 | 26 | "[jsonc]": { 27 | "editor.defaultFormatter": "biomejs.biome" 28 | }, 29 | 30 | "json.schemas": [ 31 | { 32 | "fileMatch": ["biome.json"], 33 | "url": "./node_modules/@biomejs/biome/configuration_schema.json" 34 | } 35 | ], 36 | 37 | "typescript.tsdk": "node_modules/typescript/lib" 38 | } 39 | -------------------------------------------------------------------------------- /src/config-file/parameters/description.ts: -------------------------------------------------------------------------------- 1 | import { t } from "vscode-ext-localisation"; 2 | 3 | import type { ConfigFileParameter } from "@/types/config-file"; 4 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 5 | 6 | export const DESCRIPTION = { 7 | required: false, 8 | validation(_keys, value, line, diagnostics, document) { 9 | // Validate if exists some value on DESCRIPTION 10 | if (!value.trim()) { 11 | diagnostics.push( 12 | createDiagnostic( 13 | document, 14 | line, 15 | t("configFile.error.missing.description"), 16 | ), 17 | ); 18 | } 19 | 20 | // Validate if the DESCRIPTION exceeds 280 characters 21 | if (value.length > 280) { 22 | diagnostics.push( 23 | createDiagnostic( 24 | document, 25 | line, 26 | t("configFile.error.long.description"), 27 | ), 28 | ); 29 | } 30 | }, 31 | } satisfies ConfigFileParameter; 32 | -------------------------------------------------------------------------------- /src/config-file/parameters/display-name.ts: -------------------------------------------------------------------------------- 1 | import { t } from "vscode-ext-localisation"; 2 | 3 | import type { ConfigFileParameter } from "@/types/config-file"; 4 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 5 | 6 | export const DISPLAY_NAME = { 7 | required: true, 8 | validation(_keys, value, line, diagnostics, document) { 9 | // Validate if exists some value on DISPLAY_NAME 10 | if (!value.trim()) { 11 | diagnostics.push( 12 | createDiagnostic( 13 | document, 14 | line, 15 | t("configFile.error.missing.displayName"), 16 | ), 17 | ); 18 | } 19 | 20 | // Validate if the DISPLAY_NAME exceeds 32 characters 21 | if (value.length > 32) { 22 | diagnostics.push( 23 | createDiagnostic( 24 | document, 25 | line, 26 | t("configFile.error.long.displayName"), 27 | ), 28 | ); 29 | } 30 | }, 31 | } satisfies ConfigFileParameter; 32 | -------------------------------------------------------------------------------- /resources/squarecloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 15 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/managers/commands.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import type { Command } from "@/structures/command"; 4 | import { Logger } from "@/structures/logger"; 5 | 6 | import type { SquareCloudExtension } from "./extension"; 7 | import * as commands from "../commands"; 8 | 9 | export class CommandsManager { 10 | private readonly logger = new Logger("Square Cloud"); 11 | 12 | constructor(private readonly extension: SquareCloudExtension) { 13 | this.loadCommands(); 14 | } 15 | 16 | async loadCommands() { 17 | let commandCounter = 0; 18 | 19 | for (const command of Object.values(commands) as Command[]) { 20 | const disposable = vscode.commands.registerCommand( 21 | command.name, 22 | (...args) => command.execute(this.extension, ...args), 23 | ); 24 | 25 | this.extension.context.subscriptions.push(disposable); 26 | commandCounter++; 27 | } 28 | 29 | this.logger.log(`Loaded ${commandCounter} commands.`); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/dark/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /resources/light/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/commands/set-api-key.ts: -------------------------------------------------------------------------------- 1 | import { ProgressLocation, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import { Command } from "@/structures/command"; 5 | 6 | export const setApiKey = new Command("setApiKey", async (extension) => { 7 | const apiKey = await window.showInputBox({ 8 | title: t("setApiKey.apiKey"), 9 | placeHolder: t("generic.paste"), 10 | ignoreFocusOut: true, 11 | password: true, 12 | }); 13 | 14 | if (!apiKey) { 15 | return; 16 | } 17 | 18 | const isKeyValid = await window.withProgress( 19 | { 20 | location: ProgressLocation.Notification, 21 | title: t("setApiKey.testing"), 22 | }, 23 | () => extension.config.apiKey.test(apiKey), 24 | ); 25 | 26 | if (!isKeyValid) { 27 | window.showInformationMessage(t("setApiKey.invalid")); 28 | return; 29 | } 30 | 31 | window.showInformationMessage(t("setApiKey.success")); 32 | 33 | await extension.config.apiKey.set(apiKey); 34 | await extension.api.refresh(true); 35 | }); 36 | -------------------------------------------------------------------------------- /src/config-file/parameters/version.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import type { ConfigFileParameter } from "@/types/config-file"; 5 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 6 | 7 | export const VERSION = { 8 | required: true, 9 | validation(_keys, value, line, diagnostics, document) { 10 | if (value !== "recommended" && value !== "latest") { 11 | diagnostics.push( 12 | createDiagnostic(document, line, t("configFile.error.invalid.version")), 13 | ); 14 | } 15 | }, 16 | autocomplete(document, position) { 17 | return ["recommended", "latest"].map((value, i) => { 18 | const item = new vscode.CompletionItem( 19 | value, 20 | vscode.CompletionItemKind.EnumMember, 21 | ); 22 | 23 | item.range = document.getWordRangeAtPosition(position, /(?<=VERSION=).*/); 24 | item.sortText = String.fromCharCode(97 + i); 25 | 26 | return item; 27 | }); 28 | }, 29 | } satisfies ConfigFileParameter; 30 | -------------------------------------------------------------------------------- /src/treeviews/base.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Event, 3 | EventEmitter, 4 | type ProviderResult, 5 | type TreeDataProvider, 6 | type TreeItem, 7 | } from "vscode"; 8 | 9 | export class BaseTreeViewProvider 10 | implements TreeDataProvider 11 | { 12 | protected _onDidChangeTreeData = new EventEmitter< 13 | // biome-ignore lint/suspicious/noConfusingVoidType: This is a valid type for the event emitter 14 | TreeItemType | undefined | void 15 | >(); 16 | 17 | public readonly onDidChangeTreeData: Event< 18 | // biome-ignore lint/suspicious/noConfusingVoidType: This is a valid type for the event emitter 19 | TreeItemType | TreeItemType[] | null | undefined | void 20 | > = this._onDidChangeTreeData.event; 21 | 22 | refresh(): void { 23 | this._onDidChangeTreeData.fire(); 24 | } 25 | 26 | getChildren(): ProviderResult { 27 | return []; 28 | } 29 | 30 | getTreeItem(element: TreeItemType): TreeItemType { 31 | return element; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/managers/treeviews.ts: -------------------------------------------------------------------------------- 1 | import { window } from "vscode"; 2 | 3 | import { ApplicationsTreeViewProvider } from "@/treeviews/applications/provider"; 4 | import { UserTreeViewProvider } from "@/treeviews/user/provider"; 5 | 6 | import type { SquareCloudExtension } from "./extension"; 7 | 8 | type TreeViewsKey = keyof TreeViewsManager["views"]; 9 | 10 | export class TreeViewsManager { 11 | public views = { 12 | applications: new ApplicationsTreeViewProvider(this.extension), 13 | user: new UserTreeViewProvider(this.extension), 14 | }; 15 | 16 | constructor(private readonly extension: SquareCloudExtension) { 17 | window.registerTreeDataProvider("apps-view", this.views.applications); 18 | window.registerTreeDataProvider("user-view", this.views.user); 19 | } 20 | 21 | refreshViews(...views: TreeViewsKey[]) { 22 | for (const view of views) { 23 | this.views[view].refresh(); 24 | } 25 | } 26 | 27 | refreshAll() { 28 | for (const view of Object.values(this.views)) { 29 | view.refresh(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "watch", 6 | "dependsOn": ["npm: watch:tsc", "npm: watch:esbuild"], 7 | "presentation": { 8 | "reveal": "never" 9 | }, 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | }, 15 | { 16 | "type": "npm", 17 | "script": "watch:esbuild", 18 | "group": "build", 19 | "problemMatcher": "$esbuild-watch", 20 | "isBackground": true, 21 | "label": "npm: watch:esbuild", 22 | "presentation": { 23 | "group": "watch", 24 | "reveal": "never", 25 | "close": true 26 | } 27 | }, 28 | { 29 | "type": "npm", 30 | "script": "watch:tsc", 31 | "group": "build", 32 | "problemMatcher": "$tsc-watch", 33 | "isBackground": true, 34 | "label": "npm: watch:tsc", 35 | "presentation": { 36 | "group": "watch", 37 | "reveal": "never", 38 | "close": true 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/config-file/parameters/autorestart.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import type { ConfigFileParameter } from "@/types/config-file"; 5 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 6 | 7 | export const AUTORESTART = { 8 | required: false, 9 | validation(_keys, value, line, diagnostics, document) { 10 | if (value !== "true" && value !== "false") { 11 | diagnostics.push( 12 | createDiagnostic( 13 | document, 14 | line, 15 | t("configFile.error.invalid.autoRestart"), 16 | ), 17 | ); 18 | } 19 | }, 20 | autocomplete(document, position) { 21 | return ["true", "false"].map((value) => { 22 | const item = new vscode.CompletionItem( 23 | value, 24 | vscode.CompletionItemKind.EnumMember, 25 | ); 26 | 27 | item.range = document.getWordRangeAtPosition( 28 | position, 29 | /(?<=AUTORESTART=).*/, 30 | ); 31 | 32 | return item; 33 | }); 34 | }, 35 | } satisfies ConfigFileParameter; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Square Cloud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /resources/dark/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /resources/light/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/config-file/parameters/subdomain.ts: -------------------------------------------------------------------------------- 1 | import { t } from "vscode-ext-localisation"; 2 | 3 | import type { ConfigFileParameter } from "@/types/config-file"; 4 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 5 | 6 | export const SUBDOMAIN = { 7 | required: false, 8 | validation(_keys, value, line, diagnostics, document) { 9 | // Validate if exists some value on SUBDOMAIN 10 | if (!value) { 11 | diagnostics.push( 12 | createDiagnostic( 13 | document, 14 | line, 15 | t("configFile.error.missing.subdomain"), 16 | ), 17 | ); 18 | } 19 | 20 | // Validate if the SUBDOMAIN exceeds 62 characters 21 | if (value.length > 62) { 22 | diagnostics.push( 23 | createDiagnostic(document, line, t("configFile.error.long.subdomain")), 24 | ); 25 | } 26 | 27 | // Validate if the SUBDOMAIN contains invalid characters 28 | if (!/^[a-zA-Z0-9-]+$/.test(value)) { 29 | diagnostics.push( 30 | createDiagnostic( 31 | document, 32 | line, 33 | t("configFile.error.invalid.subdomain"), 34 | ), 35 | ); 36 | } 37 | }, 38 | } satisfies ConfigFileParameter; 39 | -------------------------------------------------------------------------------- /src/lib/api-key/index.ts: -------------------------------------------------------------------------------- 1 | import type { SecretStorage } from "vscode"; 2 | import { SquareCloudAPI } from "@squarecloud/api"; 3 | 4 | import { Config, ExtensionID } from "../constants"; 5 | import { ApiKeyStore } from "./store"; 6 | 7 | export class ApiKey { 8 | private readonly store = new ApiKeyStore(); 9 | 10 | constructor(private readonly secrets: SecretStorage) {} 11 | 12 | async get() { 13 | let apiKey = await this.store.get(); 14 | 15 | if (!apiKey) { 16 | const secretsApiKey = await this.secrets.get( 17 | `${ExtensionID}.${Config.APIKey}`, 18 | ); 19 | if (secretsApiKey) { 20 | await this.set(secretsApiKey); 21 | apiKey = secretsApiKey; 22 | } 23 | } 24 | 25 | return apiKey; 26 | } 27 | 28 | async set(value: string | undefined) { 29 | if (!value) return void (await this.store.delete()); 30 | await this.store.store(value); 31 | } 32 | 33 | async test(apiKey?: string) { 34 | apiKey = apiKey || (await this.get()); 35 | 36 | if (!apiKey) { 37 | await this.set(undefined); 38 | return; 39 | } 40 | 41 | const api = new SquareCloudAPI(apiKey); 42 | const user = await api.user.get().catch(() => null); 43 | 44 | if (!user) { 45 | await this.set(undefined); 46 | return; 47 | } 48 | 49 | return apiKey; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/commands/applications/tools/snapshot.ts: -------------------------------------------------------------------------------- 1 | import { writeFile } from "node:fs/promises"; 2 | import { join } from "node:path"; 3 | import { ProgressLocation, window } from "vscode"; 4 | import { t } from "vscode-ext-localisation"; 5 | 6 | import { ApplicationCommand } from "@/structures/application/command"; 7 | 8 | export const snapshotEntry = new ApplicationCommand( 9 | "snapshotEntry", 10 | async (extension, { application }) => { 11 | if (extension.api.paused) { 12 | return; 13 | } 14 | 15 | const dialog = await window.showOpenDialog({ 16 | canSelectFolders: true, 17 | openLabel: t("snapshot.save"), 18 | title: `Snapshot - ${application.name}`, 19 | }); 20 | 21 | if (!dialog) { 22 | return; 23 | } 24 | 25 | const [{ fsPath }] = dialog; 26 | 27 | window.withProgress( 28 | { 29 | location: ProgressLocation.Notification, 30 | title: t("snapshot.loading"), 31 | }, 32 | async (progress) => { 33 | const buffer = await extension.api.pauseUntil(() => 34 | application.snapshots.download(), 35 | ); 36 | 37 | await writeFile( 38 | join(fsPath, `snapshot-${application.id}.zip`), 39 | new Uint8Array(buffer), 40 | ); 41 | 42 | window.showInformationMessage(t("snapshot.loaded")); 43 | progress.report({ increment: 100 }); 44 | }, 45 | ); 46 | }, 47 | ); 48 | -------------------------------------------------------------------------------- /resources/dark/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /resources/light/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/treeviews/applications/item.ts: -------------------------------------------------------------------------------- 1 | import type { BaseApplication } from "@squarecloud/api"; 2 | import { TreeItem, TreeItemCollapsibleState } from "vscode"; 3 | 4 | import type { SquareCloudExtension } from "@/managers/extension"; 5 | import { getIcons } from "@/lib/utils/icons"; 6 | 7 | import type { CustomTreeItem } from "../items/custom"; 8 | import type { GenericTreeItem } from "../items/generic"; 9 | 10 | export type SquareTreeItem = 11 | | ApplicationTreeItem 12 | | CustomTreeItem 13 | | GenericTreeItem; 14 | 15 | export class ApplicationTreeItem extends TreeItem { 16 | tooltip = this.application.id; 17 | 18 | collapsibleState = this.status?.running 19 | ? TreeItemCollapsibleState.Collapsed 20 | : TreeItemCollapsibleState.None; 21 | 22 | iconPath = getIcons( 23 | this.status 24 | ? this.status.running 25 | ? "online.svg" 26 | : "offline.svg" 27 | : "loading.svg", 28 | ); 29 | 30 | contextValue = this.favorited ? "application-fav" : "application"; 31 | 32 | constructor( 33 | private readonly extension: SquareCloudExtension, 34 | public readonly application: BaseApplication, 35 | ) { 36 | super(application.name); 37 | } 38 | 39 | get favorited() { 40 | return this.extension.store.actions.isFavorited(this.application.id); 41 | } 42 | 43 | get status() { 44 | return this.extension.store.actions.getStatus(this.application.id); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/commands/applications/tools/logs.ts: -------------------------------------------------------------------------------- 1 | import { type OutputChannel, ProgressLocation, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import { ApplicationCommand } from "@/structures/application/command"; 5 | 6 | const outputChannels = new Map(); 7 | 8 | export const logsEntry = new ApplicationCommand( 9 | "logsEntry", 10 | (extension, { application }) => { 11 | if (extension.api.paused) { 12 | return; 13 | } 14 | 15 | window.withProgress( 16 | { 17 | location: ProgressLocation.Notification, 18 | title: t("logs.loading"), 19 | }, 20 | async (progress) => { 21 | const logs = await extension.api.pauseUntil(() => 22 | application.getLogs().catch(() => null), 23 | ); 24 | 25 | progress.report({ increment: 100, message: ` ${t("generic.done")}` }); 26 | 27 | if (!logs) { 28 | window.showErrorMessage(t("logs.null")); 29 | return; 30 | } 31 | 32 | const outputChannel = 33 | outputChannels.get(application.id) ?? 34 | window.createOutputChannel( 35 | `Square Cloud (${application.name})`, 36 | "ansi", 37 | ); 38 | outputChannels.set(application.id, outputChannel); 39 | 40 | outputChannel.clear(); 41 | outputChannel.append(logs); 42 | return outputChannel.show(); 43 | }, 44 | ); 45 | }, 46 | ); 47 | -------------------------------------------------------------------------------- /src/structures/application/status.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ApplicationStatus as BaseApplicationStatus, 3 | SimpleApplicationStatus as BaseSimpleApplicationStatus, 4 | } from "@squarecloud/api"; 5 | import type { ApplicationStatusUsage } from "@squarecloud/api/lib/types/application"; 6 | 7 | import type { If } from "@/types/helpers"; 8 | 9 | export class ApplicationStatus { 10 | public applicationId: string; 11 | public usage: If< 12 | Full, 13 | ApplicationStatusUsage, 14 | Pick 15 | >; 16 | public running: boolean; 17 | public status?: If; 18 | public uptimeTimestamp?: number; 19 | public uptime?: Date; 20 | 21 | /** 22 | * Constructs a new instance of the class. 23 | * 24 | * @param baseStatus - The base status object. 25 | */ 26 | constructor( 27 | private readonly baseStatus: If< 28 | Full, 29 | BaseApplicationStatus, 30 | BaseSimpleApplicationStatus 31 | >, 32 | ) { 33 | this.applicationId = baseStatus.applicationId; 34 | this.running = baseStatus.running; 35 | this.usage = baseStatus.usage as ApplicationStatusUsage; 36 | 37 | if ("status" in baseStatus) { 38 | this.status = baseStatus.status; 39 | this.uptimeTimestamp = baseStatus.uptimeTimestamp; 40 | this.uptime = baseStatus.uptime; 41 | } 42 | } 43 | 44 | isFull(): this is ApplicationStatus { 45 | return "status" in this.baseStatus; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/api-key/store.ts: -------------------------------------------------------------------------------- 1 | import { mkdir, readFile, writeFile } from "node:fs/promises"; 2 | import path from "node:path"; 3 | import XDGAppPaths from "xdg-app-paths"; 4 | 5 | import { ExtensionID } from "../constants"; 6 | 7 | const property = "api-key"; 8 | 9 | export class ApiKeyStore { 10 | private apiKey?: string; 11 | private authFilePath = path.join( 12 | XDGAppPaths({ name: ExtensionID }).config(), 13 | "auth.json", 14 | ); 15 | 16 | async reload(): Promise { 17 | try { 18 | const content = await readFile(this.authFilePath, "utf-8"); 19 | const data = JSON.parse(content); 20 | this.apiKey = (data[property] as string) || undefined; 21 | } catch { 22 | // Fallback if file not exists or invalid JSON 23 | this.apiKey = undefined; 24 | } 25 | } 26 | 27 | async store(apiKey: string): Promise { 28 | await this.reload(); 29 | this.apiKey = apiKey; 30 | await this.write(apiKey); 31 | } 32 | 33 | async delete(): Promise { 34 | await this.reload(); 35 | this.apiKey = undefined; 36 | await this.write(undefined); 37 | } 38 | 39 | async get(): Promise { 40 | if (this.apiKey === undefined) { 41 | await this.reload(); 42 | } 43 | return this.apiKey; 44 | } 45 | 46 | private async write(apiKey: string | undefined): Promise { 47 | await mkdir(path.dirname(this.authFilePath), { recursive: true }); 48 | await writeFile( 49 | this.authFilePath, 50 | JSON.stringify({ [property]: apiKey }, null, 2), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contributes.disabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "menus": { 3 | "commandPalette": [ 4 | { 5 | "command": "squarecloud.statusBarItem", 6 | "when": "false" 7 | }, 8 | { 9 | "command": "squarecloud.setWorkspaceApp", 10 | "when": "false" 11 | }, 12 | { 13 | "command": "squarecloud.commitWorkspace", 14 | "when": "false" 15 | }, 16 | { 17 | "command": "squarecloud.uploadWorkspace", 18 | "when": "false" 19 | }, 20 | { 21 | "command": "squarecloud.createConfig", 22 | "when": "workspaceFolderCount == 1" 23 | } 24 | ], 25 | "editor/context": [ 26 | { 27 | "command": "squarecloud.createConfig", 28 | "when": "resourceFilename == squarecloud.app || resourceFilename == squarecloud.config" 29 | } 30 | ], 31 | "explorer/context": [ 32 | { 33 | "command": "squarecloud.createConfig", 34 | "when": "true" 35 | } 36 | ] 37 | }, 38 | "commands": [ 39 | { 40 | "command": "squarecloud.createConfig", 41 | "title": "%command.createConfig%" 42 | }, 43 | { 44 | "command": "squarecloud.statusBarItem", 45 | "title": "Status Bar Item" 46 | }, 47 | { 48 | "command": "squarecloud.setWorkspaceApp", 49 | "title": "Set Workspace App" 50 | }, 51 | { 52 | "command": "squarecloud.commitWorkspace", 53 | "title": "Commit Workspace" 54 | }, 55 | { 56 | "command": "squarecloud.uploadWorkspace", 57 | "title": "Upload Workspace" 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /esbuild.js: -------------------------------------------------------------------------------- 1 | const esbuild = require("esbuild"); 2 | 3 | const production = process.argv.includes("--production"); 4 | const watch = process.argv.includes("--watch"); 5 | 6 | async function main() { 7 | const ctx = await esbuild.context({ 8 | entryPoints: ["src/extension.ts"], 9 | bundle: true, 10 | format: "cjs", 11 | minify: production, 12 | sourcemap: !production, 13 | sourcesContent: false, 14 | platform: "node", 15 | outfile: "dist/extension.js", 16 | external: ["vscode"], 17 | logLevel: "warning", 18 | alias: { 19 | "@": "./src", 20 | }, 21 | plugins: [ 22 | /* add to the end of plugins array */ 23 | esbuildProblemMatcherPlugin, 24 | ], 25 | }); 26 | if (watch) { 27 | await ctx.watch(); 28 | } else { 29 | await ctx.rebuild(); 30 | await ctx.dispose(); 31 | } 32 | } 33 | 34 | /** 35 | * @type {import('esbuild').Plugin} 36 | */ 37 | const esbuildProblemMatcherPlugin = { 38 | name: "esbuild-problem-matcher", 39 | 40 | setup(build) { 41 | build.onStart(() => { 42 | console.log("[watch] build started"); 43 | }); 44 | build.onEnd((result) => { 45 | for (const { text, location } of result.errors) { 46 | console.error(`✘ [ERROR] ${text}`); 47 | if (location == null) return; 48 | console.error( 49 | ` ${location.file}:${location.line}:${location.column}:`, 50 | ); 51 | } 52 | console.log("[watch] build finished"); 53 | }); 54 | }, 55 | }; 56 | 57 | main().catch((e) => { 58 | console.error(e); 59 | process.exit(1); 60 | }); 61 | -------------------------------------------------------------------------------- /src/commands/applications/tools/delete.ts: -------------------------------------------------------------------------------- 1 | import { env, ProgressLocation, Uri, window } from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import { ApplicationCommand } from "@/structures/application/command"; 5 | 6 | export const deleteEntry = new ApplicationCommand( 7 | "deleteEntry", 8 | async (extension, { application }) => { 9 | if (extension.api.paused) { 10 | return; 11 | } 12 | 13 | const confirmDelete = await window.showInputBox({ 14 | placeHolder: application.name, 15 | title: t("delete.confirm"), 16 | }); 17 | 18 | if (confirmDelete !== application.name) { 19 | window.showInformationMessage(t("delete.cancelled")); 20 | return; 21 | } 22 | 23 | window.withProgress( 24 | { 25 | location: ProgressLocation.Notification, 26 | title: t("delete.loading"), 27 | }, 28 | async (progress) => { 29 | const snapshotUrl = await extension.api.pauseUntil(async () => { 30 | const snapshot = await application.snapshots.create(); 31 | await application.delete(); 32 | return snapshot.url; 33 | }); 34 | 35 | setTimeout(() => extension.api.refreshStatus(application.id), 7000); 36 | 37 | window 38 | .showInformationMessage(t("delete.loaded"), "Download Snapshot") 39 | .then((value) => { 40 | if (value === "Download Snapshot") { 41 | env.openExternal(Uri.parse(snapshotUrl)); 42 | } 43 | }); 44 | 45 | progress.report({ increment: 100 }); 46 | }, 47 | ); 48 | }, 49 | ); 50 | -------------------------------------------------------------------------------- /src/lib/utils/format.ts: -------------------------------------------------------------------------------- 1 | export function formatMB(ram: number, hideMb?: boolean) { 2 | const formatted = Intl.NumberFormat("pt-BR", { 3 | style: "unit", 4 | unit: "megabyte", 5 | unitDisplay: "short", 6 | }).format(ram); 7 | 8 | return formatted.replace(" MB", hideMb ? "" : "MB"); 9 | } 10 | 11 | export function formatTime(timestamp: number): string { 12 | if (timestamp <= 0) { 13 | return "0s"; 14 | } 15 | 16 | const SECOND = 1000; 17 | const MINUTE = 60 * SECOND; 18 | const HOUR = 60 * MINUTE; 19 | const DAY = 24 * HOUR; 20 | const WEEK = 7 * DAY; 21 | const MONTH = 30 * DAY; 22 | const YEAR = 365 * DAY; 23 | 24 | let remaining = timestamp; 25 | const years = Math.floor(remaining / YEAR); 26 | remaining %= YEAR; 27 | 28 | const months = Math.floor(remaining / MONTH); 29 | remaining %= MONTH; 30 | 31 | const weeks = Math.floor(remaining / WEEK); 32 | remaining %= WEEK; 33 | 34 | const days = Math.floor(remaining / DAY); 35 | remaining %= DAY; 36 | 37 | const hours = Math.floor(remaining / HOUR); 38 | remaining %= HOUR; 39 | 40 | const minutes = Math.floor(remaining / MINUTE); 41 | remaining %= MINUTE; 42 | 43 | const seconds = Math.floor(remaining / SECOND); 44 | 45 | const parts: string[] = []; 46 | 47 | if (years > 0) parts.push(`${years}y`); 48 | if (months > 0) parts.push(`${months}mo`); 49 | if (weeks > 0) parts.push(`${weeks}w`); 50 | if (days > 0) parts.push(`${days}d`); 51 | if (hours > 0) parts.push(`${hours}h`); 52 | if (minutes > 0) parts.push(`${minutes}m`); 53 | if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`); 54 | 55 | return parts.slice(0, 2).join(" "); 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Square Cloud Banner 3 |
4 | 5 |

Square Cloud

6 | 7 |

A VSCode extension for managing your Square Cloud applications.

8 | 9 | --- 10 | 11 | To use Square Cloud extension first you will need an **API key** for **Square Cloud API**. 12 | - You can get your API key at [**Square Cloud Dashboard**](https://squarecloud.app/account/security). 13 | 14 | --- 15 | 16 | ## Manage your Applications! 17 | 18 | ### Start, restart and stop 19 | 20 | ![Starting application](https://public-blob.squarecloud.dev/702529018410303640/start_m9kyecqo-457b.gif) 21 | 22 | ### Commit files or folders 23 | 24 | ![Committing](https://public-blob.squarecloud.dev/702529018410303640/commit_m9kyej7j-c887.gif) 25 | 26 | ### Download snapshots 27 | 28 | ![Downloading](https://public-blob.squarecloud.dev/702529018410303640/backup_m9kyepa2-f7a5.gif) 29 | 30 | ### Configuration file Intellisense 31 | 32 | ![Autocomplete](https://public-blob.squarecloud.dev/702529018410303640/config_m9kyew9j-e05c.gif) 33 | 34 | ### Check status and logs 35 | 36 | _\* Accessible status icons._ 37 | 38 | ![Status and Logs](https://public-blob.squarecloud.dev/702529018410303640/logs_m9kyf7ec-4c2f.gif) 39 | 40 | --- 41 | 42 | ### Extension settings 43 | 44 | - `squarecloud.favApps`: Favorited applications. 45 | 46 | --- 47 | 48 | ## Contributing 49 | 50 | We are open to contributions and suggestions at our [Github Repository](https://github.com/squarecloudofc/vscode-extension). 51 | 52 | We are anxious to hear your ideas and work together to make this project even better! 🥳 53 | -------------------------------------------------------------------------------- /src/treeviews/user/provider.ts: -------------------------------------------------------------------------------- 1 | import { t } from "vscode-ext-localisation"; 2 | 3 | import type { SquareCloudExtension } from "@/managers/extension"; 4 | import { capitalize } from "@/lib/utils/capitalize"; 5 | import { formatMB } from "@/lib/utils/format"; 6 | 7 | import { BaseTreeViewProvider } from "../base"; 8 | import { GenericTreeItem } from "../items/generic"; 9 | 10 | export class UserTreeViewProvider extends BaseTreeViewProvider { 11 | constructor(private readonly extension: SquareCloudExtension) { 12 | super(); 13 | } 14 | 15 | async getChildren(): Promise { 16 | const { user } = this.extension.store.value; 17 | 18 | if (!user) { 19 | const apiKey = await this.extension.config.apiKey.get(); 20 | 21 | if (!apiKey) { 22 | return []; 23 | } 24 | 25 | return [ 26 | new GenericTreeItem( 27 | t("generic.loading"), 28 | "loading", 29 | undefined, 30 | "loading", 31 | ), 32 | ]; 33 | } 34 | 35 | const expires = user.plan.expiresIn?.toLocaleDateString("pt-BR", { 36 | dateStyle: "short", 37 | }); 38 | 39 | const treeItemsData: [string, string, string][] = [ 40 | ["Username", "username", user.name], 41 | ["ID", "id", user.id], 42 | ["E-mail", "email", user.email], 43 | [capitalize(user.plan.name).replace("-", " "), "plan", expires || "∞"], 44 | [ 45 | "RAM", 46 | "ram", 47 | `${formatMB(user.plan.memory.used, true)}/${formatMB(user.plan.memory.limit)}`, 48 | ], 49 | ]; 50 | 51 | return treeItemsData.map( 52 | (treeItemData) => new GenericTreeItem(...treeItemData), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "vcs": { 3 | "enabled": true, 4 | "clientKind": "git", 5 | "useIgnoreFile": true, 6 | "defaultBranch": "main" 7 | }, 8 | 9 | "linter": { 10 | "enabled": true, 11 | "rules": { 12 | "recommended": true, 13 | "suspicious": { "noExplicitAny": "off" }, 14 | "style": { "noParameterAssign": "off" }, 15 | "correctness": { 16 | "noUnusedImports": { "level": "warn", "fix": "safe" }, 17 | "noUnusedVariables": { "level": "warn", "fix": "none" }, 18 | "noUnusedFunctionParameters": "warn" 19 | } 20 | } 21 | }, 22 | 23 | "formatter": { 24 | "enabled": true, 25 | "formatWithErrors": false, 26 | "indentStyle": "space", 27 | "indentWidth": 2, 28 | "lineEnding": "lf", 29 | "lineWidth": 80, 30 | "attributePosition": "auto" 31 | }, 32 | 33 | "assist": { 34 | "actions": { 35 | "source": { 36 | "organizeImports": { 37 | "level": "on", 38 | "options": { 39 | "groups": [ 40 | { 41 | "type": true, 42 | "source": [ 43 | ":NODE:", 44 | ":URL:", 45 | ":PACKAGE_WITH_PROTOCOL:", 46 | ":PACKAGE:" 47 | ] 48 | }, 49 | [":NODE:", ":URL:", ":PACKAGE_WITH_PROTOCOL:", ":PACKAGE:"], 50 | ":BLANK_LINE:", 51 | { "type": true, "source": "@/**" }, 52 | "@/**", 53 | ":BLANK_LINE:", 54 | { "type": true, "source": ":PATH:" }, 55 | ":PATH:" 56 | ] 57 | } 58 | } 59 | } 60 | } 61 | }, 62 | 63 | "json": { 64 | "parser": { "allowComments": true } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/providers/config-file-action.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | /** 4 | * This provider create the fast fix suggestions on errors 5 | */ 6 | export class ConfigFileActionProvider implements vscode.CodeActionProvider { 7 | static providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; 8 | 9 | provideCodeActions( 10 | document: vscode.TextDocument, 11 | range: vscode.Range, 12 | ): vscode.CodeAction[] | undefined { 13 | const line = document.lineAt(range.start.line).text.trim(); 14 | 15 | if (line.startsWith("AUTORESTART=")) { 16 | return this.getAutoRestartFixes(document, range); 17 | } 18 | 19 | if (line.startsWith("VERSION=")) { 20 | return this.getVersionFixes(document, range); 21 | } 22 | 23 | return undefined; 24 | } 25 | 26 | private getAutoRestartFixes( 27 | document: vscode.TextDocument, 28 | range: vscode.Range, 29 | ): vscode.CodeAction[] { 30 | const fixes: vscode.CodeAction[] = []; 31 | 32 | for (const value of ["true", "false"]) { 33 | const fix = new vscode.CodeAction( 34 | `Set AUTORESTART=${value}`, 35 | vscode.CodeActionKind.QuickFix, 36 | ); 37 | fix.edit = new vscode.WorkspaceEdit(); 38 | fix.edit.replace(document.uri, range, `AUTORESTART=${value}`); 39 | fixes.push(fix); 40 | } 41 | 42 | return fixes; 43 | } 44 | 45 | private getVersionFixes( 46 | document: vscode.TextDocument, 47 | range: vscode.Range, 48 | ): vscode.CodeAction[] { 49 | const fixes: vscode.CodeAction[] = []; 50 | 51 | for (const value of ["recommended", "latest"]) { 52 | const fix = new vscode.CodeAction( 53 | `Set ${value} as the version`, 54 | vscode.CodeActionKind.QuickFix, 55 | ); 56 | fix.edit = new vscode.WorkspaceEdit(); 57 | fix.edit.replace(document.uri, range, `VERSION=${value}`); 58 | fixes.push(fix); 59 | } 60 | 61 | return fixes; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/managers/extension.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext } from "vscode"; 2 | import { getVscodeLang, loadTranslations } from "vscode-ext-localisation"; 3 | 4 | import { Config } from "@/lib/constants"; 5 | import { $extensionStore } from "@/lib/store"; 6 | import { Logger } from "@/structures/logger"; 7 | 8 | import { APIManager } from "./api"; 9 | import { CommandsManager } from "./commands"; 10 | import { ConfigManager } from "./config"; 11 | import { ConfigFileManager } from "./config-file"; 12 | import { TreeViewsManager } from "./treeviews"; 13 | 14 | export class SquareCloudExtension { 15 | private readonly logger = new Logger("Square Cloud"); 16 | 17 | public readonly config = new ConfigManager(this.context.secrets); 18 | public readonly configFile = new ConfigFileManager(this); 19 | public readonly treeViews = new TreeViewsManager(this); 20 | public readonly commands = new CommandsManager(this); 21 | public readonly api = new APIManager(this); 22 | 23 | public readonly store = $extensionStore; 24 | 25 | constructor(public readonly context: ExtensionContext) { 26 | this.logger.log("Initializing extension..."); 27 | 28 | this.loadLanguage(); 29 | this.initializeStores(); 30 | 31 | this.logger.log("Extension is ready!"); 32 | } 33 | 34 | loadLanguage() { 35 | loadTranslations( 36 | getVscodeLang(process.env.VSCODE_NLS_CONFIG), 37 | this.context.extensionPath, 38 | ); 39 | 40 | this.logger.log("Language loaded!"); 41 | } 42 | 43 | initializeStores() { 44 | const favoritedApps = this.config.root.get( 45 | Config.FavoritedApps, 46 | [], 47 | ); 48 | 49 | this.store.actions.setFavorited(favoritedApps); 50 | 51 | this.store.subscribe((state) => { 52 | this.config.root.update( 53 | Config.FavoritedApps, 54 | Array.from(state.favorited), 55 | true, 56 | ); 57 | this.treeViews.refreshAll(); 58 | }); 59 | 60 | this.logger.log("Stores initialized!"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/providers/config-file-completion.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import type { ConfigFileAllowedParams } from "@/types/config-file"; 4 | import { ConfigFileParameters } from "@/config-file/parameters"; 5 | 6 | /** 7 | * This provider is used to provide completion items for the config file. 8 | */ 9 | export const ConfigCompletionProvider: vscode.CompletionItemProvider = { 10 | provideCompletionItems(document, position) { 11 | const line = document.lineAt(position).text.trim(); 12 | const isKey = !line.includes("="); 13 | const existingKeys = new Set( 14 | document 15 | .getText() 16 | .split(/\r?\n/g) 17 | .map((line) => line.split("=")[0].trim()), 18 | ); 19 | 20 | if (isKey) { 21 | return Object.keys(ConfigFileParameters) 22 | .filter((key) => !existingKeys.has(key)) 23 | .map((key) => { 24 | const { required } = 25 | ConfigFileParameters[key as ConfigFileAllowedParams]; 26 | const item = new vscode.CompletionItem( 27 | key, 28 | vscode.CompletionItemKind.Property, 29 | ); 30 | item.insertText = `${key}=`; 31 | item.command = { 32 | command: "editor.action.triggerSuggest", 33 | title: "Trigger Suggest", 34 | }; 35 | item.sortText = required ? "a" : "z"; 36 | return item; 37 | }); 38 | } 39 | 40 | if (line.startsWith("MAIN=")) { 41 | return ConfigFileParameters.MAIN.autocomplete(document, position); 42 | } 43 | 44 | if (line.startsWith("VERSION=")) { 45 | return ConfigFileParameters.VERSION.autocomplete(document, position); 46 | } 47 | 48 | if (line.startsWith("AUTORESTART=")) { 49 | return ConfigFileParameters.AUTORESTART.autocomplete(document, position); 50 | } 51 | 52 | if (line.startsWith("MEMORY=")) { 53 | return ConfigFileParameters.MEMORY.autocomplete(document, position); 54 | } 55 | 56 | return undefined; 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/providers/config-file-validation.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import type { SquareCloudExtension } from "@/managers/extension"; 5 | import type { 6 | ConfigFileAllowedParams, 7 | ConfigFileKeys, 8 | } from "@/types/config-file"; 9 | import { ConfigFileParameters } from "@/config-file/parameters"; 10 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 11 | 12 | /** 13 | * This function validates the config file and sets the diagnostics for the document. 14 | */ 15 | export function validateConfigFile( 16 | extension: SquareCloudExtension, 17 | document: vscode.TextDocument, 18 | diagnosticCollection: vscode.DiagnosticCollection, 19 | ): void { 20 | const diagnostics: vscode.Diagnostic[] = []; 21 | const lines = document.getText().split(/\r?\n/g); 22 | const keys: ConfigFileKeys = new Map(); 23 | 24 | for (let line = 0; line < lines.length; line++) { 25 | const [key, value] = lines[line].split("="); 26 | 27 | if (!key) continue; 28 | if (!keys.has(key)) keys.set(key, { line, value }); 29 | else 30 | diagnostics.push( 31 | createDiagnostic( 32 | document, 33 | line, 34 | t("configFile.error.duplicateKey", { key }), 35 | ), 36 | ); 37 | } 38 | 39 | for (const key in ConfigFileParameters) { 40 | const parameter = ConfigFileParameters[key as ConfigFileAllowedParams]; 41 | const current = keys.get(key); 42 | 43 | if (current) { 44 | parameter?.validation( 45 | keys, 46 | current.value, 47 | current.line, 48 | diagnostics, 49 | document, 50 | extension, 51 | ); 52 | } else if (parameter.required) { 53 | diagnostics.push( 54 | createDiagnostic( 55 | document, 56 | lines.length - 1, 57 | t("configFile.error.missingKey", { key }), 58 | ), 59 | ); 60 | } 61 | } 62 | 63 | diagnosticCollection.set(document.uri, diagnostics); 64 | } 65 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## 3.2.6 6 | 7 | ### Changes 8 | 9 | - Backups renamed to snapshots. 10 | 11 | ### Fixes 12 | 13 | - Crashes and loading issues. 14 | 15 | ## 3.2.5 16 | 17 | ### Fixes 18 | - Fix comitting folders. 19 | 20 | ## 3.2.4 21 | 22 | ### Docs 23 | 24 | - Improved README examples. 25 | - Renamed extension. 26 | 27 | ## 3.2.3 28 | 29 | ### Fixes 30 | 31 | - Fix configuration file for Windows users. 32 | 33 | ## 3.2.0 34 | 35 | ### Added 36 | 37 | - Configuration file syntax highlighting & auto completion. 38 | 39 | ## 3.1.5 40 | 41 | - Update dependencies. 42 | 43 | ## 3.1.4 44 | 45 | ### Fixes 46 | 47 | - Fix restarting the application after a commit. 48 | 49 | ## 3.1.3 50 | 51 | ### Fixes 52 | 53 | - Fix application commit. 54 | - Fix application backup. 55 | - Duplication of logs output channels. 56 | 57 | ### Tweaks 58 | 59 | - Improve RAM formatting. 60 | 61 | ## 3.1.2 62 | 63 | ### Added 64 | 65 | - Now you can choose wheter you want to restart your application after the commit or not. 66 | 67 | ## 3.1.0 68 | 69 | ### Added 70 | 71 | - Readded user information view 72 | 73 | ## 3.0.0 - Massive revamp 74 | 75 | ### Improvements 76 | 77 | - Everything is more polished, ensuring performance and stability. 78 | - Now statuses come from status all API endpoint. 79 | - API key is now stored in VSCode Secrets for your safety. 80 | - More reliable and fast state and cache management. 81 | 82 | ### Fixes 83 | 84 | - Applications names not showing. 85 | - Application description error at startup. 86 | - Unstable API key handling. 87 | 88 | ## 2.0.0 89 | 90 | ### Added 91 | 92 | - Applications 93 | - Upload 94 | 95 | ## 1.0.0 96 | 97 | ### Added 98 | 99 | - General 100 | - User information view 101 | - Bots & Sites management views 102 | - Create configuration file 103 | - Applications 104 | - Start 105 | - Stop 106 | - Restart 107 | - Delete 108 | - Commit 109 | - Logs 110 | - Statusd 111 | -------------------------------------------------------------------------------- /src/managers/config-file.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { ConfigFileActionProvider } from "@/providers/config-file-action"; 4 | import { ConfigCompletionProvider } from "@/providers/config-file-completion"; 5 | import { validateConfigFile } from "@/providers/config-file-validation"; 6 | 7 | import type { SquareCloudExtension } from "./extension"; 8 | 9 | export class ConfigFileManager { 10 | constructor(private readonly extension: SquareCloudExtension) { 11 | this.initialize(); 12 | } 13 | 14 | /** 15 | * Initialize the config file manager. 16 | * this create the subscriptions and event listeners for the config file. 17 | */ 18 | async initialize() { 19 | const diagnosticCollection = 20 | vscode.languages.createDiagnosticCollection("squarecloud"); 21 | 22 | this.extension.context.subscriptions.push(diagnosticCollection); 23 | 24 | vscode.workspace.onDidChangeTextDocument((event) => { 25 | if ( 26 | event.document.fileName.endsWith("squarecloud.app") || 27 | event.document.fileName.endsWith("squarecloud.config") 28 | ) { 29 | validateConfigFile( 30 | this.extension, 31 | event.document, 32 | diagnosticCollection, 33 | ); 34 | } 35 | }); 36 | 37 | vscode.workspace.onDidOpenTextDocument((document) => { 38 | if ( 39 | document.fileName.endsWith("squarecloud.app") || 40 | document.fileName.endsWith("squarecloud.config") 41 | ) { 42 | validateConfigFile(this.extension, document, diagnosticCollection); 43 | } 44 | }); 45 | 46 | this.extension.context.subscriptions.push( 47 | vscode.languages.registerCompletionItemProvider( 48 | { pattern: "**/{squarecloud.app,squarecloud.config}" }, 49 | ConfigCompletionProvider, 50 | "=", 51 | ), 52 | ); 53 | 54 | this.extension.context.subscriptions.push( 55 | vscode.languages.registerCodeActionsProvider( 56 | { pattern: "**/{squarecloud.app,squarecloud.config}" }, 57 | new ConfigFileActionProvider(), 58 | { 59 | providedCodeActionKinds: 60 | ConfigFileActionProvider.providedCodeActionKinds, 61 | }, 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/managers/api.ts: -------------------------------------------------------------------------------- 1 | import { SquareCloudAPI } from "@squarecloud/api"; 2 | 3 | import { ApplicationStatus } from "@/structures/application/status"; 4 | import { Logger } from "@/structures/logger"; 5 | 6 | import type { SquareCloudExtension } from "./extension"; 7 | 8 | export class APIManager { 9 | private readonly logger = new Logger("Square Cloud"); 10 | 11 | public paused = false; 12 | 13 | constructor(private readonly extension: SquareCloudExtension) { 14 | this.refresh(); 15 | setInterval(() => this.refresh(), 30000); 16 | } 17 | 18 | async refresh(bypass?: boolean) { 19 | if (this.paused && !bypass) { 20 | return; 21 | } 22 | this.pause(true); 23 | 24 | const apiKey = await this.extension.config.apiKey.test(); 25 | 26 | if (!apiKey) { 27 | this.logger.log("API key not found."); 28 | return; 29 | } 30 | 31 | const api = new SquareCloudAPI(apiKey); 32 | const user = await api.users.get(); 33 | const applications = user.applications; 34 | const statuses = await api.applications.statusAll(); 35 | 36 | this.pause(false); 37 | 38 | this.logger.log( 39 | `Found ${applications.size} applications and ${statuses.length} statuses.`, 40 | ); 41 | 42 | const newApplications = applications.toJSON(); 43 | const newStatuses = statuses.map((status) => new ApplicationStatus(status)); 44 | 45 | this.extension.store.actions.setApplications(newApplications); 46 | this.extension.store.actions.setStatuses(newStatuses); 47 | this.extension.store.actions.setUser(user); 48 | } 49 | 50 | async refreshStatus(appId: string, bypass?: boolean) { 51 | if (this.paused && !bypass) { 52 | return; 53 | } 54 | this.pause(true); 55 | 56 | const apiKey = await this.extension.config.apiKey.test(); 57 | 58 | if (!apiKey) { 59 | this.logger.log("API key not found."); 60 | return; 61 | } 62 | 63 | const api = new SquareCloudAPI(apiKey); 64 | const application = await api.applications.get(appId); 65 | const status = await application.getStatus(); 66 | 67 | this.pause(false); 68 | 69 | this.extension.store.actions.setStatus(new ApplicationStatus(status)); 70 | } 71 | 72 | async pauseUntil(fn: () => Promise) { 73 | this.pause(true); 74 | return fn().finally(() => this.pause(false)); 75 | } 76 | 77 | private pause(value?: boolean) { 78 | this.paused = value || !this.paused; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/lib/store.ts: -------------------------------------------------------------------------------- 1 | import { type BaseApplication, Collection, type User } from "@squarecloud/api"; 2 | import { atom } from "xoid"; 3 | 4 | import type { ApplicationStatus } from "@/structures/application/status"; 5 | 6 | export interface ExtensionStore { 7 | applications: Collection; 8 | statuses: Collection; 9 | favorited: Set; 10 | user?: User; 11 | } 12 | 13 | export interface ExtensionStoreActions { 14 | setApplications(applications: BaseApplication[]): void; 15 | setStatuses(statuses: ApplicationStatus[]): void; 16 | setStatus(status: ApplicationStatus): void; 17 | setFavorited(applicationsId: string[]): void; 18 | toggleFavorite(applicationId: string, value?: boolean): void; 19 | 20 | getStatus(applicationId: string): ApplicationStatus | undefined; 21 | isFavorited(applicationId: string): boolean; 22 | 23 | setUser(user?: User): void; 24 | } 25 | 26 | export const $extensionStore = atom( 27 | { 28 | applications: new Collection(), 29 | statuses: new Collection(), 30 | favorited: new Set(), 31 | user: undefined, 32 | }, 33 | (atom) => ({ 34 | setApplications: (applications) => { 35 | const map = new Collection(applications.map((app) => [app.id, app])); 36 | 37 | atom.update((value) => ({ ...value, applications: map })); 38 | }, 39 | setStatus: (status) => { 40 | const map = atom.value.statuses.set(status.applicationId, status); 41 | 42 | atom.update((value) => ({ ...value, statuses: map })); 43 | }, 44 | setStatuses: (statuses) => { 45 | const map = new Collection( 46 | statuses.map((status) => [status.applicationId, status]), 47 | ); 48 | 49 | atom.update((value) => ({ ...value, statuses: map })); 50 | }, 51 | 52 | setFavorited: (applicationsId) => { 53 | atom.update((value) => ({ 54 | ...value, 55 | favorited: new Set(applicationsId), 56 | })); 57 | }, 58 | toggleFavorite: (applicationId, value) => { 59 | const favorited = atom.value.favorited; 60 | const isFavorited = favorited?.has(applicationId); 61 | const toFavorite = value !== undefined ? value : !isFavorited; 62 | 63 | favorited?.[toFavorite ? "add" : "delete"](applicationId); 64 | 65 | atom.update((value) => ({ ...value, favorited })); 66 | }, 67 | 68 | getStatus: (applicationId) => { 69 | return atom.value.statuses.get(applicationId); 70 | }, 71 | isFavorited: (applicationId) => { 72 | return atom.value.favorited.has(applicationId); 73 | }, 74 | 75 | setUser: (user) => { 76 | atom.update((value) => ({ ...value, user })); 77 | }, 78 | }), 79 | ); 80 | -------------------------------------------------------------------------------- /src/config-file/parameters/main.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, statSync } from "node:fs"; 2 | import { dirname, relative, resolve } from "node:path"; 3 | import * as vscode from "vscode"; 4 | import { t } from "vscode-ext-localisation"; 5 | 6 | import type { ConfigFileParameter } from "@/types/config-file"; 7 | import { AllowedExtensions } from "@/lib/constants"; 8 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 9 | 10 | const notAllowedFolders = ["/node_modules", "/__pycache__", "/."]; 11 | 12 | export const MAIN = { 13 | required: true, 14 | validation(_keys, value, line, diagnostics, document) { 15 | const configFilePath = dirname(document.uri.fsPath); 16 | const mainFilePath = resolve(configFilePath, value); 17 | const stats = existsSync(mainFilePath) && statSync(mainFilePath); 18 | 19 | // Validate if there is some value on MAIN 20 | if (!value) { 21 | diagnostics.push( 22 | createDiagnostic(document, line, t("configFile.error.missing.main")), 23 | ); 24 | } 25 | 26 | // Validate if the file exists, is a file, and is inside config file root path 27 | if ( 28 | !stats || 29 | !stats.isFile() || 30 | !mainFilePath.startsWith(configFilePath) || 31 | notAllowedFolders.some((folder) => 32 | mainFilePath.replaceAll("\\", "/").includes(folder), 33 | ) 34 | ) { 35 | diagnostics.push( 36 | createDiagnostic( 37 | document, 38 | line, 39 | t("configFile.error.invalid.mainFile", { file: value }), 40 | ), 41 | ); 42 | } 43 | }, 44 | autocomplete(document, position) { 45 | /** 46 | * This function maps all project files relative to the config file 47 | * and provides them as completion items. 48 | */ 49 | const configFilePath = dirname(document.uri.fsPath); 50 | const files = vscode.workspace.findFiles(`**/*.{${AllowedExtensions}}`); 51 | 52 | return files.then((uris) => 53 | uris 54 | .filter( 55 | (uri) => 56 | uri.fsPath.startsWith(configFilePath) && 57 | !uri.fsPath.includes("dist") && 58 | !notAllowedFolders.some((folder) => 59 | uri.fsPath.replaceAll("\\", "/").includes(folder), 60 | ), 61 | ) 62 | .map((uri) => { 63 | const relativePath = relative(configFilePath, uri.fsPath); 64 | const item = new vscode.CompletionItem( 65 | relativePath, 66 | vscode.CompletionItemKind.File, 67 | ); 68 | item.insertText = relativePath.replaceAll("\\", "/"); 69 | item.range = document.getWordRangeAtPosition( 70 | position, 71 | /(?<=MAIN=).*/, 72 | ); 73 | return item; 74 | }), 75 | ); 76 | }, 77 | } satisfies ConfigFileParameter; 78 | -------------------------------------------------------------------------------- /src/config-file/parameters/memory.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { t } from "vscode-ext-localisation"; 3 | 4 | import type { ConfigFileParameter } from "@/types/config-file"; 5 | import { createDiagnostic } from "@/lib/utils/diagnostic"; 6 | 7 | export const memorySuggestions = [ 8 | 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 9 | ]; 10 | 11 | function legibleMemory(memory: number) { 12 | if (memory < 1024) return `${memory}MB`; 13 | if (memory < 1048576) return `${Math.floor(memory / 1024)}GB`; 14 | return memory.toString(); 15 | } 16 | 17 | export const MEMORY = { 18 | required: true, 19 | /** 20 | * This function is used to autocomplete the memory value in the config file. 21 | */ 22 | autocomplete(document, position) { 23 | const content = document.getText(); 24 | const keys = new Set( 25 | content.split(/\r?\n/g).map((line) => { 26 | return line.split("=")[0]?.trim(); 27 | }), 28 | ); 29 | 30 | return memorySuggestions 31 | .filter((memory) => (keys.has("SUBDOMAIN") ? memory >= 512 : true)) 32 | .map((memory, i) => { 33 | const completion = new vscode.CompletionItem( 34 | `${legibleMemory(memory)}`, 35 | vscode.CompletionItemKind.EnumMember, 36 | ); 37 | 38 | completion.insertText = memory.toString(); 39 | completion.sortText = String.fromCharCode(97 + i); 40 | completion.preselect = memory === 512; 41 | completion.range = document.getWordRangeAtPosition( 42 | position, 43 | /(?<=MEMORY=).*/, 44 | ); 45 | 46 | return completion; 47 | }); 48 | }, 49 | validation(keys, value, line, diagnostics, document, extension) { 50 | const memory = keys.has("SUBDOMAIN") ? 512 : 256; 51 | const inserted = Number(value); 52 | const available = extension.store.value.user?.plan.memory.available; 53 | 54 | if ( 55 | // Prevent spaces on the end or start 56 | value !== value.trim() || 57 | // Prevent to be NaN, such as "MEMORY=dasdasd" 58 | Number.isNaN(inserted) || 59 | /** 60 | * Verifies if the memory exceeds the minimum limit 61 | * @see {@link memory} for the minimum limit definition 62 | */ 63 | memory > inserted 64 | ) { 65 | diagnostics.push( 66 | createDiagnostic( 67 | document, 68 | line, 69 | t("configFile.error.invalid.memory", { memory }), 70 | ), 71 | ); 72 | } 73 | 74 | // Verifies that the user has available memory 75 | if ( 76 | typeof available === "number" && 77 | !Number.isNaN(inserted) && 78 | inserted > available 79 | ) { 80 | // Create diagnostic message 81 | const diagnostic = createDiagnostic( 82 | document, 83 | line, 84 | t("configFile.error.unavailable.memory.message"), 85 | ); 86 | 87 | // Insert url to upgrade plan 88 | diagnostic.code = { 89 | value: t("configFile.error.unavailable.memory.code"), // Link text 90 | target: vscode.Uri.parse("https://squarecloud.app/pay?state=upgrade"), // Link to upgrade plan 91 | }; 92 | 93 | diagnostics.push(diagnostic); 94 | } 95 | }, 96 | } satisfies ConfigFileParameter; 97 | -------------------------------------------------------------------------------- /src/treeviews/applications/provider.ts: -------------------------------------------------------------------------------- 1 | import { t } from "vscode-ext-localisation"; 2 | 3 | import type { SquareCloudExtension } from "@/managers/extension"; 4 | import { formatTime } from "@/lib/utils/format"; 5 | import { ApplicationStatus } from "@/structures/application/status"; 6 | 7 | import { BaseTreeViewProvider } from "../base"; 8 | import { GenericTreeItem } from "../items/generic"; 9 | import { ApplicationTreeItem, type SquareTreeItem } from "./item"; 10 | 11 | export type GenericTreeItemData = ConstructorParameters; 12 | 13 | export class ApplicationsTreeViewProvider extends BaseTreeViewProvider { 14 | constructor(private readonly extension: SquareCloudExtension) { 15 | super(); 16 | } 17 | 18 | async getChildren( 19 | element?: SquareTreeItem | undefined, 20 | ): Promise { 21 | const contextValue = 22 | element && "contextValue" in element ? element.contextValue : undefined; 23 | 24 | if ( 25 | contextValue?.includes("application") && 26 | element instanceof ApplicationTreeItem 27 | ) { 28 | if (!element.status) { 29 | return []; 30 | } 31 | 32 | const status = this.extension.store.actions.getStatus( 33 | element.application.id, 34 | ); 35 | 36 | if (!status?.isFull()) { 37 | element.application 38 | .fetch() 39 | .then((app) => app.getStatus()) 40 | .then((status) => { 41 | this.extension.store.actions.setStatus( 42 | new ApplicationStatus(status), 43 | ); 44 | }); 45 | } 46 | 47 | const treeItemsData: GenericTreeItemData[] = [ 48 | ["CPU", "cpu", element.status.usage?.cpu], 49 | ["RAM", "ram", element.status.usage?.ram], 50 | [t("generic.loading"), "loading"], 51 | ]; 52 | 53 | if (status?.isFull()) { 54 | const fullStatus = status as ApplicationStatus; 55 | const uptime = fullStatus.uptimeTimestamp 56 | ? formatTime(Date.now() - fullStatus.uptimeTimestamp) 57 | : "Offline"; 58 | 59 | treeItemsData.pop(); 60 | treeItemsData.push( 61 | ["Uptime", "uptime", uptime], 62 | [t("generic.network"), "network", fullStatus.usage.network.now], 63 | [t("generic.storage"), "storage", fullStatus.usage.storage], 64 | ); 65 | } 66 | 67 | return treeItemsData.map( 68 | (parameters) => new GenericTreeItem(...parameters), 69 | ); 70 | } 71 | 72 | if (!this.extension.store.value.applications.size) { 73 | const apiKey = await this.extension.config.apiKey.get(); 74 | 75 | if (!apiKey) { 76 | return []; 77 | } 78 | 79 | return [ 80 | new GenericTreeItem( 81 | t("generic.loading"), 82 | "loading", 83 | undefined, 84 | "loading", 85 | ), 86 | ]; 87 | } 88 | 89 | return Array.from(this.extension.store.value.applications.values()) 90 | .sort( 91 | (a, b) => 92 | (this.extension.store.actions.isFavorited(b.id) ? 1 : 0) - 93 | (this.extension.store.actions.isFavorited(a.id) ? 1 : 0), 94 | ) 95 | .map((app) => new ApplicationTreeItem(this.extension, app)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/commands/applications/tools/commit.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises"; 2 | import { join } from "node:path"; 3 | import ignore from "ignore"; 4 | import { ProgressLocation, window } from "vscode"; 5 | import { t } from "vscode-ext-localisation"; 6 | 7 | import { ApplicationCommand } from "@/structures/application/command"; 8 | 9 | import AdmZip = require("adm-zip"); 10 | 11 | export const commitEntry = new ApplicationCommand( 12 | "commitEntry", 13 | async (extension, { application }) => { 14 | if (extension.api.paused) { 15 | return; 16 | } 17 | 18 | const fileOrFolder = await window.showQuickPick( 19 | [t("generic.file"), t("generic.folder")], 20 | { 21 | title: t("commit.fileOrFolder"), 22 | placeHolder: t("generic.choose"), 23 | }, 24 | ); 25 | 26 | if (!fileOrFolder) { 27 | return; 28 | } 29 | 30 | const shouldRestart = await window.showQuickPick( 31 | [t("generic.yes"), t("generic.no")], 32 | { 33 | title: t("commit.restart"), 34 | placeHolder: t("generic.choose"), 35 | }, 36 | ); 37 | 38 | if (shouldRestart === undefined) { 39 | return; 40 | } 41 | 42 | const isFile = fileOrFolder === t("generic.file"); 43 | const isFolder = fileOrFolder === t("generic.folder"); 44 | 45 | const files = await window.showOpenDialog({ 46 | canSelectMany: isFile, 47 | canSelectFiles: isFile, 48 | canSelectFolders: isFolder, 49 | openLabel: t("commit.select", { TYPE: fileOrFolder.toLowerCase() }), 50 | title: `Commit - ${application.name}`, 51 | }); 52 | 53 | if (!files) { 54 | return; 55 | } 56 | 57 | const ignoreDefaults = await readFile( 58 | join(__dirname, "..", "resources", "squarecloud.ignore"), 59 | ); 60 | 61 | const ig = ignore().add(ignoreDefaults.toString("utf-8")); 62 | const zipFile = new AdmZip(); 63 | 64 | await window.withProgress( 65 | { 66 | location: ProgressLocation.Notification, 67 | title: t("commit.loading"), 68 | }, 69 | async (progress) => { 70 | if (isFile) { 71 | for (let { path } of files) { 72 | path = path.slice(1); 73 | zipFile.addLocalFile(path); 74 | } 75 | } 76 | 77 | if (isFolder) { 78 | let [{ path }] = files; 79 | path = path.slice(1); 80 | 81 | const squarecloudIgnore = await readFile( 82 | join(path, "squarecloud.ignore"), 83 | ).catch(() => null); 84 | 85 | if (squarecloudIgnore) { 86 | ig.add(squarecloudIgnore.toString("utf-8")); 87 | } else { 88 | const gitIgnore = await readFile(join(path, ".gitignore")).catch( 89 | () => null, 90 | ); 91 | 92 | if (gitIgnore) { 93 | const canIgnore = await window.showInformationMessage( 94 | t("commit.useGitIgnore"), 95 | t("generic.yes"), 96 | t("generic.no"), 97 | ); 98 | 99 | if (canIgnore === t("generic.yes")) { 100 | ig.add(gitIgnore.toString("utf-8")); 101 | } 102 | } 103 | } 104 | 105 | await zipFile.addLocalFolderPromise(path, { 106 | zipPath: `${path.split("/").pop()}/`, 107 | filter: (filename) => !ig.ignores(filename), 108 | }); 109 | } 110 | 111 | await application.commit(zipFile.toBuffer(), `${application.id}.zip`); 112 | 113 | if (shouldRestart === t("generic.yes")) { 114 | await application.restart(); 115 | } 116 | 117 | setTimeout(() => extension.api.refreshStatus(application.id), 7000); 118 | 119 | progress.report({ increment: 100 }); 120 | window.showInformationMessage(t("commit.loaded")); 121 | }, 122 | ); 123 | }, 124 | ); 125 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.apiKey.description": "Your [Square Cloud](https://squarecloud.app/) API key.", 3 | 4 | "command.createConfig": "Create Square Cloud configuration file", 5 | "command.setApiKey": "Set new API key", 6 | "command.logsEntry": "Show logs", 7 | "command.copyText": "Copy", 8 | "command.refresh": "Refresh", 9 | "command.open": "View on dashboard", 10 | "command.start": "Start", 11 | "command.stop": "Stop", 12 | "command.copyId": "Copy ID", 13 | "command.restart": "Restart", 14 | "command.delete": "Delete", 15 | "command.favorite": "Favorite", 16 | "command.unfavorite": "Unfavorite", 17 | 18 | "view.user.title": "User", 19 | "view.apps.title": "Applications", 20 | "view.noApiKey": "You do not have a registered API key. Please register one.", 21 | "view.welcome": "No API key registered.\n[Set API key](command:squarecloud.setApiKey)", 22 | 23 | "generic": { 24 | "yes": "Yes", 25 | "no": "No", 26 | "loading": "Loading...", 27 | "error": "An unknown error has ocurred.", 28 | "network": "Network", 29 | "storage": "Storage", 30 | "wait": "Please wait a moment before making new requests.", 31 | "refreshing": "Refreshing...", 32 | "done": "Done!", 33 | "type": "Type here...", 34 | "paste": "Paste here...", 35 | "choose": "Choose an option...", 36 | "file": "File", 37 | "folder": "Folder" 38 | }, 39 | 40 | "setApiKey": { 41 | "tutorial": { 42 | "label": "First open the \"SquareCloud Dashboard\", then go to \"My Account\" and finally click \"Regenerate API/CLI KEY\".", 43 | "button": "Open Dashboard" 44 | }, 45 | "hasKey": "Do you have your API key already?", 46 | "apiKey": "Enter your API key.", 47 | "testing": "Verifying the provided API key.", 48 | "invalid": "The provided API key is not valid.", 49 | "success": "Your API key has been successfuly registered!" 50 | }, 51 | 52 | "logs": { 53 | "loading": "Fetching logs.", 54 | "loaded": "Logs have been successfully loaded.", 55 | "null": "No logs were found.", 56 | "button": "Show logs" 57 | }, 58 | 59 | "copy": { 60 | "copiedId": "Application ID copied to clipboard.", 61 | "copiedText": "{{TYPE}} copied to clipboard." 62 | }, 63 | 64 | "start": { 65 | "loading": "Launching application...", 66 | "loaded": "Application is now running." 67 | }, 68 | 69 | "stop": { 70 | "loading": "Shutting down application...", 71 | "loaded": "Application is no longer running." 72 | }, 73 | 74 | "restart": { 75 | "loading": "Restarting application...", 76 | "loaded": "Application has been restarted." 77 | }, 78 | 79 | "delete": { 80 | "loading": "Deleting application...", 81 | "loaded": "Application has been deleted.", 82 | "cancelled": "The delete process has been cancelled.", 83 | "confirm": "Enter the application name to confirm that you want to delete it." 84 | }, 85 | 86 | "commit": { 87 | "loading": "Committing files...", 88 | "loaded": "Committed files successfuly.", 89 | "error": "An error ocurred when trying to commit.", 90 | "fileOrFolder": "What do you want to commit?", 91 | "restart": "Would you like to restart your application?", 92 | "select": "Select {{TYPE}} to commit", 93 | "useGitIgnore": "No squarecloud.ignore file found. Ignore .gitignore listed files instead?" 94 | }, 95 | 96 | "snapshot": { 97 | "loading": "Downloading snapshot...", 98 | "loaded": "Snapshot downloaded successfuly.", 99 | "save": "Save here" 100 | }, 101 | 102 | "createConfig": { 103 | "botOrSite": "Is your application a Bot or a Website?", 104 | "nothingHere": "You must type something here.", 105 | "optional": "Do you want to set {{field}} for your application?", 106 | "prompt": { 107 | "displayName": "Type a display name for your application.", 108 | "avatar": { 109 | "subs": "an avatar", 110 | "title": "Paste your avatar URL below." 111 | }, 112 | "description": { 113 | "subs": "a description", 114 | "title": "Write the description for your application." 115 | }, 116 | "main": { 117 | "title": "What's the main file of your application?", 118 | "invalid": "This file does not exists in the current folder." 119 | }, 120 | "memory": { 121 | "title": "How much RAM memory will your application use? {{max}}", 122 | "validate": "Please type a number bigger than {{minValue}} and smaller than your available memory." 123 | }, 124 | "version": "What version would you like to use for this application?", 125 | "subdomain": "Type a subdomain for your website.", 126 | "start": { 127 | "subs": "a start command", 128 | "title": "Type the start command for your application." 129 | } 130 | } 131 | }, 132 | 133 | "statusBarItem": { 134 | "title": "Square Cloud - Select an action:", 135 | "commit": "Commit files", 136 | "upload": "Upload a new application", 137 | "setApp": "Set the workspace application", 138 | "createConfig": "Create configuration file" 139 | }, 140 | 141 | "setWorkspaceApp": { 142 | "none": "None", 143 | "select": "Select an application:", 144 | "success": "Workspace application updated successfuly!" 145 | }, 146 | 147 | "uploadWorkspace": { 148 | "createFile": "To upload your application, first create a configuration file.", 149 | "loading": "Sending files to Square Cloud...", 150 | "loaded": "Application successfuly uploaded!" 151 | }, 152 | 153 | "commitWorkspace": { 154 | "loading": "Sending files to Square Cloud...", 155 | "loaded": "Application successfuly updated!" 156 | }, 157 | 158 | "configFile": { 159 | "error": { 160 | "duplicateKey": "Your Square Cloud configuration file has 2 \"{{key}}\" settings.", 161 | "missingKey": "The property \"{{key}}\" is missing from your configuration file but is required to host your application.", 162 | "long": { 163 | "displayName": "The DISPLAY_NAME field cannot exceed 32 characters.", 164 | "description": "The DESCRIPTION field cannot exceed 280 characters.", 165 | "subdomain": "The SUBDOMAIN field cannot exceed 62 characters.", 166 | "start": "The START field can only have up to 128 characters." 167 | }, 168 | "invalid": { 169 | "mainFile": "The MAIN file \"{{file}}\" does not exist in your project.", 170 | "memory": "The MEMORY field must be a value in megabytes greater than or equal to {{memory}}.", 171 | "autoRestart": "The AUTORESTART field must be \"true\" or \"false\".", 172 | "version": "The VERSION field only allows \"recommended\" or \"latest\".", 173 | "subdomain": "Invalid SUBDOMAIN. Only alphanumeric characters and hyphens are allowed." 174 | }, 175 | "missing": { 176 | "displayName": "You need to provide a valid DISPLAY_NAME with up to 32 characters.", 177 | "description": "You need to provide a valid description with up to 280 characters. If you don't want to, just remove the DESCRIPTION field.", 178 | "subdomain": "You need to provide a valid subdomain to host a web application (site, API). If that's not your intention, just remove the SUBDOMAIN field.", 179 | "start": "You need to provide a start command. If that's not your intention, just remove the START field.", 180 | "main": "You need to provide a valid main file. This is the main file of your application, where everything starts!" 181 | }, 182 | "unavailable": { 183 | "memory": { 184 | "message": "You're out of available memory. Consider upgrading.", 185 | "code": "Upgrade" 186 | } 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /package.nls.pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.apiKey.description": "Sua chave de API da [Square Cloud](https://squarecloud.app/).", 3 | 4 | "command.createConfig": "Criar arquivo de configuração Square Cloud", 5 | "command.setApiKey": "Definir nova chave de API", 6 | "command.logsEntry": "Exibir logs", 7 | "command.copyText": "Copiar", 8 | "command.refresh": "Atualizar", 9 | "command.open": "Ver na dashboard", 10 | "command.start": "Iniciar", 11 | "command.stop": "Parar", 12 | "command.copyId": "Copiar ID", 13 | "command.restart": "Reiniciar", 14 | "command.delete": "Excluir", 15 | "command.favorite": "Favorito", 16 | "command.unfavorite": "Desfavoritar", 17 | 18 | "view.user.title": "Usuário", 19 | "view.apps.title": "Aplicações", 20 | "view.noApiKey": "Você não possui uma chave de API registrada. Por favor, registre uma.", 21 | "view.welcome": "Chave de API não registrada.\n[Definir chave de API](command:squarecloud.setApiKey)", 22 | 23 | "generic": { 24 | "yes": "Sim", 25 | "no": "Não", 26 | "loading": "Carregando...", 27 | "error": "Ocorreu um erro desconhecido.", 28 | "network": "Rede", 29 | "storage": "Armazenamento", 30 | "wait": "Por favor, aguarde um momento antes de fazer novas solicitações.", 31 | "refreshing": "Atualizando...", 32 | "done": "Feito!", 33 | "type": "Digite aqui...", 34 | "paste": "Cole aqui...", 35 | "choose": "Escolha uma opção...", 36 | "file": "Arquivo", 37 | "folder": "Pasta" 38 | }, 39 | 40 | "setApiKey": { 41 | "tutorial": { 42 | "label": "Primeiro abra o \"Dashboard da SquareCloud\", depois vá para \"Minha conta\" e finalmente clique em \"Regenerar chave de API/CLI\".", 43 | "button": "Abrir Dashboard" 44 | }, 45 | "hasKey": "Você já possui sua chave de API?", 46 | "apiKey": "Insira sua chave de API.", 47 | "testing": "Validando a chave de API fornecida.", 48 | "invalid": "A chave de API fornecida não é válida.", 49 | "success": "Sua chave de API foi registrada com sucesso!" 50 | }, 51 | 52 | "logs": { 53 | "loading": "Buscando logs.", 54 | "loaded": "Os logs foram carregados com sucesso.", 55 | "null": "Nenhum log foi encontrado.", 56 | "button": "Exibir logs" 57 | }, 58 | 59 | "copy": { 60 | "copiedId": "ID da aplicação copiado para a área de transferência.", 61 | "copiedText": "{{TYPE}} copiado para a área de transferência." 62 | }, 63 | 64 | "start": { 65 | "loading": "Iniciando aplicação...", 66 | "loaded": "Aplicação iniciada com sucesso." 67 | }, 68 | 69 | "stop": { 70 | "loading": "Desligando aplicação...", 71 | "loaded": "A aplicação foi desligada." 72 | }, 73 | 74 | "restart": { 75 | "loading": "Reiniciando aplicação...", 76 | "loaded": "A aplicação foi reiniciada." 77 | }, 78 | 79 | "delete": { 80 | "loading": "Excluindo a aplicação...", 81 | "loaded": "A aplicação foi excluída permanentemente.", 82 | "cancelled": "O processo de exclusão foi cancelado.", 83 | "confirm": "Insira o nome da aplicação para confirmar que deseja excluí-la." 84 | }, 85 | 86 | "commit": { 87 | "loading": "Realizando commit dos arquivos...", 88 | "loaded": "Commit realizado com sucesso.", 89 | "error": "Um erro ocorreu ao tentar realizar o commit.", 90 | "fileOrFolder": "O que você deseja enviar no commit?", 91 | "restart": "Deseja reiniciar a aplicação?", 92 | "select": "Selecione um(a) {{TYPE}} para o commit", 93 | "useGitIgnore": "Nenhum arquivo squarecloud.ignore encontrado. Ignorar arquivos listados em .gitignore?" 94 | }, 95 | 96 | "snapshot": { 97 | "loading": "Baixando snapshot...", 98 | "loaded": "Snapshot salvo com sucesso.", 99 | "save": "Salvar aqui" 100 | }, 101 | 102 | "createConfig": { 103 | "botOrSite": "Sua aplicação é um Bot ou um Site?", 104 | "nothingHere": "Você precisa digitar algo aqui.", 105 | "optional": "Você gostaria de definir {{field}} para a sua aplicação?", 106 | "prompt": { 107 | "displayName": "Insira um apelido para a sua aplicação.", 108 | "avatar": { 109 | "subs": "um avatar", 110 | "title": "Cole o URL de seu avatar abaixo." 111 | }, 112 | "description": { 113 | "subs": "uma descrição", 114 | "title": "Insira a descrição de sua aplicação." 115 | }, 116 | "main": { 117 | "title": "Qual o arquivo principal de sua aplicação?", 118 | "invalid": "O arquivo não existe no diretório atual." 119 | }, 120 | "memory": { 121 | "title": "Quanto de memória RAM sua aplicação usará? {{max}}", 122 | "invalid": "Por favor, insira um número maior que {{minValue}} e menor que a sua memória disponível." 123 | }, 124 | "version": "Qual versão deseja utilizar em sua aplicação?", 125 | "subdomain": "Insira um subdomínio para o seu site.", 126 | "start": { 127 | "subs": "um comando de inicialização", 128 | "title": "Insira o comando de inicialização da sua aplicação." 129 | } 130 | } 131 | }, 132 | 133 | "statusBarItem": { 134 | "title": "Square Cloud - Selecione uma ação", 135 | "commit": "Realizar commit dos arquivos", 136 | "upload": "Enviar nova aplicação", 137 | "setApp": "Definir aplicação da área de trabalho", 138 | "createConfig": "Criar arquivo de configuração" 139 | }, 140 | 141 | "setWorkspaceApp": { 142 | "none": "Nenhuma", 143 | "select": "Selecione uma aplicação:", 144 | "success": "Aplicação da área de trabalho atualizada com sucesso!" 145 | }, 146 | 147 | "uploadWorkspace": { 148 | "createFile": "Para realizar o upload, primeiro crie um arquivo de configuração.", 149 | "loading": "Enviando arquivos para a Square Cloud...", 150 | "loaded": "Aplicação enviada com sucesso!" 151 | }, 152 | 153 | "commitWorkspace": { 154 | "loading": "Enviando arquivos para a Square Cloud...", 155 | "loaded": "Aplicação atualizada com sucesso!" 156 | }, 157 | 158 | "configFile": { 159 | "error": { 160 | "duplicateKey": "O seu arquivo de configuração da Square Cloud tem 2 configurações \"{{key}}\".", 161 | "missingKey": "A propriedade \"{{key}}\" não está presente no seu arquivo de configuração, mas é obrigatória para hospedar sua aplicação.", 162 | "long": { 163 | "displayName": "O campo DISPLAY_NAME não pode ter mais de 32 caracteres.", 164 | "description": "O campo DESCRIPTION não pode ter mais de 280 caracteres.", 165 | "subdomain": "O campo SUBDOMAIN não pode ter mais do que 62 caracteres.", 166 | "start": "O campo START só pode ter até 128 caracteres." 167 | }, 168 | "invalid": { 169 | "mainFile": "O arquivo principal \"{{file}}\" não existe no seu projeto.", 170 | "memory": "O campo MEMORY deve ser um valor em megabytes maior ou igual a {{memory}}.", 171 | "autoRestart": "O campo AUTORESTART deve ser \"true\" ou \"false\".", 172 | "version": "No campo VERSION só é permitido \"recommended\" ou \"latest\".", 173 | "subdomain": "SUBDOMAIN inválido. Apenas caracteres alfanuméricos e hifens são permitidos." 174 | }, 175 | "missing": { 176 | "displayName": "Você precisa inserir um DISPLAY_NAME válido com até 32 caracteres.", 177 | "description": "Você precisa inserir uma descrição valida com até 280 caracteres. Caso não queira, basta remover o campo DESCRIPTION.", 178 | "subdomain": "Você precisa inserir um subdominio valido pra hospedar uma aplicação web (Site, api), caso não seja o seu intuito, basta remover o campo SUBDOMAIN.", 179 | "start": "Você precisa inserir um comando de inicio, caso não seja o seu intuito, basta remover o campo START.", 180 | "main": "Você precisa inserir um arquivo principal válido, esse é o arquivo principal da sua aplicação, por onde tudo começa!" 181 | }, 182 | "unavailable": { 183 | "memory": { 184 | "message": "Você está sem memória disponível, considere fazer um upgrade.", 185 | "code": "Fazer upgrade" 186 | } 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /package.nls.es.json: -------------------------------------------------------------------------------- 1 | { 2 | "config.apiKey.description": "Su clave de API de [Square Cloud](https://squarecloud.app/).", 3 | 4 | "command.createConfig": "Crear archivo de configuración de Square Cloud", 5 | "command.setApiKey": "Establecer nueva clave de API", 6 | "command.logsEntry": "Mostrar registros", 7 | "command.copyText": "Copiar", 8 | "command.refresh": "Actualizar", 9 | "command.open": "Ver en el tablero", 10 | "command.start": "Iniciar", 11 | "command.stop": "Detener", 12 | "command.copyId": "Copiar ID", 13 | "command.restart": "Reiniciar", 14 | "command.delete": "Eliminar", 15 | "command.favorite": "Favorito", 16 | "command.unfavorite": "Desfavoritar", 17 | 18 | "view.user.title": "Usuario", 19 | "view.apps.title": "Aplicaciones", 20 | "view.noApiKey": "No tiene una clave de API registrada. Por favor, regístre una.", 21 | "view.welcome": "No hay clave de API registrada.\n[Establecer clave de API](command:squarecloud.setApiKey)", 22 | 23 | "generic": { 24 | "yes": "Sí", 25 | "no": "No", 26 | "loading": "Cargando...", 27 | "error": "Ha ocurrido un error desconocido.", 28 | "network": "Red", 29 | "storage": "Almacenamiento", 30 | "wait": "Por favor espere un momento antes de hacer nuevas solicitudes.", 31 | "refreshing": "Actualizando...", 32 | "done": "¡Hecho!", 33 | "type": "Escribe aquí...", 34 | "paste": "Pegar aquí...", 35 | "choose": "Elige una opción...", 36 | "file": "Archivo", 37 | "folder": "Carpeta" 38 | }, 39 | 40 | "setApiKey": { 41 | "tutorial": { 42 | "label": "Primero abra el panel de control de \"SquareCloud\", luego vaya a \"Mi cuenta\" y finalmente haga clic en \"Regenerar clave de API/CLI\".", 43 | "button": "Abrir panel de control" 44 | }, 45 | "hasKey": "¿Ya tiene su clave de API?", 46 | "apiKey": "Ingrese su clave de API.", 47 | "testing": "Verificando la clave de API proporcionada.", 48 | "invalid": "La clave de API proporcionada no es válida.", 49 | "success": "¡Su clave de API se ha registrado con éxito!" 50 | }, 51 | 52 | "logs": { 53 | "loading": "Obteniendo registros.", 54 | "loaded": "Los registros se han cargado con éxito.", 55 | "null": "No se encontraron registros.", 56 | "button": "Mostrar registros" 57 | }, 58 | 59 | "copy": { 60 | "copiedId": "ID de aplicación copiado al portapapeles.", 61 | "copiedText": "{{TYPE}} copiado al portapapeles." 62 | }, 63 | 64 | "start": { 65 | "loading": "Iniciando aplicación...", 66 | "loaded": "La aplicación se está ejecutando ahora." 67 | }, 68 | 69 | "stop": { 70 | "loading": "Apagando la aplicación...", 71 | "loaded": "La aplicación ya no se está ejecutando." 72 | }, 73 | 74 | "restart": { 75 | "loading": "Reiniciando la aplicación...", 76 | "loaded": "La aplicación se ha reiniciado." 77 | }, 78 | 79 | "delete": { 80 | "loading": "Eliminando la aplicación...", 81 | "loaded": "La aplicación se ha eliminado.", 82 | "cancelled": "El proceso de eliminación se ha cancelado.", 83 | "confirm": "Ingrese el nombre de la aplicación para confirmar que desea eliminarla." 84 | }, 85 | 86 | "commit": { 87 | "loading": "Enviando archivos...", 88 | "loaded": "Los archivos se han enviado con éxito.", 89 | "restart": "¿Desea reiniciar la aplicación?", 90 | "select": "Seleccione la carpeta para enviar", 91 | "useGitIgnore": "No se ha encontrado el archivo squarecloud.ignore. ¿Ignorar los archivos listados en .gitignore en su lugar?" 92 | }, 93 | 94 | "snapshot": { 95 | "loading": "Descargando snapshot...", 96 | "loaded": "Snapshot guardada con éxito.", 97 | "save": "Guardar aquí" 98 | }, 99 | 100 | "createConfig": { 101 | "botOrSite": "¿Es tu aplicación un Bot o un Sitio web?", 102 | "nothingHere": "Debes escribir algo aquí.", 103 | "optional": "¿Quieres establecer {{field}} para tu aplicación?", 104 | "prompt": { 105 | "displayName": "Escribe un nombre para mostrar para tu aplicación.", 106 | "avatar": { 107 | "subs": "un avatar", 108 | "title": "Pega la URL de tu avatar a continuación." 109 | }, 110 | "description": { 111 | "subs": "una descripción", 112 | "title": "Escribe la descripción de tu aplicación." 113 | }, 114 | "main": { 115 | "title": "¿Cuál es el archivo principal de tu aplicación?", 116 | "invalid": "Este archivo no existe en la carpeta actual." 117 | }, 118 | "memory": { 119 | "title": "¿Cuánta memoria RAM usará tu aplicación? {{max}}", 120 | "validate": "Por favor, escribe un número mayor que {{minValue}} y menor que tu memoria disponible." 121 | }, 122 | "version": "¿Qué versión te gustaría usar para esta aplicación?", 123 | "subdomain": "Escribe un subdominio para tu sitio web.", 124 | "start": { 125 | "subs": "un comando de inicio", 126 | "title": "Escribe el comando de inicio para tu aplicación." 127 | } 128 | } 129 | }, 130 | 131 | "statusBarItem": { 132 | "title": "Square Cloud - Selecciona una acción", 133 | "commit": "Realizar commit de los archivos", 134 | "upload": "Enviar nueva aplicación", 135 | "setApp": "Definir aplicación del escritorio", 136 | "createConfig": "Crear archivo de configuración" 137 | }, 138 | 139 | "setWorkspaceApp": { 140 | "none": "Sin", 141 | "select": "Selecciona una aplicación:", 142 | "success": "¡Aplicación del espacio de trabajo actualizada con éxito!" 143 | }, 144 | 145 | "uploadWorkspace": { 146 | "createFile": "Para subir tu aplicación, primero crea un archivo de configuración.", 147 | "loading": "Enviando archivos a Square Cloud...", 148 | "loaded": "¡Aplicación subida con éxito!" 149 | }, 150 | 151 | "commitWorkspace": { 152 | "loading": "Enviando archivos a Square Cloud...", 153 | "loaded": "¡Aplicación actualizada con éxito!" 154 | }, 155 | 156 | "configFile": { 157 | "error": { 158 | "duplicateKey": "Tu archivo de configuración de Square Cloud tiene 2 configuraciones \"{{key}}\".", 159 | "missingKey": "La propiedad \"{{key}}\" no está presente en tu archivo de configuración, pero es obligatoria para alojar tu aplicación.", 160 | "long": { 161 | "displayName": "El campo DISPLAY_NAME no puede tener más de 32 caracteres.", 162 | "description": "El campo DESCRIPTION no puede tener más de 280 caracteres.", 163 | "subdomain": "El campo SUBDOMAIN no puede tener más de 62 caracteres.", 164 | "start": "El campo START solo puede tener hasta 128 caracteres." 165 | }, 166 | "invalid": { 167 | "mainFile": "El archivo principal \"{{file}}\" no existe en tu proyecto.", 168 | "memory": "El campo MEMORY debe ser un valor en megabytes mayor o igual a {{memory}}.", 169 | "autoRestart": "El campo AUTORESTART debe ser \"true\" o \"false\".", 170 | "version": "El campo VERSION solo permite \"recommended\" o \"latest\".", 171 | "subdomain": "SUBDOMAIN inválido. Solo se permiten caracteres alfanuméricos y guiones." 172 | }, 173 | "missing": { 174 | "displayName": "Necesitas proporcionar un DISPLAY_NAME válido con hasta 32 caracteres.", 175 | "description": "Necesitas proporcionar una descripción válida con hasta 280 caracteres. Si no lo deseas, simplemente elimina el campo DESCRIPTION.", 176 | "subdomain": "Necesitas proporcionar un subdominio válido para alojar una aplicación web (sitio, API). Si no es tu intención, simplemente elimina el campo SUBDOMAIN.", 177 | "start": "Necesitas proporcionar un comando de inicio. Si no es tu intención, simplemente elimina el campo START.", 178 | "main": "Necesitas proporcionar un archivo principal válido. Este es el archivo principal de tu aplicación, donde todo comienza." 179 | }, 180 | "unavailable": { 181 | "memory": { 182 | "message": "Te has quedado sin memoria disponible. Considera actualizar.", 183 | "code": "Actualizar" 184 | } 185 | } 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.2.10", 3 | "name": "squarecloud", 4 | "publisher": "squarecloud", 5 | "displayName": "Square Cloud", 6 | "description": "A VSCode extension for managing your Square Cloud applications.", 7 | "categories": [ 8 | "Other" 9 | ], 10 | "icon": "resources/squarecloud.png", 11 | "packageManager": "pnpm@10.25.0", 12 | "keywords": [ 13 | "squarecloud", 14 | "square cloud", 15 | "host", 16 | "bot", 17 | "website", 18 | "intellisense" 19 | ], 20 | "homepage": "https://github.com/squarecloudofc/vscode-extension/", 21 | "author": { 22 | "name": "João Gabriel Tonaco", 23 | "url": "https://github.com/joaotonaco/" 24 | }, 25 | "sponsor": { 26 | "url": "https://squarecloud.app/" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/squarecloudofc/vscode-extension" 31 | }, 32 | "engines": { 33 | "vscode": "^1.95.0" 34 | }, 35 | "activationEvents": [ 36 | "*" 37 | ], 38 | "main": "./dist/extension.js", 39 | "scripts": { 40 | "lint": "biome check --write .", 41 | "build": "pnpm run check-types && node esbuild.js --production", 42 | "check-types": "tsc --noEmit", 43 | "watch": "concurrently --raw \"pnpm:watch:*\"", 44 | "watch:tsc": "tsc --noEmit --watch", 45 | "watch:esbuild": "node esbuild.js --watch", 46 | "vscode:prepublish": "pnpm run build", 47 | "publish:vsce": "vsce publish --allow-star-activation --no-dependencies", 48 | "publish:ovsx": "ovsx publish --no-dependencies", 49 | "package": "vsce package --allow-star-activation --no-dependencies" 50 | }, 51 | "dependencies": { 52 | "@squarecloud/api": "^3.8.0", 53 | "adm-zip": "^0.5.16", 54 | "ignore": "^7.0.5", 55 | "vscode-ext-localisation": "^1.1.0", 56 | "xdg-app-paths": "^8.3.0", 57 | "xoid": "^1.0.0-beta.12" 58 | }, 59 | "devDependencies": { 60 | "@biomejs/biome": "^2.3.8", 61 | "@types/adm-zip": "^0.5.7", 62 | "@types/mocha": "^10.0.10", 63 | "@types/node": "^24.10.2", 64 | "@types/vscode": "^1.95.0", 65 | "@vscode/test-electron": "^2.4.1", 66 | "@vscode/vsce": "^3.7.1", 67 | "concurrently": "^9.2.1", 68 | "esbuild": "^0.27.1", 69 | "mocha": "^11.7.5", 70 | "ovsx": "^0.10.7", 71 | "typescript": "^5.9.3" 72 | }, 73 | "contributes": { 74 | "menus": { 75 | "commandPalette": [ 76 | { 77 | "command": "squarecloud.refreshCache", 78 | "when": "false" 79 | }, 80 | { 81 | "command": "squarecloud.refreshEntry", 82 | "when": "false" 83 | }, 84 | { 85 | "command": "squarecloud.logsEntry", 86 | "when": "false" 87 | }, 88 | { 89 | "command": "squarecloud.copyText", 90 | "when": "false" 91 | }, 92 | { 93 | "command": "squarecloud.openEntry", 94 | "when": "false" 95 | }, 96 | { 97 | "command": "squarecloud.startEntry", 98 | "when": "false" 99 | }, 100 | { 101 | "command": "squarecloud.stopEntry", 102 | "when": "false" 103 | }, 104 | { 105 | "command": "squarecloud.restartEntry", 106 | "when": "false" 107 | }, 108 | { 109 | "command": "squarecloud.deleteEntry", 110 | "when": "false" 111 | }, 112 | { 113 | "command": "squarecloud.commitEntry", 114 | "when": "false" 115 | }, 116 | { 117 | "command": "squarecloud.snapshotEntry", 118 | "when": "false" 119 | }, 120 | { 121 | "command": "squarecloud.copyIdEntry", 122 | "when": "false" 123 | } 124 | ], 125 | "view/title": [ 126 | { 127 | "command": "squarecloud.refreshCache", 128 | "when": "view == apps-view || view == user-view", 129 | "group": "navigation" 130 | }, 131 | { 132 | "command": "squarecloud.setApiKey", 133 | "when": "view == apps-view || view == user-view" 134 | } 135 | ], 136 | "view/item/context": [ 137 | { 138 | "command": "squarecloud.refreshEntry", 139 | "when": "viewItem == application || viewItem == application-fav", 140 | "group": "inline" 141 | }, 142 | { 143 | "command": "squarecloud.logsEntry", 144 | "when": "viewItem == application || viewItem == application-fav", 145 | "group": "inline" 146 | }, 147 | { 148 | "command": "squarecloud.favoriteEntry", 149 | "when": "viewItem == application", 150 | "group": "inline" 151 | }, 152 | { 153 | "command": "squarecloud.unfavoriteEntry", 154 | "when": "viewItem == application-fav", 155 | "group": "inline" 156 | }, 157 | { 158 | "command": "squarecloud.openEntry", 159 | "when": "viewItem == application || viewItem == application-fav" 160 | }, 161 | { 162 | "command": "squarecloud.startEntry", 163 | "when": "viewItem == application || viewItem == application-fav" 164 | }, 165 | { 166 | "command": "squarecloud.stopEntry", 167 | "when": "viewItem == application || viewItem == application-fav" 168 | }, 169 | { 170 | "command": "squarecloud.restartEntry", 171 | "when": "viewItem == application || viewItem == application-fav" 172 | }, 173 | { 174 | "command": "squarecloud.deleteEntry", 175 | "when": "viewItem == application || viewItem == application-fav" 176 | }, 177 | { 178 | "command": "squarecloud.commitEntry", 179 | "when": "viewItem == application || viewItem == application-fav" 180 | }, 181 | { 182 | "command": "squarecloud.snapshotEntry", 183 | "when": "viewItem == application || viewItem == application-fav" 184 | }, 185 | { 186 | "command": "squarecloud.copyIdEntry", 187 | "when": "viewItem == application || viewItem == application-fav" 188 | }, 189 | { 190 | "command": "squarecloud.copyText", 191 | "when": "viewItem == generic", 192 | "group": "inline" 193 | } 194 | ] 195 | }, 196 | "viewsWelcome": [ 197 | { 198 | "view": "apps-view", 199 | "contents": "%view.welcome%", 200 | "enablement": "!squarecloud.apiKey", 201 | "when": "!squarecloud.apiKey" 202 | } 203 | ], 204 | "commands": [ 205 | { 206 | "command": "squarecloud.setApiKey", 207 | "title": "%command.setApiKey%", 208 | "category": "Square Cloud" 209 | }, 210 | { 211 | "command": "squarecloud.refreshCache", 212 | "title": "%command.refresh%", 213 | "icon": { 214 | "light": "resources/light/refresh.svg", 215 | "dark": "resources/dark/refresh.svg" 216 | } 217 | }, 218 | { 219 | "command": "squarecloud.refreshEntry", 220 | "title": "%command.refresh%", 221 | "icon": { 222 | "light": "resources/light/refresh.svg", 223 | "dark": "resources/dark/refresh.svg" 224 | } 225 | }, 226 | { 227 | "command": "squarecloud.logsEntry", 228 | "title": "%command.logsEntry%", 229 | "icon": { 230 | "light": "resources/light/logs.svg", 231 | "dark": "resources/dark/logs.svg" 232 | } 233 | }, 234 | { 235 | "command": "squarecloud.copyText", 236 | "title": "%command.copyText%", 237 | "icon": { 238 | "light": "resources/light/copy.svg", 239 | "dark": "resources/dark/copy.svg" 240 | } 241 | }, 242 | { 243 | "command": "squarecloud.favoriteEntry", 244 | "title": "%command.favorite%", 245 | "icon": { 246 | "light": "resources/light/star.svg", 247 | "dark": "resources/dark/star.svg" 248 | } 249 | }, 250 | { 251 | "command": "squarecloud.unfavoriteEntry", 252 | "title": "%command.favorite%", 253 | "icon": { 254 | "light": "resources/light/star-fill.svg", 255 | "dark": "resources/dark/star-fill.svg" 256 | } 257 | }, 258 | { 259 | "command": "squarecloud.openEntry", 260 | "title": "%command.open%" 261 | }, 262 | { 263 | "command": "squarecloud.startEntry", 264 | "title": "%command.start%" 265 | }, 266 | { 267 | "command": "squarecloud.stopEntry", 268 | "title": "%command.stop%" 269 | }, 270 | { 271 | "command": "squarecloud.restartEntry", 272 | "title": "%command.restart%" 273 | }, 274 | { 275 | "command": "squarecloud.deleteEntry", 276 | "title": "%command.delete%" 277 | }, 278 | { 279 | "command": "squarecloud.commitEntry", 280 | "title": "Commit" 281 | }, 282 | { 283 | "command": "squarecloud.snapshotEntry", 284 | "title": "Snapshot" 285 | }, 286 | { 287 | "command": "squarecloud.copyIdEntry", 288 | "title": "%command.copyId%" 289 | } 290 | ], 291 | "viewsContainers": { 292 | "activitybar": [ 293 | { 294 | "id": "squarecloud-container", 295 | "title": "Square Cloud", 296 | "icon": "resources/squarecloud.svg" 297 | } 298 | ] 299 | }, 300 | "views": { 301 | "squarecloud-container": [ 302 | { 303 | "id": "apps-view", 304 | "name": "%view.apps.title%", 305 | "icon": "resources/squarecloud.svg" 306 | }, 307 | { 308 | "id": "user-view", 309 | "name": "%view.user.title%", 310 | "icon": "resources/squarecloud.svg" 311 | } 312 | ], 313 | "explorer": [ 314 | { 315 | "id": "favapp-view", 316 | "name": "Square Cloud", 317 | "visibility": "hidden", 318 | "icon": "resources/squarecloud.svg" 319 | } 320 | ] 321 | }, 322 | "configuration": [ 323 | { 324 | "title": "Square Cloud", 325 | "properties": { 326 | "squarecloud.favApps": { 327 | "title": "Favorited apps will show in the explorer container.", 328 | "type": "array", 329 | "default": [] 330 | } 331 | } 332 | } 333 | ], 334 | "languages": [ 335 | { 336 | "id": "squarecloud.config", 337 | "icon": { 338 | "dark": "resources/dark/squarecloud.svg", 339 | "light": "resources/light/squarecloud.svg" 340 | }, 341 | "aliases": [ 342 | "Square Cloud Config" 343 | ], 344 | "filenames": [ 345 | "squarecloud.config", 346 | "squarecloud.app" 347 | ] 348 | }, 349 | { 350 | "id": "squareignore", 351 | "icon": { 352 | "dark": "resources/dark/squareignore.svg", 353 | "light": "resources/light/squareignore.svg" 354 | }, 355 | "aliases": [ 356 | "Square Cloud Ignore" 357 | ], 358 | "filenames": [ 359 | "squarecloud.ignore" 360 | ] 361 | } 362 | ], 363 | "grammars": [ 364 | { 365 | "language": "squarecloud.config", 366 | "scopeName": "source.squarecloud", 367 | "path": "./syntaxes/config.tmLanguage.json" 368 | } 369 | ] 370 | } 371 | } 372 | --------------------------------------------------------------------------------