Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .eslintignore

This file was deleted.

50 changes: 0 additions & 50 deletions .eslintrc.js

This file was deleted.

14 changes: 14 additions & 0 deletions .gemini/config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Config for the Gemini Pull Request Review Bot.
# https://github.com/marketplace/gemini-code-assist
have_fun: false
Expand Down
32 changes: 16 additions & 16 deletions .gemini/settings.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"mcpServers": {
"workspace-developer": {
"httpUrl": "https://workspace-developer.goog/mcp",
"trust": true
}
},
"tools": {
"allowed": [
"run_shell_command(pnpm install)",
"run_shell_command(pnpm format)",
"run_shell_command(pnpm lint)",
"run_shell_command(pnpm check)",
"run_shell_command(pnpm test)"
]
}
}
"mcpServers": {
"workspace-developer": {
"httpUrl": "https://workspace-developer.goog/mcp",
"trust": true
}
},
"tools": {
"allowed": [
"run_shell_command(pnpm install)",
"run_shell_command(pnpm format)",
"run_shell_command(pnpm lint)",
"run_shell_command(pnpm check)",
"run_shell_command(pnpm test)"
]
}
}
14 changes: 14 additions & 0 deletions .github/linters/.yaml-lint.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

---
###########################################
# These are the rules used for #
Expand Down
130 changes: 73 additions & 57 deletions .github/scripts/check-gs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@

/// <reference types="node" />

import { exec } from "node:child_process";
import {
readdirSync,
statSync,
copyFileSync,
existsSync,
rmSync,
mkdirSync,
copyFileSync,
writeFileSync
} from 'fs';
import {join, relative, dirname, resolve, sep} from 'path';
import {exec} from 'child_process';
import {promisify} from 'util';
readdirSync,
rmSync,
statSync,
writeFileSync,
} from "node:fs";
import { dirname, join, relative, resolve, sep } from "node:path";
import { promisify } from "node:util";

const execAsync = promisify(exec);
const TEMP_ROOT = '.tsc_check';
const TEMP_ROOT = ".tsc_check";

