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
151 changes: 75 additions & 76 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,82 +174,81 @@ jobs:
GRAFANA_ACCESS_POLICY_TOKEN: ${{ fromJSON(steps.get-secrets.outputs.secrets).GRAFANA_ACCESS_POLICY_TOKEN }}
run: sign-plugin --rootUrls http://www.example.com --signatureType private
working-directory: ./${{ env.WORKING_DIR }}
# Temporarily disable to prevent failures due to NPM package publishing in grafana/grafana. Put back to 5.27.1 once resolved.
# test-updates:
# name: Test create-plugin update command
# runs-on: ubuntu-x64
# if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
# needs: [test]
# env:
# WORKING_DIR: 'myorg-nobackend-panel'
# steps:
# - name: Setup nodejs
# uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
# with:
# node-version: '24'

# - name: Scaffold plugin using the earliest create-plugin version mentioned in migrations
# run: npx -y @grafana/create-plugin@5.27.1 --plugin-name='no-backend' --org-name='myorg' --plugin-type='panel'

# - name: Install generated plugin dependencies
# run: npm install --no-audit
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Download packed artifacts
# uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
# with:
# name: packed-artifacts
# path: ./packed-artifacts

# - name: Install npm packages globally
# run: for file in *.tgz; do npm install -g "$file"; done
# working-directory: ./packed-artifacts

# - name: Test create-plugin update command
# run: npx create-plugin update --force
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Lint plugin frontend
# run: npm run lint
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Typecheck plugin frontend
# run: npm run typecheck
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Build plugin frontend
# run: npm run build
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Test plugin frontend
# run: npm run test:ci
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Install playwright dependencies
# run: npm exec playwright install --with-deps chromium
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Start grafana server for e2e tests (10.4.0)
# run: |
# ANONYMOUS_AUTH_ENABLED=false docker compose build --no-cache
# docker compose up -d
# env:
# GRAFANA_VERSION: '10.4.0'
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Wait for grafana server (10.4.0)
# uses: grafana/plugin-actions/wait-for-grafana@752a92aaebfcd83121acc27293c93b7013d30deb # wait-for-grafana/v1.0.1
# with:
# url: http://localhost:3000/login

# - name: Run e2e tests (10.4.0)
# id: run-e2e-tests-min-version
# run: npm run e2e
# working-directory: ./${{ env.WORKING_DIR }}

# - name: Stop grafana docker (10.4.0)
# run: docker compose down
# working-directory: ./${{ env.WORKING_DIR }}
test-updates:
name: Test create-plugin update command
runs-on: ubuntu-x64
if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
needs: [test]
env:
WORKING_DIR: 'myorg-nobackend-panel'
steps:
- name: Setup nodejs
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24'

- name: Scaffold plugin using the earliest create-plugin version mentioned in migrations
run: npx -y @grafana/create-plugin@5.27.1 --plugin-name='no-backend' --org-name='myorg' --plugin-type='panel'

- name: Install generated plugin dependencies
run: npm install --no-audit
working-directory: ./${{ env.WORKING_DIR }}

- name: Download packed artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: packed-artifacts
path: ./packed-artifacts

- name: Install npm packages globally
run: for file in *.tgz; do npm install -g "$file"; done
working-directory: ./packed-artifacts

- name: Test create-plugin update command
run: npx create-plugin update --force
working-directory: ./${{ env.WORKING_DIR }}

- name: Lint plugin frontend
run: npm run lint
working-directory: ./${{ env.WORKING_DIR }}

- name: Typecheck plugin frontend
run: npm run typecheck
working-directory: ./${{ env.WORKING_DIR }}

- name: Build plugin frontend
run: npm run build
working-directory: ./${{ env.WORKING_DIR }}

- name: Test plugin frontend
run: npm run test:ci
working-directory: ./${{ env.WORKING_DIR }}

- name: Install playwright dependencies
run: npm exec playwright install --with-deps chromium
working-directory: ./${{ env.WORKING_DIR }}

- name: Start grafana server for e2e tests (10.4.0)
run: |
ANONYMOUS_AUTH_ENABLED=false docker compose build --no-cache
docker compose up -d
env:
GRAFANA_VERSION: '10.4.0'
working-directory: ./${{ env.WORKING_DIR }}

- name: Wait for grafana server (10.4.0)
uses: grafana/plugin-actions/wait-for-grafana@752a92aaebfcd83121acc27293c93b7013d30deb # wait-for-grafana/v1.0.1
with:
url: http://localhost:3000/login

- name: Run e2e tests (10.4.0)
id: run-e2e-tests-min-version
run: npm run e2e
working-directory: ./${{ env.WORKING_DIR }}

- name: Stop grafana docker (10.4.0)
run: docker compose down
working-directory: ./${{ env.WORKING_DIR }}

