Remove grunt, add s3 publishing tests
This commit is contained in:
@@ -1,9 +0,0 @@
|
|||||||
// Legacy Gruntfile — kept only for the 'metrics' and 'version' tasks.
|
|
||||||
// The main build pipeline uses rspack + swc (see rspack.config.js and .swcrc).
|
|
||||||
module.exports = function (grunt) {
|
|
||||||
grunt.initConfig({
|
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.task.loadTasks('tasks');
|
|
||||||
};
|
|
||||||
Generated
+1564
-193
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,7 @@
|
|||||||
"test:browser": "vitest run --project browser",
|
"test:browser": "vitest run --project browser",
|
||||||
"test:unit": "vitest run --project node",
|
"test:unit": "vitest run --project node",
|
||||||
"test:tasks": "vitest run --project tasks",
|
"test:tasks": "vitest run --project tasks",
|
||||||
|
"test:publish": "npm run build && vitest run --project publish",
|
||||||
"test:browser-smoke": "playwright test --config tests/browser/playwright.config.js",
|
"test:browser-smoke": "playwright test --config tests/browser/playwright.config.js",
|
||||||
"test:serve": "npx serve -l 9999 .",
|
"test:serve": "npx serve -l 9999 .",
|
||||||
"test:integration": "npm run build && ./tests/integration/run-integration-tests.sh",
|
"test:integration": "npm run build && ./tests/integration/run-integration-tests.sh",
|
||||||
@@ -77,6 +78,7 @@
|
|||||||
"concurrently": "^5.0.0",
|
"concurrently": "^5.0.0",
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
"eslint-plugin-compat": "^7.0.1",
|
"eslint-plugin-compat": "^7.0.1",
|
||||||
|
"fauxqs": "^2.3.1",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"husky": "^3.1.0",
|
"husky": "^3.1.0",
|
||||||
"lint-staged": "^16.3.2",
|
"lint-staged": "^16.3.2",
|
||||||
|
|||||||
+34
-19
@@ -13,18 +13,7 @@ async function main() {
|
|||||||
const commitInfo = await git.commitInfo();
|
const commitInfo = await git.commitInfo();
|
||||||
console.log('tag: ', commitInfo.tagName);
|
console.log('tag: ', commitInfo.tagName);
|
||||||
|
|
||||||
const suffixes = [];
|
const suffixes = buildSuffixes(commitInfo);
|
||||||
|
|
||||||
// Publish the master as "latest" and with the commit-id
|
|
||||||
if (commitInfo.isMaster) {
|
|
||||||
suffixes.push('-latest');
|
|
||||||
suffixes.push('-' + commitInfo.headSha);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish tags by their tag-name
|
|
||||||
if (commitInfo.tagName != null && semver.valid(commitInfo.tagName)) {
|
|
||||||
suffixes.push('-' + commitInfo.tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suffixes.length > 0) {
|
if (suffixes.length > 0) {
|
||||||
validateS3Env();
|
validateS3Env();
|
||||||
@@ -33,6 +22,21 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSuffixes(commitInfo) {
|
||||||
|
const suffixes = [];
|
||||||
|
|
||||||
|
if (commitInfo.isMaster) {
|
||||||
|
suffixes.push('-latest');
|
||||||
|
suffixes.push('-' + commitInfo.headSha);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commitInfo.tagName != null && semver.valid(commitInfo.tagName)) {
|
||||||
|
suffixes.push('-' + commitInfo.tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return suffixes;
|
||||||
|
}
|
||||||
|
|
||||||
function validateS3Env() {
|
function validateS3Env() {
|
||||||
const bucket = process.env.S3_BUCKET_NAME,
|
const bucket = process.env.S3_BUCKET_NAME,
|
||||||
region = process.env.S3_REGION,
|
region = process.env.S3_REGION,
|
||||||
@@ -44,12 +48,14 @@ function validateS3Env() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function publish(suffixes) {
|
async function publish(suffixes, overrides) {
|
||||||
const publishPromises = suffixes.map((suffix) => publishSuffix(suffix));
|
const publishPromises = suffixes.map((suffix) =>
|
||||||
|
publishSuffix(suffix, overrides)
|
||||||
|
);
|
||||||
return Promise.all(publishPromises);
|
return Promise.all(publishPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function publishSuffix(suffix) {
|
async function publishSuffix(suffix, overrides) {
|
||||||
const filenames = [
|
const filenames = [
|
||||||
'handlebars.js',
|
'handlebars.js',
|
||||||
'handlebars.min.js',
|
'handlebars.min.js',
|
||||||
@@ -59,18 +65,19 @@ async function publishSuffix(suffix) {
|
|||||||
const publishPromises = filenames.map(async (filename) => {
|
const publishPromises = filenames.map(async (filename) => {
|
||||||
const nameInBucket = getNameInBucket(filename, suffix);
|
const nameInBucket = getNameInBucket(filename, suffix);
|
||||||
const localFile = getLocalFile(filename);
|
const localFile = getLocalFile(filename);
|
||||||
await uploadToBucket(localFile, nameInBucket);
|
await uploadToBucket(localFile, nameInBucket, overrides);
|
||||||
console.log(`Published ${localFile} to build server (${nameInBucket})`);
|
console.log(`Published ${localFile} to build server (${nameInBucket})`);
|
||||||
});
|
});
|
||||||
return Promise.all(publishPromises);
|
return Promise.all(publishPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadToBucket(localFile, nameInBucket) {
|
async function uploadToBucket(localFile, nameInBucket, overrides) {
|
||||||
const s3 = getS3Client();
|
const s3 = overrides?.s3Client ?? getS3Client();
|
||||||
|
const bucket = overrides?.bucket ?? process.env.S3_BUCKET_NAME;
|
||||||
|
|
||||||
return s3.send(
|
return s3.send(
|
||||||
new PutObjectCommand({
|
new PutObjectCommand({
|
||||||
Bucket: process.env.S3_BUCKET_NAME,
|
Bucket: bucket,
|
||||||
Key: nameInBucket,
|
Key: nameInBucket,
|
||||||
Body: fs.readFileSync(localFile, 'utf8'),
|
Body: fs.readFileSync(localFile, 'utf8'),
|
||||||
})
|
})
|
||||||
@@ -98,6 +105,14 @@ function getLocalFile(filename) {
|
|||||||
return 'dist/' + filename;
|
return 'dist/' + filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildSuffixes,
|
||||||
|
validateS3Env,
|
||||||
|
publish,
|
||||||
|
getNameInBucket,
|
||||||
|
getLocalFile,
|
||||||
|
};
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const { S3, GetObjectCommand } = require('@aws-sdk/client-s3');
|
||||||
|
|
||||||
|
const {
|
||||||
|
buildSuffixes,
|
||||||
|
validateS3Env,
|
||||||
|
publish,
|
||||||
|
getNameInBucket,
|
||||||
|
getLocalFile,
|
||||||
|
} = require('../publish-to-aws');
|
||||||
|
|
||||||
|
const BUCKET = 'builds.handlebarsjs.com';
|
||||||
|
const PUBLISHED_FILES = [
|
||||||
|
'handlebars.js',
|
||||||
|
'handlebars.min.js',
|
||||||
|
'handlebars.runtime.js',
|
||||||
|
'handlebars.runtime.min.js',
|
||||||
|
];
|
||||||
|
|
||||||
|
let startFauxqs;
|
||||||
|
let server;
|
||||||
|
let s3Client;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
({ startFauxqs } = await import('fauxqs'));
|
||||||
|
server = await startFauxqs({ port: 0, logger: false });
|
||||||
|
server.createBucket(BUCKET);
|
||||||
|
|
||||||
|
s3Client = new S3({
|
||||||
|
endpoint: server.address,
|
||||||
|
region: 'us-east-1',
|
||||||
|
credentials: { accessKeyId: 'test', secretAccessKey: 'test' },
|
||||||
|
forcePathStyle: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await server?.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
server.reset();
|
||||||
|
server.createBucket(BUCKET);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getObjectBody(key) {
|
||||||
|
const response = await s3Client.send(
|
||||||
|
new GetObjectCommand({ Bucket: BUCKET, Key: key })
|
||||||
|
);
|
||||||
|
return response.Body.transformToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('buildSuffixes', () => {
|
||||||
|
it('should add -latest and commit sha for master', () => {
|
||||||
|
const result = buildSuffixes({
|
||||||
|
isMaster: true,
|
||||||
|
headSha: 'abc123',
|
||||||
|
tagName: null,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(['-latest', '-abc123']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add tag suffix for valid semver tag', () => {
|
||||||
|
const result = buildSuffixes({
|
||||||
|
isMaster: false,
|
||||||
|
headSha: 'abc123',
|
||||||
|
tagName: '4.7.0',
|
||||||
|
});
|
||||||
|
expect(result).toEqual(['-4.7.0']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add both master and tag suffixes when on master with a tag', () => {
|
||||||
|
const result = buildSuffixes({
|
||||||
|
isMaster: true,
|
||||||
|
headSha: 'abc123',
|
||||||
|
tagName: '4.7.0',
|
||||||
|
});
|
||||||
|
expect(result).toEqual(['-latest', '-abc123', '-4.7.0']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array for non-master with no valid tag', () => {
|
||||||
|
const result = buildSuffixes({
|
||||||
|
isMaster: false,
|
||||||
|
headSha: 'abc123',
|
||||||
|
tagName: null,
|
||||||
|
});
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore invalid semver tags', () => {
|
||||||
|
const result = buildSuffixes({
|
||||||
|
isMaster: false,
|
||||||
|
headSha: 'abc123',
|
||||||
|
tagName: 'not-semver',
|
||||||
|
});
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNameInBucket', () => {
|
||||||
|
it('should insert suffix before .js extension', () => {
|
||||||
|
expect(getNameInBucket('handlebars.js', '-latest')).toBe(
|
||||||
|
'handlebars-latest.js'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle .min.js files', () => {
|
||||||
|
expect(getNameInBucket('handlebars.min.js', '-4.7.0')).toBe(
|
||||||
|
'handlebars.min-4.7.0.js'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle runtime files', () => {
|
||||||
|
expect(getNameInBucket('handlebars.runtime.js', '-abc123')).toBe(
|
||||||
|
'handlebars.runtime-abc123.js'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getLocalFile', () => {
|
||||||
|
it('should prefix with dist/', () => {
|
||||||
|
expect(getLocalFile('handlebars.js')).toBe('dist/handlebars.js');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateS3Env', () => {
|
||||||
|
const S3_KEYS = [
|
||||||
|
'S3_BUCKET_NAME',
|
||||||
|
'S3_REGION',
|
||||||
|
'S3_ACCESS_KEY_ID',
|
||||||
|
'S3_SECRET_ACCESS_KEY',
|
||||||
|
];
|
||||||
|
const saved = {};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
for (const key of S3_KEYS) {
|
||||||
|
saved[key] = process.env[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const key of S3_KEYS) {
|
||||||
|
if (saved[key] === undefined) {
|
||||||
|
delete process.env[key];
|
||||||
|
} else {
|
||||||
|
process.env[key] = saved[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when S3 env vars are missing', () => {
|
||||||
|
for (const key of S3_KEYS) {
|
||||||
|
delete process.env[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => validateS3Env()).toThrow('Missing S3 config values');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw when all S3 env vars are set', () => {
|
||||||
|
process.env.S3_BUCKET_NAME = 'test-bucket';
|
||||||
|
process.env.S3_REGION = 'us-east-1';
|
||||||
|
process.env.S3_ACCESS_KEY_ID = 'key';
|
||||||
|
process.env.S3_SECRET_ACCESS_KEY = 'secret';
|
||||||
|
|
||||||
|
expect(() => validateS3Env()).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('publish to S3', () => {
|
||||||
|
const overrides = {};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
overrides.s3Client = s3Client;
|
||||||
|
overrides.bucket = BUCKET;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload all 4 files for a single suffix', async () => {
|
||||||
|
await publish(['-latest'], overrides);
|
||||||
|
|
||||||
|
for (const filename of PUBLISHED_FILES) {
|
||||||
|
const key = getNameInBucket(filename, '-latest');
|
||||||
|
const body = await getObjectBody(key);
|
||||||
|
const localContent = fs.readFileSync(getLocalFile(filename), 'utf8');
|
||||||
|
expect(body).toBe(localContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload files for multiple suffixes', async () => {
|
||||||
|
await publish(['-latest', '-abc123'], overrides);
|
||||||
|
|
||||||
|
for (const suffix of ['-latest', '-abc123']) {
|
||||||
|
for (const filename of PUBLISHED_FILES) {
|
||||||
|
const key = getNameInBucket(filename, suffix);
|
||||||
|
const body = await getObjectBody(key);
|
||||||
|
expect(body).toBeTruthy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload correct content from dist/', async () => {
|
||||||
|
await publish(['-v1.0.0'], overrides);
|
||||||
|
|
||||||
|
const key = getNameInBucket('handlebars.js', '-v1.0.0');
|
||||||
|
const uploaded = await getObjectBody(key);
|
||||||
|
const local = fs.readFileSync('dist/handlebars.js', 'utf8');
|
||||||
|
expect(uploaded).toBe(local);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should produce correct keys for a version tag', async () => {
|
||||||
|
await publish(['-4.7.0'], overrides);
|
||||||
|
|
||||||
|
const expectedKeys = PUBLISHED_FILES.map((f) =>
|
||||||
|
getNameInBucket(f, '-4.7.0')
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const key of expectedKeys) {
|
||||||
|
const body = await getObjectBody(key);
|
||||||
|
expect(body).toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(expectedKeys).toEqual([
|
||||||
|
'handlebars-4.7.0.js',
|
||||||
|
'handlebars.min-4.7.0.js',
|
||||||
|
'handlebars.runtime-4.7.0.js',
|
||||||
|
'handlebars.runtime.min-4.7.0.js',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
module.exports = { createRegisterAsyncTaskFn };
|
|
||||||
|
|
||||||
function createRegisterAsyncTaskFn(grunt) {
|
|
||||||
return function registerAsyncTask(name, asyncFunction) {
|
|
||||||
grunt.registerTask(name, function () {
|
|
||||||
asyncFunction()
|
|
||||||
.catch((error) => {
|
|
||||||
grunt.fatal(error);
|
|
||||||
})
|
|
||||||
.finally(this.async());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+28
-23
@@ -1,23 +1,22 @@
|
|||||||
|
const fs = require('fs');
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const git = require('./util/git');
|
const git = require('./util/git');
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
|
|
||||||
|
|
||||||
module.exports = function (grunt) {
|
async function main() {
|
||||||
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
|
const version = process.argv[2];
|
||||||
|
|
||||||
registerAsyncTask('version', async () => {
|
|
||||||
const pkg = grunt.config('pkg');
|
|
||||||
const version = grunt.option('ver');
|
|
||||||
if (!semver.valid(version)) {
|
if (!semver.valid(version)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Must provide a version number (Ex: --ver=1.0.0):\n\t' +
|
'Must provide a valid semver version as first argument (e.g.: node tasks/version.js 1.0.0):\n\t' +
|
||||||
version +
|
version +
|
||||||
'\n\n'
|
'\n\n'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||||
pkg.version = version;
|
pkg.version = version;
|
||||||
grunt.config('pkg', pkg);
|
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||||
|
await git.add('package.json');
|
||||||
|
|
||||||
const replaceSpec = [
|
const replaceSpec = [
|
||||||
{
|
{
|
||||||
@@ -43,21 +42,27 @@ module.exports = function (grunt) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
replaceSpec.map((replaceSpec) =>
|
replaceSpec.map((spec) =>
|
||||||
replaceAndAdd(
|
replaceAndAdd(spec.path, spec.regex, spec.replacement)
|
||||||
replaceSpec.path,
|
|
||||||
replaceSpec.regex,
|
|
||||||
replaceSpec.replacement
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
execSync('npm run build', { stdio: 'inherit' });
|
|
||||||
});
|
|
||||||
|
|
||||||
async function replaceAndAdd(path, regex, value) {
|
execSync('npm run build', { stdio: 'inherit' });
|
||||||
let content = grunt.file.read(path);
|
}
|
||||||
|
|
||||||
|
async function replaceAndAdd(filePath, regex, value) {
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8');
|
||||||
content = content.replace(regex, value);
|
content = content.replace(regex, value);
|
||||||
grunt.file.write(path, content);
|
fs.writeFileSync(filePath, content);
|
||||||
await git.add(path);
|
await git.add(filePath);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
module.exports = { replaceAndAdd };
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
main().catch((err) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Execute the following commands in the project root:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npx grunt prepare
|
npm run build
|
||||||
docker pull mcr.microsoft.com/playwright:focal
|
docker pull mcr.microsoft.com/playwright:focal
|
||||||
docker run -it --rm --volume $(pwd):/srv/app --workdir /srv/app --ipc=host mcr.microsoft.com/playwright:focal npm run test:browser
|
docker run -it --rm --volume $(pwd):/srv/app --workdir /srv/app --ipc=host mcr.microsoft.com/playwright:focal npm run test:browser
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
name: 'tasks',
|
name: 'tasks',
|
||||||
include: ['tasks/tests/*.test.js'],
|
include: ['tasks/tests/*.test.js'],
|
||||||
|
exclude: ['tasks/tests/publish-to-aws.test.js'],
|
||||||
globals: true,
|
globals: true,
|
||||||
pool: 'forks',
|
pool: 'forks',
|
||||||
},
|
},
|
||||||
@@ -28,6 +29,14 @@ export default defineConfig({
|
|||||||
globals: true,
|
globals: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: {
|
||||||
|
name: 'publish',
|
||||||
|
include: ['tasks/tests/publish-to-aws.test.js'],
|
||||||
|
globals: true,
|
||||||
|
pool: 'forks',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: {
|
test: {
|
||||||
name: 'browser',
|
name: 'browser',
|
||||||
|
|||||||
Reference in New Issue
Block a user