Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ exportBackup
.tago-lock.dev-ue.lock
.tago-lock.dev-ue-2.lock
.tago-lock.dev-1.lock

# generated by `npm run man` and shipped via the `files` field at publish time
man/
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Installing the TagoIO Command Line Tools is a straightforward process. Follow th
tagoio login
```

6. **Reference (Optional)**: A man page is installed alongside the CLI. Run `man tagoio` for the full command reference. Fish users can additionally run `fish_update_completions` to enable tab-completion (fish auto-extracts flag metadata from the installed man page).

## Command List
List of commands of the CLI
**Usage**:
Expand Down
582 changes: 330 additions & 252 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@
"npm": ">=10.0.0"
},
"files": [
"build"
"build",
"man"
],
"scripts": {
"build": "rm -rf ./build; tsc --build; chmod +x ./build/index.js",
"man": "mkdir -p man && tsx src/lib/generate-man.ts > man/tagoio.1 && chmod 644 man/tagoio.1",
"build": "npm run man && rm -rf ./build; tsc --build; chmod +x ./build/index.js",
"prepublishOnly": "npm run man",
"test": "vitest run",
"test:single": "vitest --",
"test:coverage": "vitest run --coverage",
Expand All @@ -35,6 +38,7 @@
"bin": {
"tagoio": "./build/index.js"
},
"man": "./man/tagoio.1",
"author": "TagoIO LLC",
"license": "ISC",
"dependencies": {
Expand All @@ -59,8 +63,8 @@
"@types/prompts": "^2.4.9",
"@types/unzipper": "^0.10.11",
"@vitest/coverage-v8": "^4.1.5",
"oxfmt": "^0.46.0",
"oxlint": "1.61.0",
"oxfmt": "^0.47.0",
"oxlint": "1.62.0",
"typescript": "^6.0.3",
"vitest": "^4.1.5"
}
Expand Down
10 changes: 10 additions & 0 deletions src/commands/analysis/analysis-console.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ vi.mock("../../lib/messages.js", () => ({
highlightMSG: (s: string) => s,
}));

vi.mock("../../lib/resolve-scope.js", () => ({
requireLocalScope: () => ({
scope: "local" as const,
root: "/repo",
configPath: "/repo/tagoconfig.json",
envFilePath: "/repo/.tagoio/personal.env",
configExists: true,
}),
}));

describe("connectAnalysisConsole", () => {
const analysisList = [{ name: "script", fileName: "script.ts", id: "an-1" }];

Expand Down
9 changes: 6 additions & 3 deletions src/commands/analysis/analysis-console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Account, AnalysisInfo } from "@tago-io/sdk";
import { EventSource } from "eventsource";
import { getEnvironmentConfig, IEnvironment } from "../../lib/config-file.js";
import { errorHandler, highlightMSG, infoMSG, successMSG } from "../../lib/messages.js";
import { requireLocalScope } from "../../lib/resolve-scope.js";
import { searchName } from "../../lib/search-name.js";
import { pickAnalysisFromConfig } from "../../prompt/pick-analysis-from-config.js";

Expand All @@ -25,8 +26,8 @@ function apiSSE(profileToken: string, analysisID: string, urlSSERealtime?: strin
* @param analysisList - The list of analysis objects to search through.
* @returns The script object that matches the script name or the one selected by the user.
*/
async function getScriptObj(scriptName: string | void, analysisList: IEnvironment["analysisList"]) {
let scriptObj: IEnvironment["analysisList"][0] | undefined;
async function getScriptObj(scriptName: string | void, analysisList: NonNullable<IEnvironment["analysisList"]>) {
let scriptObj: NonNullable<IEnvironment["analysisList"]>[number] | undefined;
if (scriptName) {
scriptObj = searchName(
scriptName,
Expand Down Expand Up @@ -69,12 +70,14 @@ function setupSSE(sse: ReturnType<typeof apiSSE>, _script_id: string, analysis_i
* @returns void
*/
async function connectAnalysisConsole(scriptName: string | void, options: { environment: string }) {
requireLocalScope("analysis-console");

const config = getEnvironmentConfig(options.environment);
if (!config || !config.profileToken) {
errorHandler("Environment not found");
}

const scriptObj = await getScriptObj(scriptName, config.analysisList);
const scriptObj = await getScriptObj(scriptName, config.analysisList ?? []);
if (!scriptObj) {
errorHandler(`Analysis not found: ${scriptName}`);
}
Expand Down
10 changes: 10 additions & 0 deletions src/commands/analysis/analysis-set-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ vi.mock("../../lib/messages.js", () => ({
highlightMSG: (s: string) => s,
}));

vi.mock("../../lib/resolve-scope.js", () => ({
requireLocalScope: () => ({
scope: "local" as const,
root: "/repo",
configPath: "/repo/tagoconfig.json",
envFilePath: "/repo/.tagoio/personal.env",
configExists: true,
}),
}));

describe("analysisSetMode", () => {
// Factory — the command sorts these in place, so each test needs a fresh copy.
const makeAnalyses = () => [
Expand Down
3 changes: 3 additions & 0 deletions src/commands/analysis/analysis-set-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import kleur from "kleur";

import { getEnvironmentConfig } from "../../lib/config-file.js";
import { errorHandler, infoMSG, successMSG } from "../../lib/messages.js";
import { requireLocalScope } from "../../lib/resolve-scope.js";
import { chooseFromList } from "../../prompt/choose-from-list.js";
import { pickFromList } from "../../prompt/pick-from-list.js";

Expand Down Expand Up @@ -62,6 +63,8 @@ async function chooseAnalysisToUpdateRunOnMode(
* @param options.filterMode - The filter mode to use when retrieving the analysis list.
*/
async function analysisSetMode(userInputName: string | void, options: { environment: string; mode: string; filterMode: string }) {
requireLocalScope("analysis-mode");

const config = getEnvironmentConfig(options.environment);
if (!config || !config.profileToken) {
errorHandler("Environment not found");
Expand Down
14 changes: 12 additions & 2 deletions src/commands/analysis/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,18 @@ vi.mock("../../lib/current-runtime.js", () => ({
detectRuntime: detectRuntimeMock,
}));

vi.mock("../../lib/get-current-folder.js", () => ({
getCurrentFolder: () => "/repo",
vi.mock("../../lib/resolve-scope.js", () => ({
requireLocalScope: () => ({
scope: "local" as const,
root: "/repo",
configPath: "/repo/tagoconfig.json",
envFilePath: "/repo/.tagoio/personal.env",
configExists: true,
}),
}));

vi.mock("../../lib/scope-notice.js", () => ({
printScopeBanner: vi.fn(),
}));

vi.mock("../../lib/messages.js", () => ({
Expand Down
23 changes: 15 additions & 8 deletions src/commands/analysis/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { Account, RunTypeOptions } from "@tago-io/sdk";

import { getEnvironmentConfig, IConfigFile, IEnvironment } from "../../lib/config-file.js";
import { detectRuntime } from "../../lib/current-runtime.js";
import { getCurrentFolder } from "../../lib/get-current-folder.js";
import { errorHandler, infoMSG, successMSG } from "../../lib/messages.js";
import { requireLocalScope } from "../../lib/resolve-scope.js";
import { printScopeBanner } from "../../lib/scope-notice.js";
import { searchName } from "../../lib/search-name.js";
import { chooseAnalysisListFromConfig } from "../../prompt/choose-analysis-list-config.js";
import { confirmAnalysisFromConfig } from "../../prompt/confirm-analysis-list.js";
Expand All @@ -20,18 +21,19 @@ interface BuildScriptParams {
config: EnvConfig;
runtime: string;
path: string;
/** Resolved local-scope project root. */
projectRoot: string;
}

/**
* Returns an object containing the paths for analysis, build and current folder.
* @param config - An object containing the configuration for the environment.
* @returns An object containing the paths for analysis, build and current folder.
*/
function getPaths(config: EnvConfig) {
const folderPath = getCurrentFolder();
function getPaths(config: EnvConfig, projectRoot: string) {
const buildPath = config.buildPath || `./build`;
const analysisPath = config.analysisPath || `./src/analysis`;
return { analysisPath, buildPath, folderPath };
return { analysisPath, buildPath, folderPath: projectRoot };
}

/**
Expand Down Expand Up @@ -64,8 +66,8 @@ async function deleteOldFile(buildedFile: string) {
* @param params - The parameters for building and uploading the script.
*/
async function buildScript(params: BuildScriptParams) {
const { account, scriptName, analysisID, config, runtime, path } = params;
const { analysisPath, buildPath, folderPath } = getPaths(config);
const { account, scriptName, analysisID, config, runtime, path, projectRoot } = params;
const { analysisPath, buildPath, folderPath } = getPaths(config, projectRoot);

let analysisFile;
if (path) {
Expand Down Expand Up @@ -142,6 +144,10 @@ async function deployAnalysis(cmdScriptName: string, options: IDeployOptions) {
errorHandler('Did you mean "tagoio deploy --all"? The "all" positional argument is no longer supported.');
}

// Analysis development requires a project directory.
const scope = requireLocalScope("analysis-deploy");
printScopeBanner(scope, options.silent);

const config = getEnvironmentConfig(options.environment);
if (!config) {
errorHandler("Environment not found");
Expand All @@ -155,12 +161,12 @@ async function deployAnalysis(cmdScriptName: string, options: IDeployOptions) {
}

// --all skips selection entirely; everything in analysisList with a fileName ships.
let scriptList = config.analysisList.filter((x) => x.fileName);
let scriptList = (config.analysisList ?? []).filter((x) => x.fileName);
if (!options.all) {
if (!cmdScriptName) {
scriptList = await chooseAnalysisListFromConfig(scriptList);
} else {
const analysisFound: IEnvironment["analysisList"][0] = searchName(
const analysisFound: NonNullable<IEnvironment["analysisList"]>[number] = searchName(
cmdScriptName,
scriptList.map((x) => ({ names: [x.name, x.fileName], value: x })),
);
Expand Down Expand Up @@ -202,6 +208,7 @@ async function deployAnalysis(cmdScriptName: string, options: IDeployOptions) {
config,
runtime,
path: path || "",
projectRoot: scope.root,
});
}
process.exit();
Expand Down
10 changes: 10 additions & 0 deletions src/commands/analysis/duplicate-analysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ vi.mock("../../lib/messages.js", () => ({
highlightMSG: (s: string) => s,
}));

vi.mock("../../lib/resolve-scope.js", () => ({
requireLocalScope: () => ({
scope: "local" as const,
root: "/repo",
configPath: "/repo/tagoconfig.json",
envFilePath: "/repo/.tagoio/personal.env",
configExists: true,
}),
}));

const pickAnalysisFromTagoIOMock = vi.fn();
vi.mock("../../prompt/pick-analysis-from-tagoio.js", () => ({
pickAnalysisFromTagoIO: (...args: unknown[]) => pickAnalysisFromTagoIOMock(...args),
Expand Down
3 changes: 3 additions & 0 deletions src/commands/analysis/duplicate-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import zlib from "node:zlib";

import { getEnvironmentConfig } from "../../lib/config-file.js";
import { errorHandler, successMSG } from "../../lib/messages.js";
import { requireLocalScope } from "../../lib/resolve-scope.js";
import { pickAnalysisFromTagoIO } from "../../prompt/pick-analysis-from-tagoio.js";

/**
Expand Down Expand Up @@ -78,6 +79,8 @@ async function downloadScriptBase64(account: Account, analysisId: string): Promi
* @throws An error if the analysis ID is not found or if the environment is not found.
*/
async function duplicateAnalysis(analysisID: string | void, options: { environment: string; name?: string }) {
requireLocalScope("analysis-duplicate");

const config = getEnvironmentConfig(options.environment);
if (!config || !config.profileToken) {
errorHandler("Environment not found");
Expand Down
10 changes: 8 additions & 2 deletions src/commands/analysis/run-analysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ vi.mock("../../lib/current-runtime.js", () => ({
detectRuntime: detectRuntimeMock,
}));

vi.mock("../../lib/get-current-folder.js", () => ({
getCurrentFolder: () => "/tmp/test",
vi.mock("../../lib/resolve-scope.js", () => ({
requireLocalScope: () => ({
scope: "local" as const,
root: "/tmp/test",
configPath: "/tmp/test/tagoconfig.json",
envFilePath: "/tmp/test/.tagoio/personal.env",
configExists: true,
}),
}));

vi.mock("../../lib/messages.js", () => ({
Expand Down
11 changes: 7 additions & 4 deletions src/commands/analysis/run-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { Account } from "@tago-io/sdk";

import { getEnvironmentConfig, IEnvironment, resolveCLIPath } from "../../lib/config-file.js";
import { detectRuntime } from "../../lib/current-runtime.js";
import { getCurrentFolder } from "../../lib/get-current-folder.js";
import { errorHandler, highlightMSG, successMSG } from "../../lib/messages.js";
import { requireLocalScope } from "../../lib/resolve-scope.js";
import { searchName } from "../../lib/search-name.js";
import { pickAnalysisFromConfig } from "../../prompt/pick-analysis-from-config.js";

Expand Down Expand Up @@ -68,13 +68,16 @@ async function runAnalysis(
scriptName: string | undefined,
options: { environment: string; debug: boolean; clear: boolean; tsnd: boolean; deno: boolean; node: boolean },
) {
// Analysis development requires a project directory.
const scope = requireLocalScope("analysis-run");

const config = getEnvironmentConfig(options.environment);
if (!config || !config.profileToken) {
errorHandler("Environment not found");
}

const analysisList = config.analysisList.filter((x) => x.fileName);
let scriptToRun: IEnvironment["analysisList"][0];
const analysisList = (config.analysisList ?? []).filter((x) => x.fileName);
let scriptToRun: NonNullable<IEnvironment["analysisList"]>[number];
if (scriptName) {
scriptName = scriptName.toLowerCase();
scriptToRun = searchName(
Expand Down Expand Up @@ -111,7 +114,7 @@ async function runAnalysis(

const spawnOptions: SpawnOptions = {
shell: true,
cwd: getCurrentFolder(),
cwd: scope.root,
stdio: "inherit",
env: analysisEnv,
};
Expand Down
10 changes: 10 additions & 0 deletions src/commands/analysis/trigger-analysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ vi.mock("../../lib/messages.js", () => ({
highlightMSG: (s: string) => s,
}));

vi.mock("../../lib/resolve-scope.js", () => ({
requireLocalScope: () => ({
scope: "local" as const,
root: "/repo",
configPath: "/repo/tagoconfig.json",
envFilePath: "/repo/.tagoio/personal.env",
configExists: true,
}),
}));

describe("triggerAnalysis", () => {
const analysisList = [
{ name: "myScript", fileName: "my-script.ts", id: "an-1" },
Expand Down
10 changes: 7 additions & 3 deletions src/commands/analysis/trigger-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import kleur from "kleur";

import { getEnvironmentConfig, IEnvironment } from "../../lib/config-file.js";
import { errorHandler, infoMSG, successMSG } from "../../lib/messages.js";
import { requireLocalScope } from "../../lib/resolve-scope.js";
import { searchName } from "../../lib/search-name.js";
import { pickAnalysisFromConfig } from "../../prompt/pick-analysis-from-config.js";
import { pickAnalysisFromTagoIO } from "../../prompt/pick-analysis-from-tagoio.js";
Expand All @@ -16,16 +17,19 @@ import { pickAnalysisFromTagoIO } from "../../prompt/pick-analysis-from-tagoio.j
* @param options.tago - Whether to pick the analysis from TagoIO.
*/
async function triggerAnalysis(scriptName: string | void, options: { environment?: string; json?: string; tago: boolean }) {
requireLocalScope("analysis-trigger");

const config = getEnvironmentConfig(options.environment);

if (!config || !config.profileToken) {
errorHandler("Environment not found");
}

const account = new Account({ token: config.profileToken, region: config.profileRegion });
const analysisList = config.analysisList.filter((x) => x.fileName);
const fullList = config.analysisList ?? [];
const analysisList = fullList.filter((x) => x.fileName);

let script: IEnvironment["analysisList"][0] | undefined;
let script: NonNullable<IEnvironment["analysisList"]>[number] | undefined;

if (!scriptName && options.tago) {
const analysis = await pickAnalysisFromTagoIO(account);
Expand All @@ -35,7 +39,7 @@ async function triggerAnalysis(scriptName: string | void, options: { environment
} else {
script = searchName(
scriptName,
config.analysisList.map((x) => ({ names: [x.name, x.fileName], value: x })),
fullList.map((x) => ({ names: [x.name, x.fileName], value: x })),
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/commands/devices/change-bucket-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import kleur from "kleur";

import { getEnvironmentConfig } from "../../lib/config-file.js";
import { errorHandler, infoMSG, successMSG } from "../../lib/messages.js";
import { resolveScope } from "../../lib/resolve-scope.js";
import { printScopeBanner } from "../../lib/scope-notice.js";
import { chooseFromList } from "../../prompt/choose-from-list.js";
import { promptNumber } from "../../prompt/number-prompt.js";
import { pickFromList } from "../../prompt/pick-from-list.js";
Expand Down Expand Up @@ -97,6 +99,8 @@ async function chooseBucketsFromList(account: Account) {
}

async function changeBucketType(id: string, options: { environment: string }) {
printScopeBanner(resolveScope());

const config = getEnvironmentConfig(options.environment);
if (!config || !config.profileToken) {
errorHandler("Environment not found");
Expand Down
Loading