diff --git a/.gitignore b/.gitignore index 99cce13a..719a1e03 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,18 @@ dist/ *.sqlite3 progress.txt progress*.txt + +# Security-sensitive files +.env +.env.local +.env.*.local +*.key +*.pem +*.secret + +# Test/Coverage +coverage/ +.nyc_output/ + +# Logs +*.log diff --git a/README.md b/README.md index a6ad3f76..328efcda 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,12 @@ antfarm dashboard status # Check status | `antfarm dashboard` | Start the web dashboard | | `antfarm logs []` | View recent log entries | +### Scripts + +| Script | Description | +|--------|-------------| +| `npm run hello` | Run the hello-world demo script (prints current date) | + --- ## Requirements diff --git a/package-lock.json b/package-lock.json index 3fa58666..a1ad386e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "antfarm", - "version": "0.4.1", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "antfarm", - "version": "0.4.1", + "version": "0.5.1", "dependencies": { "json5": "^2.2.3", "yaml": "^2.4.5" diff --git a/package.json b/package.json index c3af3fbf..b6752467 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ }, "scripts": { "build": "tsc -p tsconfig.json && cp src/server/index.html dist/server/index.html && chmod +x dist/cli/cli.js && node scripts/inject-version.js", - "start": "node dist/cli/cli.js" + "start": "node dist/cli/cli.js", + "hello": "node scripts/hello-world.js" }, "dependencies": { "json5": "^2.2.3", diff --git a/scripts/__tests__/hello-world.test.js b/scripts/__tests__/hello-world.test.js new file mode 100644 index 00000000..19794976 --- /dev/null +++ b/scripts/__tests__/hello-world.test.js @@ -0,0 +1,139 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { execFileSync, execSync } from "node:child_process"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { readFileSync } from "node:fs"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const scriptPath = join(__dirname, "..", "hello-world.js"); +const repoRoot = join(__dirname, "..", ".."); + +describe("hello-world", () => { + it("executes without errors", () => { + assert.doesNotThrow(() => { + execFileSync("node", [scriptPath], { encoding: "utf8" }); + }, "Script should execute without throwing errors"); + }); + + it("output contains 'Hello World!'", () => { + const output = execFileSync("node", [scriptPath], { encoding: "utf8" }); + assert.ok( + output.includes("Hello World!"), + "Output should contain 'Hello World!'" + ); + }); + + it("output contains a valid date/time string", () => { + const output = execFileSync("node", [scriptPath], { encoding: "utf8" }); + assert.ok( + output.includes("Current date:"), + "Output should contain 'Current date:' label" + ); + + // Verify that output contains something that looks like a date/time + // Should have digits, colons, and hyphens typical of ISO-style datetime + const dateTimePattern = /\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}/; + assert.ok( + dateTimePattern.test(output), + "Output should contain a valid date/time string in format YYYY-MM-DD HH:MM:SS" + ); + }); + + it("output format is consistent (date format verified by regex)", () => { + const output = execFileSync("node", [scriptPath], { encoding: "utf8" }); + + // Test the exact expected format: "Hello World! Current date: YYYY-MM-DD HH:MM:SS" + const formatPattern = /^Hello World! Current date: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\n$/; + assert.ok( + formatPattern.test(output), + `Output format should match pattern exactly. Got: "${output}"` + ); + }); + + it("date string represents a valid date", () => { + const output = execFileSync("node", [scriptPath], { encoding: "utf8" }); + + // Extract the date/time portion + const match = output.match(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/); + assert.ok(match, "Should find a date/time string in output"); + + const dateString = match[1].replace(" ", "T"); + const parsedDate = new Date(dateString); + + // Verify it's a valid date (not NaN) + assert.ok( + !isNaN(parsedDate.getTime()), + "Extracted date string should be parseable as a valid date" + ); + }); +}); + +describe("hello-world npm script integration", () => { + it("package.json includes 'hello' script", () => { + const packageJsonPath = join(repoRoot, "package.json"); + const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")); + + assert.ok( + packageJson.scripts && packageJson.scripts.hello, + "package.json should have a 'hello' script defined" + ); + + assert.equal( + packageJson.scripts.hello, + "node scripts/hello-world.js", + "The 'hello' script should run the hello-world.js script" + ); + }); + + it("npm run hello executes successfully", () => { + assert.doesNotThrow(() => { + execSync("npm run hello", { + cwd: repoRoot, + encoding: "utf8", + stdio: "pipe" + }); + }, "npm run hello should execute without errors"); + }); + + it("npm run hello produces correct output", () => { + const output = execSync("npm run hello", { + cwd: repoRoot, + encoding: "utf8", + stdio: "pipe" + }); + + // Output from npm run includes extra lines, so check for the content + assert.ok( + output.includes("Hello World!"), + "npm run hello output should contain 'Hello World!'" + ); + + assert.ok( + output.includes("Current date:"), + "npm run hello output should contain 'Current date:'" + ); + + // Verify date/time format is present + const dateTimePattern = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/; + assert.ok( + dateTimePattern.test(output), + "npm run hello output should contain a valid date/time string" + ); + }); + + it("README.md documents the hello script", () => { + const readmePath = join(repoRoot, "README.md"); + const readmeContent = readFileSync(readmePath, "utf8"); + + assert.ok( + readmeContent.includes("npm run hello") || readmeContent.includes("`npm run hello`"), + "README.md should document the 'npm run hello' command" + ); + + assert.ok( + readmeContent.toLowerCase().includes("hello"), + "README.md should mention the hello script" + ); + }); +}); diff --git a/scripts/hello-world.js b/scripts/hello-world.js new file mode 100755 index 00000000..6d611e80 --- /dev/null +++ b/scripts/hello-world.js @@ -0,0 +1,10 @@ +#!/usr/bin/env node +/** + * Hello World script that prints the current date and time. + * Simple utility script for demonstration purposes. + */ + +const now = new Date(); +const formattedDate = now.toISOString().replace('T', ' ').substring(0, 19); + +console.log(`Hello World! Current date: ${formattedDate}`); diff --git a/tests/hello-world.test.ts b/tests/hello-world.test.ts new file mode 100644 index 00000000..4b88b1f2 --- /dev/null +++ b/tests/hello-world.test.ts @@ -0,0 +1,39 @@ +/** + * Tests for the hello-world.js script. + * Verifies that the script outputs "Hello World!" with current date and time. + */ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import path from "node:path"; + +const SCRIPT = path.resolve(import.meta.dirname, "..", "scripts", "hello-world.js"); + +describe("hello-world.js script", () => { + it("prints 'Hello World!' followed by current date", () => { + const output = execFileSync("node", [SCRIPT], { encoding: "utf-8" }); + assert.ok(output.includes("Hello World!"), "should include 'Hello World!'"); + assert.ok(output.includes("Current date:"), "should include 'Current date:'"); + }); + + it("output includes date in YYYY-MM-DD format", () => { + const output = execFileSync("node", [SCRIPT], { encoding: "utf-8" }); + // Match YYYY-MM-DD pattern + const datePattern = /\d{4}-\d{2}-\d{2}/; + assert.ok(datePattern.test(output), `output should contain date in YYYY-MM-DD format, got: "${output}"`); + }); + + it("output includes time in HH:MM:SS format", () => { + const output = execFileSync("node", [SCRIPT], { encoding: "utf-8" }); + // Match HH:MM:SS pattern + const timePattern = /\d{2}:\d{2}:\d{2}/; + assert.ok(timePattern.test(output), `output should contain time in HH:MM:SS format, got: "${output}"`); + }); + + it("script is executable", async () => { + const { statSync } = await import("node:fs"); + const stats = statSync(SCRIPT); + const isExecutable = !!(stats.mode & 0o111); + assert.ok(isExecutable, "script should have executable permissions"); + }); +});