Repository: auth0/auth0-deploy-cli

Issue:
Incorrect path delimiter used when exporting actions on Windows
### Description

When exporting a tenant with a action on Windows, the JSON metadata uses the Windows path delimiter (`\`) within the "code" field. This is fine if you then re-run the import on Windows again, however, for teams that use multiple OSs 
(and also CI/CD pipelines running Linux) this becomes a problem as the following error is displayed when running the import:
```bash
error: Unable to load file /opt/atlassian/pipelines/agent/build/auth0\actions\action-name\code.js due to Error: ENOENT: no such file or directory, access '/opt/atlassian/pipelines/agent/build/auth0\actions\action-name\code.js'

```

#### Example of json file
```
{
  "name": "action-name",
  "code": "auth0/actions/action-name/code.js",
  "runtime": "node16",
  "status": "built",
  "dependencies": [],
  "supported_triggers": [
    {
      "id": "credentials-exchange",
      "version": "v2"
    }
  ],
  "deployed": true
}
```

### Reproduction

- Create a tenant with an action
- Export the tenant using a0deploy on a Windows OS
- Import the tenant using a0deploy on a none Windows OS

### Environment

> Please provide the following:

- Version of this library used: 7.2.0
- Version of the platform or framework used, if applicable: Windows/Ubuntu Linux


Discussion:
@pergardebrink: I noticed the same behaviour and was curious why rules could be exported correctly with unix style path on windows while actions wouldn't (the rule json references the .js file with ./rulename.js path. Looking at the code I see that this is hardcoded here: https://github.com/auth0/auth0-deploy-cli/blob/679117d0d53b88a566cecbf91b39d2955820e7a0/src/context/directory/handlers/rules.js#L46 while the code for actions generates the path using path.join: https://github.com/auth0/auth0-deploy-cli/blob/679117d0d53b88a566cecbf91b39d2955820e7a0/src/context/directory/handlers/actions.js#L46

I'm by no means a NodeJS expert, but it seems you can use the posix version of the path module even on windows to get consistent behaviour on both: https://nodejs.org/api/path.html#windows-vs-posix

It feels like the best solution would be to use the posix version everywhere (or at least where paths are generated to be used in any .json files)?

Perhaps replace all:
```
import path from 'path';
```
with:
```
import { posix: path } from 'path';
```
@bt-Knodel: Is there any update to this?  The development team uses Windows here while deployments happen in Linux.  This is a frustrating issue...@willvedd: It would be great to support Windows machines. And assuming the above solution works, this _should_ be a straightforward fix. However, I do not have a practical means of testing it so perhaps someone with a Windows machine could test the proposed changes and create a PR. Otherwise I see this sitting for a little while. @sergiught: Hey folks, we fixed this in https://github.com/auth0/auth0-deploy-cli/pull/668 and it will be available in the next release. Appreciate everyone's patience! @willvedd: **Update:** After investigation it has been determined that this issue is only scoped to actions _and_ when the operating systems of the machines that performed the import differs from that of the machine that did the export.  Meaning, this issue won't be experience if a windows machine performs both the export and the import. 

The fix has been released with [7.15.1](https://github.com/auth0/auth0-deploy-cli/releases/tag/v7.15.1). It converts the path at runtime to support both windows and unix machines. Doing so maintains backwards-compatibility and won't necessitate a new export of your actions. 

Otherwise, we don't have reason to believe that any other Windows issues exist at this time. If anyone is still having issues after updating, please feel empowered to open a new ticket.@nathan-mxr: @willvedd I'm having the same issue with prompt templates, exporting on windows gives:
**prompts/partials.json**
```
{
  "signup": [
    {
      "signup": [
        {
          "name": "form-content-end",
          "template": "partials\\signup\\signup\\form-content-end.liquid"
        }
      ]
    }
  ]
}
```

Which fails to import from a linux CI container:

```
2025-03-14T15:23:36.321Z - error: Problem running command import during stage processChanges when processing type prompts
2025-03-14T15:23:36.322Z - error: Invalid value for 'form-content-end'
```

Version:
```
a0deploy --version
8.6.2
```

Solution patch:
diff --git a/src/context/directory/handlers/actions.ts b/src/context/directory/handlers/actions.ts
index 593423e..e174163 100644
--- a/src/context/directory/handlers/actions.ts
+++ b/src/context/directory/handlers/actions.ts
@@ -14,19 +14,23 @@ type ParsedActions = ParsedAsset<'actions', Asset[]>;
 
 function parse(context: DirectoryContext): ParsedActions {
   const actionsFolder = path.join(context.filePath, constants.ACTIONS_DIRECTORY);
+
   if (!existsMustBeDir(actionsFolder)) return { actions: null }; // Skip
+
   const files = getFiles(actionsFolder, ['.json']);
   const actions = files.map((file) => {
     const action = { ...loadJSON(file, context.mappings) };
     const actionFolder = path.join(constants.ACTIONS_DIRECTORY, `${action.name}`);
+
     if (action.code) {
-      action.code = context.loadFile(action.code, actionFolder);
+      const toUnixPath = somePath => somePath.replace(/[\\/]+/g, '/').replace(/^([a-zA-Z]+:|\.\/)/, '');
+      action.code = context.loadFile(toUnixPath(action.code), actionFolder);
     }
+
     return action;
   });
-  return {
-    actions,
-  };
+
+  return { actions };
 }
 
 function mapSecrets(secrets) {



Test patch:
diff --git a/test/context/directory/actions.test.js b/test/context/directory/actions.test.js
index e8a20c2..945c530 100644
--- a/test/context/directory/actions.test.js
+++ b/test/context/directory/actions.test.js
@@ -36,6 +36,33 @@ const actionFiles = {
   },
 };
 
+const actionFilesWin32 = {
+  [constants.ACTIONS_DIRECTORY]: {
+    'code.js':
+      '/** @type {PostLoginAction} */ module.exports = async (event, context) => { console.log(@@replace@@); return {}; };',
+    'action-one.json': `{
+      "name": "action-one",
+      "code": "local\\\\testData\\\\directory\\\\test1\\\\actions\\\\code.js",
+      "runtime": "node12",
+      "dependencies": [
+        {
+          "name": "lodash",
+          "version": "4.17.20"
+        }
+      ],
+      "secrets": [],
+      "status": "built",
+      "supported_triggers": [
+        {
+          "id": "post-login",
+          "version": "v1"
+        }
+      ],
+      "deployed": true
+    }`,
+  },
+};
+
 const actionsTarget = [
   {
     name: 'action-one',
@@ -60,7 +87,7 @@ const actionsTarget = [
 ];
 
 describe('#directory context actions', () => {
-  it('should process actions', async () => {
+  it('should process actions from unix systems', async () => {
     const repoDir = path.join(testDataDir, 'directory', 'test1');
     createDir(repoDir, actionFiles);
     const config = {
@@ -72,6 +99,18 @@ describe('#directory context actions', () => {
     expect(context.assets.actions).to.deep.equal(actionsTarget);
   });
 
+  it('should process actions from windows systems', async () => {
+    const repoDir = path.join(testDataDir, 'directory', 'test1');
+    createDir(repoDir, actionFilesWin32);
+    const config = {
+      AUTH0_INPUT_FILE: repoDir,
+      AUTH0_KEYWORD_REPLACE_MAPPINGS: { replace: 'test-action' },
+    };
+    const context = new Context(config, mockMgmtClient());
+    await context.load();
+    expect(context.assets.actions).to.deep.equal(actionsTarget);
+  });
+
   it('should ignore bad actions directory', async () => {
     const repoDir = path.join(testDataDir, 'directory', 'test2');
     cleanThenMkdir(repoDir);