#!/usr/bin/env node import { spawnSync } from 'node:child_process'; import { createRequire } from 'node:module'; import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; const args = process.argv.slice(2); const includeLint = args.includes('--lint'); const positional = args.filter(arg => !arg.startsWith('--')); const root = resolve(positional[0] ?? process.cwd()); const require = createRequire(import.meta.url); function fail(message) { throw new Error(message); } function readJson(relativePath) { const absolutePath = resolve(root, relativePath); if (!existsSync(absolutePath)) { fail(`Missing ${relativePath}`); } try { return JSON.parse(readFileSync(absolutePath, 'utf8')); } catch (error) { fail(`Invalid JSON in ${relativePath}: ${error.message}`); } } function runNpm(script) { const windows = process.platform === 'win32'; const command = windows ? (process.env.ComSpec ?? 'cmd.exe') : 'npm'; const commandArgs = windows ? ['/d', '/s', '/c', `npm run ${script}`] : ['run', script]; const result = spawnSync(command, commandArgs, { cwd: root, stdio: 'inherit' }); if (result.error) { fail(`Could not run npm script ${script}: ${result.error.message}`); } if (result.status !== 0) { fail(`npm run ${script} exited with ${result.status}`); } } function assertContributionPaths(packageJson) { const paths = [packageJson.main]; for (const language of packageJson.contributes?.languages ?? []) { paths.push(language.configuration); } for (const grammar of packageJson.contributes?.grammars ?? []) { paths.push(grammar.path); } for (const snippet of packageJson.contributes?.snippets ?? []) { paths.push(snippet.path); } for (const relativePath of paths.filter(Boolean)) { if (!existsSync(resolve(root, relativePath))) { fail(`package.json references missing path ${relativePath}`); } } } function smokeTestCore() { const tokenizerPath = resolve(root, 'out/tokenizer.js'); const parserPath = resolve(root, 'out/parser.js'); if (!existsSync(tokenizerPath) || !existsSync(parserPath)) { fail('Compiled tokenizer/parser output is missing'); } const { EftlTokenizer } = require(tokenizerPath); const { EftlParser } = require(parserPath); const sample = readFileSync(resolve(root, 'examples/sample.eftl'), 'utf8'); const sampleResult = new EftlTokenizer(sample).tokenize(); const sampleParse = new EftlParser(sampleResult.tokens).parse(); if (sampleResult.errors.length || sampleParse.errors.length) { fail(`sample.eftl is not accepted: ${sampleResult.errors.length} tokenizer and ${sampleParse.errors.length} parser errors`); } const mismatched = new EftlTokenizer('[EFTL][IF][/EFTL]').tokenize(); if (new EftlParser(mismatched.tokens).parse().errors.length === 0) { fail('Parser smoke case did not reject mismatched nesting'); } const comment = new EftlTokenizer('[EFTL][!-- unfinished').tokenize(); if (comment.errors.length === 0) { fail('Tokenizer smoke case did not reject an unterminated comment'); } } try { const packageJson = readJson('package.json'); readJson('language-configuration.json'); readJson('syntaxes/eftl.tmLanguage.json'); readJson('snippets/eftl.json'); runNpm('compile'); assertContributionPaths(packageJson); smokeTestCore(); if (packageJson.scripts?.test) { runNpm('test'); } else { console.warn('WARN: package.json has no test script; only smoke cases were run.'); } if (includeLint) { runNpm('lint'); } else if (packageJson.scripts?.lint) { console.warn('WARN: lint was skipped; pass --lint after configuring ESLint.'); } console.log('EFTL repository baseline checks passed.'); } catch (error) { console.error(`FAIL: ${error.message}`); process.exitCode = 1; }