interface Project {
files: string[];
Expand All @@ -45,14 +45,18 @@ interface CheckResult {
}

// Helper to recursively find all files with a specific extension
function findFiles(dir: string, extension: string, fileList: string[] = []): string[] {
function findFiles(
dir: string,
extension: string,
fileList: string[] = [],
): string[] {
const files = readdirSync(dir);
for (const file of files) {
if (file.endsWith('.js')) continue;
if (file.endsWith(".js")) continue;
const filePath = join(dir, file);
const stat = statSync(filePath);
if (stat.isDirectory()) {
if (file !== 'node_modules' && file !== '.git' && file !== TEMP_ROOT) {
if (file !== "node_modules" && file !== ".git" && file !== TEMP_ROOT) {
findFiles(filePath, extension, fileList);
}
} else if (file.endsWith(extension)) {
Expand All @@ -64,10 +68,14 @@ function findFiles(dir: string, extension: string, fileList: string[] = []): str

// Find all directories containing appsscript.json
function findProjectRoots(rootDir: string): string[] {
return findFiles(rootDir, 'appsscript.json').map((f) => dirname(f));
return findFiles(rootDir, "appsscript.json").map((f) => dirname(f));
}

function createProjects(rootDir: string, projectRoots: string[], allGsFiles: string[]): Project[] {
function createProjects(
rootDir: string,
projectRoots: string[],
allGsFiles: string[],
): Project[] {
// Holds files that belong to a formal Apps Script project (defined by the presence of appsscript.json).
const projectGroups = new Map<string, string[]>();

Expand Down Expand Up @@ -107,7 +115,7 @@ function createProjects(rootDir: string, projectRoots: string[], allGsFiles: str
projects.push({
files,
name: `Project: ${relative(rootDir, dir)}`,
path: relative(rootDir, dir)
path: relative(rootDir, dir),
});
}
});
Expand All @@ -116,100 +124,109 @@ function createProjects(rootDir: string, projectRoots: string[], allGsFiles: str
projects.push({
files,
name: `Loose Project: ${relative(rootDir, dir)}`,
path: relative(rootDir, dir)
path: relative(rootDir, dir),
});
}
});

return projects;
}

async function checkProject(project: Project, rootDir: string): Promise<CheckResult> {
const projectNameSafe = project.name.replace(/[^a-zA-Z0-9]/g, '_');
async function checkProject(
project: Project,
rootDir: string,
): Promise<CheckResult> {
const projectNameSafe = project.name.replace(/[^a-zA-Z0-9]/g, "_");
const projectTempDir = join(TEMP_ROOT, projectNameSafe);

// Synchronous setup is fine as it's fast and avoids race conditions on mkdir if we were sharing dirs (we aren't)
mkdirSync(projectTempDir, {recursive: true});
mkdirSync(projectTempDir, { recursive: true });

for (const file of project.files) {
const fileRelPath = relative(rootDir, file);
const destPath = join(projectTempDir, fileRelPath.replace(/\.gs$/, '.js'));
const destPath = join(projectTempDir, fileRelPath.replace(/\.gs$/, ".js"));
const destDir = dirname(destPath);
mkdirSync(destDir, {recursive: true});
mkdirSync(destDir, { recursive: true });
copyFileSync(file, destPath);
}

const tsConfig = {
extends: '../../tsconfig.json',
extends: "../../tsconfig.json",
compilerOptions: {
noEmit: true,
allowJs: true,
checkJs: true,
typeRoots: [resolve(rootDir, 'node_modules/@types')]
typeRoots: [resolve(rootDir, "node_modules/@types")],
},
include: ['**/*.js']
include: ["**/*.js"],
};

writeFileSync(
join(projectTempDir, 'tsconfig.json'),
JSON.stringify(tsConfig, null, 2)
join(projectTempDir, "tsconfig.json"),
JSON.stringify(tsConfig, null, 2),
);

try {
await execAsync(`tsc -p "${projectTempDir}"`, {cwd: rootDir});
return {name: project.name, success: true, output: ''};
} catch (e: any) {
const rawOutput = (e.stdout || '') + (e.stderr || '');

const rewritten = rawOutput.split('\n').map((line: string) => {
if (line.includes(projectTempDir)) {
let newLine = line.split(projectTempDir + sep).pop();
if (!newLine) {
return line;
await execAsync(`tsc -p \"${projectTempDir}\"`, { cwd: rootDir });
return { name: project.name, success: true, output: "" };
} catch (e) {
const err = e as { stdout?: string; stderr?: string };
const rawOutput = (err.stdout ?? "") + (err.stderr || "");

const rewritten = rawOutput
.split("\n")
.map((line: string) => {
if (line.includes(projectTempDir)) {
let newLine = line.split(projectTempDir + sep).pop();
if (!newLine) {
return line;
}
newLine = newLine.replace(/\.js(:|\()/g, ".gs$1");
return newLine;
}
newLine = newLine.replace(/\.js(:|\()/g, '.gs$1');
return newLine;
}
return line;
}).join('\n');
return line;
})
.join("\n");

return {name: project.name, success: false, output: rewritten};
return { name: project.name, success: false, output: rewritten };
}
}

async function main() {
try {
const rootDir = resolve('.');
const rootDir = resolve(".");
const args = process.argv.slice(2);
const searchArg = args.find(arg => arg !== '--');
const searchArg = args.find((arg) => arg !== "--");

// 1. Discovery
const projectRoots = findProjectRoots(rootDir);
const allGsFiles = findFiles(rootDir, '.gs');
const allGsFiles = findFiles(rootDir, ".gs");

// 2. Grouping
const projects = createProjects(rootDir, projectRoots, allGsFiles);

// 3. Filtering
const projectsToCheck = projects.filter(p => {
const projectsToCheck = projects.filter((p) => {
return !searchArg || p.path.startsWith(searchArg);
});

if (projectsToCheck.length === 0) {
console.log('No projects found matching the search path.');
console.log("No projects found matching the search path.");
return;
}

// 4. Setup
if (existsSync(TEMP_ROOT)) {
rmSync(TEMP_ROOT, {recursive: true, force: true});
rmSync(TEMP_ROOT, { recursive: true, force: true });
}
mkdirSync(TEMP_ROOT);

console.log(`Checking ${projectsToCheck.length} projects in parallel...`);

// 5. Parallel Execution
const results = await Promise.all(projectsToCheck.map(p => checkProject(p, rootDir)));
const results = await Promise.all(
projectsToCheck.map((p) => checkProject(p, rootDir)),
);

// 6. Reporting
let hasError = false;
Expand All @@ -222,18 +239,17 @@ async function main() {
}

if (hasError) {
console.error('\nOne or more checks failed.');
console.error("\nOne or more checks failed.");
process.exit(1);
} else {
console.log('\nAll checks passed.');
console.log("\nAll checks passed.");
}

} catch (err) {
console.error('Unexpected error:', err);
console.error("Unexpected error:", err);
process.exit(1);
} finally {
if (existsSync(TEMP_ROOT)) {
rmSync(TEMP_ROOT, {recursive: true, force: true});
rmSync(TEMP_ROOT, { recursive: true, force: true });
}
}
}
Expand Down
6 changes: 2 additions & 4 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{
"recommendations": [
"google-workspace.google-workspace-developer-tools"
]
}
"recommendations": ["google-workspace.google-workspace-developer-tools"]
}
8 changes: 4 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"files.associations": {
"*.gs": "javascript"
}
}
"files.associations": {
"*.gs": "javascript"
}
}
Loading
Loading