From 73fcba9be3cbb0f72b20b17584bdf8a42db8d509 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 18 Dec 2025 11:15:25 +0000 Subject: [PATCH 1/4] test: created test script to check connection to Airtable --- docs/report.md | 3 ++ package-lock.json | 100 +++++++++++++++++++++++++++++++++++++++ package.json | 3 ++ scripts/test-airtable.ts | 26 ++++++++++ 4 files changed, 132 insertions(+) create mode 100644 docs/report.md create mode 100644 scripts/test-airtable.ts diff --git a/docs/report.md b/docs/report.md new file mode 100644 index 0000000..44dc68e --- /dev/null +++ b/docs/report.md @@ -0,0 +1,3 @@ +# Creating the project (Boilerplate) +I created the boiler plate code, basic CI and deployment at the very beggining off the project (AP-3, AP-2, AP-8). +The idea is to have as soon as possible a "Hello world". In this context that would be a deployed sveltkit app that can read and write a value on Airtable. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9be3ba5..a1d2c90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "apprentice-pulse", "version": "0.0.1", "hasInstallScript": true, + "dependencies": { + "airtable": "^0.12.2" + }, "devDependencies": { "@eslint/compat": "^1.4.0", "@eslint/js": "^9.39.1", @@ -1908,6 +1911,24 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.8.tgz", + "integrity": "sha512-9f1iZ2uWh92VcrU9Y8x+LdM4DLj75VE0MJB8zuF1iUnroEptStw+DQ8EQPMUdfe5k+PkB1uUfDQfWbhstH8LrQ==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1931,6 +1952,28 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/airtable": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/airtable/-/airtable-0.12.2.tgz", + "integrity": "sha512-HS3VytUBTKj8A0vPl7DDr5p/w3IOGv6RXL0fv7eczOWAtj9Xe8ri4TAiZRXoOyo+Z/COADCj+oARFenbxhmkIg==", + "license": "MIT", + "dependencies": { + "@types/node": ">=8.0.0 <15", + "abort-controller": "^3.0.0", + "abortcontroller-polyfill": "^1.4.0", + "lodash": "^4.17.21", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/airtable/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2490,6 +2533,15 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -3104,6 +3156,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3187,6 +3245,26 @@ "dev": true, "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -3870,6 +3948,12 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -4160,6 +4244,22 @@ "vitest": "^4.0.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 6c7c161..8d02782 100644 --- a/package.json +++ b/package.json @@ -36,5 +36,8 @@ "vite": "^7.2.6", "vitest": "^4.0.15", "vitest-browser-svelte": "^2.0.1" + }, + "dependencies": { + "airtable": "^0.12.2" } } diff --git a/scripts/test-airtable.ts b/scripts/test-airtable.ts new file mode 100644 index 0000000..d61837e --- /dev/null +++ b/scripts/test-airtable.ts @@ -0,0 +1,26 @@ +import Airtable from 'airtable'; +import 'dotenv/config'; + +const apiKey = process.env.AIRTABLE_API_KEY; +const baseIdLearners = process.env.AIRTABLE_BASE_ID_LEARNERS; +const baseIdFeedback = process.env.AIRTABLE_BASE_ID_FEEDBACK; + +if (!apiKey || !baseIdLearners || !baseIdFeedback) { + console.error('Missing environment variables'); + process.exit(1); +} + +Airtable.configure({ apiKey }); + +async function testConnection() { + console.log('Testing Learners base...'); + const learnersBase = Airtable.base(baseIdLearners); + + // List first table's records (you'll need to replace 'Table 1' with actual table name) + const records = await learnersBase('Table 1').select({ maxRecords: 3 }).all(); + console.log(`Found ${records.length} records`); + + console.log('\nConnection successful!'); +} + +testConnection().catch(console.error); \ No newline at end of file From 4754e68246e9b17b8b1016b8c131432bd17092c0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 18 Dec 2025 12:24:03 +0000 Subject: [PATCH 2/4] feat: added ESlint configs --- .vscode/settings.json | 4 ++++ eslint.config.js | 7 +++++++ package-lock.json | 36 ++++++++++++++++++++++++++++++++++++ package.json | 2 ++ scripts/test-airtable.ts | 35 +++++++++++++++++++++++------------ 5 files changed, 72 insertions(+), 12 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bc31e15..d5fef79 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { "files.associations": { "*.css": "tailwindcss" + }, + "editor.formatOnSave": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" } } diff --git a/eslint.config.js b/eslint.config.js index 0496090..f415b68 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url'; import { includeIgnoreFile } from '@eslint/compat'; import js from '@eslint/js'; import svelte from 'eslint-plugin-svelte'; +import stylistic from '@stylistic/eslint-plugin'; import { defineConfig } from 'eslint/config'; import globals from 'globals'; import ts from 'typescript-eslint'; @@ -14,6 +15,12 @@ export default defineConfig( js.configs.recommended, ...ts.configs.recommended, ...svelte.configs.recommended, + stylistic.configs.customize({ + indent: 'tab', + quotes: 'single', + semi: true, + jsx: false, + }), { languageOptions: { globals: { ...globals.browser, ...globals.node } }, diff --git a/package-lock.json b/package-lock.json index a1d2c90..f7d7a84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,14 @@ "devDependencies": { "@eslint/compat": "^1.4.0", "@eslint/js": "^9.39.1", + "@stylistic/eslint-plugin": "^5.6.1", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/kit": "^2.49.1", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.17", "@types/node": "^22", "@vitest/browser-playwright": "^4.0.15", + "dotenv": "^17.2.3", "eslint": "^9.39.1", "eslint-plugin-svelte": "^3.13.1", "globals": "^16.5.0", @@ -1078,6 +1080,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.6.1.tgz", + "integrity": "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.0", + "@typescript-eslint/types": "^8.47.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", @@ -2242,6 +2265,19 @@ "dev": true, "license": "MIT" }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.4", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", diff --git a/package.json b/package.json index 8d02782..dcb9fa0 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,14 @@ "devDependencies": { "@eslint/compat": "^1.4.0", "@eslint/js": "^9.39.1", + "@stylistic/eslint-plugin": "^5.6.1", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/kit": "^2.49.1", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.17", "@types/node": "^22", "@vitest/browser-playwright": "^4.0.15", + "dotenv": "^17.2.3", "eslint": "^9.39.1", "eslint-plugin-svelte": "^3.13.1", "globals": "^16.5.0", diff --git a/scripts/test-airtable.ts b/scripts/test-airtable.ts index d61837e..a37a3cd 100644 --- a/scripts/test-airtable.ts +++ b/scripts/test-airtable.ts @@ -1,26 +1,37 @@ import Airtable from 'airtable'; -import 'dotenv/config'; +import dotenv from 'dotenv'; + +// Load environment variables from .env file +dotenv.config({ path: process.env.NODE_ENV === 'production' ? '.env.production' : '.env.local' }); const apiKey = process.env.AIRTABLE_API_KEY; const baseIdLearners = process.env.AIRTABLE_BASE_ID_LEARNERS; const baseIdFeedback = process.env.AIRTABLE_BASE_ID_FEEDBACK; if (!apiKey || !baseIdLearners || !baseIdFeedback) { - console.error('Missing environment variables'); - process.exit(1); + console.error('Missing environment variables'); + process.exit(1); } Airtable.configure({ apiKey }); async function testConnection() { - console.log('Testing Learners base...'); - const learnersBase = Airtable.base(baseIdLearners); - - // List first table's records (you'll need to replace 'Table 1' with actual table name) - const records = await learnersBase('Table 1').select({ maxRecords: 3 }).all(); - console.log(`Found ${records.length} records`); - - console.log('\nConnection successful!'); + console.log('Testing Learners base...'); + console.log('API Key starts with:', apiKey?.substring(0, 10)); + console.log('Learners Base ID:', baseIdLearners); + const learnersBase = Airtable.base(baseIdLearners ?? ''); + + const records = await learnersBase('Attendance (Current)').select().all(); + console.log(`Found ${records.length} records`); + + console.log('\nTesting write access...'); + const newRecord = await learnersBase('Attendance (Current)').create({ + Apprentice: 'Alexander Rodriguez', + Cohort: 'FAC29', + }); + console.log('Created record:', newRecord.id); + + console.log('\nConnection successful!'); } -testConnection().catch(console.error); \ No newline at end of file +testConnection().catch(console.error); From 5bd0c69f2d00afa1dec0bd8c4f0f04780f191fa7 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 18 Dec 2025 12:51:51 +0000 Subject: [PATCH 3/4] test: added write test on the airtable test script --- scripts/test-airtable.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/scripts/test-airtable.ts b/scripts/test-airtable.ts index a37a3cd..0f04c8d 100644 --- a/scripts/test-airtable.ts +++ b/scripts/test-airtable.ts @@ -1,14 +1,11 @@ import Airtable from 'airtable'; import dotenv from 'dotenv'; - -// Load environment variables from .env file -dotenv.config({ path: process.env.NODE_ENV === 'production' ? '.env.production' : '.env.local' }); +dotenv.config({ path: '.env.local' }); const apiKey = process.env.AIRTABLE_API_KEY; const baseIdLearners = process.env.AIRTABLE_BASE_ID_LEARNERS; -const baseIdFeedback = process.env.AIRTABLE_BASE_ID_FEEDBACK; -if (!apiKey || !baseIdLearners || !baseIdFeedback) { +if (!apiKey || !baseIdLearners) { console.error('Missing environment variables'); process.exit(1); } @@ -16,22 +13,27 @@ if (!apiKey || !baseIdLearners || !baseIdFeedback) { Airtable.configure({ apiKey }); async function testConnection() { - console.log('Testing Learners base...'); - console.log('API Key starts with:', apiKey?.substring(0, 10)); - console.log('Learners Base ID:', baseIdLearners); - const learnersBase = Airtable.base(baseIdLearners ?? ''); - - const records = await learnersBase('Attendance (Current)').select().all(); - console.log(`Found ${records.length} records`); - - console.log('\nTesting write access...'); - const newRecord = await learnersBase('Attendance (Current)').create({ - Apprentice: 'Alexander Rodriguez', - Cohort: 'FAC29', + const base = Airtable.base(baseIdLearners ?? ''); + const table = base('Test for Apprentice-pulse'); + + console.log('Testing write access...'); + const newRecord = await table.create({ + Name: 'Test Record', }); console.log('Created record:', newRecord.id); - console.log('\nConnection successful!'); + console.log('\nTesting read access...'); + const records = await table.select({ maxRecords: 3 }).all(); + console.log(`Found ${records.length} records:`); + records.forEach((record) => { + console.log(` - ${record.id}: ${record.get('Name')}`); + }); + + // console.log('\nTesting delete...'); + // await table.destroy(newRecord.id); + // console.log('Deleted test record'); + + console.log('\n✅ All tests passed!'); } testConnection().catch(console.error); From 674f746bc4f54c714a5509d91696bb00c474ab9a Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 18 Dec 2025 13:28:45 +0000 Subject: [PATCH 4/4] docs: updated project report --- docs/report.md | 8 +++++++- src/routes/page.svelte.spec.ts | 2 +- vite.config.ts | 16 ++++++++-------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/report.md b/docs/report.md index 44dc68e..96ac689 100644 --- a/docs/report.md +++ b/docs/report.md @@ -1,3 +1,9 @@ # Creating the project (Boilerplate) I created the boiler plate code, basic CI and deployment at the very beggining off the project (AP-3, AP-2, AP-8). -The idea is to have as soon as possible a "Hello world". In this context that would be a deployed sveltkit app that can read and write a value on Airtable. \ No newline at end of file +The idea is to have as soon as possible a "Hello world". In this context that would be a deployed sveltkit app that can read and write a value on Airtable. + +# Testing from the very beginning + +I created a test script (`scripts/test-airtable.ts`) to verify read and write access to Airtable before building any features. This script uses dotenv to load credentials from `.env.local` and tests creating and reading records from a dedicated test table. + +The project uses Vitest with Playwright for browser-based component testing, with Playwright browsers installed automatically via a postinstall hook. At the moment test is boilerplate, so we are not really testing any real functionality \ No newline at end of file diff --git a/src/routes/page.svelte.spec.ts b/src/routes/page.svelte.spec.ts index 9b564bb..d54f00a 100644 --- a/src/routes/page.svelte.spec.ts +++ b/src/routes/page.svelte.spec.ts @@ -6,7 +6,7 @@ import Page from './+page.svelte'; describe('/+page.svelte', () => { it('should render h1', async () => { render(Page); - + const heading = page.getByRole('heading', { level: 1 }); await expect.element(heading).toBeInTheDocument(); }); diff --git a/vite.config.ts b/vite.config.ts index 34e0519..2b204a6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -19,12 +19,12 @@ export default defineConfig({ browser: { enabled: true, provider: playwright(), - instances: [{ browser: 'chromium', headless: true }] + instances: [{ browser: 'chromium', headless: true }], }, include: ['src/**/*.svelte.{test,spec}.{js,ts}'], - exclude: ['src/lib/server/**'] - } + exclude: ['src/lib/server/**'], + }, }, { @@ -34,9 +34,9 @@ export default defineConfig({ name: 'server', environment: 'node', include: ['src/**/*.{test,spec}.{js,ts}'], - exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'] - } - } - ] - } + exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'], + }, + }, + ], + }, });