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
52 changes: 40 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ This guide walks you through setting up the development environment and other im
- [Export the new language bundle](#export-the-new-language-bundle)
- [Add the export to the translations index](#add-the-export-to-the-translations-index)
- [Test your translation](#test-your-translation)
- [Option 1: Using `npm` symlinks](#option-1-using-npm-symlinks)
- [Option 1: Using a `symlink`](#option-1-using-a-symlink)
- [Option 2: Integrate into an existing sample](#option-2-integrate-into-an-existing-sample)
- [Testing with AsgardeoProvider](#testing-with-asgardeoprovider)
- [Update documentation](#update-documentation)
Expand Down Expand Up @@ -244,25 +244,53 @@ pnpm build --filter @asgardeo/i18n

To test your new language translation, you have two options:

##### Option 1: Using `npm` symlinks
##### Option 1: Using a symlink

Create a symlink to test your local changes without publishing:
From the workspace root, run the `symlink` script. It builds all packages, resolves `catalog:` and `workspace:*` references, and prints ready-to-paste override snippets:

```bash
# Navigate to the i18n package
cd packages/i18n
pnpm symlink
```

# Create a global symlink
npm link
Copy the relevant snippet from the output into your test project's `package.json` and run `pnpm install` (or the equivalent for your package manager):

# Navigate to your test application
cd /path/to/your/test-app
###### pnpm

# Link the local i18n package
npm link @asgardeo/i18n
```json
{
"pnpm": {
"overrides": {
"@asgardeo/i18n": "file:/path/to/javascript/packages/i18n"
}
}
}
```

For more information about npm symlinks, see the [npm link documentation](https://docs.npmjs.com/cli/v10/commands/npm-link).
###### npm

```json
{
"overrides": {
"@asgardeo/i18n": "file:/path/to/javascript/packages/i18n"
}
}
```

###### Yarn (Berry)

```json
{
"resolutions": {
"@asgardeo/i18n": "file:/path/to/javascript/packages/i18n"
}
}
```

To restore the patched source files when you're done:

```bash
git checkout packages/*/package.json
```

##### Option 2: Integrate into an existing sample

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"e2e:docker:down": "docker compose -f e2e/docker-compose.yml down -v",
"e2e:docker:up:is": "docker compose -f e2e/docker-compose.yml up -d wso2is",
"e2e:docker:up:thunder": "docker compose -f e2e/docker-compose.yml up -d thunder",
"e2e:install": "playwright install chromium"
"e2e:install": "playwright install chromium",
"symlink": "node scripts/symlink.js"
},
"devDependencies": {
"@changesets/changelog-github": "0.5.1",
Expand Down
216 changes: 216 additions & 0 deletions scripts/symlink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/**
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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.
*/

/**
* Prepares local packages for symlinking into external projects.
*
* Problems this solves:
* - `catalog:` references in package.json are a pnpm workspace-only protocol.
* External consumers (even via `file:`) can't resolve them.
* - `workspace:*` references must become `file:` paths so the external project
* resolves inter-package dependencies to the local builds too.
*
* What it does:
* 1. Reads the `catalog:` entries from pnpm-workspace.yaml.
* 2. Builds all packages (pnpm build:packages).
* 3. Patches every `packages/<*>/package.json`, replacing:
* `"catalog:"` → the real version string from the catalog
* `"workspace:*"` → `"file:<absolute-path-to-package>"`
* 4. Prints ready-to-paste override snippets for pnpm and npm.
*
* To restore the source files after you're done:
* git checkout packages/<*>/package.json
*/

const fs = require('fs');
const path = require('path');
const {execSync} = require('child_process');

const ROOT = path.resolve(__dirname, '..');

// ---------------------------------------------------------------------------
// 1. Parse catalog from pnpm-workspace.yaml
// ---------------------------------------------------------------------------

/**
* Minimal YAML parser for the flat `catalog:` section in pnpm-workspace.yaml.
* Handles both quoted and unquoted keys/values, and multi-word values.
*/
function parseCatalog() {
const yamlPath = path.join(ROOT, 'pnpm-workspace.yaml');
const yaml = fs.readFileSync(yamlPath, 'utf-8');
const catalog = {};
let inCatalog = false;

for (const raw of yaml.split('\n')) {
const line = raw.trimEnd();

if (/^catalog:\s*$/.test(line)) {
inCatalog = true;
continue;
}

if (inCatalog) {
// A non-indented, non-empty line signals the end of the catalog block.
if (line.length > 0 && !/^\s/.test(line)) {
inCatalog = false;
continue;
}

// Match ` 'key': value` or ` key: value`
const match = line.match(/^\s+['"]?([^'":\s][^'":]*?)['"]?\s*:\s*(.+)$/);
if (match) {
catalog[match[1].trim()] = match[2].trim().replace(/^['"]|['"]$/g, '');
}
}
}

return catalog;
}

// ---------------------------------------------------------------------------
// 2. Discover all publishable packages (packages/* minus workspace exclusions)
// ---------------------------------------------------------------------------

const EXCLUDED_PACKAGES = new Set(['nuxt']); // mirrors !packages/nuxt in pnpm-workspace.yaml

function findPackages() {
const packagesDir = path.join(ROOT, 'packages');

return fs
.readdirSync(packagesDir, {withFileTypes: true})
.filter(entry => entry.isDirectory() && !EXCLUDED_PACKAGES.has(entry.name))
.map(entry => path.join(packagesDir, entry.name))
.filter(pkgPath => fs.existsSync(path.join(pkgPath, 'package.json')));
}

// ---------------------------------------------------------------------------
// 3. Build packages
// ---------------------------------------------------------------------------

function buildPackages() {
console.log('\nBuilding packages...\n');
execSync('pnpm build:packages', {cwd: ROOT, stdio: 'inherit'});
console.log('\nBuild complete.\n');
}

// ---------------------------------------------------------------------------
// 4. Patch package.json files – replace catalog: and workspace:* references
// ---------------------------------------------------------------------------

const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];

function patchPackages(pkgPaths, catalog) {
// Build a name → absolute-path map for workspace packages.
const workspaceMap = {};
for (const pkgPath of pkgPaths) {
const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'), 'utf-8'));
if (pkgJson.name) workspaceMap[pkgJson.name] = pkgPath;
}

for (const pkgPath of pkgPaths) {
const pkgJsonPath = path.join(pkgPath, 'package.json');
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
let modified = false;

for (const field of DEP_FIELDS) {
if (!pkgJson[field]) continue;

for (const [dep, version] of Object.entries(pkgJson[field])) {
// catalog: (default catalog) or catalog:name (named catalog – treated the same here)
if (typeof version === 'string' && version.startsWith('catalog:')) {
const resolved = catalog[dep];
if (resolved) {
pkgJson[field][dep] = resolved;
modified = true;
} else {
console.warn(` [warn] No catalog entry for "${dep}" in ${pkgJson.name}`);
}
}

if (version === 'workspace:*' || version === 'workspace:^' || version === 'workspace:~') {
const resolved = workspaceMap[dep];
if (resolved) {
pkgJson[field][dep] = `file:${resolved}`;
modified = true;
} else {
console.warn(` [warn] Workspace package "${dep}" not found for ${pkgJson.name}`);
}
}
}
}

if (modified) {
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n');
console.log(` patched ${pkgJson.name}`);
}
}
}

// ---------------------------------------------------------------------------
// 5. Print override snippets
// ---------------------------------------------------------------------------

function printSnippets(pkgPaths) {
const overrides = {};
for (const pkgPath of pkgPaths) {
const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgPath, 'package.json'), 'utf-8'));
if (pkgJson.name) overrides[pkgJson.name] = `file:${pkgPath}`;
}

const divider = '─'.repeat(60);

console.log(`\n${divider}`);
console.log(" pnpm — add to your project's package.json");
console.log(divider);
console.log(JSON.stringify({pnpm: {overrides}}, null, 2));

console.log(`\n${divider}`);
console.log(" npm — add to your project's package.json");
console.log(divider);
console.log(JSON.stringify({overrides}, null, 2));

console.log(`\n${divider}`);
console.log(" Yarn (Berry) — add to your project's package.json");
console.log(divider);
console.log(JSON.stringify({resolutions: overrides}, null, 2));

console.log(`\n${divider}`);
console.log(' To restore source files when done:');
console.log(' git checkout packages/*/package.json');
console.log(divider + '\n');
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

console.log('symlink — preparing local packages for external linking\n');

const catalog = parseCatalog();
console.log(`Catalog entries found: ${Object.keys(catalog).length}`);

const pkgPaths = findPackages();
console.log(`Packages found: ${pkgPaths.length} (${pkgPaths.map(p => path.basename(p)).join(', ')})`);

buildPackages();

console.log('Patching package.json files...');
patchPackages(pkgPaths, catalog);

printSnippets(pkgPaths);
Loading