@sillsdev/docu-notion 0.14.0-alpha.14 → 0.14.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -19
- package/dist/MakeImagePersistencePlan.d.ts +2 -1
- package/dist/MakeImagePersistencePlan.js +54 -12
- package/dist/images.d.ts +3 -4
- package/dist/images.js +9 -9
- package/dist/latex.spec.js +6 -2
- package/dist/makeImagePersistencePlan.spec.js +63 -44
- package/dist/plugins/internalLinks.js +5 -2
- package/dist/plugins/internalLinks.spec.js +37 -0
- package/dist/plugins/pluginTestRun.d.ts +1 -1
- package/dist/plugins/pluginTestRun.js +8 -5
- package/dist/plugins/pluginTypes.d.ts +6 -2
- package/dist/pull.d.ts +3 -0
- package/dist/pull.js +10 -5
- package/dist/run.js +4 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Example Site: https://sillsdev.github.io/docu-notion-sample-site/
|
|
|
6
6
|
|
|
7
7
|
# Instructions
|
|
8
8
|
|
|
9
|
-
## 1. Set up your documentation site
|
|
9
|
+
## 1. Set up your documentation site
|
|
10
10
|
|
|
11
11
|
First, prepare your markdown-based static file system like [Docusaurus](https://docusaurus.io/). For a shortcut with github actions, search, and deployment to github pages, you can just copy [this template](https://github.com/sillsdev/docu-notion-sample-site).
|
|
12
12
|
|
|
@@ -27,15 +27,15 @@ Go to the page that will be the root of your site. This page should have, as dir
|
|
|
27
27
|
|
|
28
28
|
<img width="318" alt="image" src="https://github.com/sillsdev/docu-notion/assets/8448/810c6dca-f9ab-4370-93b4-dc1479332af7">
|
|
29
29
|
|
|
30
|
-
## 5. Add your pages under your Outline page
|
|
30
|
+
## 5. Add your pages under your Outline page
|
|
31
31
|
|
|
32
32
|
Currently, docu-notion expects that each page has only one of the following: sub-pages, links to other pages, or normal content. Do not mix them. You can add content pages directly here, but then you won't be able to make use of the workflow features. If those matter to you, instead make new pages under the "Database" and then link to them in your outline pages.
|
|
33
33
|
|
|
34
34
|
## 6. Pull your pages
|
|
35
35
|
|
|
36
|
-
First, determine the
|
|
37
|
-
https://www.notion.so/hattonjohn/My-Docs-
|
|
38
|
-
means that the
|
|
36
|
+
First, determine the ID of your root page by clicking "Share" and looking at the url it gives you. E.g.
|
|
37
|
+
`https://www.notion.so/hattonjohn/My-Docs-0456aa5842946PRETEND4f37c97a0e5`
|
|
38
|
+
means that the ID is `0456aa5842946PRETEND4f37c97a0e5`.
|
|
39
39
|
|
|
40
40
|
Try it out:
|
|
41
41
|
|
|
@@ -114,26 +114,27 @@ NOTE: if you just localize an image, it will not get picked up. You also must lo
|
|
|
114
114
|
|
|
115
115
|
# Automated builds with Github Actions
|
|
116
116
|
|
|
117
|
-
Here is a working Github Action script to copy and customize
|
|
117
|
+
Here is a [working Github Action script to copy and customize](https://github.com/BloomBooks/bloom-docs/blob/master/.github/workflows/release.yml).
|
|
118
118
|
|
|
119
119
|
# Command line
|
|
120
120
|
|
|
121
|
-
Usage: docu-notion -n <token> -r <root> [options]
|
|
121
|
+
Usage: `docu-notion -n <token> -r <root> [options]`
|
|
122
122
|
|
|
123
123
|
Options:
|
|
124
124
|
|
|
125
125
|
| flag | required? | description |
|
|
126
126
|
| ------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
| -
|
|
127
|
+
| `-n, --notion-token <string>` | required | notion api token, which looks like `secret_3bc1b50XFYb15123RHF243x43450XFY33250XFYa343` |
|
|
128
|
+
| `-r, --root-page <string>` | required | The 31 character ID of the page which is the root of your docs page in notion. The code will look like `9120ec9960244ead80fa2ef4bc1bba25`. This page must have a child page named 'Outline' |
|
|
129
|
+
| `-m, --markdown-output-path <string>` | | Root of the hierarchy for md files. WARNING: node-pull-mdx will delete files from this directory. Note also that if it finds localized images, it will create an i18n/ directory as a sibling. (default: `./docs`) |
|
|
130
|
+
| `-t, --status-tag <string>` | | Database pages without a Notion page property 'status' matching this will be ignored. Use '\*' to ignore status altogether. (default: `Publish`) |
|
|
131
|
+
| `--locales <codes>` | | Comma-separated list of iso 639-2 codes, the same list as in docusaurus.config.js, minus the primary (i.e. 'en'). This is needed for image localization. (default: `[]`) |
|
|
132
|
+
| `-l, --log-level <level>` | | Log level (choices: `info`, `verbose`, `debug`) |
|
|
133
|
+
| `-i, --img-output-path <string>` | | Path to directory where images will be stored. If this is not included, images will be placed in the same directory as the document that uses them, which then allows for localization of screenshots. |
|
|
134
|
+
| `-p, --img-prefix-in-markdown <string>` | | When referencing an image from markdown, prefix with this path instead of the full img-output-path. Should be used only in conjunction with --img-output-path. |
|
|
135
|
+
| `--require-slugs` | | If set, docu-notion will fail if any pages it would otherwise publish are missing a slug in Notion. |
|
|
136
|
+
| `--image-file-name-format <format>` | | choices:<ul><li>`default`: {page slug (if any)}.{image block ID}</li><li>`content-hash`: Use a hash of the image content.</li><li>`legacy`: Use the legacy (before v0.16) method of determining file names. Set this to maintain backward compatibility.</li></ul>All formats will use the original file extension. |
|
|
137
|
+
| `-h, --help` | | display help for command |
|
|
137
138
|
|
|
138
139
|
# Plugins
|
|
139
140
|
|
|
@@ -155,8 +156,10 @@ The default admonition type, if no matching icon is found, is "note".
|
|
|
155
156
|
# Known Workarounds
|
|
156
157
|
|
|
157
158
|
### Start a numbered list at a number other than 1
|
|
159
|
+
|
|
158
160
|
In Notion, make sure the block is "Text," not "Numbered List".
|
|
161
|
+
|
|
159
162
|
- But make sure the number does NOT have a space in front of it. This can/will cause issues with sub-list items.
|
|
160
163
|
- One way to get Notion to let you do this:
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
- Create a numbered list item where the text duplicates the number you want. Convert that numbered list item to "Text."
|
|
165
|
+
- i.e. Type "1. 1. Item one." Notion makes the first "1." into a number in a list. When you convert back to "Text," you're left with plain text "1. Item one."
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { ImageSet } from "./images";
|
|
2
|
-
|
|
2
|
+
import { DocuNotionOptions } from "./pull";
|
|
3
|
+
export declare function makeImagePersistencePlan(options: DocuNotionOptions, imageSet: ImageSet, imageBlockId: string, imageOutputRootPath: string, imagePrefix: string): void;
|
|
3
4
|
export declare function hashOfString(s: string): number;
|
|
@@ -22,13 +22,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
25
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
29
|
exports.hashOfString = exports.makeImagePersistencePlan = void 0;
|
|
27
30
|
const Path = __importStar(require("path"));
|
|
28
31
|
const log_1 = require("./log");
|
|
29
32
|
const process_1 = require("process");
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
34
|
+
function makeImagePersistencePlan(options, imageSet, imageBlockId, imageOutputRootPath, imagePrefix) {
|
|
35
|
+
var _a, _b, _c;
|
|
32
36
|
const urlBeforeQuery = imageSet.primaryUrl.split("?")[0];
|
|
33
37
|
let imageFileExtension = (_a = imageSet.fileType) === null || _a === void 0 ? void 0 : _a.ext;
|
|
34
38
|
if (!imageFileExtension) {
|
|
@@ -39,18 +43,52 @@ function makeImagePersistencePlan(imageSet, imageOutputRootPath, imagePrefix) {
|
|
|
39
43
|
(0, process_1.exit)(1);
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
if (options.imageFileNameFormat === "legacy") {
|
|
47
|
+
// Original behavior and comment:
|
|
48
|
+
// Since most images come from pasting screenshots, there isn't normally a filename. That's fine, we just make a hash of the url
|
|
49
|
+
// Images that are stored by notion come to us with a complex url that changes over time, so we pick out the UUID that doesn't change. Example:
|
|
50
|
+
// https://s3.us-west-2.amazonaws.com/secure.notion-static.com/d1058f46-4d2f-4292-8388-4ad393383439/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220516%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220516T233630Z&X-Amz-Expires=3600&X-Amz-Signature=f215704094fcc884d37073b0b108cf6d1c9da9b7d57a898da38bc30c30b4c4b5&X-Amz-SignedHeaders=host&x-id=GetObject
|
|
51
|
+
// But around Sept 2023, they changed the url to be something like:
|
|
52
|
+
// https://prod-files-secure.s3.us-west-2.amazonaws.com/d9a2b712-cf69-4bd6-9d65-87a4ceeacca2/d1bcdc8c-b065-4e40-9a11-392aabeb220e/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230915%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230915T161258Z&X-Amz-Expires=3600&X-Amz-Signature=28fca48e65fba86d539c3c4b7676fce1fa0857aa194f7b33dd4a468ecca6ab24&X-Amz-SignedHeaders=host&x-id=GetObject
|
|
53
|
+
// The thing we want is the last UUID before the ?
|
|
54
|
+
const thingToHash = (_b = findLastUuid(urlBeforeQuery)) !== null && _b !== void 0 ? _b : urlBeforeQuery;
|
|
55
|
+
const hash = hashOfString(thingToHash);
|
|
56
|
+
imageSet.outputFileName = `${hash}.${imageFileExtension}`;
|
|
57
|
+
}
|
|
58
|
+
else if (options.imageFileNameFormat === "content-hash") {
|
|
59
|
+
// This was requested by a user: https://github.com/sillsdev/docu-notion/issues/76.
|
|
60
|
+
// We chose not to include it in the default file name because we want to maintain
|
|
61
|
+
// as much stability in the file name as feasible for an image localization workflow.
|
|
62
|
+
// However, particularly in a workflow which is not concerned with localization,
|
|
63
|
+
// this could be a good option. One benefit is that the image only needs to exist once
|
|
64
|
+
// in the file system regardless of how many times it is used in the site.
|
|
65
|
+
const imageHash = hashOfBufferContent(imageSet.primaryBuffer);
|
|
66
|
+
imageSet.outputFileName = `${imageHash}.${imageFileExtension}`;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// We decided not to do this for the default format because it means
|
|
70
|
+
// instability for the file name in Crowdin, which causes loss of localizations.
|
|
71
|
+
// If we decide to include it in the future, we should add a unit test.
|
|
72
|
+
// const imageFileName = Path.basename(urlBeforeQuery);
|
|
73
|
+
// const imageFileNameWithoutExtension = Path.parse(imageFileName).name;
|
|
74
|
+
// const originalFileNamePart = ["untitled", "unnamed"].includes(
|
|
75
|
+
// imageFileNameWithoutExtension.toLocaleLowerCase()
|
|
76
|
+
// )
|
|
77
|
+
// ? ""
|
|
78
|
+
// : `${imageFileNameWithoutExtension.substring(0, 50)}.`;
|
|
79
|
+
// Format is page slug (if there is one) followed by the image block ID from Notion.
|
|
80
|
+
// The image block ID will remain stable as long as any changes to the image are done
|
|
81
|
+
// using the Replace feature. Also, image blocks can be moved using the Move To feature.
|
|
82
|
+
// We decided to include the page slug for easier workflow during localization, particularly in Crowdin.
|
|
83
|
+
// The block ID is a unique GUID and thus provides a unique file name.
|
|
84
|
+
const pageSlugPart = ((_c = imageSet.pageInfo) === null || _c === void 0 ? void 0 : _c.slug)
|
|
85
|
+
? `${imageSet.pageInfo.slug.replace(/^\//, "")}.`
|
|
86
|
+
: "";
|
|
87
|
+
imageSet.outputFileName = `${pageSlugPart}${imageBlockId}.${imageFileExtension}`;
|
|
88
|
+
}
|
|
51
89
|
imageSet.primaryFileOutputPath = Path.posix.join((imageOutputRootPath === null || imageOutputRootPath === void 0 ? void 0 : imageOutputRootPath.length) > 0
|
|
52
90
|
? imageOutputRootPath
|
|
53
|
-
: imageSet.
|
|
91
|
+
: imageSet.pageInfo.directoryContainingMarkdown, decodeURI(imageSet.outputFileName));
|
|
54
92
|
if (imageOutputRootPath && imageSet.localizedUrls.length) {
|
|
55
93
|
(0, log_1.error)("imageOutputPath was declared, but one or more localizedUrls were found too. If you are going to localize screenshots, then you can't declare an imageOutputPath.");
|
|
56
94
|
(0, process_1.exit)(1);
|
|
@@ -76,3 +114,7 @@ function hashOfString(s) {
|
|
|
76
114
|
return Math.abs(hash);
|
|
77
115
|
}
|
|
78
116
|
exports.hashOfString = hashOfString;
|
|
117
|
+
function hashOfBufferContent(buffer) {
|
|
118
|
+
const hash = crypto_1.default.createHash("sha256").update(buffer).digest("hex");
|
|
119
|
+
return hash.slice(0, 20);
|
|
120
|
+
}
|
package/dist/images.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { FileTypeResult } from "file-type";
|
|
3
3
|
import { ListBlockChildrenResponseResult } from "notion-to-md/build/types";
|
|
4
|
-
import { IPlugin } from "./plugins/pluginTypes";
|
|
4
|
+
import { IDocuNotionContext, IDocuNotionContextPageInfo, IPlugin } from "./plugins/pluginTypes";
|
|
5
5
|
export type ImageSet = {
|
|
6
6
|
primaryUrl: string;
|
|
7
7
|
caption?: string;
|
|
@@ -9,8 +9,7 @@ export type ImageSet = {
|
|
|
9
9
|
iso632Code: string;
|
|
10
10
|
url: string;
|
|
11
11
|
}>;
|
|
12
|
-
|
|
13
|
-
relativePathToParentDocument?: string;
|
|
12
|
+
pageInfo?: IDocuNotionContextPageInfo;
|
|
14
13
|
primaryBuffer?: Buffer;
|
|
15
14
|
fileType?: FileTypeResult;
|
|
16
15
|
primaryFileOutputPath?: string;
|
|
@@ -19,6 +18,6 @@ export type ImageSet = {
|
|
|
19
18
|
};
|
|
20
19
|
export declare function initImageHandling(prefix: string, outputPath: string, incomingLocales: string[]): Promise<void>;
|
|
21
20
|
export declare const standardImageTransformer: IPlugin;
|
|
22
|
-
export declare function markdownToMDImageTransformer(block: ListBlockChildrenResponseResult,
|
|
21
|
+
export declare function markdownToMDImageTransformer(block: ListBlockChildrenResponseResult, context: IDocuNotionContext): Promise<string>;
|
|
23
22
|
export declare function parseImageBlock(image: any): ImageSet;
|
|
24
23
|
export declare function cleanupOldImages(): Promise<void>;
|
package/dist/images.js
CHANGED
|
@@ -42,7 +42,7 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
42
42
|
const Path = __importStar(require("path"));
|
|
43
43
|
const MakeImagePersistencePlan_1 = require("./MakeImagePersistencePlan");
|
|
44
44
|
const log_1 = require("./log");
|
|
45
|
-
// We several things here:
|
|
45
|
+
// We handle several things here:
|
|
46
46
|
// 1) copy images locally instead of leaving them in Notion
|
|
47
47
|
// 2) change the links to point here
|
|
48
48
|
// 3) read the caption and if there are localized images, get those too
|
|
@@ -75,16 +75,16 @@ exports.standardImageTransformer = {
|
|
|
75
75
|
type: "image",
|
|
76
76
|
// we have to set this one up for each page because we need to
|
|
77
77
|
// give it two extra parameters that are context for each page
|
|
78
|
-
getStringFromBlock: (context, block) => markdownToMDImageTransformer(block, context
|
|
78
|
+
getStringFromBlock: (context, block) => markdownToMDImageTransformer(block, context),
|
|
79
79
|
},
|
|
80
80
|
],
|
|
81
81
|
};
|
|
82
82
|
// This is a "custom transformer" function passed to notion-to-markdown
|
|
83
83
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
84
|
-
function markdownToMDImageTransformer(block,
|
|
84
|
+
function markdownToMDImageTransformer(block, context) {
|
|
85
85
|
return __awaiter(this, void 0, void 0, function* () {
|
|
86
86
|
const image = block.image;
|
|
87
|
-
yield processImageBlock(
|
|
87
|
+
yield processImageBlock(block, context);
|
|
88
88
|
// just concatenate the caption text parts together
|
|
89
89
|
const altText = image.caption
|
|
90
90
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
@@ -95,17 +95,17 @@ function markdownToMDImageTransformer(block, fullPathToDirectoryContainingMarkdo
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
exports.markdownToMDImageTransformer = markdownToMDImageTransformer;
|
|
98
|
-
function processImageBlock(
|
|
98
|
+
function processImageBlock(block, context) {
|
|
99
99
|
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
const imageBlock = block.image;
|
|
100
101
|
(0, log_1.logDebug)("processImageBlock", JSON.stringify(imageBlock));
|
|
101
102
|
const imageSet = parseImageBlock(imageBlock);
|
|
102
|
-
imageSet.
|
|
103
|
-
imageSet.relativePathToParentDocument = relativePathToThisPage;
|
|
103
|
+
imageSet.pageInfo = context.pageInfo;
|
|
104
104
|
// enhance: it would much better if we could split the changes to markdown separately from actual reading/writing,
|
|
105
105
|
// so that this wasn't part of the markdown-creation loop. It's already almost there; we just need to
|
|
106
106
|
// save the imageSets somewhere and then do the actual reading/writing later.
|
|
107
107
|
yield readPrimaryImage(imageSet);
|
|
108
|
-
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, imageOutputPath, imagePrefix);
|
|
108
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(context.options, imageSet, block.id, imageOutputPath, imagePrefix);
|
|
109
109
|
yield saveImage(imageSet);
|
|
110
110
|
// change the src to point to our copy of the image
|
|
111
111
|
if ("file" in imageBlock) {
|
|
@@ -156,7 +156,7 @@ function saveImage(imageSet) {
|
|
|
156
156
|
(0, log_1.verbose)(`No localized image specified for ${localizedImage.iso632Code}, will use primary image.`);
|
|
157
157
|
// otherwise, we're going to fall back to outputting the primary image here
|
|
158
158
|
}
|
|
159
|
-
const directory = `./i18n/${localizedImage.iso632Code}/docusaurus-plugin-content-docs/current/${imageSet.
|
|
159
|
+
const directory = `./i18n/${localizedImage.iso632Code}/docusaurus-plugin-content-docs/current/${imageSet.pageInfo.relativeFilePathToFolderContainingPage}`;
|
|
160
160
|
writeImageIfNew((directory + "/" + imageSet.outputFileName).replaceAll("//", "/"), buffer);
|
|
161
161
|
}
|
|
162
162
|
});
|
package/dist/latex.spec.js
CHANGED
|
@@ -33,8 +33,12 @@ test("Latex Rendering", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
33
33
|
getBlockChildren: (id) => {
|
|
34
34
|
return new Promise(resolve => resolve(new Array()));
|
|
35
35
|
},
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// this changes with each page
|
|
37
|
+
pageInfo: {
|
|
38
|
+
directoryContainingMarkdown: "",
|
|
39
|
+
relativeFilePathToFolderContainingPage: "",
|
|
40
|
+
slug: "",
|
|
41
|
+
},
|
|
38
42
|
layoutStrategy: layoutStrategy,
|
|
39
43
|
notionToMarkdown: new notion_to_md_1.NotionToMarkdown({ notionClient }),
|
|
40
44
|
options: {
|
|
@@ -1,60 +1,79 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const MakeImagePersistencePlan_1 = require("./MakeImagePersistencePlan");
|
|
4
|
+
const optionsUsingDefaultNaming = {
|
|
5
|
+
notionToken: "",
|
|
6
|
+
rootPage: "",
|
|
7
|
+
locales: [],
|
|
8
|
+
markdownOutputPath: "",
|
|
9
|
+
imgOutputPath: "",
|
|
10
|
+
imgPrefixInMarkdown: "",
|
|
11
|
+
statusTag: "",
|
|
12
|
+
};
|
|
13
|
+
const testImageSet = {
|
|
14
|
+
primaryUrl: "https://s3.us-west-2.amazonaws.com/primaryImage?Blah=foo",
|
|
15
|
+
localizedUrls: [],
|
|
16
|
+
pageInfo: {
|
|
17
|
+
directoryContainingMarkdown: "/pathToParentSomewhere/",
|
|
18
|
+
relativeFilePathToFolderContainingPage: "",
|
|
19
|
+
slug: "my-page",
|
|
20
|
+
},
|
|
21
|
+
fileType: { ext: "png", mime: "image/png" },
|
|
22
|
+
primaryBuffer: Buffer.from("some fake image content"),
|
|
23
|
+
};
|
|
4
24
|
test("primary file with explicit file output path and prefix", () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "./static/notion_imgs", "/notion_imgs");
|
|
12
|
-
const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("https://s3.us-west-2.amazonaws.com/primaryImage");
|
|
13
|
-
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
|
|
14
|
-
expect(imageSet.primaryFileOutputPath).toBe(`static/notion_imgs/${expectedHash}.png`);
|
|
15
|
-
expect(imageSet.filePathToUseInMarkdown).toBe(`/notion_imgs/${expectedHash}.png`);
|
|
25
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingDefaultNaming, testImageSet, "ABC-123", "./static/notion_imgs", "/notion_imgs");
|
|
26
|
+
const expectedFileName = "my-page.ABC-123.png";
|
|
27
|
+
expect(testImageSet.outputFileName).toBe(`${expectedFileName}`);
|
|
28
|
+
expect(testImageSet.primaryFileOutputPath).toBe(`static/notion_imgs/${expectedFileName}`);
|
|
29
|
+
expect(testImageSet.filePathToUseInMarkdown).toBe(`/notion_imgs/${expectedFileName}`);
|
|
16
30
|
});
|
|
17
31
|
test("primary file with defaults for image output path and prefix", () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
pathToParentDocument: "/pathToParentSomewhere/",
|
|
22
|
-
fileType: { ext: "png", mime: "image/png" },
|
|
23
|
-
};
|
|
24
|
-
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "", "");
|
|
25
|
-
const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("https://s3.us-west-2.amazonaws.com/primaryImage");
|
|
26
|
-
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
|
|
32
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingDefaultNaming, testImageSet, "ABC-123", "", "");
|
|
33
|
+
const expectedFileName = "my-page.ABC-123.png";
|
|
34
|
+
expect(testImageSet.outputFileName).toBe(`${expectedFileName}`);
|
|
27
35
|
// the default behavior is to put the image next to the markdown file
|
|
28
|
-
expect(
|
|
29
|
-
expect(
|
|
36
|
+
expect(testImageSet.primaryFileOutputPath).toBe(`/pathToParentSomewhere/${expectedFileName}`);
|
|
37
|
+
expect(testImageSet.filePathToUseInMarkdown).toBe(`./${expectedFileName}`);
|
|
30
38
|
});
|
|
31
39
|
test("falls back to getting file extension from url if not in fileType", () => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingDefaultNaming, testImageSet, "ABC-123", "", "");
|
|
41
|
+
expect(testImageSet.outputFileName).toBe("my-page.ABC-123.png");
|
|
42
|
+
});
|
|
43
|
+
// I'm not sure it is even possible to have encoded characters in the slug, but this proves
|
|
44
|
+
// we are properly encoding them in the markdown file but not in the file system.
|
|
45
|
+
// (This test originally was for including original file names, but we decided not to do that.)
|
|
46
|
+
test("handles encoded characters", () => {
|
|
47
|
+
const imageSet = Object.assign(Object.assign({}, testImageSet), { pageInfo: {
|
|
48
|
+
directoryContainingMarkdown: "/pathToParentSomewhere/",
|
|
49
|
+
relativeFilePathToFolderContainingPage: "",
|
|
50
|
+
slug: "my-page%281%29",
|
|
51
|
+
} });
|
|
52
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingDefaultNaming, imageSet, "ABC-123", "", "");
|
|
53
|
+
expect(imageSet.primaryFileOutputPath).toBe(`/pathToParentSomewhere/my-page(1).ABC-123.png`);
|
|
54
|
+
expect(imageSet.filePathToUseInMarkdown).toBe(`./my-page%281%29.ABC-123.png`);
|
|
55
|
+
});
|
|
56
|
+
const optionsUsingHashNaming = Object.assign(Object.assign({}, optionsUsingDefaultNaming), { imageFileNameFormat: "content-hash" });
|
|
57
|
+
test("hash naming", () => {
|
|
58
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingHashNaming, testImageSet, "ABC-123", "", "");
|
|
59
|
+
const expectedFileName = "fe3f26fd515b3cf299ac.png";
|
|
60
|
+
expect(testImageSet.outputFileName).toBe(`${expectedFileName}`);
|
|
61
|
+
});
|
|
62
|
+
const optionsUsingLegacyNaming = Object.assign(Object.assign({}, optionsUsingDefaultNaming), { imageFileNameFormat: "legacy" });
|
|
63
|
+
test("Legacy naming", () => {
|
|
64
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingLegacyNaming, testImageSet, "ABC-123", "./static/notion_imgs", "/notion_imgs");
|
|
65
|
+
const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("https://s3.us-west-2.amazonaws.com/primaryImage");
|
|
66
|
+
expect(testImageSet.outputFileName).toBe(`${expectedHash}.png`);
|
|
40
67
|
});
|
|
41
|
-
test("properly extract UUID from old-style notion image url", () => {
|
|
42
|
-
const imageSet = {
|
|
43
|
-
|
|
44
|
-
localizedUrls: [],
|
|
45
|
-
fileType: { ext: "png", mime: "image/png" },
|
|
46
|
-
};
|
|
47
|
-
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "./static/notion_imgs", "/notion_imgs");
|
|
68
|
+
test("Legacy naming - properly extract UUID from old-style notion image url", () => {
|
|
69
|
+
const imageSet = Object.assign(Object.assign({}, testImageSet), { primaryUrl: "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e1058f46-4d2f-4292-8388-4ad393383439/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220516%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220516T233630Z&X-Amz-Expires=3600&X-Amz-Signature=f215704094fcc884d37073b0b108cf6d1c9da9b7d57a898da38bc30c30b4c4b5&X-Amz-SignedHeaders=host&x-id=GetObject" });
|
|
70
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingLegacyNaming, imageSet, "ABC-123", "./static/notion_imgs", "/notion_imgs");
|
|
48
71
|
const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("e1058f46-4d2f-4292-8388-4ad393383439");
|
|
49
72
|
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
|
|
50
73
|
});
|
|
51
|
-
test("properly extract UUID from new-style (Sept 2023) notion image url", () => {
|
|
52
|
-
const imageSet = {
|
|
53
|
-
|
|
54
|
-
localizedUrls: [],
|
|
55
|
-
fileType: { ext: "png", mime: "image/png" },
|
|
56
|
-
};
|
|
57
|
-
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "./static/notion_imgs", "/notion_imgs");
|
|
74
|
+
test("Legacy naming - properly extract UUID from new-style (Sept 2023) notion image url", () => {
|
|
75
|
+
const imageSet = Object.assign(Object.assign({}, testImageSet), { primaryUrl: "https://prod-files-secure.s3.us-west-2.amazonaws.com/d9a2b712-cf69-4bd6-9d65-87a4ceeacca2/d1bcdc8c-b065-4e40-9a11-392aabeb220e/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230915%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230915T161258Z&X-Amz-Expires=3600&X-Amz-Signature=28fca48e65fba86d539c3c4b7676fce1fa0857aa194f7b33dd4a468ecca6ab24&X-Amz-SignedHeaders=host&x-id=GetObject" });
|
|
76
|
+
(0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(optionsUsingLegacyNaming, imageSet, "ABC-123", "./static/notion_imgs", "/notion_imgs");
|
|
58
77
|
const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("d1bcdc8c-b065-4e40-9a11-392aabeb220e");
|
|
59
78
|
expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
|
|
60
79
|
});
|
|
@@ -29,7 +29,8 @@ function convertInternalUrl(context, url) {
|
|
|
29
29
|
exports.convertInternalUrl = convertInternalUrl;
|
|
30
30
|
// handles the whole markdown link, including the label
|
|
31
31
|
function convertInternalLink(context, markdownLink) {
|
|
32
|
-
|
|
32
|
+
// match both [foo](/123) and [bar](https://www.notion.so/123) <-- the "mention" link style
|
|
33
|
+
const linkRegExp = /\[([^\]]+)?\]\((?:https?:\/\/www\.notion\.so\/|\/)?([^),^/]+)\)/g;
|
|
33
34
|
const match = linkRegExp.exec(markdownLink);
|
|
34
35
|
if (match === null) {
|
|
35
36
|
(0, log_1.warning)(`[standardInternalLinkConversion] Could not parse link ${markdownLink}`);
|
|
@@ -96,7 +97,9 @@ exports.standardInternalLinkConversion = {
|
|
|
96
97
|
// (has some other text that's been turned into a link) or "raw".
|
|
97
98
|
// Raw links come in without a leading slash, e.g. [link_to_page](4a6de8c0-b90b-444b-8a7b-d534d6ec71a4)
|
|
98
99
|
// Inline links come in with a leading slash, e.g. [pointer to the introduction](/4a6de8c0b90b444b8a7bd534d6ec71a4)
|
|
99
|
-
|
|
100
|
+
// "Mention" links come in as full URLs, e.g. [link_to_page](https://www.notion.so/62f1187010214b0883711a1abb277d31)
|
|
101
|
+
// YOu can create them either with @+the name of a page, or by pasting a URL and then selecting the "Mention" option.
|
|
102
|
+
match: /\[([^\]]+)?\]\((?!mailto:)(https:\/\/www\.notion\.so\/[^),^/]+|\/?[^),^/]+)\)/,
|
|
100
103
|
convert: convertInternalLink,
|
|
101
104
|
},
|
|
102
105
|
};
|
|
@@ -38,6 +38,43 @@ test("urls that show up as raw text get left that way", () => __awaiter(void 0,
|
|
|
38
38
|
});
|
|
39
39
|
expect(results.trim()).toBe("https://github.com");
|
|
40
40
|
}));
|
|
41
|
+
// See https://github.com/sillsdev/docu-notion/issues/97
|
|
42
|
+
test("mention-style link to an existing page", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
+
const targetPageId = "123";
|
|
44
|
+
const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
|
|
45
|
+
slug: undefined,
|
|
46
|
+
name: "Hello World",
|
|
47
|
+
id: targetPageId,
|
|
48
|
+
});
|
|
49
|
+
const results = yield getMarkdown({
|
|
50
|
+
type: "paragraph",
|
|
51
|
+
paragraph: {
|
|
52
|
+
rich_text: [
|
|
53
|
+
{
|
|
54
|
+
type: "mention",
|
|
55
|
+
mention: {
|
|
56
|
+
type: "page",
|
|
57
|
+
page: {
|
|
58
|
+
id: `${targetPageId}`,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
annotations: {
|
|
62
|
+
bold: false,
|
|
63
|
+
italic: false,
|
|
64
|
+
strikethrough: false,
|
|
65
|
+
underline: false,
|
|
66
|
+
code: false,
|
|
67
|
+
color: "default",
|
|
68
|
+
},
|
|
69
|
+
plain_text: "foo",
|
|
70
|
+
href: `https://www.notion.so/${targetPageId}`,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
color: "default",
|
|
74
|
+
},
|
|
75
|
+
}, targetPage);
|
|
76
|
+
expect(results.trim()).toBe(`[foo](/${targetPageId})`);
|
|
77
|
+
}));
|
|
41
78
|
test("link to an existing page on this site that has no slug", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
79
|
const targetPageId = "123";
|
|
43
80
|
const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
|
|
@@ -7,4 +7,4 @@ export declare function makeSamplePageObject(options: {
|
|
|
7
7
|
name?: string;
|
|
8
8
|
id?: string;
|
|
9
9
|
}): NotionPage;
|
|
10
|
-
export declare function oneBlockToMarkdown(config: IDocuNotionConfig, block:
|
|
10
|
+
export declare function oneBlockToMarkdown(config: IDocuNotionConfig, block: Record<string, unknown>, targetPage?: NotionPage): Promise<string>;
|
|
@@ -49,8 +49,11 @@ children, validApiKey) {
|
|
|
49
49
|
},
|
|
50
50
|
imports: [],
|
|
51
51
|
//TODO might be needed for some tests, e.g. the image transformer...
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
pageInfo: {
|
|
53
|
+
directoryContainingMarkdown: "not yet",
|
|
54
|
+
relativeFilePathToFolderContainingPage: "not yet",
|
|
55
|
+
slug: "not yet",
|
|
56
|
+
},
|
|
54
57
|
layoutStrategy: new HierarchicalNamedLayoutStrategy_1.HierarchicalNamedLayoutStrategy(),
|
|
55
58
|
options: {
|
|
56
59
|
notionToken: "",
|
|
@@ -90,8 +93,8 @@ children, validApiKey) {
|
|
|
90
93
|
// },
|
|
91
94
|
};
|
|
92
95
|
if (pages && pages.length) {
|
|
93
|
-
console.log(pages[0].matchesLinkId);
|
|
94
|
-
console.log(docunotionContext.pages[0].matchesLinkId);
|
|
96
|
+
// console.log(pages[0].matchesLinkId);
|
|
97
|
+
// console.log(docunotionContext.pages[0].matchesLinkId);
|
|
95
98
|
}
|
|
96
99
|
const r = yield (0, transform_1.getMarkdownFromNotionBlocks)(docunotionContext, config, blocks);
|
|
97
100
|
//console.log("blocksToMarkdown", r);
|
|
@@ -219,7 +222,7 @@ function makeSamplePageObject(options) {
|
|
|
219
222
|
metadata: m,
|
|
220
223
|
foundDirectlyInOutline: false,
|
|
221
224
|
});
|
|
222
|
-
console.log(p.matchesLinkId);
|
|
225
|
+
// console.log(p.matchesLinkId);
|
|
223
226
|
return p;
|
|
224
227
|
}
|
|
225
228
|
exports.makeSamplePageObject = makeSamplePageObject;
|
|
@@ -35,11 +35,15 @@ export type IDocuNotionContext = {
|
|
|
35
35
|
options: DocuNotionOptions;
|
|
36
36
|
getBlockChildren: IGetBlockChildrenFn;
|
|
37
37
|
notionToMarkdown: NotionToMarkdown;
|
|
38
|
-
|
|
39
|
-
relativeFilePathToFolderContainingPage: string;
|
|
38
|
+
pageInfo: IDocuNotionContextPageInfo;
|
|
40
39
|
convertNotionLinkToLocalDocusaurusLink: (url: string) => string | undefined;
|
|
41
40
|
pages: NotionPage[];
|
|
42
41
|
counts: ICounts;
|
|
43
42
|
imports: string[];
|
|
44
43
|
};
|
|
44
|
+
export type IDocuNotionContextPageInfo = {
|
|
45
|
+
directoryContainingMarkdown: string;
|
|
46
|
+
relativeFilePathToFolderContainingPage: string;
|
|
47
|
+
slug: string;
|
|
48
|
+
};
|
|
45
49
|
export {};
|
package/dist/pull.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Client } from "@notionhq/client";
|
|
2
2
|
import { ListBlockChildrenResponseResults } from "notion-to-md/build/types";
|
|
3
|
+
type ImageFileNameFormat = "default" | "content-hash" | "legacy";
|
|
3
4
|
export type DocuNotionOptions = {
|
|
4
5
|
notionToken: string;
|
|
5
6
|
rootPage: string;
|
|
@@ -9,8 +10,10 @@ export type DocuNotionOptions = {
|
|
|
9
10
|
imgPrefixInMarkdown: string;
|
|
10
11
|
statusTag: string;
|
|
11
12
|
requireSlugs?: boolean;
|
|
13
|
+
imageFileNameFormat?: ImageFileNameFormat;
|
|
12
14
|
};
|
|
13
15
|
export declare function notionPull(options: DocuNotionOptions): Promise<void>;
|
|
14
16
|
export declare function executeWithRateLimitAndRetries<T>(label: string, asyncFunction: () => Promise<T>): Promise<T>;
|
|
15
17
|
export declare function initNotionClient(notionToken: string): Client;
|
|
16
18
|
export declare function numberChildrenIfNumberedList(blocks: ListBlockChildrenResponseResults): void;
|
|
19
|
+
export {};
|
package/dist/pull.js
CHANGED
|
@@ -103,8 +103,12 @@ function outputPages(options, config, pages) {
|
|
|
103
103
|
return __awaiter(this, void 0, void 0, function* () {
|
|
104
104
|
const context = {
|
|
105
105
|
getBlockChildren: getBlockChildren,
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
// this changes with each page
|
|
107
|
+
pageInfo: {
|
|
108
|
+
directoryContainingMarkdown: "",
|
|
109
|
+
relativeFilePathToFolderContainingPage: "",
|
|
110
|
+
slug: "",
|
|
111
|
+
},
|
|
108
112
|
layoutStrategy: layoutStrategy,
|
|
109
113
|
notionToMarkdown: notionToMarkdown,
|
|
110
114
|
options: options,
|
|
@@ -117,10 +121,11 @@ function outputPages(options, config, pages) {
|
|
|
117
121
|
layoutStrategy.pageWasSeen(page);
|
|
118
122
|
const mdPath = layoutStrategy.getPathForPage(page, ".md");
|
|
119
123
|
// most plugins should not write to disk, but those handling image files need these paths
|
|
120
|
-
context.directoryContainingMarkdown = Path.dirname(mdPath);
|
|
124
|
+
context.pageInfo.directoryContainingMarkdown = Path.dirname(mdPath);
|
|
121
125
|
// TODO: This needs clarifying: getLinkPathForPage() is about urls, but
|
|
122
126
|
// downstream images.ts is using it as a file system path
|
|
123
|
-
context.relativeFilePathToFolderContainingPage = Path.dirname(layoutStrategy.getLinkPathForPage(page));
|
|
127
|
+
context.pageInfo.relativeFilePathToFolderContainingPage = Path.dirname(layoutStrategy.getLinkPathForPage(page));
|
|
128
|
+
context.pageInfo.slug = page.slug;
|
|
124
129
|
if (page.type === NotionPage_1.PageType.DatabasePage &&
|
|
125
130
|
context.options.statusTag != "*" &&
|
|
126
131
|
page.status !== context.options.statusTag) {
|
|
@@ -158,7 +163,7 @@ function getPagesRecursively(options, incomingContext, pageIdOfThisParent, order
|
|
|
158
163
|
if (!rootLevel &&
|
|
159
164
|
pageInfo.hasParagraphs &&
|
|
160
165
|
pageInfo.childPageIdsAndOrder.length) {
|
|
161
|
-
(0, log_1.error)(`Skipping "${pageInTheOutline.nameOrTitle}" and its children. docu-notion does not support pages that are both levels and have content at the same time.`);
|
|
166
|
+
(0, log_1.error)(`Skipping "${pageInTheOutline.nameOrTitle}" and its children. docu-notion does not support pages that are both levels and have text content (paragraphs) at the same time. Normally outline pages should just be composed of 1) links to other pages and 2) child pages (other levels of the outline). Note that @-mention style links appear as text paragraphs to docu-notion so must not be used to form the outline.`);
|
|
162
167
|
++counts.skipped_because_level_cannot_have_content;
|
|
163
168
|
return;
|
|
164
169
|
}
|
package/dist/run.js
CHANGED
|
@@ -62,7 +62,10 @@ function run() {
|
|
|
62
62
|
]))
|
|
63
63
|
.option("-i, --img-output-path <string>", "Path to directory where images will be stored. If this is not included, images will be placed in the same directory as the document that uses them, which then allows for localization of screenshots.")
|
|
64
64
|
.option("-p, --img-prefix-in-markdown <string>", "When referencing an image from markdown, prefix with this path instead of the full img-output-path. Should be used only in conjunction with --img-output-path.")
|
|
65
|
-
.option("--require-slugs", "If set, docu-notion will fail if any pages it would otherwise publish are missing a slug in Notion.", false)
|
|
65
|
+
.option("--require-slugs", "If set, docu-notion will fail if any pages it would otherwise publish are missing a slug in Notion.", false)
|
|
66
|
+
.addOption(new commander_1.Option("--image-file-name-format <format>", "format:\n- default: {page slug (if any)}.{image block ID}\n- content-hash: Use a hash of the image content.\n- legacy: Use the legacy (before v0.16) method of determining file names. Set this to maintain backward compatibility.\nAll formats will use the original file extension.")
|
|
67
|
+
.choices(["default", "content-hash", "legacy"])
|
|
68
|
+
.default("default"));
|
|
66
69
|
commander_1.program.showHelpAfterError();
|
|
67
70
|
commander_1.program.parse();
|
|
68
71
|
(0, log_1.setLogLevel)(commander_1.program.opts().logLevel);
|
package/package.json
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"pull-sample-site": "npm run ts -- -n $DOCU_NOTION_INTEGRATION_TOKEN -r $DOCU_NOTION_SAMPLE_ROOT_PAGE --log-level debug",
|
|
18
18
|
"// test with a semi-stable/public site:": "",
|
|
19
19
|
"pull-sample": "npm run ts -- -n $DOCU_NOTION_INTEGRATION_TOKEN -r $DOCU_NOTION_SAMPLE_ROOT_PAGE -m ./sample --locales en,es,fr,de --log-level verbose",
|
|
20
|
-
"pull-sample-with-paths": "npm run ts -- -n $DOCU_NOTION_INTEGRATION_TOKEN -r $DOCU_NOTION_SAMPLE_ROOT_PAGE -m ./sample --img-output-path ./sample_img"
|
|
20
|
+
"pull-sample-with-paths": "npm run ts -- -n $DOCU_NOTION_INTEGRATION_TOKEN -r $DOCU_NOTION_SAMPLE_ROOT_PAGE -m ./sample --img-output-path ./sample_img",
|
|
21
|
+
"lint": "eslint . --ext .ts"
|
|
21
22
|
},
|
|
22
23
|
"//file-type": "have to use this version before they switched to ESM, which gives a compile error related to require()",
|
|
23
24
|
"//chalk@4": "also ESM related problem",
|
|
@@ -90,5 +91,5 @@
|
|
|
90
91
|
"volta": {
|
|
91
92
|
"node": "18.16.0"
|
|
92
93
|
},
|
|
93
|
-
"version": "0.14.0-alpha.
|
|
94
|
+
"version": "0.14.0-alpha.16"
|
|
94
95
|
}
|