generate-plugins:
name: Test plugin scaffolding
Expand Down
14 changes: 14 additions & 0 deletions packages/create-plugin/src/codemods/migrations/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ export default [
'Add setupTests.d.ts for @testing-library/jest-dom types and remove @types/testing-library__jest-dom npm package.',
scriptPath: import.meta.resolve('./scripts/007-remove-testing-library-types.js'),
},
{
name: '008-remove-tsconfig-baseurl',
version: '7.3.1',
description:
'Fix TypeScript 6 compatibility: baseUrl is deprecated in TS6 (error TS5101) and must be replaced with an equivalent paths entry to preserve non-relative import resolution.',
scriptPath: import.meta.resolve('./scripts/008-remove-tsconfig-baseurl.js'),
},
{
name: '009-ts-node-nodenext',
version: '7.3.1',
description:
'Fix ts-node compatibility with the latest @grafana/tsconfig: outdated module/moduleResolution/target overrides break TypeScript 5/6 builds, replaced with nodenext/nodenext/es2022.',
scriptPath: import.meta.resolve('./scripts/009-ts-node-nodenext.js'),
},
// Do not use LEGACY_UPDATE_CUTOFF_VERSION for new migrations. It is only used above to force migrations to run
// for those written before the switch to updates as migrations.
] satisfies Migration[];
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, expect, it } from 'vitest';
import { parse } from 'jsonc-parser';
import migrate from './008-remove-tsconfig-baseurl.js';
import { Context } from '../../context.js';

const SCAFFOLD_TSCONFIG = `/*
* ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY \`@grafana/create-plugin\`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
*/
{
"compilerOptions": {
"alwaysStrict": true,
"rootDir": "../src",
"baseUrl": "../src",
"typeRoots": ["../node_modules/@types"],
"resolveJsonModule": true
},
"extends": "@grafana/tsconfig"
}
`;

describe('008-remove-tsconfig-baseurl', () => {
it('should remove baseUrl and add paths catch-all, preserving comment header and rootDir', () => {
const context = new Context('/virtual');
context.addFile('.config/tsconfig.json', SCAFFOLD_TSCONFIG);

const result = migrate(context);
const content = result.getFile('.config/tsconfig.json') || '';
const config = parse(content) as { compilerOptions: Record<string, unknown> };

expect(config.compilerOptions['baseUrl']).toBeUndefined();
expect(config.compilerOptions['paths']).toEqual({ '*': ['../src/*'] });
expect(config.compilerOptions['rootDir']).toBe('../src');
expect(content).toContain('DO NOT EDIT THIS FILE DIRECTLY');
});

it('should be idempotent', async () => {
const context = new Context('/virtual');
context.addFile('.config/tsconfig.json', SCAFFOLD_TSCONFIG);

await expect(migrate).toBeIdempotent(context);
});

it('should not modify file when baseUrl is already absent', () => {
const context = new Context('/virtual');
const content = `{
"compilerOptions": {
"rootDir": "../src",
"paths": { "*": ["../src/*"] }
}
}
`;
context.addFile('.config/tsconfig.json', content);

const result = migrate(context);

expect(result.getFile('.config/tsconfig.json')).toBe(content);
});

it('should not modify anything when .config/tsconfig.json does not exist', () => {
const context = new Context('/virtual');

const result = migrate(context);

expect(result.hasChanges()).toBe(false);
});

it('should merge paths["*"] into an existing paths object without overwriting other entries', () => {
const context = new Context('/virtual');
context.addFile(
'.config/tsconfig.json',
`{
"compilerOptions": {
"rootDir": "../src",
"baseUrl": "../src",
"paths": {
"@components/*": ["../src/components/*"]
}
}
}
`
);

const result = migrate(context);
const content = result.getFile('.config/tsconfig.json') || '';
const config = parse(content) as { compilerOptions: { paths: Record<string, string[]> } };

expect(config.compilerOptions.paths['@components/*']).toEqual(['../src/components/*']);
expect(config.compilerOptions.paths['*']).toEqual(['../src/*']);
expect(config.compilerOptions).not.toHaveProperty('baseUrl');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { modify, applyEdits } from 'jsonc-parser';
import type { Context } from '../../context.js';

export default function migrate(context: Context) {
if (!context.doesFileExist('.config/tsconfig.json')) {
return context;
}

const content = context.getFile('.config/tsconfig.json') || '';

if (!content.includes('"baseUrl"')) {
return context;
}

const formattingOptions = { formattingOptions: { insertSpaces: true, tabSize: 2 } };

// applyEdits accepts Edit[] and modify returns Edit[], but both modify calls must operate on
// the same base string to produce correct offsets. Chaining them sequentially is safer since
// removing baseUrl shifts positions in compilerOptions before we add paths.
const contentWithoutBaseUrl = applyEdits(
content,
modify(content, ['compilerOptions', 'baseUrl'], undefined, formattingOptions)
);
const contentWithPaths = applyEdits(
contentWithoutBaseUrl,
modify(contentWithoutBaseUrl, ['compilerOptions', 'paths', '*'], ['../src/*'], formattingOptions)
);

context.updateFile('.config/tsconfig.json', contentWithPaths);

return context;
}
Loading
Loading